Java Swing; Two Classes, Where to Put If Statements and New Actionlisteners

Java Swing; Two classes, where to put if statements and new actionlisteners?

Because ClockListener is a nested class (lower), the enclosing instance (upper) can access the listener's private fields. If you have a reference to an instance of ClockListener,

ClockListener cl = new ClockListener();

you can use it to initialize your timer

Timer t = new Timer(1000, cl);

and you can use it in your test:

if (cl.count == 2) { t.stop(); }

Addendum: For reference, here's a variation of your program that uses a JToggleButton to control the timer. As suggested earlier, you had used Calendar to minimize Timer drift. Like you, I abandoned the approach as irrelevant in a low-resolution application.

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.Timer;

/** @see https://stackoverflow.com/questions/5528939*/
class ClockExample extends JFrame {

private static final int N = 60;
private static final String stop = "Stop";
private static final String start = "Start";
private final ClockListener cl = new ClockListener();
private final Timer t = new Timer(1000, cl);
private final JTextField tf = new JTextField(3);

public ClockExample() {
t.setInitialDelay(0);

JPanel panel = new JPanel();
tf.setHorizontalAlignment(JTextField.RIGHT);
tf.setEditable(false);
panel.add(tf);
final JToggleButton b = new JToggleButton(stop);
b.addItemListener(new ItemListener() {

@Override
public void itemStateChanged(ItemEvent e) {
if (b.isSelected()) {
t.stop();
b.setText(start);
} else {
t.start();
b.setText(stop);
}
}
});
panel.add(b);

this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(panel);
this.setTitle("Timer");
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}

public void start() {
t.start();
}

private class ClockListener implements ActionListener {

private int count;

@Override
public void actionPerformed(ActionEvent e) {
count %= N;
tf.setText(String.valueOf(count));
count++;
}
}

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
ClockExample clock = new ClockExample();
clock.start();
}
});
}
}

How to Handle Events in a multiple Class Java Swing Project?

So, in most UI frameworks, you have a concept of an "observer pattern", this is way for interested parties to register interest been notified when something happens to the object.

Swing makes use of "listeners" to facilitate this basic concept. You're not stuck to using the listeners that Swing defines and you can create your own, for example...

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.EventListener;
import java.util.EventObject;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main {

public static void main(String[] args) {
new Main();
}

public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
SouthPane southPane = new SouthPane();
southPane.addSourceListener(new SouthListener() {
@Override
public void somethingDidHappen(SouthEvent evt) {
System.out.println("Something important this way did happen");
}
});
frame.add(southPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}

// So you can attach additional properties about the event itself
public class SouthEvent extends EventObject {

public SouthEvent(SouthPane source) {
super(source);
}

}

// Describes what actions the SouthPane might generate
public interface SouthListener extends EventListener {
public void somethingDidHappen(SouthEvent evt);
}

public class SouthPane extends JPanel {

public SouthPane() {
JButton btn = new JButton("Click me");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fireSomethingDidHappen();
}
});
add(btn);
}

public void addSourceListener(SouthListener listener) {
listenerList.add(SouthListener.class, listener);
}

public void removeSouthListener(SouthListener listener) {
listenerList.remove(SouthListener.class, listener);
}

protected void fireSomethingDidHappen() {
SouthListener[] listeners = listenerList.getListeners(SouthListener.class);
if (listeners.length == 0) {
return;
}
SouthEvent event = new SouthEvent(this);
for (SouthListener listener : listeners) {
listener.somethingDidHappen(event);
}
}

}
}

This example is deliberately long winded, as you don't "always" need to create an event object, but it is a good way to pass back information to the observer, as it might not actually have a direct reference to object it's monitoring.

Trouble with ActionListener and an IF statement

I recommand to declare your input variables not in your function, but in your class. Otherwise you run into scope problems. Example:

public class test {

private JFrame frame;
private JButton btnStart;
private int input;
private JTextField Input;

//...

}

Should fix the issue :)

I am not enterily sure about the second issue but if you want to count down from the entered value, you have to update your action listener:

btnStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
input = (int) (Double.parseDouble(Input.getText()));
t.start();
}
});

implementing actionListener with multiple classes?

The errors are due to unimplemented methods of the Action class anonymous instance.

Any instance of the Action interface requires that all the its methods be implemented. However using Action as an anonymous instance if neither practical or good practice. Rather create a single concrete instance of a class that extends AbstractAction and set the Action for for each component.

button.setAction(mySingleAction);

where

class SingleAction extends AbstractAction {

@Override
public void actionPerformed(ActionEvent e) {
// do stuff
}
}

How do I make my two classes function together?

There are some good things in your code as well as several major issues that may hamper your ability to mash a GUI with your current console-program code, and these include:

  • You do have some separation of the your program logic into separate classes such as Player, Map, and Room classes, and that is a very good thing, but you still could do more separation of the logic, and doing so can simplify your code making it easier to debug and enhance.
  • The Player class contains a Player instance, I'm not sure why. The outer main class should probably be re-named from Player to the Game class, and the Player class should be a totally separate class, one that holds the state of a single player instance, and shouldn't create instances of itself as you're doing. The key is to strive to "oop-ify" your code, especially the "model" portion of the code, the code that holds the non-user interface program logic, to make it more object-oriented, since this will make it much easier to allow you to connect a GUI or any other interface, to your model.
  • Your model (again, named Player) has user interface (UI) code within it: it has Scanner input code within it and println statements. This is problematic, especially now since you want to enhance the program and make it work with a GUI. The key thing that you must do, first and foremost, is to separate the user interface code (agian the code that gets input from the user and that displays output to the user) outside of model class, which is, again, class that holds the program logic.
  • Your Player class has a lot of non-Player code cluttering it. A basic OOPs principle is that a class should have a single responsibility (called the "single responsibility rule"), and so your Player class should only have code that is needed to encapsulate Player state (such as name, Room, perhaps other properties such as strength, weapons, health, intelligence....) and Player behavior (methods that allow Player to interact with other classes), and nothihg more or less.
  • Going on the same vein, it appears that all the program data and behaviors seem to be hard-wired into this single main Player class that drives the program. This is very limiting and will hinder your ability to create a rich playing environment with many rooms, items in the rooms, and things like this. Divide and conquer -- get that information out of the main program and put it into OOP-compliant classes, including a Room class that has fields for items it might contain, that has knowledge of its own connections to other rooms.
  • Your GUI program extends the Player object. This is a major problem since this program structure does not pass the basic test of inheritance, the "is-a" test: logically is a GUI a more specific type of player (much like a Dog is a more specific type of Animal, a structure which passes the "is-a" test)? No, it is not, and while this distinction may seem pendantic, you may try to use Player fields within the GUI class because of this inheritance, but if you try to do this, the code will not work correctly. Remove this inheritance, and instead try to connect classes through "composition" where one class holds an instance of another, rather than extends another. So for instance, the GUI could possibly hold a Player variable rather than extend from Player.


Bottom line:
You will want to fully "OOP-ify" your code before trying to add a GUI

In order for the program to be able to well-meshed with a GUI, I would suggest making your underlying logical code (the non-user interface logic code) much more object-oriented with classes that all have a single responsibility, classes that are easily testable in isolation (away from GUI or any user interface). Start with first principles and the program will be much easier to build.

So, for example, classes that could be considered for an adventure game include:

  • A Direction class, or better a Direction enum.

This would encapsulate the idea of "direction" and would be used for objects in the game to know which direction they are going and to be able to communicate this to other objects using constants, rather than Strings. The user of an enum would allow the compiler to check that directions are being used correctly, since if you used Strings, say, "West", the compiler would not automatically know if you mis-typed the String and used "Best" instead.

Something simple like this, for example:

public enum Direction {
NORTH, EAST, SOUTH, WEST
}
  • A GameRoom class

This would hold information telling it its location in the grid of rooms, Strings for both name and description properties, and a game player field or a List of game players if more than one are allowed, perhaps something that contains code like this:....

