---
category: swing
folder: MenuSelectionIndicator
title: JMenuに選択状態のハイライトを追加で表示する
title-en: Adds a selection highlight to a JMenu
tags: [JMenu, JMenuBar, MatteBorder, JLayer, LookAndFeel, MenuSelectionManager]
author: aterai
pubdate: 2025-12-08T00:48:22+09:00
description: JMenuBarに追加したトップレベルのJMenuに下線または上線を追加して、選択状態を強調表示します。
summary-jp: JMenuBarに追加したトップレベルのJMenuに下線または上線を追加して、選択状態を強調表示します。
summary-en: Adds an underline or overline to the top-level JMenu you added to the JMenuBar to highlight its selected state.
image: https://drive.google.com/uc?id=12PpiesaiPwqEVqf-gqtri5UjfmlZG7k5
---
* Summary [#summary]
JMenuBarに追加したトップレベルのJMenuに下線または上線を追加して、選択状態を強調表示します。
`JMenuBar`に追加したトップレベルの`JMenu`に下線または上線を追加して、選択状態を強調表示します。
// #en{{Adds an underline or overline to the top-level JMenu you added to the JMenuBar to highlight its selected state.}}

#download(https://drive.google.com/uc?id=12PpiesaiPwqEVqf-gqtri5UjfmlZG7k5)

* Source Code Examples [#sourcecode]
#code(link){{
class MenuHighlightLayerUI extends LayerUI<JMenuBar> {
  private static final Color SELECTION_COLOR = new Color(0x00_AA_FF);
  private static final int SZ = 3;
  private final Rectangle rect = new Rectangle();

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(
          AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    if (c instanceof JLayer && !rect.isEmpty()) {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setPaint(SELECTION_COLOR);
      g2.fillRect(rect.x, rect.y + rect.height - SZ, rect.width, SZ);
      g2.dispose();
    }
  }

  @Override protected void processMouseEvent(
      MouseEvent e, JLayer<? extends JMenuBar> l) {
    super.processMouseEvent(e, l);
    if (e.getID() == MouseEvent.MOUSE_EXITED) {
      rect.setSize(0, 0);
    }
  }

  @Override protected void processMouseMotionEvent(
      MouseEvent e, JLayer<? extends JMenuBar> l) {
    super.processMouseMotionEvent(e, l);
    Component c = e.getComponent();
    if (c instanceof JMenu) {
      rect.setBounds(c.getBounds());
    } else {
      rect.setSize(0, 0);
    }
  }
}
}}

