Swing: 如何调整JFrame的大小,像Linux中的窗口一样?

7
我希望了解是否有可能实现JFrame的调整大小,就像Linux中的标准窗口一样。更确切地说,如果用户开始拖动,则仅预览窗口的未来大小,而原始内容不会调整大小。一旦用户释放鼠标,框架将调整到该大小。如下图所示:

(1)调整大小之前的状态

enter image description here

(2) 用户开始拖动(在红色圆圈处)

enter image description here

(3) 用户释放鼠标,框架大小会被调整

enter image description here

在Java Swing中实现这一点是否可能?

编辑:

由于这个程序有一天也应该在低于7的Java RE中运行,我尝试结合mKorbel的建议和评论中的透明框架建议。结果接近目标,除了以下几点:

  • 当我停止移动鼠标时,contentPane的内容会重新调整大小,而不是在鼠标释放时。
  • 窗口标题会立即调整大小,而不仅仅是在拖动窗口边框时。
  • 只有从右侧或底部调整大小才有效,否则内容会随着拖动而移动。

我认为第一个问题可以通过代码和MouseListener的组合来解决,例如如果mouseReleased(),那么resize。这是代码,请随意尝试。对于进一步的建议,我仍然乐意听取任何建议。

这段代码是对Java教程中GradientTranslucentWindowDemo.java的轻微修改。希望可以在此处发布,否则请告知是否侵犯版权。黑色JPanel应该是应用程序的内容,而contentPane保持不可见。
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.beans.PropertyChangeListener;

import javax.swing.*;
import static java.awt.GraphicsDevice.WindowTranslucency.*;

public class GroundFrame extends JFrame {

    Timer timer;
    JPanel panel2;

    public GroundFrame() {
        super("GradientTranslucentWindow");

        setBackground(new Color(0,0,0,0));
        setSize(new Dimension(300,200));
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel = new JPanel() {
        panel.setBackground(new Color(0,0,0,0));
        setContentPane(panel);

        setLayout(null);
        panel2 = new JPanel();
        panel2.setBackground(Color.black);
        panel2.setBounds(0,0,getContentPane().getWidth(), getContentPane().getHeight());
        getContentPane().add(panel2);

        addComponentListener(new ComponentListener() {

            @Override
            public void componentShown(ComponentEvent e) {}

            @Override
            public void componentResized(ComponentEvent e) {
                timer = new Timer(50, new Action() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        if(timer.isRunning()){

                        }else{
                            resizePanel(getContentPane().getSize());
                        }
                    }

                    @Override
                    public void setEnabled(boolean b) {}

                    @Override
                    public void removePropertyChangeListener(PropertyChangeListener listener) {}

                    @Override
                    public void putValue(String key, Object value) {}

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

                    @Override
                    public Object getValue(String key) {return null;}

                    @Override
                    public void addPropertyChangeListener(PropertyChangeListener listener) {}
                });
                timer.setRepeats(false);
                timer.start();

            }

            @Override
            public void componentMoved(ComponentEvent e) {}

            @Override
            public void componentHidden(ComponentEvent e) {}
        });
    }

    public void resizePanel(Dimension dim){
        panel2.setBounds(0,0,dim.width, dim.height);
        repaint();
    }

    public static void main(String[] args) {
        GraphicsEnvironment ge =
            GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        boolean isPerPixelTranslucencySupported =
            gd.isWindowTranslucencySupported(PERPIXEL_TRANSLUCENT);

        if (!isPerPixelTranslucencySupported) {
            System.out.println(
                "Per-pixel translucency is not supported");
                System.exit(0);
        }

        JFrame.setDefaultLookAndFeelDecorated(true);

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                GroundFrame gtw = new GroundFrame();
                gtw.setVisible(true);
            }
        });
    }
}

也许这个链接可以给你一个方向:http://docs.oracle.com/javase/tutorial/uiswing/misc/trans_shaped_windows.html - Udo Klimaschewski
恩...就我所知,确切的行为是由底层平台控制的,所以只有在操作系统进行了相应的配置后才会发生。但我不是很确定。 - kleopatra
据我所知,这种行为是由窗口管理器控制的。 - Denis Tulskiy
可以从ComponentListener中覆盖标准布局管理器来触发事件,并使用consume()直到事件触发,调用revalidate()repaint()可以正确完成此工作。很抱歉我找不到有关LayoutManagers适当的通知程序的描述(在Robs答案或评论中),也很抱歉太懒了。 - mKorbel
你是否可以只使用Java 7? - Denis Tulskiy
3个回答

