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.
Related
I have a TabbedPane in a class called App and i want to run a method in this class. I added two tabs with a JPanel from the class Login and an empty one. Here is the class:
public class App {
private static JTabbedPane tabbedPane;
public JPanel mainPanel;
public App(){
tabbedPane.addTab("Login", new Login().mainPanel);
tabbedPane.addTab("test", new JPanel());
changeFocus(0);
}
public void changeFocus(int i){
//CODE HERE
}
}
Now i want to run a method called changeFocus() from an outer class. A added an actionListener to the Login class with a constructor like this:
public Login() {
logInButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
App.changeFocus(1);
}
});
}
Now i ask why this doesn´t work and changeFocus() must be static. And if i change it to static why the JTabbedPane cannot be static and throws out an error.
Simply pass App as an argument to Login's constructor:
tabbedPane.addTab("Login", new Login(this).mainPanel);
and then:
public Login(App app) {
logInButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
app.changeFocus(1);
}
});
}
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();
}
}
I've tried to apply the Observable/Observer pattern but there is something wrong with my code when I try to change a the textfield of a JTextPane.
I've got 3 classes, Play, Controller and SecondWindow here are a sample of their code.
public class Play() {
Controller c = new Controller();
SecondWindow sw = new SecondWindow();
c.addObserver(sw)
c.setText("blabla");
}
My class Controller:
public class Controller extends Observable(){
private String text ="";
private static Controller getInstance() {
if (instance == null) {
instance = new Controller();
}
return instance;
}
public void setText(String s) {
text = s;
setChanged();
notifyObservers();
}
}
and SecondWindow:
public class SecondWindow extends JFrame implements Observer{
private JPanel contentPane;
private Controller c;
private JTextPane txt = new JTextPane();
public SecondWindow () {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
SecondWindow frame = new SecondWindow();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public SecondWindow() {
initComponents();
createEvents();
c = Controller.getInstance();
}
public void initComponents() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(1000, 0, 300,500);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
txt.setBounds(0, 0, 280, 460);
txt.enable(false);
contentPane.add(txt);
}
public void update(Observable arg0 , Object arg1){
// Things to change here
}
I can't manage to put the variable c in the textField (like a txt.setText(c.getText) instruction). I'm sure that it reads the method update, but I don't know how to make sure it works.
Hint: Per the Observerable API the notifyObservers method has an overload that accepts any object as a parameter:
public void notifyObservers(Object arg)
This can even be a String. And as per the Observer API, this object is then passed into the update method in the observer, and you can use it there.
void update(Observable o,
Object arg)
arg - an argument passed to the notifyObservers method.
Separate side issue here:
contentPane.setLayout(null);
For most Swing aficionados, seeing this is like hearing nails on a chalkboard -- it's painful. While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one. Instead you will want to study and learn the layout managers and then nest JPanels, each using its own layout manager to create pleasing and complex GUI's that look good on all OS's.
Side issue number two: your code is not Swing thread safe, since the Swing GUI could very well be notified by the observable off of the Swing event dispatch thread or EDT. While it is not likely to cause frequent or serious problems with this simple program, in general it would be better to use a SwingPropertyChangeSupport and PropertyChangeListeners rather than Observer / Observable if you can.
Next Side Issue
This:
public class Controller extends Observable(){
isn't compilable / kosher Java. Same for the duplicate parameter-less constructors for the SecondWindow class. Yes, we know what you're trying to do, but it's hard enough trying to understand someone else's code, you really don't want to make it harder by posting kind-of sort-of uncompilable code, trust me.
For example, something simple could be implemented in Swing using PropertyChangeListeners, like so:
import java.util.concurrent.TimeUnit;
public class Play2 {
public static void main(String[] args) {
Model2 model2 = new Model2();
View2 view2 = new View2();
new Controller2(model2, view2);
view2.show();
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// one of the few times it's OK to ignore an exception
}
String text = String.format("Counter Value: %d", i);
model2.setText(text);
}
}
}
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
public class Model2 {
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
public static final String TEXT = "text"; // name of our "bound" property
private String text = "";
public String getText() {
return text;
}
public void setText(String text) {
String oldValue = this.text;
String newValue = text;
this.text = text;
pcSupport.firePropertyChange(TEXT, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(name, listener);
}
public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(name, listener);
}
}
import javax.swing.*;
public class View2 {
private JPanel mainPanel = new JPanel();
private JTextField textField = new JTextField(10);
public View2() {
textField.setFocusable(false);
mainPanel.add(new JLabel("Text:"));
mainPanel.add(textField);
}
public JPanel getMainPanel() {
return mainPanel;
}
public void setText(String text) {
textField.setText(text);
}
public void show() {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("View");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(getMainPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class Controller2 {
private Model2 model2;
private View2 view2;
public Controller2(Model2 model2, View2 view2) {
this.model2 = model2;
this.view2 = view2;
model2.addPropertyChangeListener(Model2.TEXT, new ModelListener());
}
private class ModelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
view2.setText((String) pcEvt.getNewValue());
}
}
}
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.
Are there any benefits or drawbacks to creating a nested class that implements ActionListener:
public class Foo{
Foo(){
something.addActionListener(new ButtonListener());
}
//...
private class ButtonListener implements ActionListener{
public void actionPerformed(ActionEvent e){
//...
}
}
}
versus implementing ActionListener in the main class itself:
public class Foo implements ActionListener{
Foo(){
something.addActionListener(this);
}
//...
public void actionPerformed(ActionEvent e){
//...
}
}
I've seen both examples quite often, and just want to know if there's a 'best practice.'
#Ankur, you can still use anonymous inner classes as your listeners and have a separate free-standing control class and thus have code that's quite maintainable, a technique I like to use a bit. For example:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class AnonymousInnerEg {
private static void createAndShowUI() {
GuiPanel guiPanel = new GuiPanel();
GuiControl guiControl = new GuiControl();
guiPanel.setGuiControl(guiControl);
JFrame frame = new JFrame("AnonymousInnerEg");
frame.getContentPane().add(guiPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
class GuiPanel extends JPanel {
private GuiControl control;
public GuiPanel() {
JButton startButton = new JButton("Start");
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (control != null) {
control.startButtonActionPerformed(e);
}
}
});
JButton endButton = new JButton("End");
endButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (control != null) {
control.endButtonActionPerformed(e);
}
}
});
add(startButton);
add(endButton);
}
public void setGuiControl(GuiControl control) {
this.control = control;
}
}
class GuiControl {
public void startButtonActionPerformed(ActionEvent ae) {
System.out.println("start button pushed");
}
public void endButtonActionPerformed(ActionEvent ae) {
System.out.println("end button pushed");
}
}
I think first approach is better, as your class will have a separate code for handling action. And usually also composition is better than inheritance so a class should extend a class or implement a interface only if it truly is-a super type.
Also for maintainability, let us say Foo class has a new requirement to listen for another different type of events and then perform action, in that case also first class can be easily modified.
If I am not worried about maintainability I would rather go for a anonymous class.
If the class Foo has no other responsibility than encapsulating this button, then the first solution is sort of ok.
However, as soon as Foo gets more "somethings" that it has to listen to then it gets messy. I prefer the second solution since it is more explicit and has a better scalability.
An even better solution may be to create an anomymous inner class.
public class Foo{
Foo(){
something.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
//...
}
});
}
}
Usually you want to use a nested or even anonymous class rather than exposing ActionListener to the API of the enclosing class. (public class Foo implements ActionListener -> Javadoc will state that Foo is an ActionListener, though this is usually just an implementation detail -> bad)