public class GameRoom {
// map with connections to other game rooms
private Map<Direction, GameRoom> connections = new HashMap<Direction, GameRoom>();

// location information
private int x;
private int y;

// identifying information
private String roomName;
private String description;

// holds any player objects that may be in the room
private List<GamePlayer> playersInRoom = new ArrayList<>();

The class will have appropriate constructors as well as getter and setter methods and also:

An addPlayer(...) method that allows the game to add a player into the room:

public void addPlayer(GamePlayer gamePlayer) {
playersInRoom.add(gamePlayer);
gamePlayer.setCurrentRoom(this); // we'll get to this later
}

A public boolean move(...) method that moves a player contained by the room to a connecting room. It first checks that the room actually contains the player being moved, and then next checks if the room has a connection to another room in the requested direction. If either are false, then the method returns false to let the calling code that the attempted move failed. Otherwise, if the move is allowed, it returns true:

public boolean move(GamePlayer gamePlayer, Direction direction) {

// if the room doesn't currently hold this player
if (!playersInRoom.contains(gamePlayer)) {
return false; // invalid move request
}

// if the room doesn't have a connecting room in the requested direction
if (!connections.containsKey(direction)) {
return false; // invalid move request
}

// otherwise, we're good
playersInRoom.remove(gamePlayer);
connections.get(direction).addPlayer(gamePlayer);
return true;
}
  • A GamePlayer class

This class will hold several properties such as a String for name and a GameRoom field for the current room, constructors, getter and setter methods. It's declaration and fields could look something like:

public class GamePlayer {
private String name;
private GameRoom currentRoom;
//.... other properties

It too should have a move method that calls the currentRoom GameRoom's move method and returns the same boolean, a result that tells the calling code if the move was valid and successful:

public boolean move(Direction direction) {
return currentRoom.move(this, direction);
}
  • A GameModel class:

This will hold the state of the current game, fields for players, a data structure to hold all the rooms. It can have its own move method.....


Only after creating your OOP-compliant classes should you move to the next step: the creation of your GUI or "view" class/classes. This class could hold a GameModel instance, and would be responsible for 1) displaying the state of the game (a visual representation of the rooms and the items that they hold), and 2) getting input from the user, and passing that input to the game model for processing.

I like to use an M-V-C program structure, which stands for "Model-View-Controller" where the program logic and GUI are kept as separate as possible, and there may be a controller class or classes that help tie the model and view together. Another principle that I try to follow is to keep the GUI as "dumb" as possible. By dumb, I mean that it should get input from the user, perhaps do the most basic of input verifications, and should display the state of the model, but that's it. Almost all the "brains" of the program should be held by the model itself and not the view.



Proof-of-concept Example:

An incomplete but running "proof-of-concept" example of what I am describing above is shown below. You should copy the program whole, paste it into your IDE into a single file named VideoGame.java, and then should be able to run it. It uses some concepts that you may not yet be familiar with, including the use of Key Bindings to get user input from the GUI and the use of PropertyChangeListeners and PropertyChangeSupport objects to allow clean communication between objects (to notify listeners if the state of one of the model object has changed). This program should respond to pressing of the arrow keys:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;

