JMenuに選択状態のハイライトを追加で表示する
Total: 10, Today: 10, Yesterday: 0
Posted by aterai at
Last-modified:
Summary
JMenuBarに追加したトップレベルのJMenuに下線または上線を追加して、選択状態を強調表示します。
Screenshot

Advertisement
Source Code Examples
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);
}
}
}
View in GitHub: Java, KotlinDescription
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の選択状態の変化を取得している
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を設定している
- このサンプルでは
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));
}
}
}
}