My Java swing button doesn't work - java

I've been making a battleship program that I've been trying to get working with a GUI, but it doesn't want to work. The way in theory it should work is that the GUI starts, it outputs a question to a box(which works), and then the computer waits and executes nothing until you press the button after you've answered your answer to the question. The problem is, my method that waits until you've clicked the button to fetch the data in the text field doesn't do anything. I've written a similar piece of code which demonstrates my problem below.
Test.java (main class)
package taest;
import javax.swing.*;
public class Test {
public static void main(String args[]){
SwingUtilities.invokeLater(new Runnable() {
public void run() {
#SuppressWarnings("unused")
JFrame frame = new Frame();
}
});
Frame.display.setText(getButtonClick());
}
public static String getButtonClick(){
while(true){
if (Frame.hasClicked){
break;
}
}
return Frame.text.getText();
}
}
Frame.java (Frame class)
package taest;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Frame extends JFrame{
JFrame panel = new JFrame("Something");
public static JTextArea text = new JTextArea();
JButton button = new JButton("Click");
public static JTextField display = new JTextField("NOthing");
static boolean hasClicked = false;
static String storage = "";
public Frame(){
setLayout(new BorderLayout());
setSize(400,400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
hasClicked = true;
storage = text.getText();
}
});
Container c = getContentPane();
c.add(display, BorderLayout.CENTER);
c.add(text, BorderLayout.PAGE_START);
c.add(button, BorderLayout.PAGE_END);
setVisible(true);
}
}

Static is not your friend and it's use should be greatly limited. It should NEVER be used to provide "easy" access to class fields for inter class communication
You need to turn the concept on it's head and possibly use some kind of Observer Pattern. This is where you have a class which is "observing" changes on your other class. When a change occurs the observed class notifies the observing class of the change. This decouples the responsibility as the observed class shouldn't care beyond notifying interested parties about something that happens
As a really primitive example...
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String args[]) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
#SuppressWarnings("unused")
JFrame frame = new Frame(new ViewController() {
#Override
public void messageChanged(View view, String msg) {
view.appendLog(msg);
}
});
}
});
}
public interface ViewController {
public void messageChanged(View view, String msg);
}
public interface View {
public void appendLog(String log);
}
public class Frame extends JFrame implements View {
// JFrame panel = new JFrame("Something");
private JTextArea text = new JTextArea(5, 5);
private JButton button = new JButton("Click");
private JTextField display = new JTextField("NOthing");
private String storage = "";
private ViewController viewController;
public Frame(ViewController controller) {
this.viewController = controller;
setLayout(new BorderLayout());
setSize(400, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
storage = text.getText();
viewController.messageChanged(Frame.this, storage);
}
});
System.out.println("display = " + display.hashCode());
System.out.println("text = " + text.hashCode());
Container c = getContentPane();
c.add(display, BorderLayout.CENTER);
c.add(text, BorderLayout.PAGE_START);
c.add(button, BorderLayout.PAGE_END);
setVisible(true);
}
#Override
public void appendLog(String log) {
display.setText(log);
}
}
}
You should also become farmiluar within the concept of Model–view–controller

You are mixing things up,
First things first, the difference between Classes and Objects. A class is a blueprint for an object, so an example of a class is Car. The blueprint of such an object however knows nothing about the state of a particular instance of that class, lets assume that you drive 100 km/u, then you have an instance of Car that stores that it is going at 100 km/u. Blueprints are classes, Objects are instances.
So, public class Car makes an blueprint for cars, and new Car() makes a specific instance of that blueprint in which you can store runtime information.
Now there is a way to tell Java that things belong to the blueprint, static. If a variable is static it is attached to the blueprint. So to keep up with the analogy of cars, a static variable for a car can be its wheelbase, thats something that is defined at compiletime (or in the car analogy at create time).
Back to your problem, you are mixing up classes and objects, what you want to do is have a BattleshipWindow of which instances exist. Of this BattleshipWindow an instance can be created with new, and then its properties can be changed.
Not exactly the answer you want probably, but I hope you now understand the difference between classes and objects, that will help you solve your problem.

