Java/AWT/Swing: about validation and sizes
I'm a bit confused at how to manage the layout of my components in Java (I want to do it manually and not handle it by a layout manager). There are these methods in a Component
:
layout
,doLayout
validate
,invalidate
,revalidate
validateTree
,invalidateTree
setSize
,setBounds
,setPreferredSize
getSize
,getBounds
,getPreferr开发者_如何转开发edSize
paint
,repaint
,update
updateUI
Earlier, I have tried to overload various combinations of the above but I was not quite sure which one to overload and what exactly I have to do inside and what functions I have to call on child components.
What I am doing now is:
- Only overload
doLayout
of the above. - In
doLayout
, for all child components:- Call
child.doLayout
. - Call
child.setBounds
(sometimes beforechild.setBounds
, sometimes after, sometimes both).
- Call
- In
doLayout
, because I am doing the layout, I automatically have calculated also its preferred size. - In
doLayout
, callthis.setPreferredSize
. - In all constructors, call:
this.setLayout(null)
. - In some constructors, call:
this.doLayout
. (And if I don't, it doesn't display correctly.) - When I do some operation where I must redo the layout (e.g. I dynamically have added some textfield in some container and thus I want to resize the container and also all parents accordingly), I call
container.revalidate()
.
Remaining problems:
- I think I still haven't really understood what function is calling what, what I have to overload and how to handle.
- In
doLayout
, I callthis.setPreferredSize
.doLayout
itself often depends onthis.getSize()
. For the parent, oftenchild.setBounds
depends onchild.getPreferredSize()
. So I have the dilema that in some cases, I first have to callchild.doLayout
and thenchild.setBounds
and in some other cases the other way around. And in some cases even more complicated. So it all seems that I have to callthis.setPreferredSize
somewhere else. But where? Because it always has to be updated when the size changes (that is why I had overloadedsetBounds
earlier but that was even more ugly). - I have everything inside a
JScrollPane
which sets the scrollbars according toviewportView.getPreferredSize()
. Therevalidate
I am calling in cases where I want to recalculate the layout causesdoLayout
calls which correctly callsetPreferredSize
for all components/containers in the hierarchy. Though, it seems that theJScrollPane
sets its scrollbar before thedoLayout
s got called and thus it is always wrong. How can I fix that?
Some further thoughts (please comment on them) about how I maybe could fix the JScrollPane
problem (haven't really tried because it would require some major rewrites, so I wanted to ask first):
- Remove all
setPreferredSize
calls indoLayout
. - Overload
getPreferredSize
and calldoLayout
from there (to get the preferred size).
-- OR --
- Instead of calling
revalidate
when I do something which requires to redo the layout, callvalidateTree
.
-- OR --
- Instead of calling
revalidate
when I do something which requires to redo the layout, call alldoLayout
manually and then arevalidate
on theJScrollPanel
.
And finally, how to I go about the circular dependency of size and preferred size? I.e., I quite often have this case:
comp.width
is fixed at the root. I can set the width on the root and recursively go down to all childs and set its width.comp.height
is fixed at the most inner child and depends on its width. So after I have set all widths, I can calculate and sets the heights from the bottom up.
I cannot call setPreferredSize
before I haven't called setSize
. And I cannot call setSize
before I haven't called setPreferredSize
.
All of your issues and questions could be solved if you used LayoutManagers. You seem to be trying really hard to avoid them but really they are your best ally when it comes to layout out GUI items.
A lot of people try to avoid LayoutManagers at first because of the learning curve. What I found very useful when I was learning them was the visual guide to layout managers: http://download.oracle.com/javase/tutorial/uiswing/layout/visual.html.
My suggestion would be to find a way to get your layout to work with LayoutManagers. Even you solve some of these issues, making changes or someone else having to read your code is going to be very time consuming. Plus with everything you have to do to get this to work there is a lot of room for bugs due to leaving something out on one component.
I am afraid you are in a wrong track.
Use Stewart's answer and set a null layout manager. That allows you to set the position of all components by calling setBounds().
Then, add a listener on the window which is called whenever the window is resized, and recalculate the positions and call setBounds().
This all can be automated if you implement your positioning code as a layoutmanager. (After all, positioning the components at resize == layout management, so believe or not, you are developing a layout manager, it is this simple!)
public class MyLayout implements LayoutManager,LayoutManager2 {
@Override
public void addLayoutComponent(Component comp, Object constraints) {
// TODO Auto-generated method stub
}
@Override
public Dimension maximumLayoutSize(Container target) {
// TODO Auto-generated method stub
return null;
}
@Override
public float getLayoutAlignmentX(Container target) {
// TODO Auto-generated method stub
return 0;
}
@Override
public float getLayoutAlignmentY(Container target) {
// TODO Auto-generated method stub
return 0;
}
@Override
public void invalidateLayout(Container target) {
// TODO Auto-generated method stub
}
@Override
public void addLayoutComponent(String name, Component comp) {
// TODO Auto-generated method stub
}
@Override
public void removeLayoutComponent(Component comp) {
// TODO Auto-generated method stub
}
@Override
public Dimension preferredLayoutSize(Container parent) {
// TODO Auto-generated method stub
return null;
}
@Override
public Dimension minimumLayoutSize(Container parent) {
// TODO Auto-generated method stub
return null;
}
@Override
public void layoutContainer(Container parent) {
// Now call setBounds of your components here
}
}
In the layoutContainer method, you can call the setBounds of all the components. This method is called when the window is laid out initially, as well as every time when there is a resize.
Then, when you e.g. put things to a window or to a JPanel, simply setLayoutManager(new MyLayoutManager()) and you're golden.
However, a very crude question still remains. Your layout manager is a separate class, but still it has to access to the components you're created elsewhere in your window code. The brute-force solution is to simply get a reference to all components in the constructor, e.g.:
class MyWindow extends JFrame {
public MyWindow() {
JLabel label=new JLabel("Hello");
JButton button=new JButton("Ok");
setLayoutManager(new MyLayoutManager(label,button)); // PASS THEM
add(label);
add(button);
pack();
setVisible(true);
}
}
Of course, it is a naive approach, but could work. The proper way for handling this is to implement the addLayoutComponent in your layout manager, which is getting called whenever you add anything to a JFrame (such as when calling add(label)). This way the layout manager is aware of the components in the layout.
After reading your comments, it seems that you just want to do absolute positioning using a null layout. There's a tutorial on it, but the gist is that you set the layout manager to null for your container, and then call setBounds() on each component in that container to set its size and position, like this:
JPanel p = new JPanel();
p.setLayout(null);
JButton b = new JButton ("Hit It");
p.add(b);
b.setBounds(new Rectangle(10, 20, 100, 50));
精彩评论