• category: swing folder: MenuBarOverflowMenu title: JMenuBarからあふれたJMenuをオーバーフローメニューに移動する tags: [JMenu, JMenuBar, JPopupMenu, FlowLayout] author: aterai pubdate: 2025-08-18T01:13:09+09:00 description: JMenuBarの表示幅に収まらずにあふれてしまうJMenuを新規作成したオーバーフロー用メニューに移動して表示可能にします。 image: https://drive.google.com/uc?id=1awkuBPpsKphzVEF-qzWtIGBO9Ec8rdh7

Summary

JMenuBarの表示幅に収まらずにあふれてしまうJMenuを新規作成したオーバーフロー用メニューに移動して表示可能にします。

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);
      // This 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, Kotlin

Description

  • JMenuBarのレイアウトをFlowLayoutを継承するLayoutManagerに変更
    • FlowLayout#layoutContainer(...)をオーバーライドして、JMenuy座標を調査して2行目に配置されている場合はあふれているとみなし、そのJMenuJMenuBarからオーバーフロー用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()しなくても特に問題なく表示可能

Reference

Comment