setPreferredSize не работает на панелях, когда кадр развернут в Windows XP?

Я переопределил validate() в своем JFrame, чтобы я мог вручную контролировать размер пары вложенных панелей JPanel (они используются для прокрутки содержимого на экране, и ни один менеджер компоновки, о котором я знаю, не позволяет вам размещать компоненты за пределами границ родительского контейнера). Это отлично работает при перетаскивании окна для изменения его размера, но при нажатии кнопки «Развернуть» вызывается функция validate(), вызывается setPreferredSize(), но размеры панели не обновляются. Проблема замечена в XP, не замечена в OSX.

public void validate() {
    super.validate();

    LOGGER.debug("Validate called on Frame. Resizing panel");
    if (inited == true) {
        Dimension size = panelLeft.getSize();
        int referenceHeight = size.height;
        LOGGER.info("referenceHeight is " + referenceHeight);
        size = lower.getSize();
        size.height = referenceHeight;
        lower.setPreferredSize(size);
        lower.setMinimumSize(size);
        size.height = size.height * 2;
        movingPanel.setPreferredSize(size);
        movingPanel.setMinimumSize(size);
        LOGGER.info("sizes now: panel: " + lower.getSize().height + ", scrollpane: "
                + movingPanel.getSize().height);
        if (panelSlideController != null) {
            panelSlideController.redraw();
            LOGGER.debug("redrawing panel slide controller");
        }
    }
}

PanelLeft — это панель, размер которой менеджер компоновки автоматически изменяет на всю высоту фрейма. Поэтому его высота используется в качестве эталона.

Соответствующие панели расположены:

 ----------------------------------
|scrollPane                        |
| -------------------------------- |
||movingPanel                     ||
|| ------------------------------ ||
|||upper                         |||
|||                              |||
|||                              |||
|||                              |||
|| ------------------------------ ||
|| ------------------------------ ||
|||lower                         |||
|||                              |||
|||                              |||
|||                              |||
|| ------------------------------ ||
| -------------------------------- |
 ----------------------------------

Пользователь видит только верхнюю половину этого макета. MovingPanel JPanel находится в области просмотра JScrollPane. Цель состоит в том, чтобы верхняя панель занимала все видимое пространство по вертикали, чтобы она была примерно такой же высоты, как и окружающий JFrame. Нижняя панель готова к прокрутке и сохраняется на той же высоте, что и верхняя панель. Сохраняя lower.height == panelLeft.height и movingPanel.height == panelLeft.heightx2, это означает, что отображается половина movingPanel, а верхние концы находятся внизу фрейма.

Как я уже сказал, работает нормально. При перетаскивании окна некоторые примеры вывода:

2013-06-20 23:15:41,298 [WT-EventQueue-0] DEBUG s.billing.ui.Form  - Validate called on Frame. Resizing panel
2013-06-20 23:15:41,298 [WT-EventQueue-0] INFO  s.billing.ui.Form  - newheight is 617
2013-06-20 23:15:41,298 [WT-EventQueue-0] INFO  s.billing.ui.Form  - sizes now: panel: 607, scrollpane: 1214
2013-06-20 23:15:41,538 [WT-EventQueue-0] DEBUG s.billing.ui.Form  - Validate called on Frame. Resizing panel
2013-06-20 23:15:41,538 [WT-EventQueue-0] INFO  s.billing.ui.Form  - newheight is 640
2013-06-20 23:15:41,538 [WT-EventQueue-0] INFO  s.billing.ui.Form  - sizes now: panel: 636, scrollpane: 1272

При максимизации окна вывод выглядит следующим образом:

2013-06-20 22:08:21,234 [WT-EventQueue-0] DEBUG s.billing.ui.Form  - Validate called on Frame. Resizing panel
2013-06-20 22:08:21,234 [WT-EventQueue-0] INFO  s.billing.ui.Form  - newheight is 783
2013-06-20 22:08:21,234 [WT-EventQueue-0] INFO  s.billing.ui.Form  - sizes now: panel: 543, scrollpane: 1086

Я добавил туда setMinimumSize, чтобы попытаться быть более убедительным, но это не помогло.

Любые мысли очень приветствуются

РЕДАКТИРОВАТЬ: добавлен SSCCE

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.Timer;
import javax.swing.UIManager;

class Frame extends JFrame {

    private JPanel panelLeft;
    private JScrollPane scrollPane;
    private JPanel movingPanel;
    private JPanel upper;
    private JPanel lower;
    private boolean inited;
    private JLabel labelUpper;
    private JLabel labelLower;
    private JButton scrollBtn;
    private Frame.PanelSlideController panelSlideController;
    private JButton resizeBtn;

    private boolean lowerShowing = false;

