我使用多行JTextArea来编辑我的JTree中的值。
通过一些哄骗,我可以让JTextArea调整大小以容纳其中的文本,但编辑器周围的JTree节点/行不会移开。 (SCCEE w /截图如下)
如何让JTree“重排”编辑器组件周围的所有节点?
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的屏幕截图 - 编辑器(灰色框和右边的文本)显示在所有其他树节点的顶部。
答案 0 :(得分:0)
我找到了解决问题的方法,它与the answer to a related question中的实现非常相似。如果您只是处理渲染器,那么通过查看other question and answer可能会更好,但在我的情况下,我正在处理一个在我输入时调整大小的编辑器。
两种情况下都可以调用AbstractLayoutCache.invalidateSizes()
。这个缓存位于受BasicTreeUI
的受保护成员中,您可以从JTree.getUI()
获取(此假定您的树的L&amp; F扩展BasicTreeUI
)
您应该注意,虽然该方法被称为“invalidateSizes”,但它确实使所有节点 bounds 无效。节点 bounds 还包括组件的位置。我宁愿扩展UI对象或插入转发/代理对象,但我无法在分配的时间内找到解决方案。
在大小/边界无效后,对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) {
}
}
}
}
}