Search code examples
javaswingjscrollpanepreferredsizejdesktoppane

JDesktopPane preferred size set by content


I have been trying to tame JDesktopPane to work nicely with a resizable GUI & a scroll pane, but am having some troubles doing so. It seems that unless the drag mode is outline, the desktop pane will not resize as expected (when an internal frame is dragged beyond the edge of the desktop pane) & therefore not produce scroll-bars.

enter image description here

Am I doing something very silly in this source? Have I missed a far better approach?

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

Edit

Amongst the information printed, see also the 3 system properties:

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

Those are the values here.

MouseMotionListener fixed code

Thanks to Jonathan Drapeau's suggestion of a MouseListener, this fixed example actually uses a MouseMotionListener to allow the desktop pane to be resized actively while dragging. It might suffer some quirks beyond use of a MouseListener that cause problems (none yet known), if so, go back to the simpler technique of 'resize desktop pane on internal frame drop' (MouseListener only).

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

Quirks

It might suffer some quirks beyond use of a MouseListener that cause problems (none yet known).

That was then..

  1. In full render mode, the desktop pane will grow dynamically as far as the user drags the internal frame (even off the GUI). (Good.) In outline mode, the container will only resize on drop, not drag. (Less good, but at least the scroll-bars appear/disappear reliably.)

Solution

  • Adding a MouseListener to the JInternalFrame title pane while in JDesktopPane.LIVE_DRAG_MODE to revalidate the JDesktopPane after release is a way to get the exact same behavior in each mode.

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

    I remove them in the JDesktopPane.OUTLINE_DRAG_MODE since it already reacts properly.