Related

Why is my program not listening to keyEvents?

I am trying to develop a main menu for a game in java, but my JMenuItems wouldn't listen to the KeyEvents and I don't understand why. Note that I don't want to set any JMenuBars nor JMenus as this program is intended to be a game used with screen readers, so I don't want accessibility roles to be read. Furthermore, adding a menu complicates the access to the elements with the keyboard and I want the focus to start in the first option.
This is my code:
import java.util.Set;
import java.util.HashSet;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.MenuKeyEvent;
import javax.swing.event.MenuKeyListener;
public class Screen {
public Screen() {
// Accept arrow keys as focus traversal keys
Set<AWTKeyStroke> set = new HashSet<AWTKeyStroke>(KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTrave rsalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
set.add(KeyStroke.getKeyStroke("DOWN"));
KeyboardFocusManager.getCurrentKeyboardFocusManager().setDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,set);
set = new HashSet<AWTKeyStroke>(KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
set.add(KeyStroke.getKeyStroke("UP"));
KeyboardFocusManager.getCurrentKeyboardFocusManager().setDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,set);
// definition of Menu Items
JMenuItem mi=new JMenuItem("play");
JMenuItem mi2=new JMenuItem("exit");
mi.setFocusable(true);
mi2.setFocusable(true);
// Attempt with MenuKeyListener
mi.addMenuKeyListener(new MenuKeyListener() {
public void menuKeyReleased(MenuKeyEvent e) {
System.out.println("Play released");
}
public void menuKeyTyped(MenuKeyEvent e) {}
public void menuKeyPressed(MenuKeyEvent e) {}
});
// Attempt with ActionListener
mi2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
mi.setVisible(true);
mi2.setVisible(true);
JPanel mp = new JPanel();
JFrame mf = new JFrame("Game");
mf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mp.add(mi);
mp.add(mi2);
mf.add(mp);
mf.setVisible(true);
}
public static void main(String args[]) {
new Screen();
}
}
I've tried with both ActionListener and MenuKeyListener, with and without the JPanel, changing visibilities... I also tried to use KeyEventDispatcher but I didn't know how to send a KeyEvent to the component that returns KeyboardFocusManager.getFocusOwner().
Please help.
There's a lot to help with in this case. For starters,
public static void main(String args[]) {
new Screen();
}
is wrong. It is not appropriate to perform any operation that alters the Swing layout or presentation on the main thread. Instead do
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Screen();
}
});
}
if you decide that you want to put swing calls into an object's constructor in this way.
Ideally your Screen would be a JFrame but one that is customized to meet your needs. That means you might want to create a new class GamePanel that extends JPanel
public class Screen extends JFrame {
public Screen() {
super("Game"); // to remind us that the JFrame is created
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new GamePanel());
pack();
setVisible(true);
}
}
public class GamePanel extends JPanel {
public GamePanel() {
super();
}
}
Now, if you want that panel to listen to keys, you need to add a KeyListener interface. This only gives it the capability to listen, it doesn't make it listen.
public class GamePanel extends JPanel implements KeyListener {
...
public void keyTyped(KeyEvent e) {
System.out.println("KEY TYPED: " + e);
}
public void keyPressed(KeyEvent e) {
System.out.println("KEY PRESSED: " + e);
}
public void keyReleased(KeyEvent e) {
System.out.println("KEY RELEASED: " + e);
}
}
Now you can add your key listener to the Screen or to the GamePanel (it will listen to itself). Whenever the focus is in one of these items, and a key is pressed, the even will be routed to the listener.
public class GamePanel extends JPanel implements KeyListener {
public GamePanel() {
super();
addKeyListener(this);
}
...
}
Ideally, you might want to not combine your key listener with the panel itself, but make it a "Command Processor" of some sort. In that case, remove the KeyListener code from the JPanel and make a completely new CommmandHandler object that implements KeyListener.
As far as Actions go, they are convenience items that pre-configure menu entries with lots of things (icons, acceleration keys, text, callback functions). If you are avoiding menus for whatever reason, you will find that much of their utility in setting up menus is misdirected for your purpose. Effectively, they are configuration entries that configure the MenuItem objects to handle a key (through the KeyListener interface and then dispatch a swing Event object. This provide "better than key" tracking of items through an application as it converts a keyboard letter k to a class ActionEvent which is passed to the registered "action handler" typically a subclass of AbstractAction.
An example of an Action would be
public class MoveLeft extends AbstractAction { // which extends ActionListener
private final GameState gameState;
public MoveLeft(GameState gameState) {
super("move left", new ImageIcon(MoveLeft.class.getResource("/images/moveleft.png"));
putValue("SHORT_DESCRIPTION", "left");
putValue("MNEMONIC_KEY", "l");
this.gameState = gameState;
}
public void actionPerformed(ActionEvent e) {
gamestate.getSelected().moveLeft();
}
}
Assuming you wanted this convenience, you would initialize your CommandHandler with Actions, maybe like so:
public CommandHandler implements KeyListener {
private int actionId;
...
public void addAction(Action action) {
handlers.put(action.getValue("MNEMONIC_KEY")., action);
}
public void keyTyped(KeyEvent e) {
Action action = handlers.get(String.valueOf(e.getKeyChar());
ActionEvent event = new ActionEvent(this, id, action.getValue("NAME"));
action.actionPerformed( event );
}
}
As you can see, the added convenience of having Actions defined for the actions within your game is a balanced by making your components that use them be configured by them (and using them in the KeyListener implementations).
For large projects, the simplicity of having all your actions listed generally makes creating Actions well worth the effort; but, for smaller projects that are mostly using custom components, the added one-time-cost of making the KeyListener components use actions might outweigh the benefits.
Don't use JMenuItem this way, that's not how it's intended to be used
Instead, I'd start with JButton. The following makes use of your focus transversal code, the Action API and the key bindings API
import java.awt.AWTKeyStroke;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
Set<AWTKeyStroke> set = new HashSet<AWTKeyStroke>(KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
set.add(KeyStroke.getKeyStroke("DOWN"));
KeyboardFocusManager.getCurrentKeyboardFocusManager().setDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set);
set = new HashSet<AWTKeyStroke>(KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
set.add(KeyStroke.getKeyStroke("UP"));
KeyboardFocusManager.getCurrentKeyboardFocusManager().setDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, set);
Action playAction = new AbstractAction("Play") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("play");
}
};
playAction.putValue(Action.MNEMONIC_KEY, (int)'P');
Action exitAction = new AbstractAction("Exit") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("exit");
}
};
exitAction.putValue(Action.MNEMONIC_KEY, (int)'x');
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0), "play");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, 0), "exit");
am.put("play", playAction);
am.put("exit", exitAction);
JButton playButton = new JButton(playAction);
JButton exitButton = new JButton(exitAction);
add(playButton, gbc);
add(exitButton, gbc);
}
}
}
Now, you could do something similar with JLabel, but, JLabel isn't focusable by default, nor does it render any kind of focus indication. Instead, I might be tempted to just strip down a JButton so it didn't paint its border or content/background instead