6

只有窗口的未来大小会被预览,而原始内容不会被调整大小。一旦用户释放鼠标,框架将调整为该大小

非常复杂的想法,但是

编辑

使用此代码进行测试

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Vector;

public class ComponentEventDemo extends JPanel
        implements ComponentListener, HierarchyListener,
        ItemListener {

    private JFrame frame;
    private static final long serialVersionUID = 1L;
    private JTextArea display;
    private JLabel label;
    private JComboBox comboBox;
    private JComboBox comboBox1;
    private String newline = "\n";
    private Vector<String> listSomeString = new Vector<String>();
    private Vector<String> listSomeAnotherString = new Vector<String>();

    public ComponentEventDemo() {
        listSomeString.add("-");
        listSomeString.add("Snowboarding");
        listSomeString.add("Rowing");
        listSomeString.add("Knitting");
        listSomeString.add("Speed reading");
        listSomeString.add("Pool");
        listSomeString.add("None of the above");
//
        listSomeAnotherString.add("-");
        listSomeAnotherString.add("XxxZxx Snowboarding");
        listSomeAnotherString.add("AaaBbb Rowing");
        listSomeAnotherString.add("CccDdd Knitting");
        listSomeAnotherString.add("Eee Fff Speed reading");
        listSomeAnotherString.add("Eee Fff Pool");
        listSomeAnotherString.add("Eee Fff None of the above");
        comboBox = new JComboBox(listSomeString);
        comboBox1 = new JComboBox(listSomeAnotherString);
        display = new JTextArea();
        display.setEditable(false);
        JScrollPane scrollPane = new JScrollPane(display);
        scrollPane.setPreferredSize(new Dimension(350, 200));

        label = new JLabel("This is a label", JLabel.CENTER);
        label.addComponentListener(this);

        JCheckBox checkbox = new JCheckBox("Label visible", true);
        checkbox.addItemListener(this);
        checkbox.addComponentListener(this);

        JPanel panel = new JPanel(new GridLayout(1, 2));
        panel.add(label);
        panel.add(checkbox);
        panel.addComponentListener(this);
        frame = new JFrame("ComponentEventDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(scrollPane, BorderLayout.CENTER);
        frame.add(panel, BorderLayout.PAGE_END);
        frame.pack();
        frame.setVisible(true);
    }

    public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
            label.setVisible(true);
            label.revalidate();
            label.repaint();
        } else {
            label.setVisible(false);
        }
    }

    protected void displayMessage(String message) {
        //If the text area is not yet realized, and
        //we tell it to draw text, it could cause
        //a text/AWT tree deadlock. Our solution is
        //to ensure that the text area is realized
        //before attempting to draw text.
        // if (display.isShowing()) {
        display.append(message + newline);
        display.setCaretPosition(display.getDocument().getLength());
        //}
    }

    public void componentHidden(ComponentEvent e) {
        displayMessage(e.getComponent().getClass().getName() + " --- Hidden");
    }

    public void componentMoved(ComponentEvent e) {
        displayMessage(e.getComponent().getClass().getName() + " --- Moved");
    }

    public void componentResized(ComponentEvent e) {
        displayMessage(e.getComponent().getClass().getName() + " --- Resized ");
    }

    public void componentShown(ComponentEvent e) {
        displayMessage(e.getComponent().getClass().getName() + " --- Shown");

    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                ComponentEventDemo componentEventDemo = new ComponentEventDemo();
            }
        });
    }

    public void hierarchyChanged(HierarchyEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}

我觉得在实现你的建议时出了一些问题,我把它粘贴在问题中。 - Valentino Ru
你的想法是创建一堆新的Timer实例,测试timer.isRunning(),如果是,则重新启动计时器timer.restart(),否则启动计时器timer.start(),同时设置timer.setRepeats(false) - mKorbel
@Denis Tulskiy,请查看已编辑的问题,我实现了接近所需解决方案的东西。 - Valentino Ru

6

