提问者:小点点

JDesktopPane按内容设置的首选大小


我一直试图驯服<code>JDesktopPane</code>,使其能够很好地与可调整大小的GUI一起工作

我在这个源代码中做了什么非常愚蠢的事情吗?我错过了一个更好的方法吗?

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

这些就是这里的价值观。

由于Jonathan Drapeau建议使用< code>MouseListener,这个固定的示例实际上使用了< code>MouseMotionListener来允许在拖动时主动调整桌面窗格的大小。除了使用导致问题的< code>MouseListener之外,它可能还会遇到一些奇怪的问题(目前还不知道),如果是这样,请回到更简单的技术“在内部框架放下时调整桌面窗格的大小”(仅限< code>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));
    }
}

除了使用导致问题的< code>MouseListener之外,它可能还会遇到一些奇怪的问题(目前还不知道)。

那是然后…

  1. 在完全渲染模式下,桌面窗格将随着用户拖动内部框架(甚至离开 GUI)而动态增长。(好。在大纲模式下,容器只会在拖放时调整大小,而不是拖动。(不太好,但至少滚动条可靠地出现/消失。

共3个答案

匿名用户

添加一个鼠标监听器JInternalFrame标题窗格,同时在JDesktopPane.LIVE_DRAG_MODE中重新验证发布后的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);
                        }
                      }
                    }
                  }
                }
            };

我在< code>JDesktopPane中删除了它们。OUTLINE_DRAG_MODE,因为它已经正确反应。

匿名用户

当拖动组件时,您应该能够使用拖动布局来处理桌面窗格的大小调整。

匿名用户

周六早上的有趣问题:-)

没有完整的解决方案,只有几条评论和替代方法的概述:

  • 依靠鼠标/运动/侦听器是不完整的,因为它不处理键盘控制的移动
  • 每个内部帧组件侦听器救援:如果不在大纲模式下工作正常
  • 在大纲模式下,重新验证无论如何都不起作用,因为它依赖于实际的帧位置,该位置在拖动过程中保持不变

所以真正的问题是大纲模式,需要

    < li >跟踪拖动的框架的中间边界 < li >让桌面的prefSize计算考虑这些中间界限 < li >轮廓的绘制(出乎我的意料,请参见下面的[*])

负责移动框架的协作者是DesktopManager.drag框架:如果不在大纲模式下,它的默认实现会重置框架边界,或者如果在大纲模式下,则跟踪中间位置并绘制大纲矩形。

显而易见的想法是一个覆盖dragFrame的自定义DesktopManager:

    < li >让super做自己的事情 < li >在大纲模式下,获取框架的中间位置,并将其存储在框架本身的某个位置,例如作为clientProperty

现在有人,例如PropertyChangeListener,可以监听中间位置的更改并触发重新验证。desktopPane的prefSize计算可以考虑除实际边界之外的中间边界,例如

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

[*]默认的轮廓绘制会闪烁(在某种程度上是不可见的)-原因是默认实现使用。。。获取图形()。。。因此,我们需要接管轮廓绘制,例如在专用的glassPane中(由注释代码完成),或者更好地由桌面上的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的版本-现在我们将全新的行为(听众注册、需要时绘制轮廓、安装管理器)留给装饰。优点:

  • 简化使用
  • 一个位置显示所有肮脏的细节

图层UI:

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——它的实现为tracksXX返回false(如果修饰组件本身不是可滚动的——JDesktopPane不是),这意味着scrollPane内的桌面总是调整为其prefSize大小,如果scrollPane更大,它会在尾部/底部区域显示灰色的视口。