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.
Related
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
Background:
From the documentation, the method SwingUtilities.updateComponentTreeUI() ask to each component in the tree (What tree?) to update their own UI properties calling the method updateUI().
SwingUtilities.updateComponentTreeUI() is strictly recommended to be called after we set the LAF, calling the method: UIManager.setLookAndFeel(LAFClassName), otherwise the LAF theme could not be applied to some components and, worst, some components could not be redesigned (and some exceptions could be thrown?).
I am new to this kind of things and I discovered the method SwingUtilities.updateComponentTreeUI() after the discover of the more suggestive UIManager.setLookAndFeel(LAFClassName) method.
Before discovering SwingUtilities.updateComponentTreeUI() I had been having some problems/Exceptions/NullPointerExceptions, and the truth is that I really wasn't understanding the reason of them until I didn't discover the SwingUtilities.updateComponentTreeUI() method, which made me understand that some components in the tree could not be updated to the new LAF.
Concrete/real problem:
For instance, if I have a
subJFrame
(instantiated dynamically or triggered by an ActionEvent (click) in the
mainJFrame
), where I set the look and feels properties, and then I call SwingUtilitiesupdateComponentTreeUI() passing to it a reference to the mainJFrame:
SwingUtilities.updateComponentTreeUI(mainJFrame),
is this last one going to update all its sub-components/subJFrames (and hence updates the subJFrame where I set the LAF) ?
This is not a trivial question (also keeping in my mind what the documentation says), because I call the UIManager.setLookAndFeel(LAFClassName) method in the subJFrame (what actually this call does?) and then I call SwingUtilities.updateComponentTreeUI() method passing to it a reference to the mainJFrame, so what LAF theme is going to be applied to all the sub-components of the mainJFrame? And if some of the sub-components/subJFrames haven't been initialized yet?
You never need to call updateComponentTreeUI if you only set the look and feel before creating components.
If you change the look and feel after having already created components then you need to call the updateUI() method on every existing component. This is what updateComponentTreeUI assists with: it calls updateUI() on the component, as well as all the components it contains (and all the components that they contain; this is what it means by "tree").
If I have a subJFrame where I set the look and feel, and then I call SwingUtilities.updateComponentTreeUI() passing to it a reference to the mainJFrame: is this going to update all its sub-components/subJFrames (and hence updates the subJFrame where I set the LAF)?
If by subJFrame you mean a window that is directly contained within the other (a JInternalFrame) then yes. If you mean a frame which was merely opened from the other frame, but is a separate top-level window, then no, it won't update it, because it is not contained within the component that is being updated.
This is the loop I use for updating all top-level windows after changing the look and feel:
for (Window w : Window.getWindows()) {
SwingUtilities.updateComponentTreeUI(w);
if (w.isDisplayable() &&
(w instanceof Frame ? !((Frame)w).isResizable() :
w instanceof Dialog ? !((Dialog)w).isResizable() :
true)) w.pack();
}
That loop won't catch any components that are not currently attached to windows. For example, this can include JFileChoosers, if you keep instances of those in variables when they're not actively being displayed, in which case you will need to call updateComponentTreeUI separately on those.
When creating an MDI Swing GUI, I have a number of JInternalFrames that are added to a JDesktopPane in a JFrame. I make these internal frames invisible by adding setVisible(false) in the constructor, after the initComponents method (as the GUI builder automatically sets these frames visible in this method).
At runtime, the user can choose to open and close the JInternalFrames by invoking listeners that call setVisible(true) and setVisible(false), depending on the current state of the frames. I like how the previous position and state of an internal frame remains intact using this design. However, something tells me this must be terribly wrong, even though I haven't seen any drawbacks yet.
So, my question is: is this poor design?
In the context of a Multiple Document Interface (MDI), this approach is quite reasonable. In addition, you can use the JInternalFrame method setSelected() to highlight a particular frame. To ease navigation, this and other methods can be used in Action, as shown here.
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()
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().