向左调整大小时 Swing 弹出窗口闪烁

问题描述 投票:0回答:1

我想要实现的是在框架右侧显示一个弹出窗口:

此弹出窗口的目标是模仿

ComboBox
下拉菜单,但以更灵活的方式。例如。它可以扩展额外的内容。 另外,我试图使弹出窗口保留在主
JFrame
的范围内,因为触发器组件位于
JFrame
的右侧,所以弹出窗口必须在其左侧展开。

我有一段代码的行为符合预期,即:弹出窗口在左侧展开(显示以前不可见的

JPanel
)或缩小(附加面板设置为不可见)。

但是,当它重新定位(扩大或缩小)时,我注意到弹出窗口闪烁

在屏幕录制中逐帧显示弹出窗口闪烁时会发生什么:

下面的屏幕截图来自在 QuickTime 中打开的屏幕录制,屏幕截图被裁剪以集中在弹出窗口上,显然父 JFrame 没有调整大小。

  • 单击“更多”按钮(展开)后:弹出窗口大小和位置已更新,但发生的内容重新绘制得太早了

  • 点击“减少”按钮(收缩)后:位置已更新,但大小和内容已过时。

该功能是以这种方式实现的,使用由

locationAtBottomRight
计算的坐标在按钮的右下角创建弹出窗口:

var popupLocation = locationAtBottomRight(dropdownButton, popupContent);
popup.ref = PopupFactory.getSharedInstance().getPopup(
        dropdownButton,
        popupContent,
        popupLocation.x,
        popupLocation.y
);
popup.ref.show();

当单击弹出窗口中的“更多”按钮时,它会设置可见面板并更新父面板大小

moreButton.addActionListener(ae -> {
    secondary.setVisible(!secondary.isVisible());
    moreButton.setText(secondary.isVisible() ? "Less..." : "More...");
    var newSize = new Dimension(main.getPreferredSize());
    if (secondary.isVisible()) {
        newSize.width += secondary.getPreferredSize().width;
    }
    popupContent.setSize(newSize);
});

发生这种情况时,

popupContent
上的组件侦听器将被调用并更新弹出窗口的大小和位置。

popupContent.addComponentListener(new ComponentAdapter() {
    @Override
    public void componentResized(ComponentEvent e) {
        resizeAndRelocatePopup(dropdownButton, popupContent);
        System.out.println("componentResized: " +popupContent.getPreferredSize());
    }
});

设置弹出窗口的位置

Window::setLocation
和大小
Window::setSize
在弹出窗口的
Window
上调用。

private static void resizeAndRelocatePopup(JComponent owner, JComponent popupContent) {
    var popupWindow = SwingUtilities.getWindowAncestor(popupContent);
    var newPopupLocation = locationAtBottomRight(owner, popupContent);
    popupWindow.setLocation(newPopupLocation);
    popupWindow.setSize(popupContent.getPreferredSize());
    // failed attempt to avoid the popup getting painted too early
    popupWindow.revalidate();
}

我相信这是导致闪烁的最后一段代码。我尝试过使用常用技巧,例如

revalidate
/
repaint
,但这没有帮助。


这是重现该问题的完整代码。示例在 JDK 21 上运行,但也应该在 JDK 11 上运行。

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

public class PopupStuffJ {
    /**
     * Resize and relocate the popup window, according to the preferred size of the popup content.
     */
    private static void resizeAndRelocatePopup(JComponent owner, JComponent popupContent) {
        var popupWindow = SwingUtilities.getWindowAncestor(popupContent);
        var newPopupLocation = locationAtBottomRight(owner, popupContent);
        popupWindow.setLocation(newPopupLocation);
        popupWindow.setSize(popupContent.getPreferredSize());

        // failed attempt to avoid the popup getting painted too early
        popupWindow.revalidate();
    }

    /**
     * Compute location at bottom right of owner component, using the preferred width of the popup content.
     */
    private static Point locationAtBottomRight(JComponent owner, JComponent popupContent) {
        var popupLocation = owner.getLocationOnScreen();
        popupLocation.x = popupLocation.x + owner.getWidth() - popupContent.getPreferredSize().width;
        popupLocation.y += owner.getHeight();
        return popupLocation;
    }

