Skip to main content

Are there any easy ways to calculate a component's new bounds?

13 replies [Last post]
irond13
Offline
Joined: 2007-06-26

Hi,

I'm looking for a way to calculate the new bounds of a component after manually changing its min,max or preferred sizes - but I need the new bounds before the component is painted to them. Adding a ComponentListener and catching the component on resize won't work because the component will already be painted int its new position by the time componentResized is invoked on the ComponentListener.

By adding a PropertyChangeListener, I can get hold of the new preferred size dimensions, this doesn't help in all cases however, since not all layout managers use preferred sizes the same way.

A simple solution would simply be to override paintComponent and catch the bounds before calling super.paintCompont - this requires subclassing, however, and is messy in any circumstance, but especially mine as I am developing a container and don't necessarily know in advance which components will be added to it.

Any comments are appreciated,

Regards,
Pierre

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
swv
Offline
Joined: 2007-05-28

hey irondi13,

your code has dependencies on substance (and therefore CVS ;) ) and also org.jdesktop stuff... have a version without those?

irond13
Offline
Joined: 2007-06-26

Hi swv,

it turns out when not running under Substance, I do receive the events on time, i.e the ComponentEvents are received before the calls to paint. As such I think this issue is only relevant under Substance. I have posted a new demo app on the substance page and it includes all the libraries you need. On my last post there I did explore another problem related to this - that is if you resize one component which in turn affects the bounds of another one, there will be calls to paint before all the component events are received (this will happen under any LAF)

Regards,

Pierre

swv
Offline
Joined: 2007-05-28

you said:
By adding a PropertyChangeListener, I can get hold of the new preferred size dimensions, this doesn't help in all cases however, since not all layout managers use preferred sizes the same way.

This seems to my mind to be the bottom line. The layout manager is going to determine the size of the component on screen, and you won't know what it's spitting out until JUST before it's painted, at least, not without doing something really invasive and unnatural, it seems. You canoverride paintComponent and snag it, or, you have to know where the code was executing some number of steps before paintComponent, but after the bounds were determined. That seems (all but) hopeless on the face of it.

If I were doing something like this that was the case, I'd think about doing something else or get an OK result some other way.

irond13
Offline
Joined: 2007-06-26

Hi swv,

I don't think I agree with you, because as Kirill pointed out, the setBounds call should theoretically fire a componentResized event via Component.reshape. All Layout Managers have to place the components using setBounds, so this componentResized event (technically a ComponentEvent) should be adequate. It turns out it is adequate, except it doesn't arrive before the repaint is actually carried out in same cases, and in all cases using Substance, the event arrives after the component is repainted.

@Karl,

I didn't actually have a demo, but I have since made one and posted it on the Substance forum (I didn't know how to attach it here). It should be at
https://substance.dev.java.net/servlets/ProjectForumMessageView?forumID=...

Something strange happend when making the demo; initially I had the controls (spinners & buttons) directly on the Frame's content Pane - and it seemed to work (under substance that is). As soon as I put the controls on the panel, the resizes started to play up (hopefully this error will be reproduced so you can see what is going on)

Regards,

Pierre

swv
Offline
Joined: 2007-05-28

But that' s the problem, right? You don't get that message until it's too late... it's put on the EDT like all such messages and it's dispatched at a time you're not in control of- apparently too late!

So what's left for you to do? Find out where setBounds() is actually invoked by the layout manager. That's what I meant.

kschaefe
Offline
Joined: 2006-06-08

It sounds like you are contemplating a few things that you shouldn't be, such as overriding paintComponent to get at some component state. Can I ask why you need to know the (new) bounds? That would help in providing feedback.

Karl

irond13
Offline
Joined: 2007-06-26

Hi,

I wasn't seriously considering subclassing - just saying that that was one possible solution :-)

Want I have is a container which has its clip shape determined by the bounds of it children. Obviously when any of the children are repositioned or resized, the container's clip shape needs to be recalculated. At present, the clip shape function contains getWidth() and getHeight() calls to all of its children, and I recalculate the clip shape each time componentResized() and componentMoved() is called. This means that the new clip shape is only calculated AFTER the component is moved or sized (and painted as such). The result is that when a child component is made smaller, there is delay between the actual component resizing and the container's clip shape resizing. It's this delay which is quite visually evident that bothers me.

Essentially, I'd like to 'catch' the component resize, so I can update the parent's clip shape before it repaints. The property changes are fired before the repaint, so I thought I could use some info from there to recalculate the clip shape before the repaint.

Regards,
Pierre

kirillcool
Offline
Joined: 2004-11-17

You can install a ComponentListener on each child component. Or you can override setBounds on each one of the children. Or, you can let Swing decide on the clip - why bother computing it yourself? Unless the painting of the container is so heavy (and aware of the clip) - which can be addressed with background image caching.

Kirill

irond13
Offline
Joined: 2007-06-26

Hi Kirill, thanks for the reply

I did install ComponentListeners on all components added to my container, but the problem is that these listeners are only fired after the fact i.e. after the repaint call that follows the call to setBounds. I need something which gives me the bounds before that repaint call. For custom components I think overriding setBounds will be my best bet, but otherwise I'm not sure.