How to copy JButton action and change its references to underlying objects?

The following example creates a JFrame with JButton, JTextField and JLabel.
When the button is pressed it increments the value in the text field and label.
This example also creates a 2nd JFrame that is a copy of the first.
The button, text field and label is copied as well.
The issue at hand is the button on the copied frame still updates the text field and label on the original. The 'why' is fairly obvious and is because the code makes specific reference to the text field and label.
Although this isn't written in the best manner but it is a great example of the scenario in which I am addressing.
The objective is, without a major rewrite, what would be the least invasive way to have the copied button action update the copied test field and label instead of the original?
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
class ButtonTextFieldLabel extends JFrame
{
JButton bnt1 = new JButton("B1");
JTextField tf1 = new JTextField("1");
JLabel lbl1 = new JLabel("100");
public ButtonTextFieldLabel()
{
super("Main Frame");
setLayout(null);
bnt1.setBounds(50,100,120,40);
tf1.setBounds(300,100, 80,40);
lbl1.setBounds(200,100,80,40);
bnt1.addActionListener(new ListenerHolder(this));
add(bnt1);
add(tf1);
add(lbl1);
setSize(500,500);
makeCopy(this);
setVisible(true);
}
private void makeCopy(ButtonTextFieldLabel originalObj)
{
JFrame copyFrame = new JFrame();
copyFrame.setTitle("Copy of " + originalObj.getTitle());
copyFrame.setSize(originalObj.getSize());
copyFrame.setLocation(originalObj.getX()+100, originalObj.getY()+100);
copyFrame.setLayout(null);
JButton copyBnt1 = new JButton();
copyBnt1.setBounds(originalObj.bnt1.getBounds());
copyBnt1.setLabel(originalObj.bnt1.getLabel());
copyFrame.add(copyBnt1);
for (ActionListener al : originalObj.bnt1.getActionListeners())
{
copyBnt1.addActionListener(al);
}
JTextField copyTf1 = new JTextField();
copyTf1.setBounds(originalObj.tf1.getBounds());
copyTf1.setText(originalObj.tf1.getText());
JLabel copyLbl1 = new JLabel();
copyLbl1.setBounds(originalObj.lbl1.getBounds());
copyLbl1.setText(originalObj.lbl1.getText());
copyFrame.add(copyBnt1);
copyFrame.add(copyTf1);
copyFrame.add(copyLbl1);
copyFrame.setVisible(true);
}
public void runThis()
{
tf1.setText( Integer.toString(Integer.parseInt(tf1.getText())+1) );
lbl1.setText( Integer.toString(Integer.parseInt(lbl1.getText())+1) );
}
}
class ListenerHolder implements ActionListener
{
ButtonTextFieldLabel ph;
public ListenerHolder(ButtonTextFieldLabel ph)
{
this.ph = ph;
}
#Override
public void actionPerformed(ActionEvent arg0)
{
ph.runThis();
}
}
public class TestBTL
{
public static void main(String[] args){
new ButtonTextFieldLabel();
}
}
You already know the reason for the problem -- you're copying the original ActionListener, complete with its reference to the original GUI components. The overall solution is not to copy the action listener but rather to create your GUI's to hold and maintain their own unique state. One solution is rather than try to copy components via kludge, to create a self-contained GUI object that holds and updates its own state. You can create multiple GUI's using a factory method if desired.
For example:
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import javax.swing.*;
public class TestBtl2 {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
createAndDisplayFrame("Frame 1").setVisible(true);
createAndDisplayFrame("Frame 2").setVisible(true);
});
}
// Factory method
private static JFrame createAndDisplayFrame(String text) {
BtlPanel btlPanel = new BtlPanel();
JFrame frame = new JFrame(text);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(btlPanel);
frame.pack();
frame.setLocationByPlatform(true);
return frame;
}
}
class BtlPanel extends JPanel {
private int value = 0;
private JButton button1 = new JButton(new ButtonAction("Button 1"));
private JLabel label1 = new JLabel("00");
private JTextField textField1 = new JTextField("00");
public BtlPanel() {
textField1.setFocusable(false);
add(button1);
add(Box.createHorizontalStrut(20));
add(label1);
add(Box.createHorizontalStrut(20));
add(textField1);
setPreferredSize(new Dimension(300, 100));
}
public void incrementValue() {
value++;
String text = String.format("%02d", value);
label1.setText(text);
textField1.setText(text);
}
private class ButtonAction extends AbstractAction {
public ButtonAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
incrementValue();
}
}
}
Side Recommendations:
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.
Check out: The Use of Multiple JFrames, Good/Bad Practice?

