---
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