在可见时更新JPopupMenu高度

时间:2014-10-06 10:23:30

标签: java swing jpopupmenu

我的应用程序中有一个弹出菜单,我想用自定义的菜单替换它,使其与外观相匹配。感觉其余的应用程序。本质上,我不想在弹出窗口中使用普通的菜单项,而是希望重用一个已经存在于应用程序中其他位置的组件,该组件允许您浏览"分页中的项目层次结构。方式而不是子菜单。因此,如果您单击列表中包含子项的项目,则将显示下一页,将列表中的当前项目替换为所单击项目的子项列表。

使用"分页"组件是它适合应用程序的其余部分(它已经在其他非弹出窗口中使用),并且在页面之间导航时它具有一些漂亮的动画效果。

我遇到的问题是,当显示不同的页面(列表中的项目数量发生变化)时,页面组件的首选高度会发生变化,我希望更新弹出菜单的高度以使其适合分页组件完全正确,但到目前为止我尝试在弹出菜单可见时更新弹出菜单的高度失败。

以下是演示此问题的示例应用程序:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;

public class PopupSizeTestFrame extends JFrame {

    private static final int PREFFERED_WIDTH = 300;
    private static JPanel resizingPanel;
    private static JPopupMenu popupMenu;
    private static PopupSizeTestFrame frame;

    private static void resizePopupMenu() {
        // What should I do here so that the size of the popup menu is updated?

        /**
         * Calling pack() works in this example, but I cannot call pack in the
         * actual application since there is a smooth transition animation when
         * clicking on the inner panel and pack() essentially re-adds the
         * components of the popup menu (removeNotify and addNotify is called),
         * which interferes with the animation and causes the menu to flicker.
         * The flickering when clicking the inner panel can also be seen in this
         * example when uncommenting the pack() call.
         */
        //popupMenu.pack();
        // 
        // I tried combinations of the following, without success:
        //popupMenu.invalidate();
        //popupMenu.revalidate();
        //popupMenu.validate();
        //popupMenu.doLayout();

        // Repaint
        popupMenu.repaint();
    }

    public static void main(String args[]) {
        // Create popup child panel with height that changes when clicked
        resizingPanel = new JPanel(new BorderLayout());
        int initialHeight = 30;
        final JLabel label = new JLabel("Click me (" + initialHeight + "px)");
        resizingPanel.add(label);
        resizingPanel.setBackground(Color.GREEN);
        resizingPanel.setPreferredSize(new Dimension(PREFFERED_WIDTH, initialHeight));
        resizingPanel.addMouseListener(new MouseAdapter() {
            private int clickCount = 0;

            @Override
            public void mouseClicked(MouseEvent e) {
                int height = ((clickCount % 3) + 1) * 50;
                resizingPanel.setPreferredSize(new Dimension(PREFFERED_WIDTH, height));
                clickCount++;
                label.setText("Click me (" + height + "px)");
                resizePopupMenu();
            }
        });

        // Create popup
        popupMenu = new JPopupMenu();
        popupMenu.setLayout(new BorderLayout());
        popupMenu.add(new JLabel("Header"), BorderLayout.NORTH);
        popupMenu.add(resizingPanel, BorderLayout.CENTER);
        popupMenu.add(new JLabel("Footer"), BorderLayout.SOUTH);

        // Create frame
        frame = new PopupSizeTestFrame();
        frame.setLayout(new BorderLayout());
        frame.setPreferredSize(new Dimension(400, 400));
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseReleased(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    popupMenu.show(frame, e.getX(), e.getY());
                }
            }
        });

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.setVisible(true);
            }
        });
    }
}

运行上面的示例时,如果在单击内部面板后关闭并打开弹出菜单大小,则会看到弹出菜单大小已更新,但弹出窗口可见时不会更新弹出菜单大小。

如何在resizePopupMenu()中更新JPopupMenu的高度?

2 个答案:

答案 0 :(得分:1)

对于问题中的测试应用程序,弹出窗口始终是一个重量级组件,以下工作(由mKorbel建议)

private static void resizePopupMenu() {
    SwingUtilities.getWindowAncestor(popupMenu).pack();
}

对于实际应用程序,当弹出窗口位于边界内时,应用程序将其创建为轻量级组件,这会阻止它使用pack()调整大小,并且还会阻止它调整大小超过应用程序边界。

我尝试设置此属性...

JPopupMenu.setDefaultLightWeightPopupEnabled(false);

但随后创建了一个中等重量组件,它仍然不会超出应用程序范围。

所以我必须首先强制所有"所有者"的所有弹出窗口使用以下不幸代码重量级的组件

// Set owner to component on which popup is shown or any of its parents
final Component owner = ...; 
AccessController.doPrivileged(new PrivilegedAction<Void>() {
    @Override
    public Void run() {
        try {
            Field field;
            if (System.getProperty("java.version").startsWith("1.6.0")) {
                Class clazz = Class.forName("javax.swing.PopupFactory");
                field = clazz.getDeclaredField("forceHeavyWeightPopupKey");
            } else { //JDK 1.7.0, 1.8.0
                Class clazz = Class.forName("javax.swing.ClientPropertyKey");
                field = clazz.getDeclaredField("PopupFactory_FORCE_HEAVYWEIGHT_POPUP");
            }
            field.setAccessible(true);
            owner.putClientProperty(field.get(null), Boolean.TRUE);
        } catch (Exception ex) {
            Exceptions.printStackTrace(ex);
        }
        return null;
    }
});

强制弹出菜单始终是重量级组件

SwingUtilities.getWindowAncestor(popupMenu).pack();

也更新了弹出窗口的大小,甚至超出了应用程序的范围。

答案 1 :(得分:0)

我认为您可以在弹出菜单中添加PopupMenuListener并处理menuWillBecomeVisible(...)事件以设置弹出窗口的大小。