JButton ActionListener - GUI updates only after JButton is clicked
Do not perform any intensive operations within EDT, otherwise the GUI will be unresponsive and you might not see the GUI updates. Best choice you can use is SwingWorker
:
Override
doInBackground()
, and put any long operations inside this method so that it will be run on a separate thread rather than the EDT.For any GUI creation or changing states of GUI components within
doInBackground()
, usepublish(V... chunks)
to send data toprocess(List<V> chunks)
. You need to overrideprocess(List<V> chunks)
. Also note thatprocess(List<V> chunks)
is executed on EDT.After
doInBackground()
returns,done()
executes on EDT and you can override it to use it for any GUI updates. You can also retrieve the value returned fromdoInBackground()
by usingget()
.Note that
SwingWorker<T,V>
is generic, and you need to specify the types.T
is the type of object returned fromdoInBackground()
andget()
, whileV
is the type of elements you passed toprocess(List<V> chunks)
viapublish(V... chunks)
.execute()
method starts the swing worker by invokingdoInBackground()
first.
For more on this, please read Concurrency in Swing.
GUI not updating visually before running ActionEvent
Every time you execute logic from the GUI you should be using the SwingWorker in the following way:
SwingWorker myWorker= new SwingWorker<String, Void>() {
@Override
protected String doInBackground() throws Exception {
//Execute your logic
return null;
}
};
myWorker.execute();
If you want to update the GUI from inside this logic use InvokeLater:
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
//To update your GUI
}
});
With this you can be sure that both your logic and your GUI stay responsive.
Edit:
You could also use invokeAndWait
if this suits your needs more. Link to related answer
Clicking on one button calls the actionListener of another button in JAVA Swing
This happens because setText
eventually calls paintComponent
, which changes the color to a random color. This happens because the text cannot be changed without redrawing the graphics, which is going to include calling paintComponent
as that is the method used to draw the graphics.
But his only happens when the text is actually changed, so the second time you click the button nothing will happen, since you are not actually changing the text - it will still be "Good Job!".
So, basically the button does not call the action listener of the other button, but both action listeners eventually call paintComponent
(at least on the first click, when the text of the label is actually changed).
If you want to fix this, move the part where color is randomized into the action listener and out of the paintComponent
method.
JButtons won't update on button click
Building on to what this answer points out (using layout managers instead of setting size, as you should be doing) you could reset the the images just by looping through the components (JLabels
) of the JPanel
and changing their icons.
This particular example uses JLabels
with MouseListeners
but it could easily be switched to JButtons
with ActionListeners
newGame.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
reset(iconPanel, icons); <--- call reset method from below
score = 0;
scoreField.setText(String.valueOf(score));
}
});
....
private void reset(JPanel panel, ImageIcon[] icons) {
Component[] comps = panel.getComponents();
Random random = new Random();
for(Component c : comps) {
if (c instanceof JLabel) {
JLabel button = (JLabel)c;
int index = random.nextInt(icons.length);
button.setIcon(icons[index]);
}
}
}
Here's the complete running code. You just need to replace the image paths.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
public class CircleImages {
private int score = 0;
private JTextField scoreField = new JTextField(10);
public CircleImages() {
scoreField.setEditable(false);
final ImageIcon[] icons = createImageIcons();
final JPanel iconPanel = createPanel(icons, 8);
JPanel bottomLeftPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
bottomLeftPanel.add(new JLabel("Score: "));
bottomLeftPanel.add(scoreField);
JPanel bottomRightPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
JButton newGame = new JButton("New Game");
bottomRightPanel.add(newGame);
JButton quit = new JButton("Quit");
bottomRightPanel.add(quit);
JPanel bottomPanel = new JPanel(new GridLayout(1, 2));
bottomPanel.add(bottomLeftPanel);
bottomPanel.add(bottomRightPanel);
newGame.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
reset(iconPanel, icons);
score = 0;
scoreField.setText(String.valueOf(score));
}
});
JFrame frame = new JFrame();
frame.add(iconPanel);
frame.add(bottomPanel, BorderLayout.PAGE_END);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void reset(JPanel panel, ImageIcon[] icons) {
Component[] comps = panel.getComponents();
Random random = new Random();
for(Component c : comps) {
if (c instanceof JLabel) {
JLabel button = (JLabel)c;
int index = random.nextInt(icons.length);
button.setIcon(icons[index]);
}
}
}
private JPanel createPanel(ImageIcon[] icons, int gridSize) {
Random random = new Random();
JPanel panel = new JPanel(new GridLayout(gridSize, gridSize));
for (int i = 0; i < gridSize * gridSize; i++) {
int index = random.nextInt(icons.length);
JLabel label = new JLabel(icons[index]);
label.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e) {
score += 1;
scoreField.setText(String.valueOf(score));
}
});
label.setBorder(new LineBorder(Color.GRAY, 2));
panel.add(label);
}
return panel;
}
private ImageIcon[] createImageIcons() {
String[] files = {"blackcircle.png",
"bluecircle.png",
"greencircle.png",
"greycircle.png",
"orangecircle.png",
"redcircle.png",
"yellowcircle.png"
};
ImageIcon[] icons = new ImageIcon[files.length];
for (int i = 0; i < files.length; i++) {
icons[i] = new ImageIcon(getClass().getResource("/circles/" + files[i]));
}
return icons;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new CircleImages();
}
});
}
}
How to create a JLabel that updates on a click?
You have two ActionListeners:
- one added in the constructor of your button class.
- the second added in your main method.
Swing invokes ActonListeners in the reverse order the listener is added to the component.
So you update the text on the label before the counter variable has been incremented.
A better design is to create a JPanel class. The you create an instance of the panel and add it to the frame. This class would contain:
- the button
- the label
- the counter.
You create the components and add them to the panel and add a single listener to the button.
With this design all the variable are defined in the same class. So all the components can work together with one another. Don't define Swing components in a main() method.
For example see the section from the Swing tutorial on How to Use Buttons for a better design of your code showing how to create a panel with multiple related components.
How to update components listening a button in java
You're removing all components from this
(which in this case is the JFrame
(as you're extending it, which isn't needed, and instead you should create an instance from it rather than inherit from it, as you're not changing the behavior of the JFrame
so it's better to just create an instance of it). See: Extends JFrame vs. creating it inside the program
In this case you're adding your components in this way:
JFrame > buttons (JPanel) > JButtons
And you're trying to remove
JFrame > everything
That includes the contentPane, instead you should call.
buttons.removeAll()
Inside the update()
method.
And also call this.repaint()
so your update()
method should become:
public void update() {
buttons.removeAll();
buttons.add(button3);
this.revalidate();
this.repaint();
}
Or the best approach is to use CardLayout
as recommended by @AndrewThompson in the comment below. This way you don't have to handle removing / repainting for each component, as CardLayout will do it for you. For example
Actions performed on jButton after disabling
I got this working by running task to be performed on click of button on new thread.
How do I update the appearance of my JButton when it's clicked?
The problem is that when I add the code to modify the JButton, none of the changes happen until after the validation has completed.
Your code is executing on the EDT, so you long running code prevents the GUI from repainting itself until the task is finished executing. You need to use a separate Thread for the long running task, maybe a SwingWorker
. Read the section from the Swing tutorial on Concurrency for more information.
Related Topics
How to Parse a JSON String to an Array Using Jackson
Comparing Strings by Their Alphabetical Order
Jpa: What Is the Proper Pattern for Iterating Over Large Result Sets
Why Can Final Object Be Modified
Integer Arithmetic in Java with Char and Integer Literal
Generate Uml Class Diagram from Java Project
Is Memory Leak? Why Java.Lang.Ref.Finalizer Eat So Much Memory
Inter Thread Communication in Java
Plsql Jdbc: How to Get Last Row Id
Arrays.Aslist(Int[]) Not Working
When Is It Ok to Catch Nullpointerexception
Generating a Random Number Between 1 and 10 Java
Method Overload Resolution in Java
Why Are the Level.Fine Logging Messages Not Showing
Compare Two Objects in Java with Possible Null Values
What Hashing Function Does Java Use to Implement Hashtable Class