概要
JToolTip
にJTextArea
を配置して割当て幅に収まりきらない長さの行を自動的に折り返します。
Screenshot
Advertisement
サンプルコード
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());
// @see BasicTextUI.java
// margin required to show caret in the rightmost position
int caretMargin = -1;
Object property = UIManager.get("Caret.width");
if (property instanceof Number) {
caretMargin = ((Number) property).intValue();
}
property = textArea.getClientProperty("caretWidth");
if (property instanceof Number) {
caretMargin = ((Number) property).intValue();
}
if (caretMargin < 0) {
caretMargin = 1;
}
Insets i = getInsets();
Insets ti = textArea.getInsets();
int pad = i.left + i.right + ti.left + ti.right + caretMargin;
// 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)
で行の折り返しを設定したJTextArea
をJToolTip
に配置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
に文字列を設定してその推奨サイズと比較して判断- Fontから文字列の境界を取得する
- 推奨サイズの計算に
JTextArea
のCaret
幅を考慮するよう修正
- 割当て幅は
JTextArea#setColumns(...)
で設定Windows 10
+Java 1.8.0_432
環境では一度JToolTip
が重量ポップアップウィンドウとして表示されるとJTextArea
に配置する文字列が変更されてもその推奨サイズが正しく更新されない?ため割り当て幅は200px
固定で設定Java 8.0.432
:NG
- 初めて
JTextArea
を表示すると推奨サイズの高さも正しい値が取得できないため、LineBreakMeasurer
を使用してJTextArea
とは別に高さを計算して使用- JLabelの文字列を折り返し
Java 21.0.5
以上では初回でも正しい推奨サイズが取得可能なことを確認したが、いつどの修正で正しく動作するようになったかは不明 → 調査中JTextArea
の推奨サイズの高さが初回だけおかしくなるのは[JDK-8226513] JEditorPane is shown with incorrect size - Java Bug Systemが影響している?Java 21.0.5
:OK
Java 20.0.2
:OK
Java 19.0.2
:OK
Java 18.0.2.1
:OK
Java 17.0.12
:OK
Java 11.0.25
:NG