要模仿这种行为的一个简单技巧是将框架设置为不可调整大小,并将一些部分设为调整大小的手柄,然后添加鼠标监听器并自行调整大小:

    public static class GroundFrame extends JFrame {
        private boolean doResize = false;

        public GroundFrame() throws HeadlessException {
            setResizable(false);

            getContentPane().addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    Point point = e.getPoint();
                    if (point.x >= getWidth() - 50) {
                        doResize = true;
                    }
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    if (doResize) {
                        setSize(e.getX(), getHeight());
                        doResize = false;
                    }
                }
            });
        }
    }

你甚至可以将JFrame设置为无边框并自己执行所有操作,例如调整大小、移动、关闭等等。

但是,这种行为受窗口管理器或合成管理器控制(例如,您可以调整Compiz以为您执行此操作)。我认为实时调整大小是NeXT的关键宣传功能之一,在那个时候是一个重要的进步 :)

另外,我尝试的另一个技巧基于Java 7在调整大小事件发生时报告而不是在鼠标释放时报告,因此可以在首次调整大小后保存窗口大小,然后恢复它,直到调整大小完成。但是效果不是很流畅:

    public static class GroundFrame extends JFrame {
        private Timer timer;
        private Dimension oldSize;

        public GroundFrame() throws HeadlessException {
            timer = new Timer(500, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    oldSize = null;
                    invalidate();
                }
            });
            timer.setRepeats(false);

            addComponentListener(new ComponentAdapter() {
                public void componentResized(ComponentEvent e) {
                    if (oldSize == null) {
                        oldSize = getSize();
                        timer.start();
                    } else {
                        setSize(oldSize);
                        timer.restart();
                    }
                }
            });
        }
    }

6

+1 给 mKorbel 和 Denis Tulskiy 的答案。

我提供了一种抽象的解决方案,可能会有所帮助。它支持从 JFrame 的四个方向(NORTH、EAST、SOUTH 和 WEST)调整大小(增加和减小高度和宽度),当鼠标移动到其中一个角落时,还可以同时调整宽度和高度。

基本上我做的是:

  • MouseMotionListenerMouseListener 添加到 JFrame
  • 重写 ListenermouseDragged(..)mouseMoved(..)mousePressed(..)mouseReleased(..) 方法。
  • JFrame 设置为不可调整大小。
  • mouseMoved(..) 中监听鼠标是否距离 JFrame 的底部或右侧不到 10px,然后设置调整的适当方向(高度或宽度),更改鼠标 Cursor(对于最右侧/宽度调整,使用 Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR),对于底部/高度调整,使用 Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR),对于框架顶部高度调整,使用 Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR),对于左侧宽度调整,使用 Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR)),然后调用 canResize(true)
  • mouseDragged(..) 中,根据拖动方向更新新大小的宽度或高度。
  • mouseReleased(..) 中,将 JFrame 设置为新大小。
  • mousePressed(..) 中,我们检查用户按下的坐标(这使我们可以看到框架大小是减小还是增加)。

查看我制作的示例:

import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class JFrameSizeAfterDrag extends JFrame {

    //direction holds the position of drag
    private int w = 0, h = 0, direction, startX = 0, startY = 0;

    public JFrameSizeAfterDrag() {
        setResizable(false);
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        addMouseListener(new MouseAdapter() {
            //so we can see if from where the user clikced is he increasing or decraesing size
            @Override
            public void mousePressed(MouseEvent me) {
                super.mouseClicked(me);
                startX = me.getX();
                startY = me.getY();
                System.out.println("Clicked: " + startX + "," + startY);
            }

            //when the mouse is relaeased set size
            @Override
            public void mouseReleased(MouseEvent me) {
                super.mouseReleased(me);
                System.out.println("Mouse released");
                if (direction == 1 || direction == 2 || direction == 5 || direction == 6) {
                    setSize(w, h);
                } else {//this should move x and y by as much as the mouse moved then use setBounds(x,y,w,h);
                    setBounds(getX() - (startX - me.getX()), getY() - (startY - me.getY()), w, h);
                }
                validate();
            }
        });

        addMouseMotionListener(new MouseAdapter() {
            private boolean canResize;

            //while dragging check direction of drag
            @Override
            public void mouseDragged(MouseEvent me) {
                super.mouseDragged(me);
                System.out.println("Dragging:" + me.getX() + "," + me.getY());
                if (canResize && direction == 1) {//frame height  change
                    if (startY > me.getY()) {//decrease in height
                        h -= 4;
                    } else {//increase in height
                        h += 4;
                    }
                } else if (canResize && direction == 2) {//frame width  change
                    if (startX > me.getX()) {//decrease in width
                        w -= 4;
                    } else {//increase in width
                        w += 4;
                    }
                } else if (canResize && direction == 3) {//frame height  change
                    if (startX > me.getX()) {//decrease in width
                        w += 4;
                    } else {//increase in width
                        w -= 4;
                    }
                } else if (canResize && direction == 4) {//frame width  change
                    if (startY > me.getY()) {//decrease in height
                        h += 4;
                    } else {//increase in height
                        h -= 4;
                    }
                } else if (canResize && direction == 5) {//frame width and height  change bottom right
                    if (startY > me.getY() && startX > me.getX()) {//increase in height and width
                        h -= 4;
                        w -= 4;
                    } else {//decrease in height and with
                        h += 4;
                        w += 4;
                    }
                } /* Windows dont usually support reszing from top but if you want :) uncomment code in mouseMoved(..) also
                 else if (canResize && direction == 6) {//frame width and height  change top left
                 if (startY > me.getY() && startX > me.getX()) {//decrease in height and with
                 h += 4;
                 w += 4;
                 } else {//increase in height and width
                 h -= 4;
                 w -= 4;
                 }
                 } else if (canResize && direction == 8) {//frame width and height  change top right
                 if (startY > me.getY() && startX > me.getX()) {//increase in height and width
                 h -= 4;
                 w -= 4;
                 } else {//decrease in height and with
                 h += 4;
                 w += 4;
                 }
                 }
                 */ else if (canResize && direction == 7) {//frame width and height  change bottom left
                    if (startY > me.getY() && startX > me.getX()) {//increase in height and width
                        h -= 4;
                        w -= 4;
                    } else {//decrease in height and with
                        h += 4;
                        w += 4;
                    }
                }
            }

            @Override
            public void mouseMoved(MouseEvent me) {
                super.mouseMoved(me);
                if (me.getY() >= getHeight() - 10 && me.getX() >= getWidth() - 10) {//close to bottom and right side of frame show south east cursor and allow height witdh simaltneous increase/decrease
                    //System.out.println("resize allowed..");
                    canResize = true;
                    setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
                    direction = 5;
                } /*Windows dont usually support reszing from top but if you want :) uncomment code in mouseDragged(..) too
                 else if (me.getY() <= 28 && me.getX() <= 28) {//close to top side and left side of frame show north west cursor and only allow increase/decrease in width and height simultaneosly
                 //System.out.println("resize allowed..");
                 canResize = true;
                 setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
                 direction = 6;
                 }  else if (me.getY() <= 28 && me.getX() >= getWidth() - 10) {//close to top and right side of frame show north east cursor and only allow increase/decrease in width and height simultaneosly
                 //System.out.println("resize allowed..");
                 canResize = true;
                 setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
                 direction = 8;
                 } 
                 */ else if (me.getY() >= getHeight() - 10 && me.getX() <= 10) {//close to bottom side and left side of frame show north west cursor and only allow increase/decrease in width and height simultaneosly
                    //System.out.println("resize allowed..");
                    canResize = true;
                    setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
                    direction = 7;
                } else if (me.getY() >= getHeight() - 10) {//close to bottom of frame show south resize cursor and only allow increase height
                    //System.out.println("resize allowed");
                    canResize = true;
                    setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
                    direction = 1;
                } else if (me.getX() >= getWidth() - 10) {//close to right side of frame show east cursor and only allow increase width
                    //System.out.println("resize allowed");
                    canResize = true;
                    setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
                    direction = 2;
                } else if (me.getX() <= 10) {//close to left side of frame show east cursor and only allow increase width
                    //System.out.println("resize allowed");
                    canResize = true;
                    setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
                    direction = 3;
                } else if (me.getY() <= 28) {//close to top side of frame show east cursor and only allow increase height
                    // System.out.println("resize allowed..");
                    canResize = true;
                    setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
                    direction = 4;
                } else {
                    canResize = false;
                    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                    // System.out.println("resize not allowed");
                }
            }
        });

        //just so GUI is visible and not small
        add(new JPanel() {
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
        });

        pack();
        setVisible(true);
    }

    @Override
    public void setVisible(boolean bln) {
        super.setVisible(bln);
        w = getWidth();
        h = getHeight();
    }

    public static void main(String[] args) {

        /**
         * Create GUI and components on Event-Dispatch-Thread
         */
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new JFrameSizeAfterDrag();
            }
        });
    }
}

