I have a problem working with instances of different objects and this is what happens:
I have been developing a small game in Java (Swing & AWT) for a while and I have the following classes:
App.java
Play.java
Event.java
GameScene.java
MenuScene.java
Timer.java
Where:
App extends JFrame and is a frame with the main function of the application (main), this class creates the game window, and only exists this JFrame
The MenuScene and GameScene classes are scenes of the application, for example when you see the menu and you want to see the highest score, it is a scene, the levels of game are a scene, etc., but in this case I have only two scenes and I have represented them in JPanels: MenuScene extends JPanel and creates the game menu (buttons, images, etc.), the same applies to the GameScene class, this also extends JPanel and creates the game.
The other classes (Play, Event, Timer) are simple classes, they have the "logic of the game", keyboard control, timers, game operation and are instantiated in three global variables of the GameScene class.
Everything starts with App, creates an instance of it and in its constructor calls a method to "create" the menu (MenuScene.java). Now the menu has a JButton that when pressed "creates" the game (GameScene.java) and this class has a JButton to return to the menu at any time ... It is here where I have problems because if I am playing and I return to the menu Game still exists and I can lose, it does not make sense, it is as if you play but instead of seeing the game you see the menu, interestingly the graphic part works excellent, ie if I press a button it removes what I have and draws the scene that I want it quickly. It is because Play, Timer and Event are instantiated or "exist" in memory if I am not mistaken. So if I press again the "create game" JButton I would recreate a second instance of GameScene? And so infinitely for MenuScene and GameScene. Is there a solution to this? How do you think I should structure the application?
I give you an outline of the most important classes:
App.java
public class App extends JFrame {
private JPanel rootPanel;
public App() {
//Define frame
...
runScene(new MenuScene(this));
}
public void runScene(JPanel scene) {
destroyScene();
rootPanel.setBackground(scene.getBackground());
rootPanel.add(scene);
rootPane.validate();
rootPanel.repaint();
}
private void destroyScene() {
rootPanel.removeAll();
rootPanel.revalidate();
rootPanel.repaint();
}
public static void main(String[] args) { //Main
new App();
}
}
MenuScene.java
public class MenuScene extends JPanel {
private App app;
public MenuScene(App app) {
this.app = app;
//Define JPanel
...
buttonStart.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
app.runScene(new GameScene(app));
}
});
}
}
GameScene.java
public class GameScene extends JPanel {
private App;
private Play;
private Timer;
private Event; //Define controls (keyboard)
public GameScene(App app) {
this.app = app;
//Define JPanel, Play, Timer and Event
...
buttonBackMenu.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
app.runScene(new MenuScene(app));
}
});
}
}
Play.java
public class Play {
private JLabel[][] x;
public Play(JLabel[][] x) { //This matrix is important (is an reference), is instanced in GameScene, this is an problem?
this.x = x;
//Define others variables
}
}
I appreciate any help.
I have found a somewhat peculiar solution, but I do not know if it is the best:
The best way is that since the GC does not select the active timers then it is better to stop them and match the other objects to null. Using the Singleton pattern I have a single instance of a Frame, that same instance would be used in any class (Scene) that wants to run another scene, here an implementation:
App.java
public class App extends JFrame {
private JPanel rootPanel;
private static App app;
private App() {
super("x");
createApp();
}
public static App getInstance() {
if (app == null) {
app = new App();
}
return app;
}
private void createApp() {
//Define JFrame, rootPanel, buttons, etc ...
}
public void runScene(JPanel scene) {
rootPanel.removeAll();
rootPanel.add(scene);
rootPanel.revalidate();
rootPanel.repaint();
}
public static void main(String[] args) { //Main
getInstance().runScene(new MenuScene());
}
}
GameScene.java
public class GameScene extends JPanel {
private Play p;
private Timer t;
private Event e; //Define controls (keyboard)
private JLabel[][] mat;
public GameScene() {
//Define JPanel, Matrix, Play, Timer and Event
...
buttonBackMenu.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent x) {
This method is useful to create another scene for example from another instance other than this (GameScene)
changeScene(new MenuScene());
}
});
}
public void changeScene(JPanel scene) {
e.removeKeyDispatcher(); //You must create a method that allows you to move the event key dispatcher
t.stopAllTimers(); //You must create a method to stop all timers, or any object that is active.
t = null;
e = null;
p = null;
//If you have more active objects and work with other instances of other classes they should be "broken" or "stopped" and then match their instance to null
App.getInstance().runScene(scene);
}
//Optional...
public JLabel[][] getMat() {
return mat;
}
}
Play.java, Event.java, Timer.java (X)
public class X {
private GameScene g;
private JLabel[][] mat;
public X(GameScene g) {
this.g = g;
//I would use the instance of the class I need to access the variables I'm going to use, for example:
this.mat = g.getMat();
}
}
Related
So what I am trying to accomplish is to add ActionListener to a button which is defined in another class, without breaking encapsulation of this button.
My GUI class:
public class GUI extends JFrame {
private JButton button;
public GUI () {
this.button = new JButton ();
}
public void setText (Text text) {
this.button.setText (text);
}
public JButton getButton () {
return this.button;
}
}
My Game class:
public class Game {
private GUI gui;
public Game () {
this.gui = new GUI ();
this.gui.getButton ().addActionListener (new ActionListener () {
public void actionPerformed (ActionEvent evt) {
play ();
}
});
}
public void play () {
this.gui.setText ("Play");
}
}
Then I call a new Game instance in the Main class.
I would like to get rid of the getter in GUI class, otherwise there is no point in using text setter or setters similar to that.
When I add ActionListener to GUI constructor, I have no access to Game methods than. Is there a solution that I don't see?
Normally when I do this, I add an interface that describes the View (GUI), and then have the view implement that interface.
public interface MyView {
void addActionListener( ActionListener l );
}
And the view:
public class GameGui implements MyView {
// lots o' stuff
public void addActionListener( ActionListener l ) {
button.addActionListener( l );
}
}
Then your main code is free from dependencies on what kind of view you actually implement.
public class Main {
public static void main( String... args ) {
SwingUtils.invokeLater( Main::startGui );
}
public static void startGui() {
MyView gui = new GameGui();
gui.addActionListener( ... );
}
}
Don't forget that Swing is not thread safe and must be invoked on the EDT.
Let the GUI add the action listener to the button, let the Game create the action listener:
public class GUI extends JFrame {
public void addActionListenerToButton(ActionListener listener) {
button.addActionListener(listener);
}
....
}
public class Game {
private GUI gui;
public Game () {
this.gui = new GUI ();
this.gui.addActionListenerToButton (new ActionListener () {
public void actionPerformed (ActionEvent evt) {
play ();
}
});
}
...
}
Alternatively just pass in a functional interface instead of a fully built ActionListener.
I can add/remove elements to/from a panel and repaint it when the method used to fill the panel is called by one of its parent JFrame events, but I can not repaint it by events from other classes even if their sources have been added to it, or that is how I understand the problem for now.
I want to understand what is going on here, Thank you.
Main Class
public class Principal extends JFrame implements ActionListener{
private static Principal instPrincipal = null;
private SubClass subClassInst =new SubClass();
public JPanel panelPrincipal;
public static Principal getInstance() {
if (instPrincipal != null)
return instPrincipal ;
else {
instPrincipal = new Principal ();
return instPrincipal ;
}
}
public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
try {
if(source == btnSub)
{
subClassInst.fillPanelPrincipal();
}
}
catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
Sub Classes Example
public class SubClass implements ActionListener {
private JPanel tempPanel;
private JButton btnSave;
private Principal instPrincipal;
public void fillPanelPrincipal() {
instPrincipal = Principal.getInstance();
instPrincipal.panelPrincipal.removeAll();
//Start adding elements..
tempPanel = new JPanel();
instPrincipal.panelPrincipal.add(tempPanel);
btnSave = new JButton("Save");
btnSave.addActionListener(this);
tempPanel.add(btnSave);
//End.
instPrincipal.panelPrincipal.repaint();
}
public void actionPerformed(ActionEvent event) {
instPrincipal = Principal.getInstance();
Object source = event.getSource();
if (source == btnSave) {
// modify local data, Database .. ; //work but need to be repainted on panelPrincipal
instPrincipal.panelPrincipal.repaint();//does not work
}
}
}
Update
To clarify the problem more, I have one single JPanel on a JFrame and there are different classes to fill it for multiple functionalities, I call their methods using JMenuItems on the main frame, these Classes implement ActionListener, passing the panel didn't work, and also the method I am trying here.
I thought about changing the design to use CardLayout, but it was very difficult.
You are calling Principal as a static reference, so how is it supposed to know what frame to repaint? You should pass the instance of the JFrame through the constructor of the subclass. Like so:
private SubClass subClassInst = new SubClass(this);
And create the constructor like this
private JFrame parent;
public SubClass(JFrame parent) { this.parent = parent; }
You can then use it like so
this.parent.repaint();
I'm trying to do a simple calculator program by building my swing interface on netbeans.
I want to have 3 Classes:
GUI Class - which holds the codes for building the interface
Listener Class - holds all the listener in the GUI interface
Boot Class - this will start the application
For simplicity, I will post my code for a single button. My goal here is to change the Buttons visible text from "1" to "11" to test my design. After verifying that my design works I will continue on working on other buttons.
calculatorGUI.class
import javax.swing.JButton;
public class calculatorGUI extends javax.swing.JFrame {
public calculatorGUI() {
initComponents();
}
private void initComponents() {
oneBtn = new javax.swing.JButton();
oneBtn.setText("1");
}
private javax.swing.JButton oneBtn;
public JButton getOneBtn() {
return oneBtn;
}
public void setOneBtn(String name) {
oneBtn.setText(name);
}
}
Listener.class
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class Listener {
class oneBtnListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent ev) {
calculatorGUI g = new calculatorGUI();
g.setOneBtn("11");
}
}
}
Boot.class
public class Boot {
public static void main(String[] args) {
calculatorGUI gui = new calculatorGUI();
Listener listen = new Listener();
Listener.oneBtnListener oneListen = listen.new oneBtnListener();
gui.getOneBtn().addActionListener(oneListen);
gui.setVisible(true);
}
}
The problem is, nothing happens when I click the button. It seems that the actionListener is not being registered to the button. Can I ask for your help guys on which angle I missed?
The issue I am seeing is how you are initializing calculatorGUI twice, once with the default value and another with the changed value. Take out the initialization of calculatorGUI within your Listener class and pass it from your Boot class and it should work fine.
Although if I were you, I would add the GUI implementations within the GUI class, having it within the listener class that is using within the main function is not something I have seen before and would probably not advise.
Modify your code accordingly,
class Listener {
class oneBtnListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent ev) {
if(ev.getActionCommand() == "1")
{
JButton btn = (JButton)ev.getSource();
btn.setText("11");
}
}
}
}
class calculatorGUI extends javax.swing.JFrame {
public calculatorGUI() {
initComponents();
}
private void initComponents() {
oneBtn = new javax.swing.JButton();
oneBtnListener btnListener = new Listener().new oneBtnListener();
oneBtn.setText("1");
oneBtn.setBounds(100,100,100,25);
oneBtn.addActionListener(btnListener);
add(oneBtn);
setLayout(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400,400);
}
private javax.swing.JButton oneBtn;
public JButton getOneBtn() {
return oneBtn;
}
public void setOneBtn(String name) {
oneBtn.setText(name);
}
}
You can change now other part according to your requirement, I just
gave you "1" -> "11", but you can do more.
Best of Luck.
I'm programming in Java, trying to use a cardholder in order to switch between 2 JPanels which are each an extension of their own class. I think I understand the basic concepts but I am having errors in my current revision, when calling the classes. I'm getting a null pointer exception and I think it's a structural problem but I'm not sure how or why.
The main method points to this class
public class Skeleton implements ActionListener{
JPanel cardHolder;
CardLayout cards;
String cardA = "A";
String cardB = "B";
JPanel Jboard;
JPanel Jmenu;
JFrame frame2;
Board board;
Menu menu;
boolean menuSet;
boolean boardSet;
Timer timer;
public class Switcher implements ActionListener{
String card;
Switcher(String card){
this.card = card;
}
#Override
public void actionPerformed(ActionEvent e) {
cards.show(cardHolder, card);
}
}
public Skeleton(JFrame frame){
JPanel menu = new Menu();
JPanel board = new Board();
JFrame frame2 = frame;
timer = new Timer(5, this);
timer.start();
cardHolder = new JPanel();
cards = new CardLayout();
cardHolder.setLayout(cards);
cardHolder.add(menu, cardA);
cardHolder.add(board, cardB);
frame2.add(cardHolder);
frame2.revalidate();
frame2.setVisible(true);
}
public JFrame getSkeleton(){
return frame2;
}
public JPanel getCardHolder(){
return cardHolder;
}
public void checkStatus(){
if (menuSet == true){
new Switcher(cardB);
boardSet = false;
}
if (boardSet == true){
new Switcher(cardA);
menuSet = false;
}
}
#Override
public void actionPerformed(ActionEvent e) {
menuSet = menu.getMenuset();
boardSet = board.getBoardset();
checkStatus();
}
}
This is the board class, one of the JPanels I'm trying to switch between
public class Board extends JPanel{
boolean boardset;
Menu menu = new Menu();
public Board(){
setBackground(Color.WHITE);
}
public JPanel getPanel(){
return this;
}
public void setBoardset(boolean x){
boardset = x;
}
public boolean getBoardset(){
return boardset;
}
}
Here is the other JPanel class, which contains a button used to switch to the other JPanel class. This is also the original starting JPanel used.
public class Menu extends JPanel implements ActionListener{
boolean menuset;
public Menu(){
setBackground(Color.BLACK);
JButton button = new JButton("hello");
button.addActionListener(this);
this.add(button);
}
public JPanel getPanel(){
return this;
}
#Override
public void actionPerformed(ActionEvent e) {
menuset = true;
}
public void setMenuset(boolean x){
menuset = x;
}
public boolean getMenuset(){
return menuset;
}
}
Like I said, I'm getting a null pointer exception. It is occuring on this line of the Skeleton() class
menuSet = menu.getMenuset();
The line above is right after the actionPerformed event above (from the timer), and I have tested the timer a little, it works doing basic print statements but whenever I try to use the 'menu' or 'board' instance inside the actionPerformed, I get this null pointer exception.
I would appreciate any advice. I get the idea that the way I'm doing this may be a little convoluted. If anyone has any suggestions on a better way to do this it would also be helpful. My main goal is to be able to call 2 separate classes from one main class containing a cardholder. That way I can separate the code in order to keep everything isolated and in order.
Your Skeleton class has a "menu" member but it isn't set anywhere that I can see. The constructor declares its own "menu" local variable, which is local to the constructor and hides the member. Setting "menu" inside the constructor won't set the member. I don't see anywhere else where the "menu" member is set, unless I've missed something or unless another class in the same package is setting it.
First off - sorry for the wall of code but it's not too horrendous, just a framework for what I'm trying to explain. It runs without errors.
The goal
I'm making a reuseable button class for my GUI and each button object needs to have a different handler when it's clicked. I want to to assign a ClickHandler object to each new button. Then, the button would call init() on the handler, and be on its way. Unfortunately, there's a typing problem, since each handler class would have a different name.
Current progress
Right now, the handler is typed as HandlerA, but I'd like to have it handle any name, like "SettingsHandler" or "GoToTheWahWah" etc.
I've tried messing about with generic types, but since I'm new to this, and from a webdev background, there seems to be a conceptual hurdle I keep knocking over. Is this the right way to approach the problem?
The code
ReuseableButton.java is a reuseable class, the only thing that changes is the action when it's clicked:
package gui;
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
public class ReuseableButton extends JButton implements ActionListener {
private static final long serialVersionUID = 1L;
// I want a generic type here, not just HandlerA!
private HandlerA ClickHandler;
// Assemble generic button
public ReuseableButton(Container c, String s) {
super(s);
addActionListener(this);
c.add(this);
}
// Once again, generic type, not just HandlerA!
public void SetClickHandler(HandlerA ch) {
ClickHandler = ch;
}
// Call init() from whatever class has been defined as click handler.
public void actionPerformed(ActionEvent e) {
ClickHandler.init();
}
}
Controller.java fires the frame and assembles buttons as needed (right now, only one button).
package gui;
import javax.swing.*;
import java.awt.*;
public class Controller extends JFrame {
private static final long serialVersionUID = 1L;
public Controller() {
JFrame frame = new JFrame("Handler Test GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container pane = frame.getContentPane();
pane.setLayout(new FlowLayout());
ReuseableButton b = new ReuseableButton(pane,"Reuseable Button A");
// THE QUESTION IS HERE: Pass a generic object?
b.SetClickHandler(new HandlerA());
frame.pack();
frame.setSize(200,200);
frame.setVisible(true);
}
public static void main(String args[]) {
new Controller();
}
}
HandlerA.java is a sample of a random handler for the button click. Later, there could be HandlerB, HandlerC, etc.
package gui;
// A random handler
public class HandlerA {
public void init() {
System.out.println("Button clicked.");
}
}
Thanks very much in advance!
All of you handlers should implement an interface like Clickable or something. That way the interface specifies the existence of the init function:
public interface Clickable
{
public void init();
}
Making the HandlerA definition:
public class HandlerA implements Clickable {
public void init() {
System.out.println("Button clicked.");
}
}
I recommend to work with inheritence in this case:
public abstract class AbstractHandler {
public abstract void init();
}
Then:
public class ConcreteHandlerA extends AbstractHandler {
#Override
public void init() {
// do stuff...
}
}
Controller
public class ReuseableButton extends JButton implements ActionListener {
// I want a generic type here, not just HandlerA!
private AbstractHandler ClickHandler;
public Controller() {
//...
ReuseableButton b = new ReuseableButton(pane,"Reuseable Button A");
AbstractHandler handlerA = new ConcreteHandlerA();
b.SetClickHandler(handlerA);
// ...
}
}
Not sure if this is what you're looking for...
BTW: You can define the AbstractHandler as an interface as well, but you may want to implement some common logic here as well - shared across handlers.
You should use an interface for the handler.
public interface ClickHandler() {
void init();
}
ReuseableButton b = new ReuseableButton(pane,"Reuseable Button A");
b.SetClickHandler(object which implements the ClickHandler interface);
This is the same concept as the normal JButton. There you have the ActionListener interface and the actionPerformed method in it.
P.S. If I don't understand your question, please correct me.