JDesktopPane按内容设置的首选大小

时间:2013-08-30 12:29:44

标签: java swing jscrollpane preferredsize jdesktoppane

我一直试图驯服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,这个固定示例实际上使用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. 在完全渲染模式下,只要用户拖动内部框架(甚至是GUI),桌面窗格就会动态增长。 (好。)在大纲模式下,容器只会在掉落时调整大小,而不是拖动。 (不太好,但至少滚动条可靠地出现/消失。)

3 个答案:

答案 0 :(得分:2)

MouseListenerJInternalFrame JDesktopPane.LIVE_DRAG_MODE发布后revalidateJDesktopPane标题窗格添加 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

我在{{1}}中将其删除,因为它已经做出了正确的反应。

答案 1 :(得分:1)

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

答案 2 :(得分:1)

星期六早上的有趣问题: - )

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

  • 依赖鼠标/动作/监听器是不完整的,因为它不处理键盘控制的移动
  • per-internalframe componentListener to rescue:如果在大纲模式下工作正常
  • 在大纲模式下,重新验证无论如何都无法工作,因为它依赖于实际框架位置,在拖动过程中没有改变

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

  • 跟踪拖动框架的中间边界
  • 让桌面的prefSize计算考虑这些中间界限
  • 大纲图(意外,对我而言,见下文[*])

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

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

  • 让超级做它的东西
  • 在轮廓模式下,获取框架的中间位置并将其存储在框架本身的某个位置,f.i。作为clientProperty

现在是某人,f.i。 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);
}

[*]默认的轮廓绘画闪烁(延伸到它是不可见的) - 原因是默认实现使用... getGraphics()...所以我们需要接管轮廓绘画,f.i。在一个专用的glassPane(由评论代码完成)或者可能更好的桌面上的LayerUI。

粗糙的glassPane,就像一个没有正确剪辑的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不是),这意味着scrollPane中的桌面总是按其prefSize调整大小,如果scrollPane较大,则会在尾随/底部区域显示灰色视口。