In general, my goal is to draw lines defined by the user's cursor. To accomplish this, I am figuring out the calculations for those lines in one class, and then updating the class that paints the lines with those new values. Overall I want to access a list of line segments, a list of nodes, and the current abstract "node" that the cursor is located at. (Nodes and Line Segments are my own defined classes). The class in which the lines are drawn is called GraphicsPanel.
I set up access between the classes as follows:
public class MainClass extends JFrame {
protected static ArrayList<LineSegment> lineList = new ArrayList<LineSegment>();
protected static ArrayList<Node> nodeList = new ArrayList<Node>();
protected static Node current = new Node();
// Code for calculations and user interactions
{
GraphicsPanel displayPanel = new GraphicsPanel();
// Values are updated
displayPanel.revalidate();
displayPanel.repaint();
}
}
public class GraphicsPanel extends JPanel {
private ArrayList<LineSegment> lineList = package.MainClass. lineList;
private ArrayList<Node> nodeList = package.MainClass.nodeList;
private Node current = package.MainClass.current;
public GraphicsPanel() {
}
public void paint(Graphics g) {
// Paint lines and other shapes
}
}
While the lineList and nodeList objects get correctly updated when a new LineSegment or Node is added to the list, the current Node is never updated and always shows the default of (0, 0).
As an example, within the main class, I have two mouse listeners, one for mouse clicks and one for mouse movement. They have similar functions, but the mouse click listener updates both the current Node and the lineList ArrayList of LineSegments.
displayPanel.addMouseListener(new MouseListener() {
#Override
public void mousePressed(MouseEvent e) {
Point p = e.getPoint();
current = new Node(p.getX(), p.getY());
lineList.add(new LineSegment(current, current);
// Don't worry, the line segment gets updated (correctly) with a new end node as
// the cursor moves around the window
displayPanel.revalidate();
displayPanel.repaint();
}
});
When I click on the window to create lines, the lines show up as expected but the current Node remains at (0, 0). I am completely flabbergasted by this, since it seems like only one of the variables is updating, even though both are written to update in basically the same way: I modify the instance of the class variable in the main class, which should modify the instance of the variable in the GraphicsPanel class.
I appreciate any help with this conundrum and welcome suggestions for what's wrong, as well as better ways to approach this application.
You don't modify the instance, you create a new instance, replacing the old. This means that GraphicsPanel.current will keep pointing to the original instance, but MainClass.current will point to a new distance.
If you did something like instance.setY(p.getY()), it would modify the single original instance that both classes are pointing to.
Your MouseListener adds objects to the Main class list.
Then it assigns a different new object to the panel reference current. But that doesn't change the Main class refence!
You could just not have another current reference in the panel code. Simply directly assign to the current instance that belongs to the Main class!
Related
I'm using GraphStream to show a map of an area and I've tried to inherit from the default MouseManager DefaultMouseManager and to override the mouseClicked method so that when clicking on a node the following will happened:
node's color will change.
node's label will show.
node's data will show in terminal.
I do know that the method works because the node's data does get printed to terminal, but I think some other mouse event repaint the node and rehide the label so they doesn't change when clicking on a node.
here is my MouseManager's code:
public class CustomMouseManager2 extends DefaultMouseManager {
protected View view;
protected GraphicGraph graph;
private GraphicElement focusedNode;
#Override
public void init(GraphicGraph graph, View view) {
super.init(graph, view);
.
.
.
}
#Override
public void mouseClicked(MouseEvent e) {
GraphicElement currentNode = view.findNodeOrSpriteAt(e.getX(), e.getY());
if(currentNode != null){
OGraph graph = OGraph.getInstance();
Random r = new Random();
currentNode.setAttribute("ui.style", "fill-color: red; text-mode: normal;");
ONode oNode = graph.getNode(Long.parseLong(currentNode.getLabel()));
System.out.println(oNode.toString());
}
if(focusedNode!= null)
focusedNode.setAttribute("ui.style", "fill-color: black;size: 10px, 10px; text-mode: hidden;");
focusedNode = currentNode;
}
}
I've tried to check what methods from the base class DefaultMouseManager are called after my mouseClicked is called so I could override them too, but there was to many of them to follow.
Is there an elegant way to make sure my changes will execute after all other method from the base class?
Is there an elegant way to make sure my changes will execute after all other method from the base class?
Read the documentation and look at the code in DefaultMouseManager. I googled DefaultMouseManager, looked at the documentation, went through the inheritance of the different interfaces until I got to MouseListener, which describes the order of operations. Then I looked at mouseClicked and mouseReleased since they would be called last, mouseClicked is empty so that leaves mouseReleased and the methods that are called in it.
So, something similar to this question has happened, the mouseClicked() method was called twice.
In my code, I repaint black the previous node and hide its label after a new node is clicked. And for that reason, when the mouseClicked() method was called twice then the first call changed the node`s appearance and the second one changed it back.
In that case, an easy fix will be to check if the previous node and current node are the same. replace this if(focusedNode!= null) with this
if(focusedNode!= null && focusedNode != currentNode)
but a more straightforward solution will be to understand why the method is been called twice.
My guess is that it has something to do with the inheritance but I'm not sure.
public void move()
{
//if (this.getWorld().getObjects(Marker.class).isEmpty())
Dog bill = getOneIntersectingObject(Dog.class);
Marker bone = getOneIntersectingObject(Dog.class);
if (bone == null);
{
Marker bone= new Marker();
getWorld().addObject(marker.getX(), marker.getY());
}
super.move(1);
}
I'm trying to reference the location of my current actor subclass (dog) in order to place a marker(bone) of another subclass at the coordinates it is located at.
You can retrieve the coordinates of the actor by calling getX() and getY(), as you do it for other actors.
If you like to call methods for the "current" actor, you might like to use this, in example this.getX(). But it is only necessary, if you need to resolve some ambiguity. Commonly you can just call the method.
public void move()
{
Dog bill = getOneIntersectingObject(Dog.class);
Marker bone = getOneIntersectingObject(Marker.class);
if (bone == null)
{
Marker marker = new Marker();
getWorld().addObject(marker, getX(), getY());
}
super.move(1);
}
Changes necessary as obvious from the shown excerpt:
The parameter of the second getOneIntersectingObject() is Marker.class, because you seem to want this.
No semicolon after the parenthesis of the if. If you don't remove it: This semicolon is an empty statement, so the if will have no effect. The block between the braces following it will always be executed.
Rename the second bone as marker to show your intention. (It would "shadow" the "outer" bone, additionally, but this is no problem here.)
Call addObject() with the right parameters: the object to place (the new marker), and its coordinates (retrieved by calling getX() and getY()).
So I'm trying to use Key Bindings, and the action map's put() method takes an action and a string parameter.
/* all declartion is above
* the class extends JPanel so the keyword "this" can be used
* xlist is an ArrayList of Integers
* keyActionRight ka = new keyActionRight(x); is declared above, where x is a global int
* this is part of the keyBindingsTest class */
xlist.add(x);
im = this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right");
am = this.getActionMap();
am.put("right", ka);
System.out.println(ka.getNextX(xlist)); //Any way for this to be called just like if I printed in the actionPerformed of the action class?
This is the keyActionRight class. It is an action as you get an action when you extend AbstractAction:
public class keyActionRight extends
AbstractAction
{
private int x;
private ArrayList<Integer> xlist;
public keyActionRight(int x)
{
this.x = x;
xlist = new ArrayList<Integer>();
xlist.add(x);
}
public int getNextX(ArrayList<Integer> x)
{
x = xlist;
return x.get(0);
}
public void actionPerformed(ActionEvent e)
{
if(x != 440)
{
x++; //this incrementing works fine
xlist.add(0, x); //this updates xlist fine
}
}
}
The goal is essentially just to update the instance variable x in the keyBindingsTest class whenever I press or hold the right arrow key. The x in the Action class is updating just fine when I do this (I printed it out and it works). It's been pointed out why it's not updating - it is only being called once in the print statement. I want to know if there is a way to make this work with a separate class for the action or if I need to take a different approach.
I could try making the Action in the keyBindingsTest class, but that was giving me strange errors last time I tried that. Any help would be appreciated.
Thanks.
You have faulty assumptions:
xlist.add(x);
im = this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right");
am = this.getActionMap();
am.put("right", ka);
// **** the comment below is incorrect ****
//only prints out zero - should print out ascending values of x as I hold down the right arrow key
System.out.println(ka.getNextX(xlist));
The assumption you're making is that the println gets called when the Key Bindings action is called, but that is simply not so. The println is called once and only once when the key binding is created. The only code that gets called repeatedly is that which is within the Action's actionPerformed method, the code that is called in response to an event.
If you want code called multiple times and in response to an event, it must be placed within an event listener, not the listener's creation code.
A frequent problem I encounter when programming is how to handle an unknown number of objects. By handling I mean referencing them, manipulating them etc. As for me, this would be when developing smaller games and programs.
Currently I am working on a score-keeping program, which should display the names of the players, their score as well and various other features. Furthermore, there should be two buttons that allow for adding and removing players from the score table which is what I'll be focusing on here. It might look something like this:
//A very limited version of my program
import java.awt.*;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
class Application extends JFrame implements ActionListener{
//Fields, variables and components
Container mainCont = getContentPane(); //Main container, the window itself
private JPanel buttonPanel;
private JPanel namePanel;
private JButton addPlayerButton;
private JButton removePlayerButton;
//...
//Many more components
public Application(){
//Basic window initiation
setTitle("Score Keeper");
this.setSize(650, 700);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainCont.setBackground(Color.BLACK);
this.setContentPane(mainCont);
buttonPanel = new JPanel();
namePanel = new JPanel();
addPlayerButton = new JButton();
addPlayerButton.addActionListener(this);
buttonPanel.add(addPlayerButton);
removePlayerButton = new JButton();
removePlayerButton.addActionListener(this);
buttonPanel.add(removePlayerButton);
this.add(buttonPanel);
this.add(namePanel);
this.setVisible(true);
//Other code omitted for now
//Includes other graphic components, layout managers etc.
}
/*
* Action-Listener.
* Performs an event on an action.
*/
#Override
public void actionPerformed(ActionEvent event){
if(event.getSource() == addPlayerButton){
Application.Player newPlayer = this.new Player(); //Creates a new object, i.e. a new player
//See below for Player class
}
if(event.getSource() == removePlayerButton){
//******
// This is where the problem lies
}
}
//I use a nested class to create a new player
public class Player{
//Components etc.
private String name;
private JLabel nameLabel;
public Player(){
name = getName();
nameLabel = new JLabel(name);
namePanel.add(nameLabel);
}
public String getName(){
//This basically gets input from the user to assign a name to the new player
//Code omitted for now
}
}
}
So far all is good. This program basically only has two buttons, where the addPlayerButton adds a player object, which has a name that is displayed on the screen. Every time this button is pressed a new player is added to the screen. And this can be done an indefinite number of times.
The problem comes when we want to remove a player. How can we do that? We can't reference it by name, as all player objects are practically anonymous.
The alternative, of course, would be to pre-define a fixed amount of player objects:
class Application extends JFrame implements ActionListener{
//Fields, variables and components
Container mainCont = getContentPane(); //Main container, the window itself
private JPanel buttonPanel;
private JPanel namePanel;
private JButton addPlayerButton;
private JButton removePlayerButton;
private Player player1;
private Player player2;
private Player player3;
//...
//Etc.
Then we would be able to directly address each player object, but this is simply too impractical. We cannot add more players than the pre-defined amount, and if we want fewer players we have a bunch of player objects that are never used. Furthermore, we would have to hardcode every initiation of every player - every nameLabel would have to be manually added to the screen etc.
Please share your knowledge on how you handle this kind of problem, of how you deal with an unknown number of objects.
Thanks for taking the time and for the help!
P.S. I'm still pretty new to this forum. Please let me know if there is anything with this question I can change etc. I did my research and found no previous question that tackled this, but if there is one I missed feel free to let me know!
EDIT 1: Okay. There were a lot of great answers. I chose the one using hashmaps as the right solution, since I consider this the best solution for the premises I provided. The way I actually solved my problem is that I added, to the player object, a JButton that removes the player object it is stored in. I also scrapped the concept of using a nested class for the player and just implemented it in a separate class.
What I've learnt overall, though, is that when handling objects and you don't know the amount of objects it is generally best to store them in a collection of some sort. My preference is the Hashmap, as it provides an easy way of accessing the object based on one of its properties, like a String name or similar.
You can use a Map/Hashmap and each time you create a player, add them to the map.
You also have to change from directly drawing the player on the screen, probably to drawing all the players in the Map, that way when a player gets removed from the Map it would no longer be drawn.
You would do something like this:
Map<String, Player> map = new HashMap<>();
map.put(player.Name, player);
And then you would draw everything in that hashmap. To remove you just need to provide the name of the player to remove.
map.remove(player.Name);
And of course then you would change your code a bit to render everything inside the map, I believe you need a way to know which player is to be removed, you might want to add a text field to input the name of the player to be removed.
What you could do if you want to delete a Player based on it's name is the following:
// Create a list of players, which you can define globally
ArrayList<Player> players = new ArrayList<>();
// The name of the player to find
String name = "theNameOfThePlayerToFind";
// Loop through the players and remove the player with the given name
for (Player player : players) {
if (player.getName().equals(name)) {
players.remove(player);
}
}
You can also add new players to lists easily:
players.add(new Player());
I would #Katada Freije method of using a HashMap. Just to elaborate a little, you basically have a collection of Players with their names as a key. You then use the key to remove the Player.
But I might also avoid this as some scenarios have multiple Players with the same name. I'd go with a List<Player>. This way the Player will be defined by the index rather than the name. You'd then use the index to remove the player with some in inbuilt methods.
Let us assume that you are using a JList to display the current players. Java's Swing separates the model (where the objects that are displayed are actually stored) and the view and control (the JList that displays them). This design is called MVC, and is very common.
We have different ways of storing the actual Player objects. Which to choose depends on how you plan to manipulate your players. The most straightforward is to use an array, but that only works if you will never have too many players:
Player[] players = new Player[MAX_PLAYERS](); // define MAX_PLAYERS somewhere
int currentPlayers = 0; // always < MAX_PLAYERS
To expose this to the JList, you would use a custom adapter model as follows (in an inner class with access to the players array):
private final class PlayerListModel extends AbstractListModel<Player> {
#Override
Player getElementAt(int position) { return players[position]; }
#Override
int getSize() { return currentPlayers; }
}
You can then pass this to the JList at construction time:
private PlayerListModel playerListModel = new PlayerListModel();
private JList playersListView = new JList(playerListModel);
Now, to remove a player by name, you would first update the model, and then cause the view to be refreshed:
private void removePlayerByName(String name) {
int toRemove = -1;
for (int i=0; i<currentPlayers; i++) {
Player p = players[i];
if (p.getName().equals(name)) {
toRemove = i;
break;
}
}
if (toRemove != -1) {
// update model
currentPlayers --;
players[toRemove] = players[currentPlayers];
// update views
playerListModel.fireContentsChanged(this, toRemove, currentPlayers);
}
}
Instead of a players array, it would be much easier and safer to use an ArrayList<Player> players. However, if you are naming your variables player1, player2 and so on, I think that you should begin with arrays. If you want to have much faster lookup of players, then a TreeMap<String, Player> would keep them sorted by name and easy to find. You would, in both cases, have to update the model and the removePlayerByName function accordingly. For example, if using the TreeMap, it would be much shorter (and faster):
private void removePlayerByName(String name) {
if (players.containsKey(name)) {
players.remove(name);
playerListModel.fireContentsChanged(this, 0, currentPlayers);
}
}
On the other hand, it is far more common to find interfaces where you click on the player that you want to remove to select it, and then click the remove button to actually do so. You can know which player was selected with this code:
Player selected = playersListView.getSelectedValue();
If there is a selection (selected != null) you could either call removePlayerByName(selected.getName()), or even better, code a removePlayer(Player p) that did not rely on names, but on a (currently missing) implementation of Player.equals(Player another).
Having trouble filling the grid. Everytime I do it I get a stackoverflow error. Here is my current code :
public void removeSelfFromGrid() {
Grid<Actor> grid = getGrid();
int rows = grid.getNumRows();
int cols = grid.getNumCols();
for (int i=0; i<rows; i++) {
for (int j=0; j<cols; j++) {
Location loc = new Location(i, j);
laugh = new CKiller();
laugh.putSelfInGrid(grid, loc);
}
}
}
and here is the constructor if needed
public CKiller()
{
Color c = null;
setColor(c);
getGrid();
getLocation();
location = new ArrayList<Location>();
location.add(getLocation());
setDirection(direction);
}
And here is the error (part of it, too big to post all. it's just those 2 statements repeated):
java.lang.StackOverflowError
at info.gridworld.actor.Actor.putSelfInGrid(Actor.java:123)
at CKiller.removeSelfFromGrid(CKiller.java:120)
It's saying this is the problem
laugh.putSelfInGrid(grid, loc);
Go through the following:
-Are you defining laugh prior to the removeSelfFromGrid() method call? It doesn't have a type specified before it.
-Should the variable location not be an ArrayList? It might be a Location object.
-Is the int direction defined already?
-Why are you calling getGrid() and getLocation()? They aren't doing anything beneficial.
-Does CKiller inherit the putSelfInGrid() method from the Actor class?
Please include the full code of the CKiller class as well as the main class that contains removeSelfFromGrid().
I think your problem is that you overrode the removeSelfFromGrid() method. You should have created a new method, such as fillGrid().
When an actor calls putSelfInGrid(), if there is currently another actor in that Location it calls removeSelfFromGrid(), which you overrode to fill every Location on the Grid with an actor. If there are any other actors on the grid they will then call removeSelfFromGrid(), which leads to again filling the grid, etc.
Just fix the code in removeSelfFromGrid(), put it in a new method and restore the previous code, and you should be good.