简体   繁体   English

需要无效的Swing组件的高度

[英]Need the height of an invalidated Swing component

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) . 基本设置是这样的:我有一个垂直的JSplitPane,我希望有一个固定大小的底部组件和一个调整大小的顶部组件,我通过调用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. 窗口的默认高度是桌面高度,默认分隔符位置距离拆分窗格底部100个像素。

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. 要将分隔符位置设置为100px,我采用JSplitPane高度 - 100.问题是,在此之前我调整了JFrame的大小,并且由于代码处于按钮回调中,因此JSplitPane已经失效但尚未调整大小。 So the divider location is set incorrectly. 因此分配器位置设置不正确。

Here is a SSCCE. 这是一个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. 我想到了一些可能解决这个问题的方法,但它们看起来都像是一种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. 到目前为止,我最好的想法是在设置帧大小和设置分隔符位置之间调用f.validate() ,但我担心可能会有早期强制验证的副作用。

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. 我想到的另一个选项是使用EventQueue.invokeLater()来调用以在事件队列的末尾设置分隔符位置。 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. 但这对我来说似乎有风险 - 我假设JSplitPane将在那时得到验证,而我担心这可能是一个错误的假设。

Is there a better way? 有没有更好的办法?

Took a while (probably due to being early morning here :-) to understand the problem, so just to make sure I got it: 花了一段时间(可能是因为在这里清晨:-)了解问题,所以只是为了确保我得到它:

  • the size of the bottom component can be whatever the user decides at all times 底部组件的大小可以是用户始终决定的任何大小
  • when resizing the frame all height change should happen to the top component 当调整框架大小时,顶部组件应发生所有高度变化
  • there's an option to restore to default sizes, independent of any setting before 有一个选项可以恢复到默认大小,与之前的任何设置无关
  • "default" means the bottom component must have a fixed height of xx “默认”表示底部组件必须具有固定高度xx

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). 你的第二个选择是死的:调整框架的大小并将底部的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);  

        }
    });
}

That's guaranteed to work as expected, because the invokeLater puts the request as last after all already queued events: 这保证按预期工作,因为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.

nothing complicated, basic Swing Rules 没什么复杂的,基本的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));
    }
}

You could create a custom action class that handles the button click and the resize event. 您可以创建一个自定义操作类来处理按钮单击和调整大小事件。 This approach would look like this: 这种方法看起来像这样:

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 */ }

    }

}

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. 这样,负责处理设置标准大小的逻辑任务的代码将在一个易于理解的类中。

but I think pack() may be better than validate() 但我认为pack()可能比validate()更好

I generally try to avoid invoking setPreferredSize() on any component. 我通常会尽量避免在任何组件上调用setPreferredSize()。 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. 在这种情况下,这将意味着设置框架的大小,并让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
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM