Размер JDesktopPane, заданный по содержимому

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

enter image description here

Я делаю что-то очень глупо в этом источнике? Я пропустил гораздо лучший подход?

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;

public class MDIPreferredSize {

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                final JDesktopPane dt = new JDesktopPane() {

                    @Override
                    public Dimension getPreferredSize() {
                        Dimension prefSize = super.getPreferredSize();
                        System.out.println("prefSize: " + prefSize);
                        // inititialize the max to the first normalized bounds
                        Rectangle max = getAllFrames()[0].getNormalBounds();
                        for (JInternalFrame jif : this.getAllFrames()) {
                            max.add(jif.getNormalBounds());
                        }
                        System.out.println("maxBounds(): "
                                + max);
                        int x1 = max.width + (max.x * 2) < prefSize.width
                                ? prefSize.width
                                : max.width + (max.x * 2);
                        int y1 = max.height + (max.y * 2) < prefSize.height
                                ? prefSize.height
                                : max.height + (max.y * 2);
                        System.out.println("x,y: "
                                + x1
                                + ","
                                + y1);
                        return new Dimension(x1, y1);
                    }
                };
                dt.setAutoscrolls(true);

                int xx = 5;
                int yy = 5;
                int vStep = 10;
                int yStep = 22;
                for (int ii = 0; ii < 3; ii++) {
                    JInternalFrame jif = new JInternalFrame(
                            "Internal Frame " + (ii + 1),
                            true,
                            true,
                            true);
                    dt.add(jif);
                    jif.setLocation(xx, yy);
                    xx += vStep;
                    yy += yStep;
                    jif.setSize(200, 75);
                    jif.setVisible(true);
                }

                ComponentListener componentListener = new ComponentListener() {

                    @Override
                    public void componentResized(ComponentEvent e) {
                        e.getComponent().validate();
                    }

                    @Override
                    public void componentMoved(ComponentEvent e) {
                        e.getComponent().validate();
                    }

                    @Override
                    public void componentShown(ComponentEvent e) {
                        e.getComponent().validate();
                    }

                    @Override
                    public void componentHidden(ComponentEvent e) {
                        // do nothing 
                    }
                };
                // causes maximized internal frames to be resized..
                dt.addComponentListener(componentListener);

                final JCheckBox outLineDragMode = new JCheckBox("Outline Drag Mode");
                ActionListener dragModeListener = new ActionListener() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        if (outLineDragMode.isSelected()) {
                            dt.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
                        } else {
                            dt.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
                        }
                    }
                };
                outLineDragMode.addActionListener(dragModeListener);

                JPanel gui = new JPanel(new BorderLayout());
                gui.add(outLineDragMode, BorderLayout.PAGE_START);
                gui.setBorder(new EmptyBorder(2, 3, 2, 3));
                gui.add(new JScrollPane(dt), BorderLayout.CENTER);

                JFrame f = new JFrame("DTP Preferred");
                f.add(gui);
                // Ensures JVM closes after frame(s) closed and
                // all non-daemon threads are finished
                f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                // See http://stackoverflow.com/a/7143398/418556 for demo.
                f.setLocationByPlatform(true);

                // ensures the frame is the minimum size it needs to be
                // in order display the components within it
                f.pack();
                f.setMinimumSize(f.getSize());

                // should be done last, to avoid flickering, moving,
                // resizing artifacts.
                f.setVisible(true);

                printProperty("os.name");
                printProperty("java.version");
                printProperty("java.vendor");
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
        SwingUtilities.invokeLater(r);
    }

    public static void printProperty(String name) {
        System.out.println(name + ": \t" + System.getProperty(name));
    }
}

Изменить

Среди напечатанной информации см. также 3 системных свойства:

os.name:    Windows 7
java.version:   1.7.0_21
java.vendor:    Oracle Corporation

Вот значения здесь.

MouseMotionListener фиксированный код

Благодаря предложению Jonathan Drapeau о MouseListener, этот фиксированный пример фактически использует MouseMotionListener, чтобы позволить панели рабочего стола активно изменять размер при перетаскивании. Это может повлечь за собой некоторые причуды, не связанные с использованием MouseListener, которые вызывают проблемы (пока неизвестно), если да, вернемся к более простой методике "изменить размер панели рабочего стола во внутренней кадре" (MouseListener).

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.basic.BasicInternalFrameTitlePane;

public class MDIPreferredSize {

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                final JDesktopPane dt = new JDesktopPane() {

                    @Override
                    public Dimension getPreferredSize() {
                        Dimension prefSize = super.getPreferredSize();
                        System.out.println("prefSize: " + prefSize);
                        // inititialize the max to the first normalized bounds
                        Rectangle max = getAllFrames()[0].getNormalBounds();
                        for (JInternalFrame jif : this.getAllFrames()) {
                            max.add(jif.getNormalBounds());
                        }
                        System.out.println("maxBounds(): "
                                + max);
                        int x1 = max.width + (max.x * 2) < prefSize.width
                                ? prefSize.width
                                : max.width + (max.x * 2);
                        int y1 = max.height + (max.y * 2) < prefSize.height
                                ? prefSize.height
                                : max.height + (max.y * 2);
                        System.out.println("x,y: "
                                + x1
                                + ","
                                + y1);
                        return new Dimension(x1, y1);
                    }
                };

                int xx = 5;
                int yy = 5;
                int vStep = 10;
                int yStep = 22;
                for (int ii = 0; ii < 3; ii++) {
                    JInternalFrame jif = new JInternalFrame(
                            "Internal Frame " + (ii + 1),
                            true,
                            true,
                            true);
                    dt.add(jif);
                    jif.setLocation(xx, yy);
                    xx += vStep;
                    yy += yStep;
                    jif.setSize(200, 75);
                    jif.setVisible(true);
                }

                /*final MouseListener mouseListener = new MouseAdapter() {

                    @Override
                    public void mouseReleased(MouseEvent e) {
                        dt.revalidate();
                    }
                };
                */
                final MouseMotionListener mouseMotionListener = new MouseMotionAdapter() {

                    @Override
                    public void mouseDragged(MouseEvent e) {
                        dt.revalidate();
                    }
                };
                for (JInternalFrame jif : dt.getAllFrames()) {
                    for (Component comp : jif.getComponents()) {
                        if (comp instanceof BasicInternalFrameTitlePane) {
                            //comp.addMouseListener(mouseListener);
                            comp.addMouseMotionListener(mouseMotionListener);
                        }
                    }
                }

                dt.setAutoscrolls(true);


                final JCheckBox outLineDragMode =
                        new JCheckBox("Outline Drag Mode");
                ActionListener dragModeListener = new ActionListener() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        if (outLineDragMode.isSelected()) {
                            dt.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
                        } else {
                            dt.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
                        }
                    }
                };
                outLineDragMode.addActionListener(dragModeListener);

                JPanel gui = new JPanel(new BorderLayout());
                gui.add(outLineDragMode, BorderLayout.PAGE_START);
                gui.setBorder(new EmptyBorder(2, 3, 2, 3));
                gui.add(new JScrollPane(dt), BorderLayout.CENTER);

                JFrame f = new JFrame("DTP Preferred");
                f.add(gui);
                // Ensures JVM closes after frame(s) closed and
                // all non-daemon threads are finished
                f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                // See http://stackoverflow.com/a/7143398/418556 for demo.
                f.setLocationByPlatform(true);

                // ensures the frame is the minimum size it needs to be
                // in order display the components within it
                f.pack();
                f.setMinimumSize(f.getSize());

                // should be done last, to avoid flickering, moving,
                // resizing artifacts.
                f.setVisible(true);

                printProperty("os.name");
                printProperty("java.version");
                printProperty("java.vendor");
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
        SwingUtilities.invokeLater(r);
    }

    public static void printProperty(String name) {
        System.out.println(name + ": \t" + System.getProperty(name));
    }
}

Причуды

Он может столкнуться с некоторыми причудами, не использующими MouseListener, которые вызывают проблемы (пока неизвестно).

Это было тогда..

  • В режиме полного рендеринга панель рабочего стола будет динамически расти, пока пользователь перетаскивает внутренний кадр (даже с графического интерфейса). (Хорошо). В режиме контура контейнер будет изменять размер только при капле, а не в перетаскивании. (Менее хорошо, но, по крайней мере, полосы прокрутки появляются/исчезают надежно.)

Ответ 1