* Description [#description]
- `JLayer`でラップした`JMenuBar`を使用して選択された`JMenu`に下線を描画
-- `LayerUI#processMouseMotionEvent(...)`などをオーバーライドして`JMenu`領域内に下線を上書きで表示する
-- `LookAndFeel`に依存しない
-- `JMenuBar`を`JLayer`でラップしているため`JRootPane#setJMenuBar(JMenuBar)`が使用不可
--- このため、このサンプルでは`JPanel#add(menuBar, BorderLayout.SOUTH)`で`JPanel`の下部に配置している

- `JMenuBar`の上余白を追加し、その領域に選択された`JMenu`の上線を描画
-- `JMenuBar#paintComponent(...)`をオーバーライドして選択`JMenu`領域内ではなく、その直上の`JMenuBar`上余白にハイライトを描画する
-- 下線を描画する場合は`JMenuBar`に下余白を追加し、トップレベル`JMenu`が開く`JPopupMenu`の位置を`JMenu`の下辺ではなく`JMenuBar`の下辺になるよう調整する必要がある
-- `JMenuBar`に`MouseMotionListener`を追加しても`JMenu`上でのマウスイベントを取得できないので、`MenuSelectionManager`に`ChangeListener`を追加して`JMenu`の選択状態の変化を取得している

#code{{
class SelectionIndicatorMenuBar extends JMenuBar {
  private static final Color SELECTION_COLOR = new Color(0x00_AA_FF);
  private static final int SZ = 3;
  private final Rectangle rect = new Rectangle();
  private transient ChangeListener listener;

  @Override public void updateUI() {
    MenuSelectionManager manager = MenuSelectionManager.defaultManager();
    manager.removeChangeListener(listener);
    super.updateUI();
    Border inside = BorderFactory.createEmptyBorder(SZ + 1, 0, 0, 0);
    Border outside = UIManager.getBorder("MenuBar.border");
    Border border = BorderFactory.createCompoundBorder(outside, inside);
    setBorder(border);
    listener = this::updateTopLevelMenuBorder;
    manager.addChangeListener(listener);
  }

  @Override protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    if (!rect.isEmpty()) {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setPaint(SELECTION_COLOR);
      g2.fillRect(rect.x, rect.y - SZ, rect.width, SZ);
      g2.dispose();
    }
  }

  private void updateTopLevelMenuBorder(ChangeEvent e) {
    Object o = e.getSource();
    rect.setSize(0, 0);
    MenuElement[] p = ((MenuSelectionManager) o).getSelectedPath();
    if (p != null && p.length > 1 && Objects.equals(this, p[0].getComponent())) {
      updateMenuIndicator(p[1].getComponent());
    }
    repaint();
  }

  private void updateMenuIndicator(Component c) {
    if (c instanceof JMenu && ((JMenu) c).isTopLevelMenu()) {
      JMenu menu = (JMenu) c;
      ButtonModel m = menu.getModel();
      if (m.isArmed() || m.isPressed() || m.isSelected()) {
        rect.setBounds(menu.getBounds());
      }
    }
  }
}
}}

- 選択`JMenu`に`MatteBorder`を設定して下線を描画
-- このサンプルでは`JInternalFrame#setJMenuBar(...)`で`JMenuBar`を配置し、その子`JMenu`の選択状態変化を`MenuSelectionManager`に追加した`ChangeListener`で取得
-- `MetalLookAndFeel`や`MotifLookAndFeel`は有効で、`WindowsLookAndFeel`や`NimbusLookAndFeel`では無効
-- `BasicMenuUI#paintBackground(...)`をオーバーライドする方法もあるが、各`JMenu`に`setUI(...)`で`BasicMenuUI`を設定する手間を省くため`JMenuBar`を継承して`JMenu`に`MatteBorder`を設定している

#code{{
class SelectionHighlightMenuBar extends JMenuBar {
  private static final Color ALPHA_ZERO = new Color(0x0, true);
  private static final Color SELECTION_COLOR = new Color(0x00_AA_FF);
  private static final int SZ = 3;
  private transient ChangeListener listener;

  @Override public void updateUI() {
    MenuSelectionManager manager = MenuSelectionManager.defaultManager();
    manager.removeChangeListener(listener);
    super.updateUI();
    listener = e -> updateTopLevelMenuHighlight();
    manager.addChangeListener(listener);
    EventQueue.invokeLater(this::updateTopLevelMenuHighlight);
  }

  private void updateTopLevelMenuHighlight() {
    for (MenuElement me : getSubElements()) {
      updateMenuBorder(me.getComponent());
    }
  }

  private void updateMenuBorder(Component c) {
    if (c instanceof JMenu) {
      JMenu menu = (JMenu) c;
      if (menu.isTopLevelMenu() && menu.getParent().equals(this)) {
        ButtonModel model = menu.getModel();
        boolean b = model.isArmed() || model.isPressed() || model.isSelected();
        Color color = b ? SELECTION_COLOR : ALPHA_ZERO;
        Border inside = UIManager.getBorder("Menu.border");
        Border outside = BorderFactory.createMatteBorder(0, 0, SZ, 0, color);
        menu.setBorder(BorderFactory.createCompoundBorder(outside, inside));
      }
    }
  }
}
}}

* Reference [#reference]
- [[JTabbedPaneのタブが選択されている場合のフォーカスBorderを下線に変更する>Swing/UnderlineTabFocusIndicator]]

* Comment [#comment]
#comment
#comment