概要

JPopupMenuの背景を透明化し、これに角丸Borderを設定します。

サンプルコード

UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
UIManager.put("PopupMenuUI", "example.RoundedPopupMenuUI");
// ...
public final class RoundedPopupMenuUI extends BasicPopupMenuUI {
  public static ComponentUI createUI(JComponent c) {
    return new RoundedPopupMenuUI();
  }

  @Override public Popup getPopup(JPopupMenu popup, int x, int y) {
    Popup pp = super.getPopup(popup, x, y);
    if (pp != null) {
      EventQueue.invokeLater(() -> {
        Window w = SwingUtilities.getWindowAncestor(popup);
        if (w != null && w.getType() == Window.Type.POPUP) {
          w.setBackground(new Color(0x0, true));
        }
      });
      popup.setBorder(new RoundedBorder());
      popup.setOpaque(false);
    }
    return pp;
  }
}

class RoundedBorder extends AbstractBorder {
  @Override public Insets getBorderInsets(Component c) {
    return new Insets(5, 5, 5, 5);
  }

  @Override public void paintBorder(
      Component c, Graphics g, int x, int y, int w, int h) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setPaint(c.getBackground());
    Shape s = makeShape((JComponent) c);
    g2.fill(s);
    g2.setPaint(Color.GRAY);
    g2.draw(s);
    g2.dispose();
  }

  private static Shape makeShape(JComponent c) {
    float w = c.getWidth() - 1f;
    float h = c.getHeight() - 1f;
    Insets i = c.getInsets();
    float r = Math.min(i.top + i.left, i.bottom + i.right);
    return new RoundRectangle2D.Float(0f, 0f, w, h, r, r);
  }
}
View in GitHub: Java, Kotlin

解説

  • BasicPopupMenuUI#getPopup(...)をオーバーライドしてJPopupMenuの背景を透明化するRoundedPopupMenuUIを作成
    • JFrame内に表示される場合はJPopupMenu#setOpaque(false)で透明化が可能
    • JFrame外に表示される場合はJPopupMenu#setOpaque(false)に加えて、その親となる重量コンポーネントのWindowの背景も透明化する必要がある
  • RoundRectangle2D.Float(...)で作成したラウンド矩形を表示するBorderを透明化したJPopupMenuに設定
  • UIManager.put("PopupMenuUI", "example.RoundedPopupMenuUI")JMenuから開く場合を含めたすべてのJPopupMenuRoundedPopupMenuUIを使用するよう設定

  • 特定のJPopupMenuのみ角を丸めたい場合は、直接以下のメソッドをオーバーライドしたJPopupMenuを使用する方法がある
    • JPopupMenu#isOpaque()をオーバーライドして自身を透明化
    • JPopupMenu#show(...)をオーバーライドして親Popup$HeavyWeightWindowを透明化
    • JPopupMenu#paintComponent(...)をオーバーライドしてラウンド矩形を描画
final class RoundedPopupMenu extends JPopupMenu {
  @Override public void updateUI() {
    setBorder(null);
    super.updateUI();
    setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
  }

  @Override public boolean isOpaque() {
    return false;
  }

  @Override protected void paintComponent(Graphics g) {
    // super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setPaint(getBackground());
    Shape s = makeShape();
    g2.fill(s);
    g2.setPaint(Color.GRAY);
    g2.draw(s);
    g2.dispose();
  }

  @Override public void show(Component c, int x, int y) {
    EventQueue.invokeLater(() -> {
      Window top = SwingUtilities.getWindowAncestor(this);
      // Popup$HeavyWeightWindow
      if (top != null && top.getType() == Window.Type.POPUP) {
        top.setBackground(new Color(0x0, true));
      }
    });
    super.show(c, x, y);
  }

  private Shape makeShape() {
    float w = getWidth() - 1f;
    float h = getHeight() - 1f;
    Insets i = getInsets();
    float r = Math.min(i.top + i.left, i.bottom + i.right);
    return new RoundRectangle2D.Float(0f, 0f, w, h, r, r);
  }
}

参考リンク

コメント