Добавление области заголовка MouseListener в JInternalFrame, а в JDesktopPane.LIVE_DRAG_MODE - revalidate JDesktopPane после выпуска - способ получить то же самое поведение в каждом режиме.

            final MouseListener testList = new MouseListener() {

              @Override
              public void mouseReleased(MouseEvent e) {
                dt.revalidate();
              }

              @Override
              public void mousePressed(MouseEvent e) {
              }

              @Override
              public void mouseExited(MouseEvent e) {
              }

              @Override
              public void mouseEntered(MouseEvent e) {
              }

              @Override
              public void mouseClicked(MouseEvent e) {
              }
            };
            // causes maximized internal frames to be resized..
            dt.addComponentListener(componentListener);

            for (JInternalFrame jif : dt.getAllFrames()) {
              for (Component comp : jif.getComponents()) {
                if (comp instanceof BasicInternalFrameTitlePane) {
                  comp.addMouseListener(testList);
                }
              }
            }        

            final JCheckBox outLineDragMode = new JCheckBox("Outline Drag Mode");
            ActionListener dragModeListener = new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                  if (outLineDragMode.isSelected()) {
                    dt.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
                    for (JInternalFrame jif : dt.getAllFrames()) {
                      for (Component comp : jif.getComponents()) {
                        if (comp instanceof BasicInternalFrameTitlePane) {
                          comp.removeMouseListener(testList);
                        }
                      }
                    }
                  } else {
                    dt.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
                    for (JInternalFrame jif : dt.getAllFrames()) {
                      for (Component comp : jif.getComponents()) {
                        if (comp instanceof BasicInternalFrameTitlePane) {
                          comp.addMouseListener(testList);
                        }
                      }
                    }
                  }
                }
            };

Я удаляю их в JDesktopPane.OUTLINE_DRAG_MODE, так как он уже реагирует правильно.

Ответ 2

Вы можете использовать Drag Layout для обработки изменения размера панели рабочего стола при перетаскивании компонентов.

Ответ 3

Интересная проблема для субботнего утра: -)

Нет полного решения, всего несколько комментариев и схема альтернативного подхода:

  • полагаясь на мышь/Motion/прослушиватель, является неполным в том смысле, что он не обрабатывает перемещенные клавиатурой ходы.
  • per-internalframe componentListener для спасения: работает отлично, если не в режиме контура
  • В режиме контура повторная аттестация не может работать в любом случае, поскольку она зависит от фактического местоположения кадра, которое не изменяется во время перетаскивания.

Итак, реальная проблема - это режим схемы, требуется

  • отслеживать промежуточные границы перетаскиваемого фрейма
  • пусть вычисление prefSize на рабочем столе учитывает эти промежуточные границы
  • рисунок контура (неожиданно для меня, см. ниже [*])

Соавтором, ответственным за перемещение фрейма, является DesktopManager.dragFrame: его реализация по умолчанию сбрасывает рамки кадра, если не в режиме контура, или отслеживает промежуточное местоположение и рисует прямоугольник контура, если он находится в режиме контура.

Очевидной идеей является пользовательский DesktopManager, который переопределяет dragFrame:

  • пусть супер делает свои вещи
  • в режиме контура, получить промежуточное местоположение кадра и сохранить его где-то на самом кадре, f.i. как clientProperty

Теперь кто-то, f.i. свойство PropertyChangeListener может прослушивать изменения промежуточного местоположения и запускать повторную проверку. И вычисление prefSize для desktopPane может учитывать промежуточные границы в дополнение к реальным границам, что-то вроде

public static class MyDesktopManager extends DefaultDesktopManager {
    private Point currentLoc;

    @Override
    public void dragFrame(JComponent f, int newX, int newY) {
        // let super handle outline drawing
        super.dragFrame(f, newX, newY);
        if (isOutline(f)) {
            // take over the drawing
            currentLoc = new Point(newX, newY);
            Rectangle bounds = new Rectangle(currentLoc, f.getSize());
            f.putClientProperty("outlineBounds", bounds);
        } else {
            // call super only if not outline
            // handle outline drawing ourselves
            // super.dragFrame(f, newX, newY);
        }
    }

    @Override
    public void beginDraggingFrame(JComponent f) {
        super.beginDraggingFrame(f);
        if (isOutline(f)) {
            currentLoc = f.getLocation();
            RootPaneContainer r = (RootPaneContainer) SwingUtilities.getWindowAncestor(f);
            // do the painting in the glassPane
            // r.getGlassPane().setVisible(true);
        }
    }

