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()
Related
A quick question,
Is it necessary to call revalidate() method each time, you change the location of components?
And what are other instances, when it is necessary to call revalidate() method?
Every time you create or modify (location, size or internal content) or remove a component or multiple components, it tells the layout manager the new component list.
Yes, once you change the location, it is necessary to call revalidate().
Is it necessary to call revalidate() method each time, you change the location of components?
You should not be setting the location of components. That is the job of the layout manager.
The point of using revalidate() IS to invoke the layout manager so the size/location of the components can be determined based on the rules of the layout manager.
So you invoke revalidate() and typically repaint() AFTER you add or remove components from a visible container.
I have written a custom component that extends JPanel and overridden its paint() method. Now I can see that this method is called once per 10 milliseconds when the component is displaying. Nothing changes in the component but paint() is still called. I have several calls repaint() but none of them is called actually. How to know what is causing such frequent updates?
UPDATE!
There was "bug" in my code. I was updating inner components form paint() method so it was the root cause of continuous repainting.
But still, the question is not answered: how to understand what supplies events to the queue?
how to understand what supplies events to the queue?
Whenever a property of a Swing component is changed the component will automatically invoke repaint() on itself. The paint request is passed to the RepaintManager.
The RepaintManager will then consolidate multiple repaint requests into a single painting of all components. The consolidation is done to make painting more efficient.
So the individual component that made the request is not available because in many cases multiple components will make the repaint request at the same time.
You can read Painting in AWT and Swing for a more detailed explanation.
using debug
make a break point in the paint() function
and when it called you can watch the stack trace of the call back
I'm trying to understand when to use revalidate/repaint/pack.
Surprisingly I haven't found much detailed under-the-hood documentation (feel free to link).
So far I have understood that this is all the responsibility of the RepaintManager.
paint/repaint refer to what sees as dirty/clean
pack/validate/revalidate refer to what is valid
This article on Oracle explains that calling a repaint enqueues a job on the Event Dispatcher Thread that will in turn call paintImmediately() on the component (this redraws the component).
This trail indicates that to be or not to be valid is associated with the LayoutManager. And that this is all about the size of the component rather than the content.
Is it true that you should call revalidate when you move/resize your component and repaint when you change it's contents?
Is the pack() method really a deprecated thing that you should never call?
Are any of the above claims wrong?
Here are a few basic cases where you need to invoke those methods (I cover the basics but I may have missed a few other cases where calling those methods would be required).
You should call revalidate() on a container when you have either: added one or more component, removed one or more components, changed the constraints of one or more contained components (constraints or XXXSize(), although the latter is not recommended), changed the LayoutManager of the container.
You should call repaint() whenever you want that component (and its descendants) to be repainted. Eventually, this will call paintComponent() (for basic widgets this will delegate to XXXUI.paint()), paintBorder() and paintChildren() (at least in Swing)
pack() actually sets the size of a window to its preferred size. You should usually call this right before making the window visible. You may call it later on but this will give a weird user experience (I don't know many applications that resize their windows once displayed)
The main advantage of using revalidate() and repaint() is that they can coalesce themselves. This means that if you call several times repaint(), the painting will only be performed once.
For some reason, my paintComponent(Graphics g) method is being called infinitely. I can't seem to tell who is calling it, even if I dump a StackTrace in the call (it's an event that's dispatched and handled).
Are there any easy ways to find out who's triggering the event?
Update: I've found the cause and I now understand the reason. Whoever answers it correctly will get the answer to the question.
Here is the code that's causing the issue:
#Override
public void paintComponent(Graphics g)
{
myJButton.setIcon(ResourceLoader.getImageIconWithLocale(MY_BUTTON_IMAGE));
super.paintComponent(g);
}
FYI: It's a really tricky one!! It's not obvious by looking at the code. I made an assumption that was wrong.
I don't know which component this is, but setting an icon on a button from within a paint routine is a bad idea. It will definitely cause the button to be repainted. If the button is a child of your component then setting the button invalidate the component too causing an infinite loop.
Set the icon somewhere else such as where the dialog / window is set up initially.
The setIcon(ImageIcon) will revalidate and repaint itself ONLY if the ImageIcon is another instance.
When working with Locales, most people are use to the ResourceBundle, which returns Strings, which in turn are immutable. Therefore setting the text over and over doesn't matter.
However, in this case, the ResourceLoader (custom class) returned a new instance of an ImageIcon. Sure it was the same Image, but it was another instance. And if you decompile the code, you'll see that setIcon (at least for JButtons), it will repaint and revalidate if newIcon != oldIcon.
The solution was to use a HashMap in the ResourceLoader, this way it saves from loading the images more than once since most of the images are used very frequently (might as well reuse the instances if you can). Turns out that overall this quick adjustment also saved a decent amount of overall memory consumption as an added bonus.
Are you calling repaint() anywhere? Also, when a window becomes visible (uncovered or deminimized) or is resized, the "system" automatically calls the paintComponent() method for all areas of the screen that have to be redrawn.
The problem is that you are setting the icon in the paintComponent() method. You should never set a property in this method.
Swing components are smart enough to repaint themselves whenever a property changes. In this case you have the problem of the component repainting itself because the Icon changes, but you are also rereading the Icon every time the component repaints itself which is also not very efficient.
Whenever I remove and add swing components from say JPanel, shall I perform call on validate or revalidate?
revalidate() would be better. revalidate() marks all the container upto the top level as not proper or not valid. Then it calls validate() on the top level. The validate() method of the parent checks if at least one of its immediate children is signaled invalid or improper. it calls validate of the parent.
so calling revalidate() automatically means calling validate().
revalidate() is basically a invalidate() followed by a validate().
Look at Sun's Java source code.
You want to call revalidate().
At least in Java 7, revalidate() doesn't necessarily "erase" removed components from the screen. I believe that happens when the bounding box shrinks. For these cases, call repaint() after the revalidate().
I would think revalidate() is what you want. The validate() method will be automatically called for you after a call to revalidate(). See the Java API for JComponent.revalidate().