基本设置如下:我有一个垂直的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));
}
}
我想到了一些可以解决这个问题的方法,但它们看起来都像是一种hackish。到目前为止,我最好的想法是在设置帧大小和设置分频器位置之间调用f.validate()
,但我担心可能会有早期强制验证的副作用。
我想到的另一个选项是使用EventQueue.invokeLater()
来调用以在事件队列的末尾设置分隔符位置。但这对我来说似乎有风险 - 我假设JSplitPane将在那时得到验证,而我担心这可能是一个错误的假设。
有更好的方法吗?
答案 0 :(得分:5)
花了一段时间(可能是因为在这里的清晨:-)来理解这个问题,所以只是为了确保我得到它:
如果是这样,解决方案是将帧大小调整与底部组件的大小分离。你的第二个选择是死的:调整框架的大小并将底部的comp大小调整为invokeLater(EventQueue或SwingUtilities,无所谓)。
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
sp.setDividerLocation(sp.getSize().height - 100);
}
});
}
保证按预期工作,因为invokeLater在所有已经排队的事件之后将请求放在最后:
/**
* Causes <i>doRun.run()</i> to be executed asynchronously on the
* AWT event dispatching thread. This will happen after all
* pending AWT events have been processed. [...]
* If invokeLater is called from the event dispatching thread --
* for example, from a JButton's ActionListener -- the <i>doRun.run()</i> will
* still be deferred until all pending events have been processed.
答案 1 :(得分:2)
没什么复杂的,基本的Swing规则
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
public class SSCCE {
/**
* @param args unused
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
SSCCE sSCCE = 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") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(sp.getLastDividerLocation());
restoreDefaults();
}
}), BorderLayout.PAGE_END);
f.setPreferredSize(new Dimension(400, 300));
f.pack();
f.setVisible(true);
}
void restoreDefaults() {
//EventQueue.invokeLater(new Runnable() {
// @Override
// public void run() {
f.setPreferredSize(new Dimension(f.getWidth(),
getDesktopRect(f.getGraphicsConfiguration()).height));
f.pack();
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));
}
}
答案 2 :(得分:2)
您可以创建一个自定义操作类来处理按钮单击和调整大小事件。这种方法看起来像这样:
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 java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
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);
CustomListener resizeViaButtonListener = new CustomListener("Resize to Default");
f.getContentPane().add(new JButton(resizeViaButtonListener), BorderLayout.PAGE_END);
f.addComponentListener(resizeViaButtonListener);
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));
}
class CustomListener extends AbstractAction implements ComponentListener {
CustomListener(String actionDescription) {
super(actionDescription);
}
private boolean resizedViaButtonClick = false;
@Override
public void actionPerformed(ActionEvent arg0) {
resizedViaButtonClick = true;
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100);
// you need this also here because if the component is not resized when clicking the button
// it is possible that the divider location must be changed. This happens when the user clicks
// the button after changing the divider but not resizing the frame.
}
@Override
public void componentResized(ComponentEvent e) {
if ( resizedViaButtonClick ) {
resizedViaButtonClick = false;
sp.setDividerLocation(sp.getSize().height - 100);
}
}
@Override
public void componentHidden(ComponentEvent e) { /* do nothing */ }
@Override
public void componentMoved(ComponentEvent e) { /* do nothing */ }
@Override
public void componentShown(ComponentEvent e) { /* do nothing */ }
}
}
这样,负责处理设置标准大小的逻辑任务的代码将在一个易于理解的类中。
答案 3 :(得分:2)
但我认为pack()可能比validate()
更好
我通常会尽量避免在任何组件上调用setPreferredSize()。我宁愿让布局管理器完成它的工作。在这种情况下,这意味着设置框架的大小,让BorderLayout占用所有可用空间。
void restoreDefaults() {
// f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle bounds = env.getMaximumWindowBounds();
f.setSize(f.getWidth(), bounds.height);
f.validate();
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}