---
category: swing
folder: RoundedTitledBorder
title: TitledBorderのタイトル背景とボーダーの角を丸める
tags: [TitledBorder, JLayer, JLabel]
author: aterai
pubdate: 2025-04-21T03:47:26+09:00
description: TitledBorderのタイトル背景を右下隅を丸めたラウンド矩形で塗りつぶし、ボーダーも四隅を丸めたラウンド矩形で描画します。
image: https://drive.google.com/uc?id=1x9dT7eDjhM8HPhD10Bv0L7YV9VfE4KXJ
---
* Summary [#summary]
`TitledBorder`のタイトル背景を右下隅を丸めたラウンド矩形で塗りつぶし、ボーダーも四隅を丸めたラウンド矩形で描画します。

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

* Source Code Examples [#sourcecode]
#code(link){{
class RoundedTitledBorder extends TitledBorder {
  private final JLabel label = new JLabel(" ");
  private final int arc;

  protected RoundedTitledBorder(String title, int arc) {
    super(new RoundedBorder(arc), title);
    this.arc = arc;
  }

  @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
  @Override public void paintBorder(
      Component c, Graphics g, int x, int y, int width, int height) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setRenderingHint(
      RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    Border b = getBorder();
    if (b instanceof RoundedBorder) {
      Dimension d = getLabel(c).getPreferredSize();
      Insets i = b.getBorderInsets(c);
      int a2 = arc * 2;
      int w = d.width + i.left + i.right + a2;
      int h = d.height + i.top + i.bottom;
      g2.setClip(((RoundedBorder) b).getBorderShape(x + i.left, y + i.top, w, h));
      g2.setClip(((RoundedBorder) b).getBorderShape(
        x + i.left, y + i.top, w, h));
      g2.setPaint(Color.GRAY);
      Shape titleBg = new RoundRectangle2D.Float(x - a2, y - a2, w + a2, h + a2, arc, arc);
      Shape titleBg = new RoundRectangle2D.Float(
        x - a2, y - a2, w + a2, h + a2, arc, arc);
      g2.fill(titleBg);
      g2.dispose();
    }
    g2.dispose();
    super.paintBorder(c, g, x, y, width, height);
  }

  private JLabel getLabel(Component c) {
    this.label.setText(getTitle());
    this.label.setFont(getFont(c));
    this.label.setComponentOrientation(c.getComponentOrientation());
    this.label.setEnabled(c.isEnabled());
    return this.label;
  }
}

class RoundedBorder extends EmptyBorder {
  private static final Paint ALPHA_ZERO = new Color(0x0, true);
  private final int arc;

  protected RoundedBorder(int arc) {
    super(2, 2, 2, 2);
    this.arc = arc;
  }

  @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
  @Override public void paintBorder(
      Component c, Graphics g, int x, int y, int width, int height) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setRenderingHint(
      RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    Shape border = getBorderShape(x, y, width, height);
    g2.setPaint(ALPHA_ZERO);
    Area clear = new Area(new Rectangle2D.Double(x, y, width, height));
    clear.subtract(new Area(border));
    g2.fill(clear);
    g2.setPaint(Color.GRAY);
    g2.setStroke(new BasicStroke(1.5f));
    g2.draw(border);
    g2.dispose();
  }

  protected Shape getBorderShape(int x, int y, int w, int h) {
    return new RoundRectangle2D.Double(x, y, w - 1, h - 1, arc, arc);
  }
}
}}

* Explanation [#explanation]
- `RoundedTitledBorder`
-- [https://docs.oracle.com/javase/jp/8/docs/api/javax/swing/border/TitledBorder.html#TitledBorder-javax.swing.border.Border-java.lang.String- TitledBorder(Border, String)]でボーダーを角丸の`RoundedBorder`を使用する`TitledBorder`を生成
--- この`RoundedBorder`は[[JTextFieldの角を丸める>Swing/RoundedTextField]]などで作成した角丸ボーダーと同一
-- さらに`TitledBorder#paintBorder(...)`をオーバーライドして右下隅を丸めたラウンド矩形をタイトル文字列の背景に描画
--- [[TitledBorderの背景色を設定する>Swing/TitledBorderBackground]]
- `TitleLayerUI`
-- `TitledBorder`は使用せず、`RoundedBorder`を設定してボーダーを角丸に設定し、タイトルは`JLayer`で描画するよう設定
-- `LayerUI#paint(...)`をオーバーライドしてコンポーネント内部にタイトル文字列を描画
-- コンポーネントのフォーカスの有無でタイトル文字列の表示・非表示を切り替えるよう`LayerUI#paint(...)`内で設定し、`LayerUI#processFocusEvent`もオーバーライドしてフォーカス移動イベントが発生したらコンポーネント全体を再描画してボーダーとタイトル文字列を更新

#code{{
class TitleLayerUI extends LayerUI<JScrollPane> {
  private final JLabel label;

  protected TitleLayerUI(String title, int arc) {
    super();
    label = new RoundedLabel(title, arc);
    label.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));
    // label.setOpaque(false);
    label.setForeground(Color.WHITE);
    label.setBackground(Color.GRAY);
  }

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    if (c instanceof JLayer) {
      JScrollPane sp = (JScrollPane) ((JLayer<?>) c).getView();
      Rectangle r = SwingUtilities.calculateInnerArea(sp, sp.getBounds());
      if (r != null && !sp.getViewport().getView().hasFocus()) {
        Dimension d = label.getPreferredSize();
        SwingUtilities.paintComponent(g, label, sp, r.x - 1, r.y - 1, d.width, d.height);
        SwingUtilities.paintComponent(
          g, label, sp, r.x - 1, r.y - 1, d.width, d.height);
      }
    }
  }

  @Override public void updateUI(JLayer<? extends JScrollPane> l) {
    super.updateUI(l);
    SwingUtilities.updateComponentTreeUI(label);
  }

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

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

  @Override protected void processFocusEvent(FocusEvent e, JLayer<? extends JScrollPane> l) {
  @Override protected void processFocusEvent(
      FocusEvent e, JLayer<? extends JScrollPane> l) {
    super.processFocusEvent(e, l);
    l.getView().repaint();
  }

  private static class RoundedLabel extends JLabel {
    private final int arc;

    public RoundedLabel(String title, int arc) {
      super(title);
      this.arc = arc;
    }

    @Override protected void paintComponent(Graphics g) {
      if (!isOpaque()) {
        Dimension d = getPreferredSize();
        int w = d.width - 1;
        int h = d.height - 1;
        int h2 = h / 2;
        Graphics2D g2 = (Graphics2D) g.create();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setPaint(getBackground());
        g2.fillRect(0, 0, w, h2);
        g2.fillRoundRect(-arc, 0, w + arc, h, arc, arc);
        g2.dispose();
      }
      super.paintComponent(g);
    }
  }
}
}}

* Reference [#reference]
- [[JTextFieldの角を丸める>Swing/RoundedTextField]]
- [[TitledBorderの背景色を設定する>Swing/TitledBorderBackground]]
- [[JTextAreaの検索ハイライトに縁を描画する>Swing/BorderHighlightPainter]]

* Comment [#comment]
#comment
#comment