• category: swing folder: LineWrapToolTip title: JToolTipにJTextAreaを配置して自動的に行折返しする tags: [JToolTip, JTextArea] author: aterai pubdate: 2024-12-23T01:04:04+09:00 description: JToolTipにJTextAreaを配置して割当て幅に収まりきらない長さの行を自動的に折り返します。 image: https://drive.google.com/uc?id=1jP4wQX4NE_Be8XMG1WNw_pMQ2IX_wf8v

概要

JToolTipJTextAreaを配置して割当て幅に収まりきらない長さの行を自動的に折り返します。

サンプルコード

class LineWrapToolTip extends JToolTip {
  private final JTextArea textArea = new JTextArea(0, 20);
  private final JLabel label = new JLabel(" ");

  protected LineWrapToolTip() {
    super();
    textArea.setLineWrap(true);
    textArea.setWrapStyleWord(true);
    // textArea.setColumns(20);
    textArea.setOpaque(true);
    LookAndFeel.installColorsAndFont(
        textArea, "ToolTip.background", "ToolTip.foreground", "ToolTip.font");
    setLayout(new BorderLayout());
    add(textArea);
  }

  @Override public final void setLayout(LayoutManager mgr) {
    super.setLayout(mgr);
  }

  @Override public final Component add(Component comp) {
    return super.add(comp);
  }

  @Override public Dimension getPreferredSize() {
    // return getLayout().preferredLayoutSize(this);
    Dimension d = getLayout().preferredLayoutSize(this);
    label.setText(textArea.getText());
    Insets i = getInsets();
    Insets ti = textArea.getInsets();
    Insets tm = textArea.getMargin();
    int pad = i.left + i.right + ti.left + ti.right + tm.left + tm.right;
    d.width = Math.min(d.width, label.getPreferredSize().width + pad);
    return d;
  }

  @Override public void setTipText(String tipText) {
    String oldValue = textArea.getText();
    if (!Objects.equals(oldValue, tipText)) {
      textArea.setText(tipText);
      firePropertyChange("tiptext", oldValue, tipText);
      revalidate();
      repaint();
    }
  }

  @Override public String getTipText() {
    return Optional.ofNullable(textArea).map(JTextArea::getText).orElse(null);
  }
}
View in GitHub: Java, Kotlin

解説

  • JTextArea#setLineWrap(true)JTextArea#setWrapStyleWord(true)で行の折り返しを設定したJTextAreaJToolTipに配置
  • JComponent#getToolTipText(...)をオーバーライドしてJToolTipが親JFrameの外に表示されて重量ポップアップウィンドウ(共用)が使用される場合は現在のJToolTipの推奨サイズに合わせてリサイズされるようWindow#pack()を実行
JTextField field = new JTextField(20) {
  private transient JToolTip tip;

  @Override public JToolTip createToolTip() {
    if (tip == null) {
      tip = new LineWrapToolTip();
      tip.setComponent(this);
    }
    return tip;
  }

  @Override public String getToolTipText(MouseEvent e) {
    String tipText = getText();
    EventQueue.invokeLater(() ->
        Optional.ofNullable(SwingUtilities.getWindowAncestor(tip))
            .filter(w -> w.getType() == Window.Type.POPUP)
            .ifPresent(Window::pack));
    return tipText;
  }
};
  • 文字列の長さが割当て幅に収まるかどうかはJLabelに文字列を設定してその推奨サイズと比較して判断
  • 割当て幅はJTextArea#setColumns(...)で設定
    • Windows 10 + Java 1.8.0_432環境ではJTextAreaに配置する文字列が変更されてもそのJTextAreaの推奨サイズが正しく更新されない?ため割り当て幅は200px固定で設定
    • 初回は推奨サイズの高さも正しい値が取得できないため、LineBreakMeasurerを使用してJTextAreaとは別に計算して使用
    • JLabelの文字列を折り返し
    • Java 21.0.5以上では正しい推奨サイズが取得可能なことを確認したが、いつどの修正で正しく動作するようになったかは不明 → 調査中
      • Java 21.0.5: OK
      • Java 17.0.13: NG
      • Java 11.0.25: NG
      • Java 8.0.432: NG

参考リンク

コメント