    public Frame() {
        getContentPane().setLayout(new BorderLayout());

        panelLeft = new JPanel();
        panelLeft.setBackground(Color.CYAN);
        panelLeft.setPreferredSize(new Dimension(300, 400));
        getContentPane().add(panelLeft, BorderLayout.WEST);

        labelUpper = new JLabel("upper");
        panelLeft.add(labelUpper);

        labelLower = new JLabel("lower");
        panelLeft.add(labelLower);

        resizeBtn = new JButton("resize");
        resizeBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doResize();
            }
        });
        panelLeft.add(resizeBtn);

        scrollBtn = new JButton("Scroll");
        scrollBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doScroll();
            }
        });
        panelLeft.add(scrollBtn);

        scrollPane = new JScrollPane();
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);

        movingPanel = new JPanel(new GridLayout(2, 1));
        movingPanel.setOpaque(false);
        movingPanel.setPreferredSize(new Dimension(300, 400));

        upper = new JPanel();
        upper.setBackground(Color.YELLOW);
        movingPanel.add(upper);

        lower = new JPanel();
        lower.setBackground(Color.RED);
        movingPanel.add(lower);

        scrollPane.setViewportView(movingPanel);

        getContentPane().add(scrollPane, BorderLayout.EAST);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack();

        inited = true;
    }

    /**
     * This is a manual step instead of overriding validate()
     */
    protected void doResize() {
        // Get the height we want
        int referenceHeight = panelLeft.getSize().height;

        // Update the height of the lower panel to equal this
        Dimension size = lower.getSize();
        size.height = referenceHeight;
        lower.setPreferredSize(size);
        lower.setMinimumSize(size);

        // Update the height of the surrounding panel
        size = scrollPane.getSize();
        size.height = referenceHeight * 2;
        movingPanel.setPreferredSize(size);
        movingPanel.setMinimumSize(size);

        if (panelSlideController != null) {
            panelSlideController.redraw();
            System.out.println("redrawing panel slide controller");
        }

        upper.invalidate();
        lower.invalidate();
        scrollPane.revalidate();
    }

    protected void doScroll() {
        panelSlideController = new PanelSlideController(scrollPane, 20);
        int scrollDirection = lowerShowing ? -1 : 1;
        panelSlideController.scrollY(panelLeft.getHeight() * scrollDirection);
        lowerShowing = !lowerShowing;
    }

    @Override
    public void validate() {
        super.validate();

        System.out.println("Validating");
        if (inited) {
            labelUpper.setText("upper: " + upper.getSize().height);
            labelLower.setText("lower: " + lower.getSize().height);
        }
    }

    class PanelSlideController implements ActionListener {

        private final JScrollPane scrollPane;
        private final int speed;
        private Timer timer;
        private int endPos;

        private boolean scrollingPositive;

        public PanelSlideController(JScrollPane scrollPane, int speed) {
            this.scrollPane = scrollPane;
            this.speed = speed;
        }

        public void scrollY(int scrollDistance) {
            endPos = scrollPane.getViewport().getViewPosition().y + scrollDistance;
            scrollingPositive = scrollDistance > 0;
            timer = new Timer(speed, this);
            timer.start();
        }

        public void redraw() {
            JViewport viewport = scrollPane.getViewport();
            Point position = viewport.getViewPosition();

            if (scrollingPositive) {
                position.y = endPos;
            }
            else {
                position.y = 0;
            }
            viewport.setViewPosition(position);

        }

        @Override
        public void actionPerformed(ActionEvent e) {
            JViewport viewport = scrollPane.getViewport();
            Point position = viewport.getViewPosition();
            int offset = scrollingPositive ? 10 : -10;
            position.y += offset;
            viewport.setViewPosition(position);

            if ((scrollingPositive && position.y >= endPos)
                    || (!scrollingPositive && (position.y <= endPos || position.y <= 0))) {
                timer.stop();
            }
        }
    }

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        } catch (Exception e) {
            e.printStackTrace();
        }

        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new Frame().setVisible(true);
            }
        });
    }
}

Правильно, так что вышеприведенное можно запустить. PanelLeft (голубой) используется в качестве эталонной высоты. Я переместил код из validate() и вместо этого запустил его, нажав кнопку изменения размера.

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

Я обновил исходный фрагмент и «диаграмму», чтобы отразить используемые здесь имена.

Спасибо


person Brad M    schedule 20.06.2013    source источник
comment
Ничего себе, вы делаете свою жизнь намного сложнее, чем это может быть с такой реализацией. Переопределение validate() не кажется правильным выбором. Почему бы вам просто не установить правильный LayoutManager или даже не реализовать свой собственный? Кстати, вызов setPreferredSize() всегда плохая идея, и он не гарантирует размер вашего компонента (это может гарантировать только LayoutManager). Я бы рекомендовал опубликовать SSCCE для получения дополнительной помощи.   -  person Guillaume Polet    schedule 20.06.2013
comment
для получения дополнительной помощи скорее опубликуйте SSCCE, короткую, исполняемую, компилируемую, только о JFrame с JPanels   -  person mKorbel    schedule 20.06.2013
comment
comment
Еще лучше используйте Scrollable.   -  person MadProgrammer    schedule 20.06.2013


Ответы (1)


