Java,拖动时调整 jframe 的大小问题

问题描述

我希望我的 jframe 根据 x 坐标更改其大小和内容。 最初,jframe 出现在“主要区域”(x 期望:当它被拖动并离开“主要区域”并进入“次要区域”时,所有内容都从内容窗格中删除,该面板被包裹在一个 200x200 大小的 jscrollpane 中,并添加了 jscrollpane内容窗格。当 jframe 离开“次要区域”时,jscrollpane 将从内容窗格中删除并重新添加该面板。
实际:结果不稳定。当 jframe 离开主要区域时,我可以看到一些翻转。滚动条出现,但框架改变了它的大小,但随后立即将大小调整回以前的大小。在 changeSizeAndContent invokelater 代码块中的 runnables 内的断点处停止(实际上不是一个好习惯)会带来所需的结果,条件断点也是如此。
发生了一些我不明白的 Swing 多线程。我可以看到 EDT 调用 EventQueue 的 dispatchEvent 和由 changeSizeAndContent 中的 runnables 触发的 COMPONENT_RESIZED(新的,正确的大小)事件,然后是 COMPONENT_MOVED(旧的,现在不正确的大小)事件,它们引用具有旧大小的组件。
已尝试使用 Java 8 和 11。

package Jna;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

/* 0------------------1200-1400--------->
 *                      +---------------+
 *                      |   SECONDARY   |
 * +--------------------+---+           |                     
 * |    PRIMARY         | B | x: 1200-  |
 * |    x: 0-1400       | U |           |
 * |                    | F |           |
 * |                    | F |           |
 * |                    | E |           |
 * |                    | R |           |
 * |                    +---+-----------+
 * |                        |
 * +------------------------+
 */
public class FrameDemo3 {

    static final JPanel panel;
    static final JScrollPane jsp;
    
    static {
        panel = getinitializedPanel();
        jsp = getinitilalizedJScrollPane();
    }
    
    static boolean isPrimaryZone = true;
    static boolean isCurrentPrimaryZone = true;

    public static void main(String[] args) {
        SwingUtilities.invokelater(() -> createAndShowGUI());
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("FrameDemo");

        frame.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentMoved(ComponentEvent e) {
                int x = frame.getX();

                if (x > 1400) {
                    isCurrentPrimaryZone = false;
                } else if (x < 1200) {
                    isCurrentPrimaryZone = true;
                }

                if (isPrimaryZone != isCurrentPrimaryZone) {
                    isPrimaryZone = isCurrentPrimaryZone;
                    changeSizeAndContent(frame);
                }
            }
        });
        frame.setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(panel);
        frame.pack();
        frame.setVisible(true);
    }

    public static void changeSizeAndContent(JFrame frame) {
        if (isPrimaryZone) {
            SwingUtilities.invokelater(() -> {
                frame.getContentPane().removeAll();
                frame.getContentPane().add(panel);
                frame.pack();
            });
        } else {
            SwingUtilities.invokelater(() -> {
                frame.getContentPane().removeAll();
                frame.getContentPane().add(jsp);
                frame.pack();
            });            
        }
    }

    private static JPanel getinitializedPanel() {
        JPanel panel = new JPanel(new GridBagLayout());
        panel.setBackground(Color.WHITE);

        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                panel.add(new JLabel(getLabelText(i,j)),getConstraints(i,j));
            }
        }
        panel.setPreferredSize(new Dimension(500,500));
        return panel;
    }
    
    private static JScrollPane getinitilalizedJScrollPane() {
        JScrollPane jsp = new JScrollPane(panel,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        jsp.setPreferredSize(new Dimension(200,200));
        return jsp;
    }

    static GridBagConstraints getConstraints(int gridX,int gridY) {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = gridX;
        gbc.gridx = gridY;
        gbc.ipady = 40;
        gbc.ipadx = 40;
        return gbc;
    }

    static String getLabelText(int gridX,int gridY) {
        StringBuilder sb = new StringBuilder("EXAMPLE ")
                .append(gridX)
                .append(',')
                .append(gridY);
        return sb.toString();
    }

}

解决方法

正如评论中提到的,一个组件只能有一个父级,因此更简单的解决方案可能是将组件添加到滚动窗格并根据需要更改滚动条策略。下面的代码演示了这一点。