Using Keybinding

I'm doing some very basic coding, just trying to learn the basic concepts behind keybinding. It all seems very straightforward but there's something wrong with my logic or structure that is keeping my code from executing the way I want it to.
Here is my code
public class Board {
ButtonListener buttonlistener;
EnterAction enterAction;
public Board(){
JFrame skeleton = new JFrame();
skeleton.setDefaultCloseOperation(EXIT_ON_CLOSE);
skeleton.setVisible(true);
skeleton.setSize(400, 400);
buttonlistener = new ButtonListener();
enterAction = new EnterAction();
JPanel panel = new JPanel();
panel.setBackground(Color.BLACK);
JButton button = new JButton("button");
button.addActionListener(buttonlistener);
panel.add(button);
skeleton.add(panel);
panel.getInputMap().put(KeyStroke.getKeyStroke("s"), "doEnterAction");
panel.getActionMap().put("doEnterAction", enterAction);
}
public class ButtonListener implements ActionListener{
#Override
public void actionPerformed(ActionEvent arg0) {
System.out.println("button pressed");
}
}
public class EnterAction extends AbstractAction{
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("enter pressed");
}
}
public static void main(String[] args){
new Board();
}
So, it should be pretty simple. As you can see I'm just trying to make it print out "enter pressed" whenever you press enter, but it isn't printing out anything (unless you click the button also shown in the code above). Also, in eclipse, the EnterAction class is underlined in yellow, I think it may not be being called right, but I don't know why it wouldn't be.
Any help is appreciated, thanks.
Change
panel.getInputMap().put(KeyStroke.getKeyStroke("s"), "doEnterAction");
To
panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("S"), "doEnterAction");
Also
skeleton.setDefaultCloseOperation(EXIT_ON_CLOSE);
the parameter must be JFrame.EXIT_ON_CLOSE or just put number 3.
The immediate issue I can see is with the following statement
panel.getInputMap().put(KeyStroke.getKeyStroke("s"), "doEnterAction");
KeyStroke.getKeyStroke("s") is going to return null. The requirements for the String passed to this method are very particular and not well documented (IMHO).
You could use KeyStroke.getKeyStroke("S") instead, but I prefer to use KeyStroke.getKeyStroke(KeyEvent.VK_S, 0) as there is no chance of ambiguity in the statement.
I would also recommend that you define the focus boundaries as well for the input map...
Instead of panel.getInputMap(), try using panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) to ensure that the key event will be triggered if the window is focused
Take a look at JComponent#getInputMap for more details.
If you haven't already done so, you should also take a look at How to use Key Bindings
I think Azad and MadProgrammer are correct, I only had to make one more simple change in addition to what they recommended to get the program running. I have numbered the three items for you as a comment in the code: (copy and paste and you are good to go).
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class Board {
ButtonListener buttonlistener;
EnterAction enterAction;
public Board() {
JFrame skeleton = new JFrame();
//Change #1 below
skeleton.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
skeleton.setVisible(true);
skeleton.setSize(400, 400);
buttonlistener = new ButtonListener();
enterAction = new EnterAction();
JPanel panel = new JPanel();
panel.setBackground(Color.BLACK);
JButton button = new JButton("button");
button.addActionListener(buttonlistener);
panel.add(button);
skeleton.add(panel);
//Change #2 below
panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke("S"), "doEnterAction");
panel.getActionMap().put("doEnterAction", (Action) enterAction);
}
public class ButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent arg0) {
System.out.println("button pressed");
}
}
public class EnterAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("enter pressed");
}
}
public static void main(String[] args) {
new Board();
}
//Change #3 below
}
here is the screenshot:

