3

I have a JPopupMenu and I want to change it's inner size dynamically depending on it's inner components' size. In my SSCCE I described the problem.

SSCCE:

public class PopupTest2 {
    public static void main(String[] a) {
        final JFrame frame = new JFrame();
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createLineBorder(Color.RED));

        final JPopupMenu menu = new JPopupMenu();

        // critical step
        JPanel itemPanel = new JPanel();
        itemPanel.setLayout(new BoxLayout(itemPanel, BoxLayout.Y_AXIS));

        final JMenuItem[] items = new JMenuItem[10];
        for (int i = 0; i < 10; i++) {
            JMenuItem item = new JMenuItem("Item #"+String.valueOf(i));
            itemPanel.add(item);
            items[i] = item;
        }

        menu.add(itemPanel);

        JToggleButton button = new JToggleButton("Press me");
        button.addActionListener(new ActionListener() {
            boolean pressed = false;
            @Override
            public void actionPerformed(ActionEvent e) {
                pressed = !pressed;
                if (pressed) {
                    for (JMenuItem item : items) {
                        item.setText(item.getText()+" changed");
                    }
                } else {
                    for (JMenuItem item : items) {
                        item.setText(item.getText().substring(0, item.getText().length() - 8));
                    }
                }
            }
        });

        panel.add(button, BorderLayout.NORTH);

        panel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getButton() == MouseEvent.BUTTON3) {
                    menu.show(panel, (int) (e.getX() - menu.getPreferredSize().getWidth()), e.getY());
                }
            }
        });
        frame.setContentPane(panel);
        frame.setUndecorated(true);
        frame.setBackground(new Color(50, 50, 50, 200));

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

Steps 2 reproduce:

  1. Right-click to show popup menu.
  2. Click the Press me button (on the top of window).
  3. Right-click to show popup menu again (bug #1 - popup is still small-size)
  4. Right-click to show popup menu again (popup menu size is OK)
  5. Click the Press me button again.
  6. Right-click to show popup menu. (bug #2 - popup is still large-size)

How can I force JPopupMenu to change its size before showing? And why does it work if I add items directly to popupMenu?

SeniorJD
  • 6,946
  • 4
  • 36
  • 53
  • Not an SSCCE. What class is `MyUI` ? – An SO User Oct 21 '13 at 12:17
  • don't use setUI, instead register your custom ui with the uiManager and let it handle the setting automatically – kleopatra Oct 21 '13 at 12:24
  • _// YES, I NEED THAT PANEL, IT IS NOT BUG, I REALLY NEED THAT_ why? – kleopatra Oct 21 '13 at 12:25
  • @kleopatra I need `JPanel` because I have a complicated content inside `JPopupMenu`. – SeniorJD Oct 21 '13 at 12:40
  • 1
    don't want to be rude, but please leave to **ME** what/if/when I answer/comment. In about 99% of problems due to unusual setups it's the mis-perceived need for the unusual that's the real problem ;-) – kleopatra Oct 21 '13 at 12:44
  • you are aware that the sizing is not the worst (IMO) of the problems introduced by the panel? To see what I mean: move the mouse over a menuItem and see the popup hiding ... The base problem is that menuItems et al build a complex ecosystem that tends to blow if used elsewhere – kleopatra Oct 21 '13 at 16:10
  • I know about that problem, but, as it has no relation to the current question, I did not post an extra code which resolve it. @kleopatra will you try to help me **with my question** or not? – SeniorJD Oct 21 '13 at 18:34
  • 1
    _as it has no relation to the current question_ haha ... and you base that evaluation on .. what? Sorry, it's the exact same reason (most probably): you are doing thingies in a fundamentally unusual (to put it mildly, looks wrong to me) way. My advice is to take a step back and describe your requirements (vs. the problems you have with a perceived solution) – kleopatra Oct 21 '13 at 22:31
  • 1
    *fundamentally unusual* and what? If I need turn-screw, I'll create it, but I would not use hammer. Swing API allows to change its default components according to dev' requirements. The problem is, I don't know all of the current component niceties, that's why this question is here. – SeniorJD Oct 22 '13 at 06:34
  • still don't understand, whats issue there to create, build, modify, remove, add Items in JPopup before is visible (see API), or call pack() to the already visible JPopup Items, there isn't some diff to JFrame, JDialog, sure you'd need to check used LayoutManager for JPopup, hmmmm please is there another issue, because JPopup, or BasicsXxxPopup is there mentioned a few times with excelent code workarounds – mKorbel Oct 22 '13 at 09:53
  • @mKorbel I've solved the problem with myself. The problem was in `layout` used in `JPanel` (between `Popup` and `items`). It works excellent now. `pack()` would not solve that. – SeniorJD Oct 22 '13 at 11:14
  • cool, how about sharing the solution? – kleopatra Oct 22 '13 at 11:37

3 Answers3

4

Here is one way to do this:

public static void main(String[] a) {
    final JFrame frame = new JFrame();
    frame.setSize(500, 500);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    final JPanel panel = new JPanel(new BorderLayout());
    panel.setBorder(BorderFactory.createLineBorder(Color.RED));

    final JPopupMenu menu = new JPopupMenu();

    // critical step
    final JPanel itemPanel = new JPanel();
    itemPanel.setLayout(new BoxLayout(itemPanel, BoxLayout.Y_AXIS));

    final JMenuItem[] items = new JMenuItem[10];
    for (int i = 0; i < 10; i++) {
        JMenuItem item = new JMenuItem("Item #"+String.valueOf(i));
        itemPanel.add(item);
        items[i] = item;
    }

    menu.add(itemPanel);

    JToggleButton button = new JToggleButton("Press me");
    button.addActionListener(new ActionListener() {
        boolean pressed = false;
        @Override
        public void actionPerformed(ActionEvent e) {
            pressed = !pressed;
            if (pressed) {
                for (JMenuItem item : items) {
                    item.setText(item.getText()+" changed");
                    item.setMaximumSize(new Dimension(70, 50));
                    item.setPreferredSize(new Dimension(70, 50));
                    item.setMinimumSize(new Dimension(70, 50));
                    itemPanel.invalidate();
                }
            } else {
                for (JMenuItem item : items) {
                    item.setText(item.getText().substring(0, item.getText().length() - 8));
                    item.setMaximumSize(new Dimension(130, 50));
                    item.setPreferredSize(new Dimension(130, 50));
                    item.setMinimumSize(new Dimension(130, 50));
                    itemPanel.invalidate();
                }
            }
        }
    });

    panel.add(button, BorderLayout.NORTH);

    panel.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
            if (e.getButton() == MouseEvent.BUTTON3) {
                menu.show(panel, (int) (e.getX() - menu.getPreferredSize().getWidth()), e.getY());
            }
        }
    });
    frame.setContentPane(panel);
    frame.setUndecorated(true);
    frame.setBackground(new Color(50, 50, 50, 200));

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            frame.setVisible(true);
        }
    });
}
  • Thanks for you attempt, but what should I do if don't know the size of each item? – SeniorJD Oct 22 '13 at 09:35
  • `but what should I do if don't know the size of each item` == cal pack(), SwingUtilities returns ancesor, parent, window for conponent only have to decide, which one is proper for this job – mKorbel Oct 22 '13 at 09:58
  • @mKorbel please see the answer of mine and you'll understand why that should not work. – SeniorJD Oct 22 '13 at 12:41