发生了一些我不明白的 Swing 多线程?

问题似乎是框架的标题栏。

(请注意,我将调整大小行为更改为 1000/800 而不是 1400、1200)

当框架移动到位置 1001 时,您的逻辑会被调用,并且框架会按预期调整大小。

然而,问题是总是会在框架上生成额外的事件。

例如,如果您将框架移动到 1001 并按住鼠标,框架将保持正确的大小。但是,一旦您松开鼠标,它就会恢复到之前的大小。

或者,如果您将框架移动到位置 1002 或更大的位置,它会返回到之前的大小。

在上述两种情况下,不会执行应用程序中的代码来更改帧大小。

我不知道如何解决这个问题,因为这个逻辑是由操作系统框架小部件控制的。这可以解释为什么它适用于 user153...我使用的是 Windows 10,我看到了您描述的问题。

在下面的代码中,我添加了一个 Swing Timer 以在 2 秒延迟后将帧大小重置为预期大小。这不是一个实用的解决方案,但它确实表明您的代码按预期工作,您只是无法控制框架的外部行为。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class FrameDemo4 {

    static final JPanel panel;
    static final JScrollPane jsp;
    static Dimension preferredSize;

    static {
        panel = getInitializedPanel();
        jsp = getInitilalizedJScrollPane();
    }

    static boolean isPrimaryZone = true;
    static boolean isCurrentPrimaryZone = true;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("FrameDemo");

        frame.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentMoved(ComponentEvent e) {
                int x = frame.getX();
                System.out.println(x);

                if (x > 1000) {
                    isCurrentPrimaryZone = false;
                } else if (x < 800) {
                    isCurrentPrimaryZone = true;
                }

                if (isPrimaryZone != isCurrentPrimaryZone) {
                    isPrimaryZone = isCurrentPrimaryZone;
                    changeSizeAndContent(frame);
                }
            }
        });

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//        frame.getContentPane().add(panel);
        frame.getContentPane().add(jsp);
        frame.pack();
        frame.setLocation(1000,0);
        frame.setVisible(true);
    }

    public static void changeSizeAndContent(JFrame frame) {
        System.out.println(isPrimaryZone);

        if (isPrimaryZone) {
                //frame.getContentPane().removeAll();
                //frame.getContentPane().add(panel);
                jsp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
                jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
                jsp.setPreferredSize( panel.getPreferredSize() );
                frame.pack();
                preferredSize = frame.getPreferredSize();
        } else {
                //frame.getContentPane().removeAll();
                //frame.getContentPane().add(jsp);
                jsp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
                jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
                jsp.setPreferredSize( new Dimension(200,200));
                frame.pack();
                preferredSize = frame.getPreferredSize();
        }

        Timer timer = new Timer(2000,(e) -> frame.setSize( preferredSize.width,preferredSize.height ) );
        timer.start();
    }

    private static JPanel getInitializedPanel() {
        JPanel panel = new JPanel(new GridBagLayout());
        panel.setBackground(Color.WHITE);

        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                panel.add(new JLabel(getLabelText(i,j)),getConstraints(i,j));
            }
        }
        panel.setPreferredSize(new Dimension(500,500));
        return panel;
    }

    private static JScrollPane getInitilalizedJScrollPane() {
//        JScrollPane jsp = new JScrollPane(panel,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
//        jsp.setPreferredSize(new Dimension(200,200));
        JScrollPane jsp = new JScrollPane(panel);
        jsp.setPreferredSize(panel.getPreferredSize());
        return jsp;
    }

    static GridBagConstraints getConstraints(int gridX,int gridY) {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = gridX;
        gbc.gridx = gridY;
        gbc.ipady = 40;
        gbc.ipadx = 40;
        return gbc;
    }

    static String getLabelText(int gridX,int gridY) {
        StringBuilder sb = new StringBuilder("EXAMPLE ")
                .append(gridX)
                .append(',')
                .append(gridY);
        return sb.toString();
    }

}

我能想到的唯一解决方案是:

  1. 也许可以使用 Metal LAF。它使用带有自定义标题栏的未修饰框架
  2. 创建您自己的标题栏以在未装饰的框架上使用。