Java - Is this use of a class to edit a label bad practice?

I'm building a much much larger program, and I've tried several methods (all of which work) but I'm liking this method currently, although I don't know if certain aspects of it represent bad programming practice. The code used in this example is just to get the idea across without pasting the whole code.
This code (pasted below) creates a new ClassMain object with a label and a static method to edit the label. ClassEditor is instantiated from ClassMain, which returns a button.
Now here is where I want to know if it's bad practice, I have an action on the button which, when clicked, calls the static method in ClassMain and sets the label to a random number. The reason I'm wondering whether it's bad practice is because I don't actually call the method from a direct instantiation of the ClassMain object, I just do: ClassMain.setLabel("");. And I'm not entirely sure what this is calling. I have one instantiation of ClassMain, but if I had multiple, would it still work? So how can I edit aspects of a created object through this way of doing it rather than using a reference variable? If I had multiple classes would it create issues?
Sorry if these questions are rambled, it's hard to ask exactly. I've provided the code below so you can see what I'm on about.
PS: Relating to the issue of if it would be an issue of more than one object of ClassMain, I created another an both buttons in both windows only updated one label. Why is this? And does this mean it's not bad practice if used for one instantiation but bad if used for more? I hope someone can help me out with these issues!
ClassMain:
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class ClassMain extends JFrame {
private static JLabel l;
public static void main(String[] args) {
new ClassMain();
}
public ClassMain() {
super("This is my app");
setSize(450,80);
setLayout(new GridLayout(0,2));
l = new JLabel("Hi");
ClassEditor ce = new ClassEditor();
add(l);
add(ce.getButton());
setVisible(true);
}
public static void setLabel(String stringA) {
l.setText(stringA);
}
}
ClassEditor:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
public class ClassEditor implements ActionListener {
public ClassEditor() {
ClassMain.setLabel("Click the button for a random number!");
}
public JButton getButton() {
JButton b = new JButton("Click me!");
b.addActionListener(this);
return b;
}
public void actionPerformed(ActionEvent arg0) {
int i = (int) (Math.random()*10);
ClassMain.setLabel("Random Number: "+i);
}
}
Big thanks to anyone who can help me out, very much appreciated. Just trying to learn and understand good practices and why they work.
I probably wouldn't use static metods and variables and simply rewrite it like this (I also changed names - a good practice is to have everything named in a way that everyone knows what does it mean):
ClassMain:
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class ClassMain extends JFrame {
private JLabel label;
public static void main(String[] args) {
new ClassMain();
}
public ClassMain() {
super("This is my app");
setSize(450,80);
setLayout(new GridLayout(0,2));
label = new JLabel("Hi");
ClassEditor classEditor = new ClassEditor(this);
add(label);
add(classEditor.getButton());
setVisible(true);
}
public void setLabel(String text) {
label.setText(text);
}
}
ClassEditor:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
public class ClassEditor implements ActionListener {
private ClassMain classMain;
public ClassEditor(ClassMain classMain) {
this.classMain = classMain;
classMain.setLabel("Click the button for a random number!");
}
public JButton getButton() {
JButton button = new JButton("Click me!");
button.addActionListener(this);
return button;
}
public void actionPerformed(ActionEvent event) {
int i = (int) (Math.random()*10);
classMain.setLabel("Random Number: "+i);
}
}
Although this works, it has the big problem that ClassEditor is tightly-coupled to ClassMain, i.e. it cannot be re-used for any other purpose.
It would be better to pass the JButton to the editor class in its constructor.
This kind of loose coupling makes your code simpler, easier to modify, easier to re-use and easier to test.
public class ClassEditor implements ActionListener {
JButton button;
public ClassEditor(JButton b) {
button = b;
button.setLabel("Click the button for a random number!");
}
public void actionPerformed(ActionEvent arg0) {
int i = (int) (Math.random()*10);
button.setLabel("Random Number: "+i);
}
}
Another common pattern is to use an anonymous listener:
final JButton button = new JButton();
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
button.setLabel("Boo!");
}
});
Both instances updated the same label because you made it a static variable.That means any instance of your main class will refer to the same jlabel object. It took me a long time to really understand how the statics work. I had static variables everywhere in my swing app.