2

Thanks all, but I've found the solution after continuous debugging. The problem was in layout used in JPanel (between Popup and items). It called the getPreferredSize() of JPanel which called directly menuItem.getPrefferedSize(), which called BasicMenuItemUI.getPrefferedSize(). The last method uses the MainMenuLayoutHelper class to get the preferred size. This class stores the data about size in Properties static object.

The default JPopupMenu layout - DefaultMenuLayout - clears this static data each time its preferredLayoutSize() method called, with call MenuItemLayoutHelper.clearUsedClientProperties(popupMenu). We will do the same - call this method with our JPanel parameter with further revalidate() call:

public class PopupTest2 {
    public static void main(String[] a) {
        final JFrame frame = new JFrame();
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final JPanel panel = new JPanel(new BorderLayout());

        final JPopupMenu menu = new JPopupMenu();

        final JPanel itemPanel = new JPanel();
        itemPanel.setLayout(new BoxLayout(itemPanel, BoxLayout.Y_AXIS));

        final JMenuItem[] items = new JMenuItem[1];
        for (int i = 0; i < 1; i++) {
            JMenuItem item = new JMenuItem("Item #"+String.valueOf(i));
            itemPanel.add(item);
            items[i] = item;
        }
        menu.updateUI();

        menu.add(itemPanel);

        JToggleButton button = new JToggleButton("Press me");
        button.addActionListener(new ActionListener() {
            boolean pressed = false;
            @Override
            public void actionPerformed(ActionEvent e) {
                pressed = !pressed;
                if (pressed) {
                    for (JMenuItem item : items) {
                        item.setText(item.getText()+" changed");
                    }
                } else {
                    for (JMenuItem item : items) {
                        item.setText(item.getText().substring(0, item.getText().length() - 8));
                    }
                }
            }
        });

        panel.add(button, BorderLayout.NORTH);

        panel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getButton() == MouseEvent.BUTTON3) {
                    MenuItemLayoutHelper.clearUsedClientProperties(itemPanel);
                    itemPanel.revalidate();
                    menu.show(panel, (int) (e.getX() - menu.getPreferredSize().getWidth()), e.getY());
                }
            }
        });
        frame.setContentPane(panel);
        frame.setUndecorated(true);
        frame.setBackground(new Color(50, 50, 50, 200));

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

PS: Dear reader, it is not the best way to write your code. I do that because I really need that (I have such requirements). If you can build your JPopupMenu without JPanel inside, do not use code in this answer, please.

SeniorJD
  • 6,946
  • 4
  • 36
  • 53
-2

You have to add

menu.updateUI();

after add menu items.

    final JMenuItem[] items = new JMenuItem[10];
    for (int i = 0; i < 10; i++) {
        JMenuItem item = new JMenuItem("Item #"+String.valueOf(i));
        itemPanel.add(item);
        item.setUI(new MyUI());
        items[i] = item;
    }
    menu.updateUI(); <<<<<<--------

    menu.add(itemPanel);
nashuald
  • 805
  • 3
  • 14
  • 31
  • @nashuald And how should that help? – SeniorJD Oct 21 '13 at 12:42
  • When you add or remove components on JComponent class, it is necessary to update UI. The API does not explain much about how it works internally. In this [post](https://forums.oracle.com/thread/1350516#jive-7913418063723235912154) DarrylBurke explains it a little more. – nashuald Oct 21 '13 at 12:52
  • *> it is necessary to update UI* ok. right. but use the `updateUI` method for that is too prodigally. – SeniorJD Oct 21 '13 at 13:01
  • By example, when you have to edit JTree node, and text is largest than its original value, node looks truncated. Then you have to update the node renderer in order to resize the node. – nashuald Oct 21 '13 at 14:11
  • 2
    no, you never use updateUI in application code: if you think you need it for a tree, then something is wrong with your model - most probably changing the underlying data structure under the feet of the model – kleopatra Oct 21 '13 at 14:36
  • @kleopatra strange but I'm agree with you =D – SeniorJD Oct 21 '13 at 18:35