6

基本的なセットアップは次のとおりです。私は固定サイズの下部コンポーネントとサイズ変更可能な上部コンポーネントを持ちたい垂直 JSplitPane を持っていますsetResizeWeight(1.0)。このアプリケーションには、「デフォルト」のウィンドウ構成を復元するためのボタンがあります。ウィンドウのデフォルトの高さはデスクトップの高さで、デフォルトの分割位置は分割ペインの下部から 100 ピクセルです。

仕切りの位置を 100px に設定するには、JSplitPane の高さを 100 に設定します。問題は、この直前に JFrame のサイズを変更することです。コードはボタン コールバックにあるため、JSplitPane は無効化されていますが、まだサイズ変更されていません。そのため、仕切りの位置が正しく設定されていません。

これがSSCCEです。ボタンを 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 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()、早期に検証を強制すると副作用が生じるのではないかと懸念しています。

私が考えたもう 1 つのオプションEventQueue.invokeLater()は、イベント キューの最後に仕切りの位置を設定するための呼び出しを配置するために使用することです。しかし、それは私には危険に思えます。私は、JSplitPane がその時点で検証済みであると想定しています。

より良い方法はありますか?

4

4 に答える 4

5

問題を理解するのにしばらく時間がかかりました(おそらくここが早朝だったからです:-)。

  • 下部コンポーネントのサイズは、ユーザーがいつでも決定できます
  • フレームのサイズを変更するとき、すべての高さの変更は上部コンポーネントに発生する必要があります
  • 以前の設定に関係なく、デフォルトのサイズに復元するオプションがあります
  • 「デフォルト」は、下部コンポーネントの高さが xx に固定されている必要があることを意味します

その場合、解決策は、フレームのサイズ変更を下部コンポーネントのサイズ変更から分離することです。フレームのサイズを変更し、下部のコンプのサイズ変更を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.
于 2011-08-05T09:05:45.700 に答える
2

複雑なことは何もない、基本的なスイングル​​ール

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));
    }
}
于 2011-08-04T19:51:04.557 に答える
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 */ }

    }

}

このようにして、標準サイズを設定する論理タスクの処理を担当するコードは、1 つの理解しやすいクラスになります。

于 2011-08-04T20:21:26.293 に答える
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
    }
于 2011-08-05T06:03:45.013 に答える