更新:

你提供了一个很好的示例,我通过添加MouseAdapter来修改代码,它重写mouseReleased(..)并在mouseReleased(..)中调用resizePanel(...)

请点击此处查看修复后的代码(还修复了一些小问题,例如使用ComponentAdapter代替ComponentListener以及使用AbstractAction代替Action)。

import java.awt.*;
import static java.awt.GraphicsDevice.WindowTranslucency.*;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;

public class JFrameSizeAfterDrag2 extends JFrame {

    private Timer timer;
    private JPanel panel2;
    boolean canResize = true,firstTime = true;


    public JFrameSizeAfterDrag2() {
        super("GradientTranslucentWindow");

        setBackground(new Color(0, 0, 0, 0));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setContentPane(new JPanel(null) {//contentpane layout is null only
            @Override
            protected void paintComponent(Graphics g) {
                Paint p = new GradientPaint(0.0f, 0.0f, new Color(0, 0, 0, 0), 0.0f, getHeight(), new Color(0, 0, 0, 0), true);
                Graphics2D g2d = (Graphics2D) g;
                g2d.setPaint(p);
                g2d.fillRect(0, 0, getWidth(), getHeight());
            }

            @Override
            public Dimension getPreferredSize() {
                return new Dimension(300, 300);
            }
        });

        panel2 = new JPanel();
        panel2.setBackground(Color.black);
        getContentPane().add(panel2);

        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent me) {
                super.mouseReleased(me);
                if (canResize) {
                    resizePanel(getContentPane().getSize());
                }
            }
        });

        addComponentListener(new ComponentAdapter() {

            @Override
            public void componentResized(ComponentEvent e) {
                timer = new Timer(50, new AbstractAction() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        if (timer.isRunning()) {
                            canResize = false;
                        } else {
                            canResize = true;
                            if (firstTime == true) {
                                firstTime = false;
                                resizePanel(getContentPane().getSize());
                            }
                        }
                    }
                });
                timer.setRepeats(false);
                timer.start();

            }
        });
        pack();
    }

    public void resizePanel(Dimension dim) {
        panel2.setBounds(0, 0, dim.width, dim.height);
        revalidate();
    }

    public static void main(String[] args) {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        boolean isPerPixelTranslucencySupported = gd.isWindowTranslucencySupported(PERPIXEL_TRANSLUCENT);

        if (!isPerPixelTranslucencySupported) {
            System.out.println("Per-pixel translucency is not supported");
            System.exit(0);
        }

        JFrame.setDefaultLookAndFeelDecorated(true);

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrameSizeAfterDrag2 gtw = new JFrameSizeAfterDrag2();
                gtw.setVisible(true);
            }
        });
    }
}

1
请看修改后的问题,我实现了接近所需解决方案的东西。 - Valentino Ru
@ValentinoRu 的代码非常棒,我必须承认。我的代码只能在拖动后调整大小...我还修复了你的代码,请查看更新的帖子。 - David Kroukamp
@DavidKroukamp 很好,这正是我对MouseListener所想的!我会进一步研究是否有可能从左侧或顶部拖动(我认为在释放之前不让JFrame标题调整大小是不可能的)。如果我找到了什么,当然会分享! - Valentino Ru
刚刚发现了一个小bug:如果你通过点击窗口标题栏上的按钮或双击窗口标题栏来最大化窗口,那么MouseListener当然不会将其识别为mouseReleased事件,因此它不会进行调整大小(只有在再次单击后才会)。嗯.... :-) - Valentino Ru
@ValentinoRu 嗯,我明白了... 我会考虑修复的,请看我的更新帖子,我已经更新了你的代码,使其更加高效/遵循最佳实践。 - David Kroukamp
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接