    @Override
    public void endDraggingFrame(JComponent f) {
        super.endDraggingFrame(f);
        f.putClientProperty("outlineBounds", null);
        if (isOutline(f)) {
            RootPaneContainer r = (RootPaneContainer) SwingUtilities.getWindowAncestor(f);
            r.getGlassPane().setVisible(false);
        }
    }

    protected boolean isOutline(JComponent f) {
        return ((JInternalFrame) f).getDesktopPane().getDragMode() == 
             JDesktopPane.OUTLINE_DRAG_MODE;
    }
}

Использование:

final JDesktopPane dt = new JDesktopPane() {

    @Override
    public Dimension getPreferredSize() {
        Dimension prefSize = super.getPreferredSize();
        System.out.println("prefSize: " + prefSize);
        // inititialize the max to the first normalized bounds
        Rectangle max = getAllFrames()[0].getNormalBounds();
        for (JInternalFrame jif : this.getAllFrames()) {
            max.add(jif.getNormalBounds());
            Rectangle outline = (Rectangle) jif.getClientProperty("outlineBounds");
            if (outline != null) {
                max.add(outline);
            }
        }
        int x1 = max.width + (max.x * 2) < prefSize.width ? prefSize.width
                : max.width + (max.x * 2);
        int y1 = max.height + (max.y * 2) < prefSize.height ? prefSize.height
                : max.height + (max.y * 2);
        return new Dimension(x1, y1);
    }
};
dt.setDesktopManager(new MyDesktopManager());
dt.setAutoscrolls(true);
int xx = 5;
int yy = 5;
int vStep = 10;
int yStep = 22;

// oer-internalframe componentListener
ComponentListener il = new ComponentAdapter() {

    @Override
    public void componentMoved(ComponentEvent e) {
        dt.revalidate();
    }

};
// per-internalframe outlineListener
PropertyChangeListener propertyL = new PropertyChangeListener() {

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        dt.revalidate();
    }
};
for (int ii = 0; ii < 3; ii++) {
    JInternalFrame jif = new JInternalFrame(
            "Internal Frame " + (ii + 1),
            true,
            true,
            true);
    dt.add(jif);
    jif.addComponentListener(il);
    jif.addPropertyChangeListener("outlineBounds", propertyL);
    jif.setLocation(xx, yy);
    xx += vStep;
    yy += yStep;
    jif.setSize(200, 75);
    jif.setVisible(true);
}

[*] Мигание по умолчанию начертания рисунка мерцает (до такой степени, что оно невидимо) - причина в том, что реализация по умолчанию использует... getGraphics()... Поэтому нам нужно взять на себя рисование контура, f.i. в выделенной стеклянную панель (что сделано прокомментированным кодом) или, вероятно, лучше на LayerUI на рабочем столе.

Неочищенная стеклянная панель, как и poc, которая не застревает правильно и имеет некоторые проблемы, когда кадр перемещается обратно в видимый прямоугольник:

public static class OutlinePanel extends JPanel {

    private JDesktopPane desktop;

    public OutlinePanel(JDesktopPane desktop) {
        this.desktop = desktop;
    }

    @Override
    public boolean isOpaque() {
        return false;
    }

    @Override
    protected void paintComponent(Graphics g) {
        JInternalFrame selected = desktop.getSelectedFrame();
        Rectangle outline = (Rectangle) selected.getClientProperty("outlineBounds");
        if (outline == null) return;
        Rectangle bounds = SwingUtilities.convertRectangle(desktop, outline, this);
        g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
    }

}

Обновить

Версия с LayerUI - теперь мы оставляем совершенно новое поведение (регистрацию слушателя, при необходимости нарисовывая контур, устанавливая менеджера) в оформление. Преимущества:

  • упрощенное использование
  • одно местоположение для всех грязных деталей

LayerUI:

public class DesktopLayerUI extends LayerUI<JDesktopPane> {