ОК, благодаря подсказкам использовать LayoutManager, я немного покопался в этом направлении. Оказывается, JScrollPanes используют JViewports, которые, в свою очередь, используют реализацию ViewportLayout для LayoutManager. Они контролируют свой делегированный компонент View. Как видно из модифицированного кода ниже, я переопределяю здесь метод layoutContainer, чтобы удвоить высоту моей «movingPanel» по отношению к области просмотра, и это работает заметно лучше, чем раньше, и при максимальном увеличении в XP.

Спасибо

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.ViewportLayout;

class Frame extends JFrame {

    private JPanel panelLeft;
    private JScrollPane scrollPane;
    private JPanel movingPanel;
    private JPanel upper;
    private JPanel lower;
    private boolean inited;
    private JLabel labelUpper;
    private JLabel labelLower;
    private JButton scrollBtn;
    private Frame.PanelSlideController panelSlideController;
    private JButton resizeBtn;

    private boolean lowerShowing = false;

    public Frame() {
        getContentPane().setLayout(new BorderLayout());

        panelLeft = new JPanel();
        panelLeft.setBackground(Color.CYAN);
        panelLeft.setPreferredSize(new Dimension(300, 400));
        getContentPane().add(panelLeft, BorderLayout.WEST);

        labelUpper = new JLabel("upper");
        panelLeft.add(labelUpper);

        labelLower = new JLabel("lower");
        panelLeft.add(labelLower);

        scrollBtn = new JButton("Scroll");
        scrollBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doScroll();
            }
        });
        panelLeft.add(scrollBtn);

        scrollPane = new JScrollPane();
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);

        movingPanel = new JPanel(new GridLayout(2, 1));
        movingPanel.setOpaque(false);
        movingPanel.setPreferredSize(new Dimension(300, 400));

        upper = new JPanel();
        upper.setBackground(Color.YELLOW);
        movingPanel.add(upper);

        lower = new JPanel();
        lower.setBackground(Color.RED);
        movingPanel.add(lower);

        // ------------------------------
        // This is the key bit
        // ------------------------------
        JViewport viewport = new JViewport() {
            @Override
            protected LayoutManager createLayoutManager() {
                return new ViewportLayout() {

                    @Override
                    public void layoutContainer(Container parent) {
                        JViewport vp = (JViewport) parent;
                        Component view = vp.getView();

                        Dimension viewPrefSize = view.getPreferredSize();
                        Dimension vpSize = vp.getSize();
                        Dimension viewSize = new Dimension(viewPrefSize);

                        viewSize.width = vpSize.width;
                        viewSize.height = vpSize.height * 2;

                        vp.setViewSize(viewSize);
                    }
                };
            }

        };
        scrollPane.setViewport(viewport);
        viewport.setView(movingPanel);
        // ------------------------------
        // End of key bit
        // ------------------------------

        getContentPane().add(scrollPane, BorderLayout.EAST);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack();

        inited = true;
    }

    protected void doScroll() {
        panelSlideController = new PanelSlideController(scrollPane, 20);
        int scrollDirection = lowerShowing ? -1 : 1;
        panelSlideController.scrollY(panelLeft.getHeight() * scrollDirection);
        lowerShowing = !lowerShowing;
    }

    @Override
    public void validate() {
        super.validate();

        System.out.println("Validating");
        if (inited) {
            labelUpper.setText("upper: " + upper.getSize().height);
            labelLower.setText("lower: " + lower.getSize().height);
        }
    }

    class PanelSlideController implements ActionListener {

        private final JScrollPane scrollPane;
        private final int speed;
        private Timer timer;
        private int endPos;

        private boolean scrollingPositive;

        public PanelSlideController(JScrollPane scrollPane, int speed) {
            this.scrollPane = scrollPane;
            this.speed = speed;
        }

        public void scrollY(int scrollDistance) {
            endPos = scrollPane.getViewport().getViewPosition().y + scrollDistance;
            scrollingPositive = scrollDistance > 0;
            timer = new Timer(speed, this);
            timer.start();
        }

        public void redraw() {
            JViewport viewport = scrollPane.getViewport();
            Point position = viewport.getViewPosition();

            if (scrollingPositive) {
                position.y = endPos;
            }
            else {
                position.y = 0;
            }
            viewport.setViewPosition(position);

        }

        @Override
        public void actionPerformed(ActionEvent e) {
            JViewport viewport = scrollPane.getViewport();
            Point position = viewport.getViewPosition();
            int offset = scrollingPositive ? 10 : -10;
            position.y += offset;
            viewport.setViewPosition(position);

            if ((scrollingPositive && position.y >= endPos)
                    || (!scrollingPositive && (position.y <= endPos || position.y <= 0))) {
                timer.stop();
            }
        }
    }

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        } catch (Exception e) {
            e.printStackTrace();
        }

        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new Frame().setVisible(true);
            }
        });
    }
}

Примечание. Перед удалением у меня был открыт следующий вопрос, и я включаю заголовок сюда для целей SEO: Как использовать менеджеры компоновки Swing для вывода объектов за пределы кадра?

person Brad M    schedule 26.06.2013