代码之家  ›  专栏  ›  技术社区  ›  Andrew Thompson

JDesktopPane由内容设置的首选大小

  •  10
  • Andrew Thompson  · 技术社区  · 11 年前

    我一直在努力驯服 JDesktopPane 为了与可调整大小的GUI&滚动窗格,但我在这样做时遇到了一些问题。看起来 除非拖动模式是轮廓, 桌面窗格将不会按预期调整大小(当内部框架被拖到桌面窗格的边缘之外时)&因此不产生滚动条。

    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 ,这个固定的例子实际上使用了 鼠标移动 以允许在拖动时主动调整桌面窗格的大小。除了使用 鼠标定位器 如果是这样的话,那就回到“内部框架下降时调整桌面窗格大小”这一更简单的技术( 鼠标定位器 仅限)。

    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公司

    除了使用 鼠标定位器 导致问题(目前还不知道)。

    那是当时。。

    1. 在完全渲染模式下,只要用户拖动内部框架(甚至离开GUI),桌面窗格就会动态增长。(很好。)在大纲模式下,容器只会在放置时调整大小,而不会拖动。(不太好,但至少滚动条可以可靠地显示/消失。)
    3 回复  |  直到 11 年前
        1
  •  2
  •   Jonathan Drapeau    11 年前

    添加 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
  •  1
  •   camickr    11 年前

    您应该能够使用 Drag Layout 以在拖动组件时处理桌面窗格的大小调整。

        3
  •  1
  •   kleopatra Aji kattacherry    11 年前

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

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

    • 依赖鼠标/Motion/listener是不完整的,因为它不能处理键盘控制的移动
    • 每个internalframe组件救援侦听器:如果 处于大纲模式
    • 在大纲模式下,重新验证无论如何都无法工作,因为它依赖于 真实的 拖动过程中不变的帧位置

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

    • 跟踪拖动帧的中间边界
    • 让桌面的prefSize计算将这些中间边界考虑在内
    • 轮廓图(对我来说,出乎意料,见下文[*])

    负责移动框架的合作者是DesktopManager.dragFrame:如果不处于轮廓模式,它的默认实现会重置框架边界,或者如果处于轮廓模式则会跟踪中间位置并绘制轮廓矩形。

    显而易见的想法是一个自定义桌面管理器,它可以覆盖dragFrame:

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

    现在,某人,例如PropertyChangeListener,可以监听中间位置的更改并触发重新验证。桌面窗格的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);
    }
    

    [*]默认的轮廓绘制会闪烁(在一定程度上是不可见的)——原因是默认实现使用。。。获取图形()。。。因此,我们需要接管轮廓绘制,例如在专用的玻璃窗格中(由注释代码完成),或者更好的是在桌面上使用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-它的实现为tracksXX返回false(如果装饰的组件本身不是Scrollable,JDesktopPane就不是),这意味着滚动窗格内的桌面总是按其prefSize调整大小,如果滚动窗格较大,则在尾部/底部区域显示灰色视口。