Нужна высота недействительного компонента Swing - PullRequest
6 голосов
/ 04 августа 2011

Базовая настройка такова: у меня есть вертикальная JSplitPane, для которой я хочу иметь нижний компонент фиксированного размера и верхний компонент с изменяющимся размером, который я выполнил, вызвав setResizeWeight(1.0).В этом приложении есть кнопка для восстановления конфигурации окна «по умолчанию».Высота окна по умолчанию - это высота рабочего стола, а расположение разделителя по умолчанию составляет 100 пикселей от нижней части разделенной панели.

Чтобы установить расположение разделителя на 100 пикселей, я принимаю высоту 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 будет проверен в этот момент, и я обеспокоен тем, что это ошибочное предположение.

Есть ли лучший способ?

Ответы [ 4 ]

5 голосов
/ 05 августа 2011

Потребовалось некоторое время (вероятно, из-за раннего утра здесь :-), чтобы понять проблему, поэтому просто чтобы убедиться, что я понял:

  • размер нижнего компонента может быть любым, что пользователь решит в любое время
  • при изменении размера рамки все изменения высоты должны происходить с верхним компонентом
  • есть возможность восстановить размеры по умолчанию, независимо от каких-либо настроек до
  • «по умолчанию» означает, что нижний компонент должен иметь фиксированную высоту хх

Если это так, решение состоит в том, чтобы отделить изменение размера рамки от размера нижнего компонента. Ваш второй вариант не работает: измените размер фрейма и оберните нижний размер композиции в 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.
2 голосов
/ 05 августа 2011

Вы можете создать собственный класс действий, который обрабатывает нажатие кнопки и событие изменения размера.Этот подход будет выглядеть следующим образом:

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

    }

}

Таким образом, код, отвечающий за обработку логической задачи установки стандартного размера, будет находиться в одном и простом для понимания классе.

2 голосов
/ 05 августа 2011

но я думаю, что 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
    }
2 голосов
/ 04 августа 2011

ничего сложного, основные правила свинга

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));
    }
}
...