需要无效 Swing 组件的高度
基本设置是这样的:我有一个垂直的 JSplitPane,我想要一个固定大小的底部组件和一个调整大小的顶部组件,这是通过调用 setResizeWeight(1.0)
来完成的。在此应用程序中,有一个按钮可恢复“默认”窗口配置。窗口的默认高度是桌面高度,默认分隔线位置是距离分割窗格底部 100 像素。
要将分隔线位置设置为 100px,我将 JSplitPane 高度设置为 - 100。问题是,在此之前我调整了 JFrame 的大小,并且由于代码位于按钮回调中,因此 JSplitPane 已失效但尚未调整大小。所以分隔线位置设置不正确。
这是一个 SSCCE。单击该按钮两次即可查看问题。第一次单击将调整窗口大小,但分隔线位置保持不变(相对于窗口底部)。第二次单击会正确移动分隔线,因为窗口大小没有改变。
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSplitPane;
public class SSCCE {
/**
* @param args unused
*/
public static void main(String[] args) {
new SSCCE();
}
private final JFrame f = new JFrame("JSplitPane SSCE");
private final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,true);
public SSCCE() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sp.add(new JLabel("top"));
sp.add(new JLabel("bottom"));
sp.setResizeWeight(1.0);
f.getContentPane().add(sp);
f.getContentPane().add(new JButton(new AbstractAction("Resize to Default") {
@Override
public void actionPerformed(ActionEvent e) {
restoreDefaults();
}
}),BorderLayout.PAGE_END);
f.setSize(400,300);
f.setVisible(true);
}
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}
Rectangle getDesktopRect(GraphicsConfiguration gc) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension size = toolkit.getScreenSize();
Insets insets = toolkit.getScreenInsets(gc);
return new Rectangle(insets.left, insets.top, size.width - (insets.left + insets.right), size.height - (insets.top + insets.bottom));
}
}
我想到了一些可以解决这个问题的方法,但它们看起来都有点黑客。到目前为止,我最好的想法是在设置帧大小和设置分隔线位置之间调用 f.validate()
,但我担心强制验证可能会产生副作用早期的。
我想到的另一个选项是使用 EventQueue.invokeLater() 来将设置分隔符位置的调用放在事件队列的末尾。但这对我来说似乎有风险 - 我假设 JSplitPane 将在那时得到验证,并且我担心这可能是一个错误的假设。
有更好的办法吗?
The basic setup is this: I have a vertical JSplitPane that I want to have a fixed-size bottom component and a resizing top component, which I accomplished by calling setResizeWeight(1.0)
. In this application there is a button to restore the "default" window configuration. The default height of the window is the desktop height, and the default divider location is 100 pixels from the bottom of the split pane.
To set the divider location to 100px, I take the JSplitPane height - 100. The problem is, just before this I resize the JFrame, and since the code is in a button callback, the JSplitPane has been invalidated but not yet resized. So the divider location is set incorrectly.
Here is a SSCCE. Click the button twice to see the problem. The first click will resize the window, but the divider location remains the same (relative to the bottom of the window). The second click properly moves the divider, since the window size didn't change.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSplitPane;
public class SSCCE {
/**
* @param args unused
*/
public static void main(String[] args) {
new SSCCE();
}
private final JFrame f = new JFrame("JSplitPane SSCE");
private final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,true);
public SSCCE() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sp.add(new JLabel("top"));
sp.add(new JLabel("bottom"));
sp.setResizeWeight(1.0);
f.getContentPane().add(sp);
f.getContentPane().add(new JButton(new AbstractAction("Resize to Default") {
@Override
public void actionPerformed(ActionEvent e) {
restoreDefaults();
}
}),BorderLayout.PAGE_END);
f.setSize(400,300);
f.setVisible(true);
}
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}
Rectangle getDesktopRect(GraphicsConfiguration gc) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension size = toolkit.getScreenSize();
Insets insets = toolkit.getScreenInsets(gc);
return new Rectangle(insets.left, insets.top, size.width - (insets.left + insets.right), size.height - (insets.top + insets.bottom));
}
}
I have thought of a few ways I might get around this, but they all seem sort of hackish. So far the best idea I've had has been to call f.validate()
in between setting the frame size and setting the divider location, but I'm concerned there might be side effects to forcing validation early.
The other option I thought of is to use EventQueue.invokeLater()
to put the call to set the divider location at the end of the event queue. But that seems risky to me - I'm assuming the JSplitPane will have been validated at that point, and I'm concerned that may be a faulty assumption to make.
Is there a better way?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
花了一段时间(可能是因为这里是清晨:-)来理解这个问题,所以只是为了确保我明白了:
如果是这样,解决方案是将框架大小调整与底部大小分开成分。您的第二个选择是完全正确的:调整框架大小并将底部合成调整大小包装到invokeLater(EventQueue或SwingUtilities,无关紧要)中。
这保证按预期工作,因为 invokeLater 将请求放在所有已排队事件之后的最后:
Took a while (probably due to being early morning here :-) to understand the problem, so just to make sure I got it:
If so, the solution is to separate the frame resizing from the sizing the bottom component. Your second option is dead on: resize the frame and wrap the bottom comp resize into a invokeLater (EventQueue or SwingUtilities, doesn't matter).
That's guaranteed to work as expected, because the invokeLater puts the request as last after all already queued events:
您可以创建一个自定义操作类来处理按钮单击和调整大小事件。这种方法看起来像这样:
这样,负责处理设置标准大小的逻辑任务的代码将位于一个易于理解的类中。
You could create a custom action class that handles the button click and the resize event. This approach would look like this:
This way the code that is responsible for handling the logical task of setting the standard size will be in one single and easy to understand class.
没什么复杂的,基本的挥杆规则
nothing complicated, basic Swing Rules
我通常会尽量避免在任何组件上调用 setPreferredSize() 。我宁愿让布局管理器完成它的工作。在这种情况下,这意味着设置框架的大小并让 BorderLayout 占据所有可用空间。
I generally try to avoid invoking setPreferredSize() on any component. I would rather let the layout manager do its job. In this case this would mean setting the size of the frame and let the BorderLayout take all the available space.