---
category: swing
folder: RoundedSelectionMenuItem
title: JMenuItemの選択ロールオーバーをラウンド矩形で描画する
tags: [JMenuItem, JPopupMenu, JMenu]
author: aterai
pubdate: 2024-02-05T00:45:47+09:00
description: JMenuItemの選択ロールオーバーを矩形ではなくラウンド矩形で描画するよう設定します。
image: https://drive.google.com/uc?id=1FdiqbmBLtgx7TvBg4XokxUJq6BN24u6C
hreflang:
    href: https://java-swing-tips.blogspot.com/2024/02/paint-jmenuitem-selection-rollover-with.html
    lang: en
---
* 概要 [#summary]
`JMenuItem`の選択ロールオーバーを矩形ではなくラウンド矩形で描画するよう設定します。

#download(https://drive.google.com/uc?id=1FdiqbmBLtgx7TvBg4XokxUJq6BN24u6C)

* サンプルコード [#sourcecode]
#code(link){{
class BasicRoundMenuItemUI extends BasicMenuItemUI {
  @Override protected void paintBackground(
      Graphics g, JMenuItem menuItem, Color bgColor) {
    ButtonModel m = menuItem.getModel();
    Color oldColor = g.getColor();
    int menuWidth = menuItem.getWidth();
    int menuHeight = menuItem.getHeight();
    if (menuItem.isOpaque()) {
      if (m.isArmed() || (menuItem instanceof JMenu && m.isSelected())) {
        Graphics2D g2 = (Graphics2D) g.create();
        g2.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // g2.clearRect(0, 0, menuWidth, menuHeight);
        g2.setPaint(menuItem.getBackground());
        g2.fillRect(0, 0, menuWidth, menuHeight);
        g2.setColor(bgColor);
        g2.fillRoundRect(2, 2, menuWidth - 4, menuHeight - 4, 8, 8);
        g2.dispose();
      } else {
        g.setColor(menuItem.getBackground());
        g.fillRect(0, 0, menuWidth, menuHeight);
      }
      g.setColor(oldColor);
    } else if (m.isArmed() || (menuItem instanceof JMenu && m.isSelected())) {
      g.setColor(bgColor);
      g.fillRoundRect(2, 2, menuWidth - 4, menuHeight - 4, 8, 8);
      g.setColor(oldColor);
    }
  }
}
}}

* 解説 [#explanation]
- `MetalLookAndFeel`、`MotifLookAndFeel`
-- `WindowsLookAndFeel`以外の`BasicMenuItemUI`を継承する`MenuItemUI`で`JMenuItem`の選択ロールオーバーをラウンド矩形で描画するよう`BasicMenuItemUI#paintBackground(...)`をオーバーライド
-- `JMenu#add(String)`、`JPopupMenu#add(String)`などをオーバーライドしてこのメソッドで生成される`JMenuItem`に`BasicRoundMenuItemUI`を設定
-- `JMenuItem`のフチ描画は`UIManager.put("MenuItem.borderPainted", Boolean.FALSE)`で描画しないよう設定
-- `JCheckBoxMenuItem`や`JRadioButtonMenuItem`の選択ロールオーバー描画には影響しない
-- `SynthMenuItemUI`を継承する`NimbusLookAndFeel`などの`JMenuItem`には未対応
- `WindowsLookAndFeel`
-- `WindowsMenuItemUI.isVistaPainting()`や`WindowsMenuItemUI.paintBackground(accessor, g, menuItem, bgColor)`などがパッケージプライベートなので選択ハイライトをラウンド矩形でソフトクリップした`BufferedImage`(または`VolatileImage`)を生成して`WindowsMenuItemUI#paintBackground(...)`内で描画
--- [[Windowの縁をソフトクリッピングでなめらかにする>Swing/SoftClippedWindow]]

#code{{
class WindowsRoundMenuItemUI extends WindowsMenuItemUI {
  private BufferedImage buffer;

  @Override protected void paintBackground(
      Graphics g, JMenuItem menuItem, Color bgColor) {
    ButtonModel model = menuItem.getModel();
    if (model.isArmed() ||
        (menuItem instanceof JMenu && model.isSelected())) {
      int width = menuItem.getWidth();
      int height = menuItem.getHeight();
      if (buffer == null ||
          buffer.getWidth() != width || buffer.getHeight() != height) {
        buffer = new BufferedImage(
          width, height, BufferedImage.TYPE_INT_ARGB);
      }
      Graphics2D g2 = buffer.createGraphics();
      g2.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
      g2.fill(new RoundRectangle2D.Float(0f, 0f, width, height, 8f, 8f));
      g2.setComposite(AlphaComposite.SrcAtop);
      super.paintBackground(g2, menuItem, bgColor);
      g2.dispose();
      g.drawImage(buffer, 0, 0, menuItem);
    } else {
      super.paintBackground(g, menuItem, bgColor);
    }
  }
}

class WindowsRoundMenuItemUI2 extends WindowsMenuItemUI {
  private VolatileImage buffer;

  @Override protected void paintBackground(
      Graphics g, JMenuItem menuItem, Color bgColor) {
    ButtonModel model = menuItem.getModel();
    if (model.isArmed() ||
        (menuItem instanceof JMenu && model.isSelected())) {
      int width = menuItem.getWidth();
      int height = menuItem.getHeight();
      GraphicsConfiguration config = ((Graphics2D) g)
          .getDeviceConfiguration();
      do {
        int status = VolatileImage.IMAGE_INCOMPATIBLE;
        if (buffer != null) {
          status = buffer.validate(config);
        }
        if (status == VolatileImage.IMAGE_INCOMPATIBLE ||
            status == VolatileImage.IMAGE_RESTORED) {
          if (buffer == null ||
              buffer.getWidth() != width ||
              buffer.getHeight() != height ||
              status == VolatileImage.IMAGE_INCOMPATIBLE) {
            if (buffer != null) {
              buffer.flush();
            }
            buffer = config.createCompatibleVolatileImage(
              width, height, Transparency.TRANSLUCENT);
          }
          Graphics2D g2 = buffer.createGraphics();
          g2.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
          g2.setComposite(AlphaComposite.Clear);
          g2.fillRect(0, 0, width, height);
          g2.setPaintMode(); // g2.setComposite(AlphaComposite.Src);
          g2.setPaint(Color.WHITE);
          g2.fill(new RoundRectangle2D.Float(
              0f, 0f, width, height, 8f, 8f));
          g2.setComposite(AlphaComposite.SrcAtop);
          super.paintBackground(g2, menuItem, bgColor);
          g2.dispose();
        }
      } while (buffer.contentsLost());
      g.drawImage(buffer, 0, 0, menuItem);
    } else {
      super.paintBackground(g, menuItem, bgColor);
    }
  }
}
}}

* 参考リンク [#reference]
- [[JPopupMenuの角を丸める>Swing/RoundedCornerPopupMenu]]
-- `JPopupMenu`の角丸はリンク先と同一の`RoundedPopupMenuUI.java`を使用している
- [[Windowの縁をソフトクリッピングでなめらかにする>Swing/SoftClippedWindow]]

* コメント [#comment]
#comment
#comment