• 追加された行はこの色です。
  • 削除された行はこの色です。
---
category: swing
folder: EditableTitledBorder
title: TitledBorderのタイトルを直接編集する
tags: [TitledBorder, GlassPane, JTextField]
author: aterai
pubdate: 2018-07-23T17:10:16+09:00
description: TitledBorderのタイトルをダブルクリックすると、GlassPaneに配置したJTextFieldをその上に表示して文字列を編集可能にします。
image: https://drive.google.com/uc?id=1Js7r-iMG7F3VdWkVGB0A6jcfSadZCxBz3A
---
* 概要 [#summary]
`TitledBorder`のタイトルをダブルクリックすると、`GlassPane`に配置した`JTextField`をその上に表示して文字列を編集可能にします。

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

* サンプルコード [#sourcecode]
#code(link){{
class EditableTitledBorder extends TitledBorder implements MouseListener {
  protected final Container glassPane = new EditorGlassPane();
  protected final JTextField editor = new JTextField();
  protected final JLabel dummy = new JLabel();
  protected final Rectangle rect = new Rectangle();
  protected Component comp;

  protected final Action startEditing = new AbstractAction() {
    @Override public void actionPerformed(ActionEvent e) {
      if (comp instanceof JComponent) {
        Optional.ofNullable(((JComponent) comp).getRootPane())
        .ifPresent(r -> r.setGlassPane(glassPane));
      }
      glassPane.add(editor);
      glassPane.setVisible(true);

      Point p = SwingUtilities.convertPoint(comp, rect.getLocation(), glassPane);
      rect.setLocation(p);
      rect.grow(2, 2);
      editor.setBounds(rect);
      editor.setText(getTitle());
      editor.selectAll();
      editor.requestFocusInWindow();
    }
  };
  protected final Action cancelEditing = new AbstractAction() {
    @Override public void actionPerformed(ActionEvent e) {
      glassPane.setVisible(false);
    }
  };
  protected final Action renameTitle = new AbstractAction() {
    @Override public void actionPerformed(ActionEvent e) {
      if (!editor.getText().trim().isEmpty()) {
        setTitle(editor.getText());
      }
      glassPane.setVisible(false);
    }
  };
  // ...
}}

* 解説 [#explanation]
上記のサンプルでは、`TitledBorder`のタイトルをマウスでダブルクリックすると、`GlassPane`に配置した`JTextField`を重ねて表示されてタイトル文字列が編集可能になります。
上記のサンプルでは、`TitledBorder`のタイトルをマウスでダブルクリックすると`GlassPane`に配置した`JTextField`を重ねて表示されてタイトル文字列が編集可能になります。

- タイトル文字列をダブルクリックで編集開始
- `JTextField`にフォーカスがある状態でKBD{Enter}キーを入力すると編集完了
- `JTextField`にフォーカスがある状態でKBD{ESC}キーで編集をキャンセル

----
- `TitledBorder`のタイトルは`JLabel`で表示されているが、`private`なのでその位置やサイズを取得することができない
- 以下のように`TitledBorder#paintBorder(...)`メソッドから、`JLabel`の位置、サイズを計算しているコードを抜き出してその領域を取得
- 所得した領域内でダブルクリックされたら編集を開始し、`JTextField`の表示位置とサイズをその領域に合わせて`GlassPane`上に表示
- `TitledBorder`のタイトルは`JLabel`を使用して描画されているが`private`なのでその位置やサイズの取得が不可
- 以下のように`TitledBorder#paintBorder(...)`メソッドから`JLabel`の位置、サイズを計算しているコードを抜き出してその領域を取得
- 所得した領域内でダブルクリックされたら編集を開始し`JTextField`の表示位置とサイズをその領域に合わせて`GlassPane`上に表示

#code{{
private Rectangle getTitleBounds(Component c, int x, int y, int width, int height) {
  String title = getTitle();
  if (Objects.nonNull(title) && !title.isEmpty()) {
    Border border = getBorder();
    int edge = border instanceof TitledBorder ? 0 : EDGE_SPACING;
    JLabel label = getLabel(c);
    Dimension size = label.getPreferredSize();
    Insets insets = makeBorderInsets(border, c, new Insets(0, 0, 0, 0));

    int labelY = y;
    int labelH = size.height;
    int position = getTitlePosition();
    switch (position) {
    case ABOVE_TOP:
      insets.left = 0;
      insets.right = 0;
      break;
    case TOP:
      insets.top = edge + insets.top / 2 - labelH / 2;
      if (insets.top >= edge) {
        labelY += insets.top;
      }
      break;
    case BELOW_TOP:
      labelY += insets.top + edge;
      break;
    case ABOVE_BOTTOM:
      labelY += height - labelH - insets.bottom - edge;
      break;
    case BOTTOM:
      labelY += height - labelH;
      insets.bottom = edge + (insets.bottom - labelH) / 2;
      if (insets.bottom >= edge) {
        labelY -= insets.bottom;
      }
      break;
    case BELOW_BOTTOM:
      insets.left = 0;
      insets.right = 0;
      labelY += height - labelH;
      break;
    default:
      break;
    }
    insets.left += edge + TEXT_INSET_H;
    insets.right += edge + TEXT_INSET_H;

    int labelX = x;
    int labelW = width - insets.left - insets.right;
    if (labelW > size.width) {
      labelW = size.width;
    }
    switch (getJustification(c)) {
    case LEFT:
      labelX += insets.left;
      break;
    case RIGHT:
      labelX += width - insets.right - labelW;
      break;
    case CENTER:
      labelX += (width - labelW) / 2;
      break;
    default:
      break;
    }
    return new Rectangle(labelX, labelY, labelW, labelH);
  }
  return new Rectangle();
}
}}

* 参考リンク [#reference]
- [[JTabbedPaneのタブタイトルを変更>Swing/EditTabTitle]]
- [[TitledBorderにタイトル文字列までの内余白を設定する>Swing/TitledBorderHorizontalInsetOfText]]

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