I am trying to add JPanels and JButtons from a separate class to the frame, which is in the main class. The program compiles with no errors, but only shows a black window. The visual goal is to have a button that changes color when clicked. I create the button in the GUI_1_1 class, and create the frame in the main class. What am I doing wrong?
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class main {
public static void main(String[] args) {
JFrame frame = new JFrame ("Cube GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setSize(1370,790);
frame.getContentPane().setBackground(Color.BLACK);
frame.add(new GUI_1_1());
frame.setVisible(true);
}
}
The second class is below
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class GUI_1_1 extends JPanel {
private int count;
private JButton button0;
private JPanel mainpanel;
private JPanel panel1;
public GUI_1_1() {
mainpanel = new JPanel();
mainpanel.setLayout(null);
count = 0;
button0 = new JButton("[1][1]");
button0.addActionListener(new ButtonListener());
button0.setOpaque(true);
button0.setBounds(60,310,50,50);
mainpanel.add(button0);
add(mainpanel);
}
private class ButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
if(count==0){
button0.setBackground(Color.RED);
count = count + 1;
} else if(count==1) {
button0.setBackground(Color.GREEN);
count = count + 1;
} else if(count==2) {
button0.setBackground(Color.ORANGE);
count = count + 1;
} else if(count==3) {
button0.setBackground(Color.BLUE);
count = count + 1;
} else if(count==4) {
button0.setBackground(Color.YELLOW);
count = count + 1;
} else if(count==5) {
button0.setBackground(Color.WHITE);
count = count - 5;
}
}
}
}
OK, there's a lot to explain here of what you're doing wrong, so I won't be able to break it all down for you. I can start with the basics though.
Firstly
Don't take it too hard. Swing is a thing of it's own. Java has a reputation of being very abstract so you don't have to know how it works - just call it and let Java figure it out for you. I don't totally agree with that for Java, but for Swing I absolutely disagree with it. Swing gets a bad reputation for this very reason, and I think its unwarranted 90 percent of the time.
Just because you're calling an abstracted API doesn't mean you don't have to know how it works. Swing is especially this way. That said, I can't even begin to break it down in this answer because there are lots of specifics, a lot of which don't even apply directly to your code but you should know indirectly, so you'll just have to spend time learning it which just takes time and reading. The most important thing to take away though, is that Swing is not just some abstract black box you don't have to understand - you must learn it and not just call random functions, or you will end up finding your code misbehaves if you do.
Secondly
And you might see this a peeve (and not related to your question), but it becomes important later after you're done learning - work on your class naming. Classes should be self-descriptive of what they do, so while JavaDocs are helpful, they shouldn't be 100 percent necessary for someone reading your code to understand what it's doing.
Third
And finally getting to your question.
EDT
Never do GUI work outside of the Event Dispatch Thread (EDT). In any environment that has a GUI, you have one singular thread to do that display work. Your music player has it, your internet browser deals with it - Java is no different. And to really drive it home how important this is - if you can figure out how to do multithreaded GUI work, you stand to make a lot of money and never work another day for the rest of your life.
Your main method is run in a thread of it's own, and the EDT is started implicitly by your program. Your GUI is supposed to be updated in the EDT, and those abstracted things that Java (and Swing) do that I mentioned early, will automatically happen in the EDT. Your code, however, does not. What you want to look at are SwingUtilities.html#invokeLater(java.lang.Runnable) (or alternatively invokeAndWait(...)) where you put your code as
public static void main (String [] args) {
SwingUtilities.invokeLater(()->{
//things you want to happen only in the EDT
});
//other things to happen in your main thread
JFrame
Then there's the line where you say:
frame.add(new GUI_1_1());
So a JFrame is a little different than other containers. You don't want to 'add' things to a JFrame. Instead, you want to set your JPanel as the content pane of your JFrame.
Other than that, the line before that you get the content pane, and then set the background color of it. Which, now that you'll be using your GUI_1_1 as the content pane, you can imagine how that line won't make sense anymore. BTW, with relation to your black screen you claim to be seeing, your content pane not being your display seems to be the issue.
LayoutManager
One of the things you're doing in GUI_1_1's constructor is to set the layout manager to null. This is another whole thing all it's own, which I won't really detail because it's too much to type - and Oracle has already done a nice job with that, which I fully recommend reading to figure out how to use layout managers correctly: https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html
And on and on
I could keep going for a while on this but this answer is already pretty verbose as it stands. Like I mentioned, Swing is a whole thing all it's own that requires just learning it, as with any API really.
I can keep editing this answer with more information, or links to more reading if you have other questions or other specifics. But I'm going to cut this short to keep more 'general purpose' suggestions out, from the ones I already put above.
Related
I started not so long ago learning Java through tutorials and videos, and after understanding a few things (how buttons, layouts, audio, and a few other things work) one of my goal now is to create a little interactive game.
I wrote a pretty big part of the game in the Main Class and it was working good, but it got messy after a while.
So I decided to try another time from the beginning using different classes for every part of the game to make the code look more clear and understandable.
But I have a problem from the very beginning and after a few hours of searching tutorials, answers on forums and not finding a precise answer, I think it's the best if you will see exactly my problem (which is very simple!)
-So I just constructed the JFrame in a class (I use the main Class only to launch the frame, and it works fine) :
import javax.swing.*;
import java.awt.*;
public class principalFrame {
public principalFrame(){
JFrame mainFrame = new JFrame();
mainFrame.setVisible(true);
mainFrame.setSize(1200,750);
mainFrame.getContentPane().setBackground(Color.BLACK);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.setResizable(false);
}
}
and I created a JPanel in another class :
import javax.swing.*;
import java.awt.*;
public class mainMenu{
public mainMenu(){
JPanel menuPanel = new JPanel();
menuPanel.setSize(300,300);
menuPanel.setBackground(Color.BLUE);
}
}
And my goal is to add the JPanel inside the JFrame. And... I don't understand how to do it.
I tried to add the menuPanel class as an object in the mainFrame class so I could add the JPanel but it did'nt work. Then tried a bunch of other solutions from what I read on older questions but nothing really helped me.
PS : I know that I didn't add any layout manager or any other things in the code here because I want to keep the code very simple for the question.
So if you want to make a separate class for mainMenu, you should make it extend JPanel. This way, your mainFrame class can instantiate it.
Here is what I would tell you about the lessons I learned in building things in swing:
You (almost) never need a separate class to create a JFrame. If you create a class that extends JPanel, it is easy enough to create a JFrame in a static method (like main) to put your JPanel in. That being said, if you also want to use the JLayeredPane or want to add menus on a JMenuBar, there might be a case of subclassing JFrame. The advantage of not subclassing JFrame is that it makes it easier to stick your JPanel into a JFrame, a JDialog (via JOptionPane) or a JWindow.
Unless you have a Component that you think could be useful in other applications, you should build your entire GUI in one class, and also use that class as the Controller. What is an example of a Component that could be used in other applications? Back in the day, I made a "ColorButton" swing class, for color picking. This component has a self-contained model, has its own controller and an API to get the picked color. (https://sourceforge.net/p/tus/code/HEAD/tree/tjacobs/ui/ex/) which makes it reuseable. But normally, the elements of the GUI you are building aren't really reuseable outside of what you are doing in that class.
With that said, to the code you posted above:
Your mainMenu JPanel isn't accessible outside the class, so that is probably a problem. As I said in the paragraph above, if you're not sure that the Component you are creating could be used in another place, it is better to put the entire view and model building in the same class as the controller
You were close. You have to be able to get the JPanel from the MainMenu class.
The JFrame methods must be called in a specific order. This is the order I use for my Swing applications.
Class names start with an upper case character.
Here's one way you could code your MainMenu class.
public class PrincipalFrame {
public PrincipalFrame() {
JFrame mainFrame = new JFrame();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.add(new MainMenu().getMenuPanel(), BorderLayout.CENTER);
mainFrame.setSize(1200, 750);
mainFrame.setResizable(false);
mainFrame.setVisible(true);
}
}
public class MainMenu {
private final JPanel menuPanel;
public MainMenu() {
this.menuPanel = new JPanel();
this.menuPanel.setPreferredSize(300, 300);
this.menuPanel.setBackground(Color.BLUE);
}
public JPanel getMenuPanel() {
return menuPanel;
}
}
I'm writing a Java text adventure and using Java Swing as a way to display a simple user GUI as the game play screen. I want to have three different windows, one as a start screen, one for a character creation screen where the user inputs a name and some attributes, and one where the game actually takes place.
I want to write each screen in a separate class to increase the organization and readability of my code. However whenever I click on the JButton to go to the next screen a completely new JFrame opens with the content. How can I write my GUI so that all of the screen changes take place in just one frame? Is Java supposed to function like this?
I've looked through at least a couple dozen Java Swing forum questions but still cant find out what I'm doing wrong with my Java swing implementation.
package guiPackage;
import javax.swing.*;
import thingsPackage.Player;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class StartGame extends JFrame {
JFrame Game;
JPanel buttonMenu, screen;
JButton newGame, settings, exit, loadGame;
public StartGame() {
Game = new JFrame();
Game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Game.setVisible(true);
Game.setSize(750, 750);
Game.setLocationRelativeTo(null);
screen = new JPanel();
screen.setVisible(true);
TitleScreenHandler ts = new TitleScreenHandler();
buttonMenu = new JPanel();
newGame = new JButton("New Game");
newGame.addActionListener(ts);
loadGame = new JButton("LoadGame");
loadGame.addActionListener(ts);
settings = new JButton("Settings");
settings.addActionListener(ts);
exit = new JButton("Exit");
exit.addActionListener(ts);
Game.add(screen);
screen.add(buttonMenu, BorderLayout.SOUTH);
buttonMenu.add(newGame);
buttonMenu.add(loadGame);
buttonMenu.add(settings);
buttonMenu.add(exit);
}
public class TitleScreenHandler implements ActionListener{
public void actionPerformed(ActionEvent titleScreenEvent) {
if (titleScreenEvent.getSource() == newGame) {
buttonMenu.setVisible(false);
CharacterCreator newCharacter = new CharacterCreator();
}
else if (titleScreenEvent.getSource() == loadGame) {
buttonMenu.setVisible(false);
}
else if (titleScreenEvent.getSource() == settings) {
buttonMenu.setVisible(false);
}
else if (titleScreenEvent.getSource() == exit){
System.out.println("goodbye");
System.exit(0);
}
}
}
}
package guiPackage;
import javax.swing.*;
import java.awt.*;
import java.util.Scanner;
public class CharacterCreator extends StartGame {
JTextField newName;
JPanel creatorScreen;
Scanner scan = new Scanner(System.in);
public CharacterCreator() {
creatorScreen = new JPanel();
//creatorScreen.setVisible(true);
newName = new JTextField("Create a charactor");
//newName.setLocation(null);
screen.add(creatorScreen);
creatorScreen.add(newName);
}
public String setCharacterName(){
String characterName = "";
return characterName;
}
}
Is Java supposed to function like this?
Your code is only doing what you're telling it to, and you've several problems in your code including one problem here:
public class CharacterCreator extends StartGame {
You're misusing inheritance by havng the CharacterCreator class extend from StartGame. You seem to be doing this to allow communication between classes, but that is not what inheritance is for, and in doing this you're child class is calling the parent class's constructor, creating more windows than you want or need. The solution is to not use inheritance here but rather to pass references to where they are needed.
Also please check out The Use of Multiple JFrames, Good/Bad Practice? since your application should have only one main application window or JFrame. Any child windows should be JDialogs, not JFrames.
Another issue: StartGame extends JFrame and also holds a JFrame variable -- too many JFrames, and best to get rid of one or the other. Usually you don't want to create classes that extend a top-level window since you'll find yourself painting yourself in a corner by having your class extend JFrame, forcing you to create and display JFrames, when often more flexibility is called for. In fact, I would venture that most of the Swing GUI code that I've created and that I've seen does not extend JFrame, and in fact it is rare that you'll ever want to do this. More commonly your GUI classes will be geared towards creating JPanels, which can then be placed into JFrames or JDialogs, or JTabbedPanes, or swapped via CardLayouts, wherever needed. This will greatly increase the flexibility of your GUI coding.
Other separate and unrelated issues:
You appear to be combining an event driven GUI with a console GUI since you're using a Scanner object initiated with System.in. This is not a good idea since these two programming paradigms do not easily play nice with each other, and instead it is best to stick with one or the other, either a linear console program or an event-driven GUI.
In short, you should try using jLayeredPane and multiple jPanels, not multiple frames. If you're using Netbeans, check out my guide to making GUIs at: http://philofjava.webstarts.com/
Dear friends of Stackoverflow,
here what I'm trying to do:
- I would like to have a simple frame with some buttons and a JTextArea
- I would like to have a loop that, at every iteration, expects for me to click on a button: when I click on this button a bunch of stuff happens, but I can't get it right:
- in one attempt I got the for loop to work but it would not stop, at every turn, it just took the first command and performed all 20 turns without stopping
- in the current version I click the button and nothing happens
- I have already researched SOF and a series of other sites, including the Oracle documents but (probably also due to my level of experience), I cannot find an explanation that is clear enough for me to understand
here my code
package game4_prova_forloop;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class GAME4_prova_forloop {
public static void main(String[] args) {
//create frame
JFrame frame = new JFrame("Action Listener");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 600);
frame.setLayout(new FlowLayout());
frame.setVisible(true);
//crete text area panel,
JPanel pannelloTextArea = new JPanel();
pannelloTextArea.setBackground(new Color(255, 204, 204));
pannelloTextArea.setSize(400, 400);
frame.add(pannelloTextArea);
GroupLayout pannelloTextAreaLayout = new GroupLayout(pannelloTextArea);
//create scrollPane
JScrollPane scrollTextArea = new JScrollPane();
//1) create JTextArea
JTextArea mostraTesto = new JTextArea(20, 20);
mostraTesto.setLineWrap(true); //make it wrap text
pannelloTextArea.add(scrollTextArea); //add it to scroll pane
pannelloTextArea.revalidate();
scrollTextArea.add(mostraTesto); //add the scroll pane to text area
scrollTextArea.setViewportView(mostraTesto);
//2) create buttons
JButton action1 = new JButton("1");
frame.add(action1);
JButton action2 = new JButton("2");
frame.add(action2);
JButton action3 = new JButton("3");
frame.add(action3);
//3) pass textArea in the RoundLevelAction class
RoundLevelAction roundLevelActionObj = new RoundLevelAction(mostraTesto); //first issue: I get an error
//4) add listener to JButtons
action1.addActionListener(roundLevelActionObj);
action2.addActionListener(roundLevelActionObj);
action3.addActionListener(roundLevelActionObj);
}
//THIS IS WHERE I START TO HAVE PROBLEMS: WHEN I CLICK NOTHING HAPPENS, WHEN
//I WOULD EXPECT THE FOR LOOP TO GO THROUGH ITERATIONS
public class RoundLevelAction implements ActionListener {
//add inside the listener the pieces of GUI that you'll use
private JTextArea mostraTesto;
private Object action1;
private Object action2;
private Object action3;
private Object action4;
private Object action5;
private Object action6;
//create class for JTextArea
public RoundLevelAction(JTextArea mostraTesto){
this.mostraTesto = mostraTesto;
}
//and, finally, what I really want to do: a loop that, at each turn, expects me to click on
//a button and does an action in response
public void actionPerformed(ActionEvent e) {
//now create the loop
for (int round_counter=1; round_counter<21; round_counter++) {
if (e.getSource()==action1){
mostraTesto.append("\n description action 1 and a bunch of other stuff");
}
else if (e.getSource()== action2){
mostraTesto.append("\n description action 2 and a bunch of other stuff");
}
else if (e.getSource()== action3){
mostraTesto.append("\n description action 3 and a bunch of other stuff");
}
}
}
}
}
IMPORTANT: I'm well aware that the code above does not fit Java best practices: it's just a sample code to illustrate what I'm trying to do (the original code is a lot of lines in multiple classes)
I hope you can help me to understand where I'm doing wrong
Many thanks in advance
Why is your code currently not working, and what was probably wrong with your previous attempt.
action1.addActionListener(roundLevelActionObj);
action2.addActionListener(roundLevelActionObj);
action3.addActionListener(roundLevelActionObj);
This will add the same listener to each of your buttons. When you click on either of these buttons, an ActionEvent is generated and send to the ActionListener.
When you have a for loop in your ActionListener, the whole loop will be executed each time you click on any of these buttons. It is the whole code block in the actionPerformed method that gets executed. This is probably what you discovered in your first attempt.
Your current attempt has if (e.getSource()==action1) checks in the for loop. However, the action1 in that statement is not the same as the button on which you clicked. The action1 here refers to the field
private Object action1;
in your RoundLevelAction class. If you use a debugger, you will see that none of those if statements evaluate to true, which is why you have the impression that nothing happens.
In fact, the for loop is triggered but nothing is outputted because none of the if statements evaluated to true.
All the above can easily be discovered if you use a debugger, and place some breakpoints.
Now for the solution to your problem. That is unclear to me.
I would like to have a loop that, at every iteration, expects for me
to click on a button
The problem with this requirement is that Swing is single threaded. This means that all Swing related operations should happen on a single thread (the E(vent)D(ispatch(T(hread)), and that same thread is used to handle the user input (e.g. mouse clicks) and to paint the UI. This also means that if you somehow block that single thread, your UI becomes unresponsible.
So if you have a loop, you cannot simply block the EDT and wait for a button click. Since the EDT is blocked, it becomes impossible to click on the EDT.
Either you loop on a separate thread, and wait in that thread until the user clicked on the button. The ActionListener associated to that button can then re-activate the thread
You can show a dialog asking the user what to do next, and put those buttons in that dialog. Take a look at the JOptionPane#show... methods for this.
the excellent answer from Robin, although not a direct solution, made me think about the logic of the program, and ask myself: why do I have a for loop at all? To count the turns backwards from 20 to 0?
Well..I can always count forward (duh!). So I got rid of the for loop altogether, put a counter++for each one of the else ifs, and encapsulated everything into an if counter<20.
Now every iteration is activated by the pressure of one of the buttons, the iterations go up and the game follows the logic.
NOT THE BEST SOLUTION but "a" solution.
I hope this can be useful for another user
I have a Gui and a Game class, and I'm unable to update the gui from the game. I'm not using threads, but I've seen it update before, so that isn't the problem. The game logic is really simple, there is no need for threads. No matter how furiously I call repaint() and revalidate(), it doesn't work now, no matter where I put it.
class Gui {
//...
public Gui(Game game) {
this.game = game;
initialize();
}
private void initialize() {
//...
okButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
okAction(textField.getText());
textField.setVisible(false);
okButton.setVisible(false);
textField.setText("");
}
});
}
private void okAction(String input) {
game.receiveInput(input);
}
public void output(String msg) {
textArea.append(msg + "\n");
}
public void getInput() {
textField.setVisible(true);
okButton.setVisible(true);
textField.setText("");
}
}
Also I want to be able to pass a String back to the game instance. I thought I'd call getInput() from the game, which will show a JTextField to type in, and a JButton to submit. In the actionPerformed() method I would just get the text entered, and call a method back in the game class. I don't know if this would work, since the gui is not updating, and I never had the input field and button appear. Is this right?
This would be the method which the gui "calls back":
class Game {
//...
public void receiveInput(String input) {
int n = Integer.parseInt(input);
if ( validInput(input, actualDecision.choices.size()) ) {
parser.setAction(actualDecision.choices.get(n-1).action);
}
}
}
From the game class, I just want to call gui.output() and gui.getInput() a few times.
Where is my problem? Why isn't it updating, nor freezing? If I use the debugger, the both output() and getInput() is executed, but nothing happens...
EDIT:
Ok I see a problem myself, with the getting input part... Since it returns quickly, it can never receive an input. But that doesn't explain why aren't the input field and the button, or any text is showing up
EDIT 2:
Oh god, sorry, I really don't know how to make it shorter, but you only ever need to look at the Game and the Gui, others are just there to compile.
The code: https://gist.github.com/anonymous/53bad714592792316b4d
An xml to test against: https://gist.github.com/anonymous/30b56facb78fe6ecd482
Honestly I have just taken a look to Gui class code and don't know why it doesn't update properly when it interacts with Game class. BUT I have several remarks on your code and I hope these can lead you in the right way by making this class simpler and then you can focus on the interaction with Game class.
Menu bar
You're adding the JMenuBar to a JPanel instead of setting it to the JFrame:
panel.add(menuBar, gbc);
//frame.setJMenuBar(menuBar); Use this instead
As JFrame is a top level container prepared to handle a menu bar you should consider make the suggested change.
Saving validate() call
As the JFrame is initialized at the start of initialize() method and the JPanel is added after making the frame visible then you have to call frame.revalidate() method to revalidate the components hierarchy. If you just initialize the panel before make the frame visible then you don't need to call revalidate() method. Take a look to this answer for further details.
Missing pack() call
There's no call to frame.pack() method to lay out the frame's subcomponents. Take a look to Window.pack().
Missing GridBagConstraints when adding okButton
There's no GridBagConstraints as argument when adding okButton to panel:
panel.add(okButton);
//panel.add(okButton, gbc); This is the correct way.
Use of setSize()
In this line:
frame.setSize(800, 600);
We should avoid using set(Preferred | Minimum | Maximum)Size() because of reasons discussed in this topic: Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
Use of GridBagLayout
This is just a suggestion there's nothing wrong on how you are using GridBagLayout. As probably you have noted this layout manager is a little hard to use (and I really don't like it by the way :) You can use a Nested Layout approach to make the components layout easier and your code more readable. Maybe this approach is good enough:
Set the JMenuBar to the JFrame. It's one less component to lay out
;)
Add the scroll pane with the text area directly to the frame's
content pane using
BorderLayout
constraints: frame.getContentPane().add(scrollPane,
BorderLayout.CENTER);
Create a new JPanel to hold the text field and the button used to
ask for user's input and add it to the frame's content pane.
Translated to code:
JPanel usersInput = new JPanel(new FlowLayout(FlowLayout.CENTER));
usersInput.add(textField);
usersInput.add(okButton);
frame = new JFrame();
frame.setJMenuBar(menuBar);
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
frame.getContentPane().add(usersInput, BorderLayout.SOUTH);
frame.setTitle("Choose your own adventure");
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // It's better practice DISPOSE_ON_CLOSE ;)
frame.setVisible(true);
Update
Well I'm curious and I really want to test your game (quite nice work by the way :) The thing is I have found at least two problems in the logic:
Use of == to compare strings
In Parser class there are several string comparisons using == but this is not the proper way to compare strings. We must use .equals() method to compare strings equality. Take a look to this topic: How do I compare strings in Java?
Game.processStory() has an endless loop
This method has an endless loop here:
while ( !end() ) { // this condition never is false so the loop is infinite
...
}
Looking closer to Game.end() method I have found an incorrect string comparison:
private boolean end() {
return ( parser.getAction() == "end" );
//It should be: return parser.getAction().equals("end");
}
But fixing that doesn't solve the problem either: it turns parser.getAction() always returns "d1" and consequently it will never be equal to "end".
Having said this as Game.play() is executed in the Event Dispatching Thread (a.k.a. EDT) triggered by newGameItem menu item and Game.processStory() has this endless loop, then the EDT is getting blocked and Gui is never updated.
In this case I would suggest you take a look to Concurrency in Swing trail to learn about how to avoid blocking the EDT.
I have a bunch of buttons in my JToolBar, and I set some of them to be disabled or enabled depending on the state of my application. I find when I am updating a number of buttons at once that they are not all repainted at the same time. I want to ensure that when I set a number of buttons to be disabled/enabled, that they all change state at the same time.
Below is a small test that demonstrates the problem. (It needs a file a.png in the current directory to use as a button icon.) When you run it, a toolbar with 10 buttons is shown. Pressing Enter at the terminal will toggle the disabled state of all of the buttons. On my machine at least, each time I do this the buttons are repainted in a seemingly random order, and not all at once.
It seems like double buffering might solve the problem, although the first thing I tried (setting double buffering on the JToolBar) didn't seem to affect anything.
Thanks,
Cameron
import java.awt.*;
import javax.swing.*;
import java.io.*;
import java.util.*;
public class Test {
public static void main(String[] args) throws IOException {
final JButton[] bs = new JButton[10];
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame f = new JFrame("test");
JToolBar t = new JToolBar();
f.getContentPane().add(t);
for (int i = 0; i < bs.length; i++) {
bs[i] = new JButton(new ImageIcon("a.png"));
t.add(bs[i]);
}
f.pack();
f.setVisible(true);
}
});
BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
for (;;) {
r.readLine();
EventQueue.invokeLater(new Runnable() {
public void run() {
for (JButton b : bs) {
b.setEnabled(!b.isEnabled());
}
}
});
}
}
}
As long as the enable state changes happen in one event thread run, they happen so that nothing can't go there between in invalid state. So I guess this is just a painting problem? It just looks bad?
One possible way to change the behavior is to add a repaint call for the container (the toolbar for example) so that bigger area is repainted at once instead of repainting each button separately.
Works fine for me using JDK6 on XP.
I don't see any problem with your code. I've enabled/disable more components than that at one time without a problem.
Is the problem because you are accepting input from the terminal? Try adding a separate button to your frame such that clicking on it will cause the state of the buttons to change.
#camickr I'm on JDK6 on Windows 7. I kicked off the changes with EventQueue.invokeLater() because in my application, what causes the toolbar buttons to need to update state happens in another thread, so I use EventQueue.invokeLater() there. I tried doing everything from the EDT (i.e., doing the setEnabled() calls from an ActionListener on one of the buttons) but I still had the same strange repainting.
#iny Right, it just looks bad. I did actually think that because all the state changes happen within one EventQueue Runnable that all the painting would happen at once after it ended, but I guess not. Your suggestion of calling repaint() on the JToolBar did coalesce all the painting for the buttons, thanks! (Once my unregistered account (which I can't seem to get back to) and my recently registered account are merged, I'll accept your answer.)