public class VideoGame {
private static final int[][] ROOM_GRID_KEY = {
{ 1, 0, 0, 2, 0, 0, 3, 4 },
{ 5, 6, 7, 8, 9, 10, 11, 0 },
{ 0, 12, 0, 13, 0, 0, 0, 0 },
{ 0, 14, 0, 0, 0, 0, 15, 16 },
{ 17, 18, 0, 19, 0, 0, 20, 0 },
{ 21, 22, 23, 24, 25, 26, 27, 28 }
};

public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
GameModel gameModel = new GameModel(ROOM_GRID_KEY);
GameView gameView = new GameView();

new GameController(gameModel, gameView);

JFrame frame = new JFrame("Video Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(gameView);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
class GameController {
private GameModel gameModel;
private GameView gameView;

public GameController(GameModel gameModel, GameView gameView) {
this.gameModel = gameModel;
this.gameView = gameView;

ModelListener modelListener = new ModelListener();

gameView.setModel(gameModel);
gameModel.addPropertyChangeListener(modelListener);
}

private class ModelListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
gameView.modelChange(evt);
}
}

}
@SuppressWarnings("serial")
class DisplayPanel extends JPanel {
private JPanel[][] panelGrid;
private int gridCellSize;
private GameModel gameModel;
private GameRoom[][] roomGrid;

public DisplayPanel(int gridCellSize) {
this.gridCellSize = gridCellSize;
}

public void setModel(GameModel gameModel) {
this.gameModel = gameModel;
this.roomGrid = gameModel.getRoomGrid();

int rows = roomGrid.length;
int cols = roomGrid[0].length;

setBackground(Color.BLACK);
setLayout(new GridLayout(rows, cols, 1, 1));
setBorder(BorderFactory.createLineBorder(Color.BLACK));

panelGrid = new JPanel[rows][cols];
for (int r = 0; r < panelGrid.length; r++) {
for (int c = 0; c < panelGrid[r].length; c++) {
JPanel panel = new JPanel(new GridBagLayout());
panelGrid[r][c] = panel;
panel.setPreferredSize(new Dimension(gridCellSize, gridCellSize));
if (roomGrid[r][c] == null) {
panel.setBackground(Color.BLACK);
} else {
panel.setBackground(Color.PINK);
if (roomGrid[r][c].getPlayersInRoom().size() > 0) {
GamePlayer gamePlayer = roomGrid[r][c].getPlayersInRoom().get(0);
String name = gamePlayer.getName();
JLabel label = new JLabel(name);
panel.add(label);
}
}
add(panel);

}
}

// key bindings code
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), Direction.SOUTH);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), Direction.NORTH);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), Direction.WEST);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), Direction.EAST);
}

private void addBindings(KeyStroke keyStroke, Direction direction) {
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();

Action action = new AbstractAction() {

@Override
public void actionPerformed(ActionEvent e) {
gameModel.move(direction);
}
};

inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), action);
}

public void modelChange(PropertyChangeEvent evt) {
for (int r = 0; r < panelGrid.length; r++) {
for (int c = 0; c < panelGrid[r].length; c++) {
JPanel panel = panelGrid[r][c];
if (roomGrid[r][c] != null) {
if (roomGrid[r][c].getPlayersInRoom().size() > 0) {
GamePlayer gamePlayer = roomGrid[r][c].getPlayersInRoom().get(0);
String name = gamePlayer.getName();
JLabel label = new JLabel(name);
panel.add(label);
} else {
panel.removeAll();
}
}
}
}
revalidate();
repaint();
}

public GameModel getGameModel() {
return gameModel;
}
}
class GameView extends JPanel {
private static final int CELL_SIZE = 80;
private DisplayPanel displayPanel = new DisplayPanel(CELL_SIZE);
private GameModel gameModel;
// private JTextField textField = new JTextField();

public GameView() {
setLayout(new BorderLayout());

add(displayPanel);
// add(textField, BorderLayout.PAGE_END);
}

public void setModel(GameModel gameModel) {
this.gameModel = gameModel;
displayPanel.setModel(gameModel);
}

public void modelChange(PropertyChangeEvent evt) {
displayPanel.modelChange(evt);
}

public GameModel getGameModel() {
return gameModel;
}

}
class GameModel {
public static final String GAME_MODEL = "game model";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private GameRoom[][] roomGrid;
private GamePlayer player = new GamePlayer("Fred");

public GameModel(int[][] roomGridKey) {
roomGrid = new GameRoom[roomGridKey.length][roomGridKey[0].length];
// fill room grid with rooms if 1 in grid key array, with null if 0
for (int y = 0; y < roomGridKey.length; y++) {
for (int x = 0; x < roomGridKey[0].length; x++) {
roomGrid[y][x] = roomGridKey[y][x] != 0 ? new GameRoom("Some Room", "Some Description", x, y) : null;
}
}

// make room connections:
for (int y = 0; y < roomGrid.length; y++) {
for (int x = 0; x < roomGrid[0].length; x++) {
GameRoom thisRoom = roomGrid[y][x];

// if no room present, don't
if (thisRoom == null) {
continue;
}

if (x > 0) {
GameRoom otherGameRoom = roomGrid[y][x - 1];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.WEST, otherGameRoom);
}
}
if (x < roomGrid[0].length - 1) {
GameRoom otherGameRoom = roomGrid[y][x + 1];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.EAST, otherGameRoom);
}
}

