Swing JPanel won't repaint - java

I have a simple object which extends JPanel, when the update() method is called on this object it it meant to add some labels to the panel and then repaint. However the labels do not show up after the update method is called, below is the code for update:
public void update(){
GridBagConstraints constraints = new GridBagConstraints();
if(cardsHidden){
for(int i = 0; i < 2; i++){
constraints.gridx = i;
constraints.gridy = 0;
JLabel card = new JLabel(PlayingCards.cardImages[PlayingCards.CARD_BACK_INDEX]);
add(card, constraints);
}
}
else{
Card[] holeCards = player.getHoleCards();
for(int i = 0; i < holeCards.length; i++){
constraints.gridx = i;
constraints.gridy = 0;
JLabel card = new JLabel(holeCards[i].getImageIcon());
add(card, constraints);
}
}
validate();
repaint();
}
any ideas?
Thanks
EDIT
solved:
It turns out that the HoleCardsPanel wasn't adding to its parent frame properly. Once that was fixed the adding of new JLabels works fine. I also:
added the call to the update() method to the event dispatch thread using SwingUtillities.invokeLater
had to call validate() from the uppermost component (in this case the JFrame) as Devon_C_Miller suggests in his answer.

It depends on what you want to happen and what layout managers are in use, but the basic rules are:
Make sure update is called on the EDT. If it's not (SwingUtilities.isEventDispatchThread()returns false) you will need to use SwingUtilities.invokeLater to schedule the update on the EDT. For example:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
update();
}});
Call invalidate(). Most things that change a component will do this for you. So you only need to call this if the following does not work on its own.
Call validate() on the highest affected component. This is probably the muddiest bit of Java's rendering cycle. The call to invalidate marks the component and all of its ancestors as needing layout. The call to validate performs the layout of the component and all of its descendants. One works "up" and the other works "down". You need to call validate on the highest component in the tree that will be affected by your change.
Also, calling validate on a top-level component (JWindow, JDialog, JFrame) will not necessarily resize that component. To make that happen, you'll need to call pack() or setSize().
If your changes alter the size or position of containers, The resized containers will repaint, but they will not erase the space the used to occupy. Calling repaint() on the parent of the container will cause it to repaint the background, correcting the damage.

Try calling revalidate(); repaint is not what you want.
As per:
API Docs
Note: If a component has been added to
a container that has been displayed,
validate must be called on that
container to display the new
component. If multiple components are
being added, you can improve
efficiency by calling validate only
once, after all the components have
been added.
revalidate() is basically a invalidate() followed by a validate().
See this question.....

The call to validate() should work, although revalidate() may be sufficient; repaint() should not be required. You might compare what you're doing to this example that does dynamic GridBagLayout.

You could try calling updateUI() method.

Have you tried SetVisible() on the labels ?
Have you tried to add the objets at initialisation without any update ? If they don't show up there, they will never show up.

It turns out that the HoleCardsPanel wansn't adding to its parent frame properly, once that was fixed the adding of new JLabels works fine. I added call to the update() method to the event dispatch thread using SwingUtillities.invokeLater I additionaly had to call validate() from the uppermost component (in this case the JFrame)

Related

Is frame.revalidate() important to get output after an event?

So, I've wrote a code where you get a rectangle after you click the button on the south region of the frame.
But the code didn't seem to work after the button is clicked because of this
public void actionPerformed(ActionEvent ev){
Drawing d = new Drawing();
frame.add(d);
frame.repaint();
}
and the I add revalidate() inside the block.
public void actionPerformed(ActionEvent ev){
Drawing d = new Drawing();
frame.add(d);
frame.revalidate();
frame.repaint();
}
now the code works just fine, but from where I've been studying they didn't use revalidate() and their code worked.
why is this happening?
So, the example you've cited is using a custom painting approach to paint the component. This does not (directly) affect the size or location of the component, so it doesn't need the container to perform a new layout pass.
See Performing Custom Painting and Painting in Swing for more details
Your code is adding a new component to the container, so you need to tell the container when you want the container to be laid out again.
So, from the JavaDocs
public void revalidate() Supports deferred automatic layout. Calls
invalidate and then adds this component's validateRoot to a list of
components that need to be validated. Validation will occur after all
currently pending events have been dispatched. In other words after
this method is called, the first validateRoot (if any) found when
walking up the containment hierarchy of this component will be
validated. By default, JRootPane, JScrollPane, and JTextField return
true from isValidateRoot.
This method will automatically be called on this component when a
property value changes such that size, location, or internal layout of
this component has been affected. This automatic updating differs from
the AWT because programs generally no longer need to invoke validate
to get the contents of the GUI to update.

JFrame doesn't update its content

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.

Swing: Dynamically change visibility of components

When I set the visibility of components (mainly JPanels that have other child components) to false or back to true later, do I have to call .revalidate() and .repaint() on the parent container explicitly or do the LayoutManagers handle this automatically?
When I tried this with a component in BorderLayout-North position for example, everything looked fine without calling revalidate. Just not sure if it will work on all platforms and with all LayoutManagers.
when u set the visibility of components like JPanels. JVM automatically call revalidate() and repaint() method when u set the visibility to true. u does not need to call the all these method. and it will work for all layout
The following code may solve your problem
p2_wrkrreg=new JPanel();
p2_wrkrreg.setBounds(201,0,830,720);
// p2_wrkrreg.setLayout(null);
//p2_wrkrreg.setBackground(Color.white);
p2_wrkrreg.setVisible(false);
In this code,
The JPanel declared and make it invisible, it will be visible on a button click
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==btn_wrkrreg)
{
p2_wrkrreg.setVisible(true);
}
here btn_wrkrreg is a JButton

Why isn't this gui updating?

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.

Java Swing revalidate() vs repaint()

I'm putting together a Swing application where I often want to replace the contents of a JPanel. To do this, I'm calling removeAll(), then adding my new content, then calling revalidate().
However I'm finding that the old content is still actually visible (though obscured by the the new content). If I add a call to repaint() in addition to revalidate(), it works as expected.
I'm sure on other occasions I've experienced that just calling revalidate() is enough.
So basically my question is - should I need to call both functions and if not, when should I call each of them?
You need to call repaint() and revalidate(). The former tells Swing that an area of the window is dirty (which is necessary to erase the image of the old children removed by removeAll()); the latter tells the layout manager to recalculate the layout (which is necessary when adding components). This should cause children of the panel to repaint, but may not cause the panel itself to do so (see this for the list of repaint triggers).
On a more general note: rather than reusing the original panel, I'd recommend building a new panel and swapping them at the parent.
Any time you do a remove() or a removeAll(), you should call
validate();
repaint();
after you have completed add()'ing the new components.
Calling validate() or revalidate() is mandatory when you do a remove() - see the relevant javadocs.
My own testing indicates that repaint() is also necessary. I'm not sure exactly why.
revalidate is called on a container once new components are added or old ones removed. this call is an instruction to tell the layout manager to reset based on the new component list. revalidate will trigger a call to repaint what the component thinks are 'dirty regions.' Obviously not all of the regions on your JPanel are considered dirty by the RepaintManager.
repaint is used to tell a component to repaint itself. It is often the case that you need to call this in order to cleanup conditions such as yours.
revalidate() just request to layout the container, when you experienced simply call revalidate() works, it could be caused by the updating of child components bounds triggers the repaint() when their bounds are changed during the re-layout. In the case you mentioned, only component removed and no component bounds are changed, this case no repaint() is "accidentally" triggered.
yes you need to call
repaint();
revalidate();
when you call removeAll() then you have to call repaint() and revalidate()

Categories

Resources