Handle JButton click event in another class

I'm new to java coming from C# so I'm not familiar with java best practices.
I have a main class that opens a JFrame to get several input strings from a user. When the user clicks submit the GUI should close and the main class continue processing using the input.
This is the main class:
public class Main {
FInput fInput;
public void main(String[] args) {
if(args.length==0)
{
fInput = new FInput();
fInput.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fInput.pack();
fInput.setVisible(true);
}
else
startProcess(args);
}
public void startProcess(String[] args) {
// Do stuff
}
The main class will use this frame to get input from the user:
public class FInput extends JFrame{
private JTextField txtSourceDirectory;
private JTextField txtTargetDirectory;
private JTextField txtDefectNumber;
private JTextField txtSliceTokens;
private JButton btnStart;
public FInput() {
// Initialize text fields and button
JButton.addActionListener(something);
}
}
In all the examples I could find, the listener would be a FMain itself. However in this case I want Main to listen and use the input in method startProcess.
Would having Main implement ActionListener, and passing it to FMain constructor is the way to go?
Yes, that is the right idea. You must do two things in order to be able to do that, though:
Put this at the beginning of the FInput class:
Main m = new Main(this);
Then, put these lines somewhere in the Main class...
FInput gui;
public Main(FInput in) { gui = in; }
Now you can refer to any component in the FInput class from the Main class by doing something like this.
gui.someComponent ...
To set up listeners just write someComponent.addItemListener(m); or something of the sort.
Hope this helps!
#Yoav In response to your latest comment...
You don't have to separate the listening class from the GUI class; you can combine the two into one class...
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Main extends JFrame implements ActionListener {
private JTextField txtSourceDirectory;
private JTextField txtTargetDirectory;
private JTextField txtDefectNumber;
private JTextField txtSliceTokens;
private JButton btnStart;
public Main() {
txtSourceDirectory = new JTextField(40); //change this to the amount of characters you need
txtTargetDirectory = new JTextField(40);
txtDefectNumber = new JTextField(40);
txtSliceTokens = new JTextField(40);
btnStart = new JButton("Start");
add(txtSourceDirectory);
add(txtTargetDirectory);
add(txtDefectNumber);
add(txtSliceTokens);
add(btnStart);
btnStart.addActionListener(this);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent event) {
//do stuff
}
static void startProcess(String[] ARGS) {
//do stuff
}
public static void main(String[] args) {
if (args.length == 0) {
Main frame = new Main();
} else {
startProcess(args);
}
}
}
Also consider using JOptionPane, shown here, in your Main class. You can customize the appearance, including button text, as shown in How to Make Dialogs.
First main method in java always must be public static void. Below is example how this can be done.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
* Main class is frame but also implements ActionListener interface.
*/
public class Main extends JFrame implements ActionListener {
private JButton btnStart;
private static Main frame;
public Main() {
JPanel panel = new JPanel();
btnStart = new JButton("Press me");
// Add action listener. Listener could be any class that implements
// ActionListener
btnStart.addActionListener(this);
// This means add button btnStart to panel
panel.add(btnStart);
// This means add panel to frame
this.add(panel);
}
// main method in java always must be public, static and void. You forgot to
// put static.
public static void main(String[] args) {
if (args.length == 0) {
frame = new Main();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
} else
frame.startProcess(args);
}
public void startProcess(String[] args) {
// TODO
}
#Override
public void actionPerformed(ActionEvent arg0) {
// Here you put your code that is executed every time you press button.
// For example you just want to show message.
JOptionPane.showMessageDialog(this, "You pressed Button.");
}
}
But it is much better to have special class. For example:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
public class ButtonListener implements ActionListener {
JFrame parent;
public ButtonListener(JFrame parent) {
this.parent = parent;
}
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(parent, "You pressed Button");
}
}
And in the main class you just add action listener to button:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
* Main class is frame but also implements ActionListener interface.
*/
public class Main extends JFrame {
private JButton btnStart;
private static Main frame;
public Main() {
JPanel panel = new JPanel();
btnStart = new JButton("Press me");
ButtonListener listener = new ButtonListener(this);
// Add action listener. Listener could be any class that implements
// ActionListener
btnStart.addActionListener(listener);
// This means add button btnStart to panel
panel.add(btnStart);
// This means add panel to frame
this.add(panel);
}
// main method in java always must be public, static and void. You forgot to
// put static.
public static void main(String[] args) {
if (args.length == 0) {
frame = new Main();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
} else
frame.startProcess(args);
}
public void startProcess(String[] args) {
// TODO
}
}

Categories