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.
Related
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.
I've been struggling to get my JFrame to repaint its content. I've tried using both the revaluate() and repaint() methods together after I add the new components into the JFrame but I'm not seeing the frame change.
Here's the simple GUI of a minesweeper game I'm trying to make.
When a user clicks on either of the top 3 buttons, they enter the following code block
private void drawGrid()
{
removeAll();
setLayout(new GridLayout(2,1));
add(new JButton("button"));
setVisible(true);
revalidate();
repaint();
setVisible(false);
setVisible(true);
}
When clicking a button, the whole application turns white, but I can't see the new button I added. When I remove the two setVisible() method lines, then clicking a button definitely removes the components since I can't click on any of them now, but the 4 initial buttons are still visible. Removing the revalidate or repaint methods has no effect on the application.
What else can I try to get the application to refresh and display its new content.
Calling removeAll on a JFrame is dangerous and can produce unexpected results, another reason why it's discouraged to extend from or manage UI's directly on top level containers.
Start by using a JPanel as you base UI component, then use a CardLayout to manage switching between the views.
Separate each view into it's own class (extending from JPanel or something simular) for easier management
Maybe it would work better if you used this
easyBtn.setVisible( false );
medBtn.setVisible( false );
hardBtn.setVisible( false );
customBtn.setVisible( false );
newBtn.setVisible( true );
If that does not work, try putting it into a SwingWorker.
Newbie here trying to make a simple GUI with JTabbedPane. I've looked through a lot of examples but have been unable to find a solution. Basically, I'm trying to print out a String to a JTextArea. While it seems very simple, I have been unable to get everything to work together. I understand the difference between local and global variables, but I think that is where my problem lies. Any guidance would be greatly appreciated. *Please note that we are unable to use a layout manager for this project.
The code below represents part of the tab that has the JButton and JTextArea.
//Text area that shows details. Scrolls.
JTextArea areaDeets = new JTextArea();
areaDeets.setBounds(65, 300, 250, 300 );
areaDeets.setText("");
JScrollPane scroll = new JScrollPane (areaDeets);
scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
panel2.add(areaDeets);
panel2.add(scroll);
areaDeets.addActionListener(new StopTest());
//Stop button--stops tests when pressed.
JButton stop = new JButton("Stop");
stop.setBounds(215, 650, 100, 40);
panel2.add(stop);
stop.addActionListener(new StopTest());
The code below is the method that calls the ActionListener.
//Panel 1 - Stop, shows that the test has been stopped
static class StopTest implements ActionListener{
public void actionPerformed(ActionEvent e){
String stop = "The test has been stopped";
areaDeets.setText(stop);
panel2.repaint();
}
}
Edit: Code does not compile. Eclipse says that I cannot call addActionListener on a JTextField.
You can pass the reference of the JTextArea to the constructor of the ActionListener:
public class StopTest implements ActionListener {
private JTextArea area;
public StopTest(JTextArea area) {
this.area = area;
}
public void actionPerformed(ActionEvent e) {
String stop = "The test has been stopped";
area.setText(stop);
}
}
One possible solution (guessing here due to limited information): don't make the StopTest class static. Rather make it a private inner non-static class or a stand-alone class. This will allow it access to non-static fields of your outer class.
Don't add an ActionListener to your JTextArea as this is not allowed and has no real meaning, since JTextAreas allow returns to be entered, and use this to start a new line in the JTextArea.
Other unrelated recommendations:
Also, as a general rule, you should avoid use of null layout as this makes for very inflexible GUI's that while they might look good on one platform look terrible on most other platforms or screen resolutions and that are very difficult to update and maintain.
Much better would be to use nested JPanels, each using its own layout manager, and then calling pack() on your JFrame after adding all components but prior to displaying it.
Never call setBounds(), setSize(), setPreferredSize() or any similar call on a JTextArea that goes inside of a JScrollPane as it will prevent the JScrollPane from working correctly within your JScrollPane due to your setting bounds. This will prevent the JTextArea from expanding when more lines are added, sometimes preventing display of the scrollbars, or if their displayed, preventing them from working properly. Better to set the JTextArea's viewable columns and rows via one of its constructors that has int parameters.
There's no need to call repaint() after setting the text of a JTextComponent such as a JTextArea, since the textarea's model will notify its view (the part that is rendered on the GUI) of changes, and the view will then automatically call repaint itself.
I'm trying to set a variable to be a new JPanel and then add it once a button is pressed, but it is not working and I don't know why.
code:
private void nextButtonActionPerformed(java.awt.event.ActionEvent evt) {
remove(scriptPanel);
scriptPanel = new GemPanel();
add(scriptPanel);
validate();
repaint();
pack();
}
GemPanel is just a JPanel class I made. When I press the next button, it re-sizes the frame to be as small as possible and nothing actually happens. If I re-size it to normal, the original scriptPanel is still there.
What gives?
Instead of trying to remove and add entire panels, a better, less problem prone approach would be to use a CardLayout that will allow to swap views. You can see more at How to use Cardlayout
Also, by the looks of your method signature, it seems you're using the Netbeans builder too. You may also want to take a look at How to Use CardLayout with Netbeans Gui Builder
This is the problem, I have a class MainWindow that extends JFrame in one of the setup routines I set the layout of the class to a new CardLayout(). This all works fine but when I go to request the the layout from the JFrame and cast what it returns to a CardLayout I get errors because it is returning a BorderLayout. I think you get the picture from hear.
Please find some code below:
public MainWindow()
{
initWindow();
}
public void actionPerformed(ActionEvent e)
{
CardLayout m = (CardLayout)super.getLayout();
m.next(this);
}
private void initWindow()
{
super.getContentPane().setLayout(_mainLayout);
super.setTitle(_WINDOW_NAME);
super.setSize(_DEFAULT_WINDOW_SIZE);
super.setLocationRelativeTo(null);
super.setAlwaysOnTop(true);
super.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
super.setResizable(false);
init_buttons_panel();
super.add(_buttons_panel, "bpanel");
}
I see that the code is not perfect this is because I have been trying everything to trap the problem. Yes I realize casting like that is not good but this is just test code to prove the point. Oh and _mainLayout is you guessed it a CardLayout.
I have also read how to use card layout and my code does not do anything out of the ordinary.
First, you should not cast a layout like that, at least without making any verifications. You should use the instanceof keyword first, to check if it really is a CardLayout, to prevent the program from finishing abruptly.
Second, the problem is here:
public void actionPerformed(ActionEvent e)
{
CardLayout m = (CardLayout)super.getContentPane().getLayout();
m.next(this);
}
Basicly you were getting the layout of the JFrame, and not from the JFrame's content pane, which is the one you actually set to be a CardLayout.
super.getContentPane().setLayout(_mainLayout);
The code you posted doesn't help us since we don't have all the information. We don't know what the variable _mainLayout refers to.
I suggest you start by reading the section from the Swing tutorial How to Use Card Layout for a working example and a better designed program. For one thing there is no reason to extend JFrame since you haven't added any functionality.
The getLayout method of JFrame is inherited from Container and thus does not forward on the call to the JFrame's content pane. You could try using super.getContentPane().getLayout() instead of super.getLayout().