    private static JButton buttonWithDropDown() {
        var popup = new Object() {
            Popup ref = null;
        };

        // This button is merely here to trigger the popup
        // In actual code, this triggered by a third party component
        var dropdownButton = new JButton("Dropdown Menu");
        dropdownButton.addActionListener(ae -> {
            if (popup.ref != null) {
                popup.ref.hide();
                popup.ref = null;
                return;
            }

            // Computes the location of the popup according to the button and the popup content
            var popupContent = popupContent();

            // This code that relocates and resizes the popup
            popupContent.addComponentListener(new ComponentAdapter() {
                @Override
                public void componentResized(ComponentEvent e) {
                    resizeAndRelocatePopup(dropdownButton, popupContent);
                    System.out.println("componentResized: " +popupContent.getPreferredSize());
                }
            });

            // When first displayed show popup at the right location
            var popupLocation = locationAtBottomRight(dropdownButton, popupContent);
            popup.ref = PopupFactory.getSharedInstance().getPopup(
                    dropdownButton,
                    popupContent,
                    popupLocation.x,
                    popupLocation.y
            );
            popup.ref.show();
        });
        return dropdownButton;
    }

    private static JComponent popupContent() {
        var secondary = new JPanel(new BorderLayout());
        secondary.setPreferredSize(new Dimension(100, 100));
        secondary.setVisible(false);
        secondary.setOpaque(false);
        secondary.add(new JTextArea("Expanded"), BorderLayout.CENTER);

        var main = new JPanel();
        main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS));
        main.setPreferredSize(new Dimension(100, 100));

        var textArea = new JTextArea("Click more to expand on the left");
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
        main.add(textArea);

        var moreButton = new JButton("More...");
        main.add(moreButton);
        main.setOpaque(false);

        var popupContent = new JPanel();
        popupContent.setLayout(new BoxLayout(popupContent, BoxLayout.X_AXIS));
        popupContent.add(secondary);
        popupContent.add(main);

        // update the size of the popupContent panel,
        // to either expand or shrink the popup
        // when button More... is clicked
        moreButton.addActionListener(ae -> {
            secondary.setVisible(!secondary.isVisible());
            moreButton.setText(secondary.isVisible() ? "Less..." : "More...");

            var newSize = new Dimension(main.getPreferredSize());
            if (secondary.isVisible()) {
                newSize.width += secondary.getPreferredSize().width;
            }
            popupContent.setSize(newSize);
        });
        return popupContent;
    }

    private static JComponent frameContent() {
        var toolbar = new JPanel(new FlowLayout(FlowLayout.RIGHT));
        toolbar.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
        toolbar.add(new JLabel("xxxx"));
        toolbar.add(buttonWithDropDown());
        toolbar.setOpaque(false);

        var frameContent = new JPanel(new BorderLayout());
        frameContent.add(toolbar, BorderLayout.NORTH);
        frameContent.setOpaque(false);
        return frameContent;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            var jFrame = new JFrame();
            jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            var contentPane = jFrame.getContentPane();
            contentPane.setBackground(new Color(0xFFC499));
            contentPane.add(frameContent());
            jFrame.setSize(new Dimension(600, 400));
            jFrame.setVisible(true);
        });
    }
}
macos swing popup
1个回答
0
投票

但是接触主框架的代码在哪里?

我不知道。

但是,当我运行您的代码并单击“下拉菜单”按钮时,框架的大小会调整得更小。这是我看到的:

当我调整框架大小时,我会看到以下内容:

为什么镜框变小了?我不知道。看来您有一个 setSize() 方法,该方法适用于框架而不是弹出窗口。在解决这个初始问题之前,您无法解决调整大小/重塑问题。请注意,在我提供的 3 个示例中,框架的大小没有改变。这就是为什么我建议您从工作代码开始。在弹出窗口最初正确显示之前,我什至无法开始帮助处理“更多/更少”逻辑。

我已查看链接的答案

答案展示了 3 种不同的显示弹出窗口的方法。其中之一使用 JWindow。您还可以使用未修饰的 JDialog。

但是这如何帮助我重塑已经显示的弹出窗口呢?

上述方法将允许您像任何其他 JFrame 一样创建弹出组件。然后您可以根据需要调整窗口的大小/形状。

对我来说,你所需要的只是 ActionListeners。在“下拉菜单”按钮上,ActionListener 将创建并显示弹出窗口。在弹出窗口上,您可能有两个按钮“更多...”和“更少...”。根据窗口的当前状态,一次只能看到一个。然后为每个按钮添加一个 ActionListener。 “更多...”侦听器将通过向窗口添加更多内容来调整窗口大小,然后根据新添加内容的大小重置位置。 “Les...”监听器将删除内容并重置大小/位置。

我还在按钮上看到“Mo...”,这表明有问题。您的代码有太多 setPreferredSize(...) 语句。应该不需要它们中的任何一个。只需创建组件并将它们添加到窗口即可。每个组件都会确定自己的首选尺寸。

© www.soinside.com 2019 - 2024. All rights reserved.