    @Override
    public void installUI(JComponent c) {
        super.installUI(c);
        final JDesktopPane dt = getDesktopPane(c);
        //dt.setBorder(BorderFactory.createLineBorder(Color.RED));
        dt.setDesktopManager(new MyDesktopManager());
        // per-internalframe componentListener
        ComponentListener il = new ComponentAdapter() {

            @Override
            public void componentMoved(ComponentEvent e) {
                dt.revalidate();
            }

        };
        // per-internalframe outlineListener
        PropertyChangeListener propertyL = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                dt.revalidate();
            }
        };
        for (JInternalFrame jif : dt.getAllFrames()) {
            jif.addComponentListener(il);
            jif.addPropertyChangeListener("outlineBounds", propertyL);
        }
        // TBD: register container listener to update frame listeners on adding/removing
        // TBD: componentListener on desktop that handles maximizing frame
        //   (JW: didn't really understand what that one is doing in the original)
    }


    @Override
    public Dimension getPreferredSize(JComponent c) {
        JDesktopPane dt = getDesktopPane(c);
        Dimension prefSize = super.getPreferredSize(c);
        //System.out.println("prefSize: " + prefSize);
        // inititialize the max to the first normalized bounds
        Rectangle max = dt.getAllFrames()[0].getNormalBounds();
        for (JInternalFrame jif : dt.getAllFrames()) {
            max.add(jif.getNormalBounds());
            Rectangle outline = (Rectangle) jif
                    .getClientProperty("outlineBounds");
            if (outline != null) {
                max.add(outline);
            }
        }
        // TBD: cope with frames at negative locations
        //System.out.println("maxBounds(): " + max);
        int x1 = max.width + (max.x * 2) < prefSize.width ? prefSize.width
                : max.width + (max.x * 2);
        int y1 = max.height + (max.y * 2) < prefSize.height ? prefSize.height
                : max.height + (max.y * 2);
        //System.out.println("x,y: " + x1 + "," + y1);
        return new Dimension(x1, y1);
    }


    @Override
    public void paint(Graphics g, JComponent c) {
        super.paint(g, c);
        JDesktopPane desktop = getDesktopPane(c);
        JInternalFrame selected = desktop.getSelectedFrame();
        if (selected == null) return;
        Rectangle outline = (Rectangle) selected.getClientProperty("outlineBounds");
        if (outline == null) return;
        Rectangle bounds = outline; //SwingUtilities.convertRectangle(, outline, this);
        g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
    }

    protected JDesktopPane getDesktopPane(JComponent c) {
        JDesktopPane desktop = ((JLayer<JDesktopPane>) c).getView();
        return desktop;
    }

    public static class MyDesktopManager extends DefaultDesktopManager {
        private Point currentLoc;

        @Override
        public void dragFrame(JComponent f, int newX, int newY) {
            if (isOutline(f)) {
                // take over the outline drawing
                currentLoc = new Point(newX, newY);
                Rectangle bounds = new Rectangle(currentLoc, f.getSize());
                f.putClientProperty("outlineBounds", bounds);
            } else {
                // call super only if not outline
                // handle outline drawing ourselves
                super.dragFrame(f, newX, newY);
            }
        }

        @Override
        public void beginDraggingFrame(JComponent f) {
            super.beginDraggingFrame(f);
            if (isOutline(f)) {
                currentLoc = f.getLocation();
                f.putClientProperty("outlineBounds", f.getBounds());
            }
        }

        @Override
        public void endDraggingFrame(JComponent f) {
            if (isOutline(f) && currentLoc != null) {
                setBoundsForFrame(f, currentLoc.x, currentLoc.y, f.getWidth(), f.getHeight() );
                f.putClientProperty("outlineBounds", null);
            } else {
                super.endDraggingFrame(f);
            }
        }

        protected boolean isOutline(JComponent f) {
            return ((JInternalFrame) f).getDesktopPane().getDragMode() == 
                JDesktopPane.OUTLINE_DRAG_MODE;
        }
    }

}

использование:

JDesktopPane dt = new JDesktopPane(); 
// add internalframes
...
// decorate the pane with the layer
JLayer<JDesktopPane> layer = new JLayer<>(dt, new DesktopLayerUI());
gui.add(new JScrollPane(layer), BorderLayout.CENTER);

Там небольшой улов (читайте: еще не выяснили, как его исправить): JLayer реализует Scrollable - его реализация возвращает false для trackXX (если украшенный компонент не является прокручиваемым самим собой - JDesktopPane - нет), это означает, что рабочий стол внутри scrollPane всегда имеет размер до его prefSize, который показывает серое окно просмотра в задней/нижней области, если длина scrollPane больше.