如何调整 TreeCellEditor 周围的 JTree 行的大小
How do I resize the JTree row around the TreeCellEditor
我使用多行 JTextArea 来编辑我的 JTree 中的值。
通过一些技巧,我能够调整 JTextArea 的大小以适应其中的文本,但编辑器周围的 JTree nodes/rows 不会移开。 (SCCEE 附下方截图)
如何让 JTree "reflow" 编辑器组件周围的所有节点?
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
public final class TextAreaEditorForJTree {
public static final String INITIAL_TEXT = "Line 1\nLine 2\nLine 3";
public static void main(String args[]) {
JTree tree = createSimpleTree();
addTextAreaEditor(tree);
JScrollPane scrollPane = new JScrollPane(tree);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scrollPane, BorderLayout.CENTER);
frame.setSize(300, 300);
frame.setVisible(true);
}
private static JTree createSimpleTree() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode(INITIAL_TEXT);
for (int i = 0; i < 10; i++) {
MutableTreeNode child = new DefaultMutableTreeNode(INITIAL_TEXT);
root.add(child);
}
JTree tree = new JTree(root);
tree.setEditable(true);
tree.setShowsRootHandles(true);
return tree;
}
private static void addTextAreaEditor(JTree tree) {
TreeCellEditor editor = new TextAreaTableCellEditor();
tree.setCellEditor(editor);
}
private static final class TextAreaTableCellEditor extends AbstractCellEditor implements TreeCellEditor {
private final JPanel panel;
private final JLabel label;
private final JTextArea textArea;
private DefaultMutableTreeNode currentNode;
public TextAreaTableCellEditor() {
label = new JLabel("Editor:");
textArea = new JTextArea();
textArea.setColumns(10);
panel = new JPanel();
BoxLayout boxLayout = new BoxLayout(panel, BoxLayout.X_AXIS);
panel.setLayout(boxLayout);
panel.add(label);
panel.add(textArea);
textArea.addComponentListener(new ComponentListener() {
@Override
public void componentResized(ComponentEvent e) {
setSizeToPreferredSizeLater();
}
@Override
public void componentShown(ComponentEvent e) {
setSizeToPreferredSizeLater();
}
@Override
public void componentMoved(ComponentEvent e) {
}
@Override
public void componentHidden(ComponentEvent e) {
}
});
textArea.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
setSizeToPreferredSizeLater();
}
public void removeUpdate(DocumentEvent e) {
setSizeToPreferredSizeLater();
}
public void changedUpdate(DocumentEvent e) {
setSizeToPreferredSizeLater();
}
});
}
private void setSizeToPreferredSizeLater() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
panel.setSize(panel.getPreferredSize());
}
});
}
public Object getCellEditorValue() {
return textArea.getText();
}
public boolean isCellEditable(EventObject anEvent) {
return true;
}
public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
public boolean stopCellEditing() {
currentNode.setUserObject(textArea.getText());
return true;
}
public void cancelCellEditing() {
currentNode.setUserObject(textArea.getText());
}
public Component getTreeCellEditorComponent(final JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
this.currentNode = ((DefaultMutableTreeNode) value);
textArea.setText((String) currentNode.getUserObject());
return panel;
}
}
}
SCCEE 的屏幕截图 - 编辑器(灰色框和右侧的文本)出现在所有其他树节点的顶部。
我找到了我的问题的解决方案,它与 the answer to a related question. If you're just dealing with renderers, then you may be better served by reviewing the other question and answer 中的实现非常相似,但在我的例子中,我正在处理一个在我键入时调整大小的编辑器。
两种情况下的关键都是调用AbstractLayoutCache.invalidateSizes()
。此缓存在 BasicTreeUI
的受保护成员中,您可以从 JTree.getUI()
获取(此 假设 您的树的 L&F 扩展 BasicTreeUI
)
您应该注意,虽然该方法被调用 "invalidateSizes",但它确实使所有节点 边界 无效。节点 bounds 还包括组件的位置。我真的很想扩展 UI 对象或插入 forwarding/proxy 对象,但我无法在分配的时间内找到解决方案。
在 size/bounds 无效后,调用 tree.repaint()
将更新 UI 以显示新编辑器的大小。
好的,所以我找到了两种调用此方法的方法,我也不喜欢...但它们确实有效:
private static class MyJTree extends JTree {
...
public void invalidateNodeBoundsViaSideEffect() {
if (ui instanceof BasicTreeUI) {
BasicTreeUI basicTreeUI = (BasicTreeUI) ui;
basicTreeUI.setLeftChildIndent(basicTreeUI.getLeftChildIndent());
}}
public void invalidateNodeBoundsViaRefection() {
if (ui instanceof BasicTreeUI) {
try {
Field field = BasicTreeUI.class.getDeclaredField("treeState");
field.setAccessible(true);
AbstractLayoutCache treeState = (AbstractLayoutCache) field.get(ui);
if (treeState != null) {
treeState.invalidateSizes();
}
} catch (Exception e) {
}
}}}
包含此解决方案的修订后的 SCCEE:
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.Field;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;
import javax.swing.tree.*;
public final class TextAreaEditorForJTree2 {
public static final String INITIAL_TEXT = "Line 1\nLine 2\nLine 3";
public static void main(String args[]) {
JTree tree = createSimpleTree();
addTextAreaEditor(tree);
JScrollPane scrollPane = new JScrollPane(tree);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scrollPane, BorderLayout.CENTER);
frame.setSize(300, 300);
frame.setVisible(true);
}
private static JTree createSimpleTree() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode(INITIAL_TEXT);
for (int i = 0; i < 10; i++) {
MutableTreeNode child = new DefaultMutableTreeNode(INITIAL_TEXT);
root.add(child);
}
JTree tree = new MyJTree(root);
tree.setRowHeight(0); // CRITICAL - Setting to '0' means the row heights are variable and the renderer's **bounds** should be recomputed more often!
tree.setEditable(true);
tree.setShowsRootHandles(true);
return tree;
}
private static void addTextAreaEditor(JTree tree) {
TreeCellEditor editor = new TextAreaTableCellEditor(tree);
tree.setCellEditor(editor);
}
private static final class TextAreaTableCellEditor extends AbstractCellEditor implements TreeCellEditor {
private final JPanel editorPanel;
private final JLabel editorLabel;
private final JTextArea textArea;
private DefaultMutableTreeNode currentNode;
private final JTree tree;
public TextAreaTableCellEditor(final JTree target) {
this.tree = target;
editorLabel = new JLabel("Editor:");
textArea = new JTextArea();
textArea.setColumns(10);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
editorPanel = new JPanel();
BoxLayout boxLayout = new BoxLayout(editorPanel, BoxLayout.X_AXIS);
editorPanel.setLayout(boxLayout);
editorPanel.add(editorLabel);
editorPanel.add(textArea);
editorPanel.setSize(editorPanel.getPreferredSize());
textArea.addComponentListener(new ComponentListener() {
public void componentResized(ComponentEvent e) {somethingChanged();}
public void componentShown(ComponentEvent e) {somethingChanged();}
public void componentMoved(ComponentEvent e) {}
public void componentHidden(ComponentEvent e) {}
});
textArea.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {somethingChanged();}
public void removeUpdate(DocumentEvent e) {somethingChanged();}
public void changedUpdate(DocumentEvent e) {somethingChanged();}
});
}
private void somethingChanged() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// TODO: skip if size is not changing
editorPanel.setSize(editorPanel.getPreferredSize());
((MyJTree) tree).invalidateNodeBounds();
tree.repaint();
}
});
}
public Object getCellEditorValue() {
return textArea.getText();
}
public boolean isCellEditable(EventObject anEvent) {
return true;
}
public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
public boolean stopCellEditing() {
currentNode.setUserObject(textArea.getText());
return true;
}
public void cancelCellEditing() {
currentNode.setUserObject(textArea.getText());
}
public Component getTreeCellEditorComponent(final JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
this.currentNode = ((DefaultMutableTreeNode) value);
textArea.setText((String) currentNode.getUserObject());
return editorPanel;
}
}
private static class MyJTree extends JTree {
public MyJTree(TreeNode root) {
super(root);
}
public void invalidateNodeBounds() {
invalidateNodeBoundsViaSideEffect();
//invalidateNodeBoundsViaRefection();
}
public void invalidateNodeBoundsViaSideEffect() {
if (ui instanceof BasicTreeUI) {
BasicTreeUI basicTreeUI = (BasicTreeUI) ui;
basicTreeUI.setLeftChildIndent(basicTreeUI.getLeftChildIndent());
}
}
public void invalidateNodeBoundsViaRefection() {
if (ui instanceof BasicTreeUI) {
try {
Field field = BasicTreeUI.class.getDeclaredField("treeState");
field.setAccessible(true);
AbstractLayoutCache treeState = (AbstractLayoutCache) field.get(ui);
if (treeState != null) {
treeState.invalidateSizes();
}
} catch (Exception e) {
}
}
}
}
}
我使用多行 JTextArea 来编辑我的 JTree 中的值。
通过一些技巧,我能够调整 JTextArea 的大小以适应其中的文本,但编辑器周围的 JTree nodes/rows 不会移开。 (SCCEE 附下方截图)
如何让 JTree "reflow" 编辑器组件周围的所有节点?
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
public final class TextAreaEditorForJTree {
public static final String INITIAL_TEXT = "Line 1\nLine 2\nLine 3";
public static void main(String args[]) {
JTree tree = createSimpleTree();
addTextAreaEditor(tree);
JScrollPane scrollPane = new JScrollPane(tree);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scrollPane, BorderLayout.CENTER);
frame.setSize(300, 300);
frame.setVisible(true);
}
private static JTree createSimpleTree() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode(INITIAL_TEXT);
for (int i = 0; i < 10; i++) {
MutableTreeNode child = new DefaultMutableTreeNode(INITIAL_TEXT);
root.add(child);
}
JTree tree = new JTree(root);
tree.setEditable(true);
tree.setShowsRootHandles(true);
return tree;
}
private static void addTextAreaEditor(JTree tree) {
TreeCellEditor editor = new TextAreaTableCellEditor();
tree.setCellEditor(editor);
}
private static final class TextAreaTableCellEditor extends AbstractCellEditor implements TreeCellEditor {
private final JPanel panel;
private final JLabel label;
private final JTextArea textArea;
private DefaultMutableTreeNode currentNode;
public TextAreaTableCellEditor() {
label = new JLabel("Editor:");
textArea = new JTextArea();
textArea.setColumns(10);
panel = new JPanel();
BoxLayout boxLayout = new BoxLayout(panel, BoxLayout.X_AXIS);
panel.setLayout(boxLayout);
panel.add(label);
panel.add(textArea);
textArea.addComponentListener(new ComponentListener() {
@Override
public void componentResized(ComponentEvent e) {
setSizeToPreferredSizeLater();
}
@Override
public void componentShown(ComponentEvent e) {
setSizeToPreferredSizeLater();
}
@Override
public void componentMoved(ComponentEvent e) {
}
@Override
public void componentHidden(ComponentEvent e) {
}
});
textArea.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
setSizeToPreferredSizeLater();
}
public void removeUpdate(DocumentEvent e) {
setSizeToPreferredSizeLater();
}
public void changedUpdate(DocumentEvent e) {
setSizeToPreferredSizeLater();
}
});
}
private void setSizeToPreferredSizeLater() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
panel.setSize(panel.getPreferredSize());
}
});
}
public Object getCellEditorValue() {
return textArea.getText();
}
public boolean isCellEditable(EventObject anEvent) {
return true;
}
public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
public boolean stopCellEditing() {
currentNode.setUserObject(textArea.getText());
return true;
}
public void cancelCellEditing() {
currentNode.setUserObject(textArea.getText());
}
public Component getTreeCellEditorComponent(final JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
this.currentNode = ((DefaultMutableTreeNode) value);
textArea.setText((String) currentNode.getUserObject());
return panel;
}
}
}
SCCEE 的屏幕截图 - 编辑器(灰色框和右侧的文本)出现在所有其他树节点的顶部。
我找到了我的问题的解决方案,它与 the answer to a related question. If you're just dealing with renderers, then you may be better served by reviewing the other question and answer 中的实现非常相似,但在我的例子中,我正在处理一个在我键入时调整大小的编辑器。
两种情况下的关键都是调用AbstractLayoutCache.invalidateSizes()
。此缓存在 BasicTreeUI
的受保护成员中,您可以从 JTree.getUI()
获取(此 假设 您的树的 L&F 扩展 BasicTreeUI
)
您应该注意,虽然该方法被调用 "invalidateSizes",但它确实使所有节点 边界 无效。节点 bounds 还包括组件的位置。我真的很想扩展 UI 对象或插入 forwarding/proxy 对象,但我无法在分配的时间内找到解决方案。
在 size/bounds 无效后,调用 tree.repaint()
将更新 UI 以显示新编辑器的大小。
好的,所以我找到了两种调用此方法的方法,我也不喜欢...但它们确实有效:
private static class MyJTree extends JTree {
...
public void invalidateNodeBoundsViaSideEffect() {
if (ui instanceof BasicTreeUI) {
BasicTreeUI basicTreeUI = (BasicTreeUI) ui;
basicTreeUI.setLeftChildIndent(basicTreeUI.getLeftChildIndent());
}}
public void invalidateNodeBoundsViaRefection() {
if (ui instanceof BasicTreeUI) {
try {
Field field = BasicTreeUI.class.getDeclaredField("treeState");
field.setAccessible(true);
AbstractLayoutCache treeState = (AbstractLayoutCache) field.get(ui);
if (treeState != null) {
treeState.invalidateSizes();
}
} catch (Exception e) {
}
}}}
包含此解决方案的修订后的 SCCEE:
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.Field;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;
import javax.swing.tree.*;
public final class TextAreaEditorForJTree2 {
public static final String INITIAL_TEXT = "Line 1\nLine 2\nLine 3";
public static void main(String args[]) {
JTree tree = createSimpleTree();
addTextAreaEditor(tree);
JScrollPane scrollPane = new JScrollPane(tree);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scrollPane, BorderLayout.CENTER);
frame.setSize(300, 300);
frame.setVisible(true);
}
private static JTree createSimpleTree() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode(INITIAL_TEXT);
for (int i = 0; i < 10; i++) {
MutableTreeNode child = new DefaultMutableTreeNode(INITIAL_TEXT);
root.add(child);
}
JTree tree = new MyJTree(root);
tree.setRowHeight(0); // CRITICAL - Setting to '0' means the row heights are variable and the renderer's **bounds** should be recomputed more often!
tree.setEditable(true);
tree.setShowsRootHandles(true);
return tree;
}
private static void addTextAreaEditor(JTree tree) {
TreeCellEditor editor = new TextAreaTableCellEditor(tree);
tree.setCellEditor(editor);
}
private static final class TextAreaTableCellEditor extends AbstractCellEditor implements TreeCellEditor {
private final JPanel editorPanel;
private final JLabel editorLabel;
private final JTextArea textArea;
private DefaultMutableTreeNode currentNode;
private final JTree tree;
public TextAreaTableCellEditor(final JTree target) {
this.tree = target;
editorLabel = new JLabel("Editor:");
textArea = new JTextArea();
textArea.setColumns(10);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
editorPanel = new JPanel();
BoxLayout boxLayout = new BoxLayout(editorPanel, BoxLayout.X_AXIS);
editorPanel.setLayout(boxLayout);
editorPanel.add(editorLabel);
editorPanel.add(textArea);
editorPanel.setSize(editorPanel.getPreferredSize());
textArea.addComponentListener(new ComponentListener() {
public void componentResized(ComponentEvent e) {somethingChanged();}
public void componentShown(ComponentEvent e) {somethingChanged();}
public void componentMoved(ComponentEvent e) {}
public void componentHidden(ComponentEvent e) {}
});
textArea.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {somethingChanged();}
public void removeUpdate(DocumentEvent e) {somethingChanged();}
public void changedUpdate(DocumentEvent e) {somethingChanged();}
});
}
private void somethingChanged() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// TODO: skip if size is not changing
editorPanel.setSize(editorPanel.getPreferredSize());
((MyJTree) tree).invalidateNodeBounds();
tree.repaint();
}
});
}
public Object getCellEditorValue() {
return textArea.getText();
}
public boolean isCellEditable(EventObject anEvent) {
return true;
}
public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
public boolean stopCellEditing() {
currentNode.setUserObject(textArea.getText());
return true;
}
public void cancelCellEditing() {
currentNode.setUserObject(textArea.getText());
}
public Component getTreeCellEditorComponent(final JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
this.currentNode = ((DefaultMutableTreeNode) value);
textArea.setText((String) currentNode.getUserObject());
return editorPanel;
}
}
private static class MyJTree extends JTree {
public MyJTree(TreeNode root) {
super(root);
}
public void invalidateNodeBounds() {
invalidateNodeBoundsViaSideEffect();
//invalidateNodeBoundsViaRefection();
}
public void invalidateNodeBoundsViaSideEffect() {
if (ui instanceof BasicTreeUI) {
BasicTreeUI basicTreeUI = (BasicTreeUI) ui;
basicTreeUI.setLeftChildIndent(basicTreeUI.getLeftChildIndent());
}
}
public void invalidateNodeBoundsViaRefection() {
if (ui instanceof BasicTreeUI) {
try {
Field field = BasicTreeUI.class.getDeclaredField("treeState");
field.setAccessible(true);
AbstractLayoutCache treeState = (AbstractLayoutCache) field.get(ui);
if (treeState != null) {
treeState.invalidateSizes();
}
} catch (Exception e) {
}
}
}
}
}