JMenuBarからあふれたJMenuをオーバーフローメニューに移動する
Total: 686, Today: 2, Yesterday: 2
Posted by aterai at
Last-modified:
Summary
JMenuBarの表示幅に収まらずにあふれてしまうJMenuを新規作成したオーバーフロー用メニューに移動して表示可能にします。
Screenshot

Advertisement
Source Code Examples
class OverflowMenuLayout extends FlowLayout {
public final JMenu overflowMenu = new JMenu("...");
protected OverflowMenuLayout() {
super(LEADING, 0, 0);
}
@Override public void layoutContainer(Container target) {
super.layoutContainer(target);
Rectangle r = SwingUtilities.calculateInnerArea((JComponent) target, null);
int num = target.getComponentCount();
if (target.getComponent(num - 1).getY() > r.y + getVgap()) {
target.add(overflowMenu);
overflowMenu.setSize(overflowMenu.getPreferredSize());
int popupX;
if (target.getComponentOrientation().isLeftToRight()) {
popupX = r.x + r.width - overflowMenu.getSize().width;
} else {
popupX = r.x;
}
overflowMenu.setLocation(popupX, r.y);
overflowMenu.setVisible(true);
}
if (target.isAncestorOf(overflowMenu)) {
Arrays.stream(target.getComponents())
.filter(c -> shouldMoveToPopup(target, c))
.forEach(overflowMenu::add);
// `getPopupMenu().pack()` is not necessary in Java 8,
// but if you do not do this in Java 21,
// the JPopupMenu may not be displayed correctly.
overflowMenu.getPopupMenu().pack();
}
}
private boolean shouldMoveToPopup(Container target, Component c) {
Insets insets = target.getInsets();
int y = insets.top + getVgap();
Point pt = overflowMenu.getLocation();
if (!target.getComponentOrientation().isLeftToRight()) {
pt.x += overflowMenu.getWidth();
}
boolean b = !Objects.equals(c, overflowMenu) && c.getBounds().contains(pt);
return c.getY() > y || b;
}
@Override public Dimension preferredLayoutSize(Container target) {
overflowMenu.setVisible(false);
target.remove(overflowMenu);
for (Component c : overflowMenu.getPopupMenu().getComponents()) {
target.add(c);
}
overflowMenu.removeAll();
return super.preferredLayoutSize(target);
}
}
View in GitHub: Java, KotlinDescription
JMenuBarのレイアウトをFlowLayoutを継承するLayoutManagerに変更FlowLayout#layoutContainer(...)をオーバーライドして、JMenuのy座標を調査して2行目に配置されている場合はあふれているとみなし、そのJMenuをJMenuBarからオーバーフロー用JMenuに移動- または、
JMenuがあふれていない場合でもその領域がJMenuBarの右端(ComponentOrientation.RIGHT_TO_LEFTの場合は左端)に追加したオーバーフロー用JMenuと重なる場合はオーバーフロー用JMenuに移動Java 21+Windows 10環境では、あふれたJMenuをすべて移動した後にoverflowMenu.getPopupMenu().pack()を実行しないとオーバーフロー用JMenuから開くJPopupMenuの表示がおかしくなる場合がある(JMenuItemのテキストなどが重なったり途切れたりする)Java 8+Windows 10環境では、pack()しなくても特に問題なく表示可能
LookAndFeelを変更したときオーバーフロー用JMenuがJMenuBarに配置されていない場合でもUIを更新するよう、以下のようにSwingUtilities.updateComponentTreeUI(overflowMenu)を実行する必要があるJMenuBar menuBar = new JMenuBar() { @Override public void updateUI() { super.updateUI(); LayoutManager lm = getLayout(); if (lm instanceof OverflowMenuLayout) { JMenu menu = ((OverflowMenuLayout) lm).overflowMenu; SwingUtilities.updateComponentTreeUI(menu); } } }; menuBar.setLayout(new OverflowMenuLayout());
Reference
- Menu bar overflow menu - November 2018 (version 1.30) - Visual Studio Code UPDATES
- JMenuBarのJMenuを折り返し
- [JDK-8365615] Improve JMenuBar/RightLeftOrientation.java - Java Bug System