I think I didn't fully explain my situation (I'm not setting the clip of the graphics object). My custom container (the one which needs to know the whereabouts of its children) has a ShapePainter instance painting its background. The ShapePainter's shape is an Area instance which is calculated as follows : The area is first set to the outline of the container (in my case a rounded rectangle). Next, the method iterates through all the container's children and subtracts their outlines from the original area. It then repaints the whole container. The end result is almost like a 'cookie cutter' effect (if the children are translucent). As you can see, adding ComponentListeners won't help.

Slightly off topic, is there a reason why setBounds doesn't fire property change events - they definitely would be useful here

Regards,
Pierre

kirillcool
Offline
Joined: 2004-11-17

> I did install ComponentListeners on all components
> added to my container, but the problem is that these
> listeners are only fired after the fact i.e. after
> the repaint call that follows the call to setBounds.
>
> Slightly off topic, is there a reason why setBounds
> doesn't fire property change events - they definitely
> would be useful here

Follow JComponent.setBounds into reshape. It calls notifyNewBounds *before* repainting the component. So i'm not sure why it's not enough for your needs.

irond13
Offline
Joined: 2007-06-26

Hi Kirill,

after looking at reshape() it looks as though it should function as required - but for some reason it doesn't.

This is how I add the component listeners to all the children :

private void formComponentAdded(java.awt.event.ContainerEvent evt) {

evt.getChild().addComponentListener(new ComponentAdapter(){
public void componentMoved(ComponentEvent e){
* RoundedPanel.this.reInitPainters();
RoundedPanel.this.repaint();
}
public void componentResized(ComponentEvent e){
* RoundedPanel.this.reInitPainters();
RoundedPanel.this.repaint();
}
});

}

RoundedPanel is a sub class of JXPanel and reInitPainters is where it recalculates the ShapePainters shape, amongst other things.

I placed debugging breakpoints at the * in the above code, just before manually making one of the children resize at runtime. The debugger stopped at the breakpoint in componentResized - but, contrary to what I think should be the case, by that stage the child had already been repainted!

I then overode paintComponent in one of my custom components just to see where it was being called from (I set the breakpoint at super.paintComponent). When I resized this child component at runtime, the debugger stopped at the paintComponent breakpoint and not at the componentResized breakpoint. The call stack at that time includes calls to RepaintManager.paint, JXPanel.paint, JLayeredPane.paint, JRootPane.paint, JRootPane._paintImmediately and BufferStrategyPaintManager.paint. What is even stranger is that this custom test component paints itself when I click in a totally unrelated textfield (their only common parent is JFrame's content pane).

This is really getting confusing.

Okay, some new info. I'm presently running Substance 4 (20/07/2007). It turns out that if I turn off substance, things work as expected - my component isn't painted untill after the ComponentListeners are noticed and it certainly isn't painted after some totally unrelated event somewhere else in the GUI.

I really hope this isn't going to take too much effort to sort out.

Regards,
Pierre

irond13
Offline
Joined: 2007-06-26

A slight mistake on my part - it turns out the 'unrelated' event which caused my component to repaint after clicking in the text field wasn't that unrelated - it was due to my app losing and gaining focus while I was switching between my debugger and back.

The main problem remains however, that of catching the resize/move event before the repaint & this is where the behiour in Substance deviates from that in standard Swing. I have modified my code in the mean time.

Firstly I removed the calls to repaint in the ComponentListeners to let Swing take control of the repainting responsibilities.
I then modified my resize code slightly. At present my container is using the GUI Builder's interpretation of Group Layout and my two test components (another instance of RoundedPanel and a standard JXPanel) are both horzontally resizable and are set at their default vertical sizes. To change their sizes at runtime, I set their preferred sizes to new values, then I invalidate them and call validate on their parent (my custom container in this case). The results are perfect under the default look and feel - componentResized and componentMoved are called before any further painting takes place.

I noticed that if I rather called revalidate instead of invalidate and getParent.Validate, it wouldn't work. It is as if the parent (my custom container) is one paint step behind its children - you can see the children painted in their correct new positions, but behind them the parent still looks as if the last time it was repainted was before its children were moved/resized. I installed a 'repaint' button on my GUI so I could manually repaint the parent. When pressed after a faulty move/resize, everything appears as normal. This indicates that all my shapes were updated correctly, but the parent's new shape is painted to late.

In Substance the same 'faulty' behaviour occurs - except it also happens when using the child.invalidate parent.validate route - which means Substance never behaves as it should.

I think removing the calls to repaint in the ComponentListener may seem counter-contstructive with this behaviour, but what I want is for the parent's new shape to only be painted when the child is painted in its new position. This is what seems to happen at the moment due to Component.repaintParentIfNeeded in Component.reshape. If you consider a containment hierarchy consisting of more than one RoundedPanel, it would be possible for a component further down the tree to cause a repaint before the other components have correctly calulated their new shapes.

I could definitely be wrong since I'm still a relative Java noob :-) but those are my comments

Regards,
Pierre

(I'm not sure if this should now be moved to the Substance forum, but perhaps some here could explain the difference between revalidating a component as opposed to invalidating it and then validating its parent)

kschaefe
Offline
Joined: 2006-06-08

It sounds like you have a demo that you are using. Do you mind sharing it?

Karl