if (y > 0) {
GameRoom otherGameRoom = roomGrid[y - 1][x];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.NORTH, otherGameRoom);
}
}
if (y < roomGrid.length - 1) {
GameRoom otherGameRoom = roomGrid[y + 1][x];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.SOUTH, otherGameRoom);
}
}

}
}

// put player in top left room
GameRoom currentRoom = roomGrid[0][0];
if (currentRoom == null) {
// some big error occurred
System.err.println("Current room at 0, 0 is null. Exiting");
System.exit(-1);
}

player.setCurrentRoom(currentRoom);
currentRoom.addPlayer(player);
player.addPropertyChangeListener(pce -> pcSupport.firePropertyChange(GAME_MODEL, null, player));

}

public boolean move(Direction direction) {
boolean success = player.move(direction);
return success;
}

public GamePlayer getPlayer() {
return player;
}

public GameRoom[][] getRoomGrid() {
return roomGrid;
}

public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(GAME_MODEL, listener);
}

}
class GamePlayer {
public static final String GAME_PLAYER = "game player";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private String name;
private GameRoom currentRoom;

public GamePlayer(String name) {
super();
this.name = name;
}

public String getName() {
return name;
}

public boolean move(Direction direction) {
boolean success = currentRoom.move(this, direction);
return success;
}

public void setCurrentRoom(GameRoom currentRoom) {
GameRoom oldValue = this.currentRoom;
GameRoom newValue = currentRoom;
this.currentRoom = currentRoom;
pcSupport.firePropertyChange(GAME_PLAYER, oldValue, newValue);
}

public GameRoom getCurrentRoom() {
return currentRoom;
}

@Override
public String toString() {
return "GamePlayer [name=" + name + ", currentRoom=" + currentRoom + "]";
}

public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(GAME_PLAYER, listener);
}

}
class GameRoom {
// map with connections to other game rooms
private Map<Direction, GameRoom> connections = new HashMap<Direction, GameRoom>();

// location information
private int x;
private int y;

// identifying information
private String roomName;
private String description;

// holds any player objects that may be in the room
private List<GamePlayer> playersInRoom = new ArrayList<>();

public GameRoom(String roomName, String description, int x, int y) {
this.roomName = roomName;
this.description = description;
this.x = x;
this.y = y;
}

public boolean move(GamePlayer gamePlayer, Direction direction) {

// if the room doesn't currently hold this player
if (!playersInRoom.contains(gamePlayer)) {
return false; // invalid move request
}

// if the room doesn't have a connecting room in the requested direction
if (!connections.containsKey(direction)) {
return false; // invalid move request
}

// otherwise, we're good
playersInRoom.remove(gamePlayer);
connections.get(direction).addPlayer(gamePlayer);
return true;
}

public void addPlayer(GamePlayer gamePlayer) {
playersInRoom.add(gamePlayer);
gamePlayer.setCurrentRoom(this);
}

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}

public String getRoomName() {
return roomName;
}

public String getDescription() {
return description;
}

public List<GamePlayer> getPlayersInRoom() {
return playersInRoom;
}

public Map<Direction, GameRoom> getConnections() {
return connections;
}

public void putConnection(Direction direction, GameRoom otherGameRoom) {
connections.put(direction, otherGameRoom);
}

@Override
public String toString() {
return "GameRoom [x=" + x + ", y=" + y + "]";
}

}
enum Direction {
NORTH, EAST, SOUTH, WEST
}


Related Topics



Leave a reply



Submit