Update JPopupMenu height while visible

风流意气都作罢 提交于 2019-12-24 05:35:28

问题


I have a popup menu in my application that I want to replace with a customized one so that it matches the look & feel of the rest of the application. Essentially, instead of having the normal menu items in the popup, I want reuse a component that already exist elsewhere in the application that lets you navigate through a hierarchy of items in a "paging" way instead of with sub-menus. So if you click on an item in the list that contains children then the next page will be displayed replacing the current items in the list with a list of the children of the clicked item.

The advantage of using the "paging" component is that it will fit in well with the rest of the application (it is already used in other places that are not popups) and it has some nice looking animation effects when navigating between pages.

The problem I'm having is that the preferred height of the paging component changes when showing different pages (the amount of items in the list change) and I want the popup menu height to update so that it fits the paging component exactly, but so far my attempts to update the height of the popup menu while it is visible have failed.

Below is an example application that demonstrates the problem:

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);
            }
        });
    }
}

When running the example above you will see that the popup menu size is updated if it is closed and opened after the inner panel was clicked, but it is not updated while the popup is visible.

What can I do in resizePopupMenu() to update the height of the JPopupMenu?


回答1:


For the test app in the question the popup was always a heavyweight component and the following worked (as suggested by mKorbel)

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

For the actual application when the popup was inside the bounds the application it was created as a lightweight component which prevented it from being resized with pack() and also prevented it from resizing past the application bounds.

I tried setting this property...

JPopupMenu.setDefaultLightWeightPopupEnabled(false);

but then a mediumweight component was created and it would still not resize past the application bounds.

So I had to first force all popups of the "owner" component to be heavyweight with the following piece of unfortunate code

// 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;
    }
});

After forcing the popup menu to always be a heavyweight component this

SwingUtilities.getWindowAncestor(popupMenu).pack();

also worked to update the size of the popup, even beyond the bounds of the application.




回答2:


I think you can add a PopupMenuListener to the popup menu and handle the menuWillBecomeVisible(...) event to set the size of the popup.



来源:https://stackoverflow.com/questions/26214246/update-jpopupmenu-height-while-visible

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!