我想仅在选择节点时使用包含三个文本字段的自定义 TreeCellRenderer,而在未选择节点时使用默认渲染器。问题是尽管我为面板设置了适当的首选和最小尺寸,但 JTree 不会更新编辑的行高。相反,当我使用与编辑器相同的面板时,它会正确呈现。
有人可以解释为什么会这样吗?
是否有推荐的方法来实现类似于编辑的渲染调整大小行为?
JTree 是否提供了直接设置它的方法,或者是否需要扩展 JTree 或(更糟糕的)L&F?
注意:
深入研究该BasicTreeUI.startEditing(TreePath path, MouseEvent event)
方法后,我注意到以下代码行。他们似乎负责编辑调整大小:
if(editorSize.width != nodeBounds.width ||
editorSize.height != nodeBounds.height) {
// Editor wants different width or height, invalidate
// treeState and relayout.
editorHasDifferentSize = true;
treeState.invalidatePathBounds(path);
updateSize();
// To make sure x/y are updated correctly, fetch
// the bounds again.
nodeBounds = getPathBounds(tree, path);
}
else
editorHasDifferentSize = false;
tree.add(editingComponent);
editingComponent.setBounds(nodeBounds.x, nodeBounds.y,
nodeBounds.width,
nodeBounds.height);
这是一个SSCCE,显示了不同的编辑和渲染行为。
- 未选择节点时,将使用默认渲染器。
- 通过单击一个节点,选择该节点并使用面板渲染器。
- 通过在节点上单击两次,开始编辑并使用面板编辑器。
如您所见,面板仅在编辑期间正确呈现。
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import javax.swing.AbstractCellEditor;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
public class TestResizeTreeRowsFrame {
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
@Override
public void run() {
MyTreeNode root = createRoot();
TestFrame f = new TestFrame(root);
f.setVisible(true);
}
});
}
private static MyTreeNode createRoot(){
MyTreeNode root = new MyTreeNode("RootColumnName", "RootTableName", "Root");
MyTreeNode aNode = new MyTreeNode("AcolumnName", "AtableName", "A");
MyTreeNode bNode = new MyTreeNode("BcolumnName", "BtableName", "B");
MyTreeNode cNode = new MyTreeNode("CcolumnName", "CtableName", "C");
root.add(aNode);
root.add(bNode);
root.add(cNode);
return root;
}
public static class MyTreeNode extends DefaultMutableTreeNode{
/**
*
*/
private static final long serialVersionUID = 1L;
String columnName, tableName, value;
public MyTreeNode(String columnName, String tableName, String value){
this.columnName = columnName;
this.tableName = tableName;
this.value = value;
}
public String getColumnName() {
return columnName;
}
public String getTableName() {
return tableName;
}
public String getValue() {
return value;
}
public String toString(){
return value;
}
}
public static class TestFrame extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
private JPanel contentPane;
private JTree tree;
/**
* Create the frame.
*/
public TestFrame(MyTreeNode root) {
this.setTitle("RECORD Frame");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
JScrollPane scrollPane = new JScrollPane();
contentPane.add(scrollPane, BorderLayout.CENTER);
tree = new JTree(root);
scrollPane.setViewportView(tree);
tree.setEditable(true);
tree.setInvokesStopCellEditing(true);
tree.setCellRenderer(new NodeRenderer());
tree.setCellEditor(new PanelRenderer());
}
private static class Renderer_Panel extends JPanel{
/**
*
*/
private static final long serialVersionUID = 1L;
private JTextField propertyTextField;
private JTextField prototypeTextField;
private JTextField valueTextField;
/**
* Create the panel.
*/
public Renderer_Panel() {
setPreferredSize(new Dimension(480, 97));
setMinimumSize(new Dimension(480, 97));
setLayout(new BorderLayout(0, 0));
JPanel panel = new JPanel();
panel.setMinimumSize(new Dimension(480, 97));
panel.setPreferredSize(new Dimension(480, 97));
add(panel, BorderLayout.CENTER);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
Component verticalGlue_1 = Box.createVerticalGlue();
panel.add(verticalGlue_1);
JScrollPane scrollPane = new JScrollPane();
scrollPane.setBorder(null);
scrollPane.setPreferredSize(new Dimension(20, 60));
JPanel nodePropertiesPanel = new JPanel();
nodePropertiesPanel.setBorder(new EmptyBorder(0, 3, 0, 0));
nodePropertiesPanel.setPreferredSize(new Dimension(200, 30));
nodePropertiesPanel.setMinimumSize(new Dimension(0, 0));
scrollPane.setViewportView(nodePropertiesPanel);
GridBagLayout gbl_panel = new GridBagLayout();
gbl_panel.columnWidths = new int[]{0, 0, 0};
gbl_panel.rowHeights = new int[]{0, 0, 0, 0};
gbl_panel.columnWeights = new double[]{0.0, 1.0, Double.MIN_VALUE};
gbl_panel.rowWeights = new double[]{0.0, 0.0, 0.0, Double.MIN_VALUE};
nodePropertiesPanel.setLayout(gbl_panel);
JLabel lblProperty = new JLabel("Column:");
GridBagConstraints gbc_lblProperty = new GridBagConstraints();
gbc_lblProperty.insets = new Insets(0, 0, 5, 5);
gbc_lblProperty.anchor = GridBagConstraints.WEST;
gbc_lblProperty.gridx = 0;
gbc_lblProperty.gridy = 0;
nodePropertiesPanel.add(lblProperty, gbc_lblProperty);
propertyTextField = new JTextField();
GridBagConstraints gbc_propertyTextField = new GridBagConstraints();
gbc_propertyTextField.insets = new Insets(0, 0, 5, 0);
gbc_propertyTextField.fill = GridBagConstraints.HORIZONTAL;
gbc_propertyTextField.gridx = 1;
gbc_propertyTextField.gridy = 0;
nodePropertiesPanel.add(propertyTextField, gbc_propertyTextField);
propertyTextField.setColumns(10);
JLabel lblPrototype = new JLabel("Table:");
GridBagConstraints gbc_lblPrototype = new GridBagConstraints();
gbc_lblPrototype.anchor = GridBagConstraints.WEST;
gbc_lblPrototype.insets = new Insets(0, 0, 5, 5);
gbc_lblPrototype.gridx = 0;
gbc_lblPrototype.gridy = 1;
nodePropertiesPanel.add(lblPrototype, gbc_lblPrototype);
prototypeTextField = new JTextField();
GridBagConstraints gbc_prototypeTextField = new GridBagConstraints();
gbc_prototypeTextField.insets = new Insets(0, 0, 5, 0);
gbc_prototypeTextField.fill = GridBagConstraints.HORIZONTAL;
gbc_prototypeTextField.gridx = 1;
gbc_prototypeTextField.gridy = 1;
nodePropertiesPanel.add(prototypeTextField, gbc_prototypeTextField);
prototypeTextField.setColumns(10);
JLabel lblNewLabel = new JLabel("Value:");
GridBagConstraints gbc_lblNewLabel = new GridBagConstraints();
gbc_lblNewLabel.anchor = GridBagConstraints.WEST;
gbc_lblNewLabel.insets = new Insets(0, 0, 0, 5);
gbc_lblNewLabel.gridx = 0;
gbc_lblNewLabel.gridy = 2;
nodePropertiesPanel.add(lblNewLabel, gbc_lblNewLabel);
valueTextField = new JTextField();
GridBagConstraints gbc_valueTextField = new GridBagConstraints();
gbc_valueTextField.fill = GridBagConstraints.HORIZONTAL;
gbc_valueTextField.gridx = 1;
gbc_valueTextField.gridy = 2;
nodePropertiesPanel.add(valueTextField, gbc_valueTextField);
valueTextField.setColumns(10);
panel.add(scrollPane);
Component verticalGlue = Box.createVerticalGlue();
panel.add(verticalGlue);
}
public void setProperty(String property){
this.propertyTextField.setText(property);
}
public void setPrototype(String prototype){
this.prototypeTextField.setText(prototype);
}
public void setValue(String value){
this.valueTextField.setText(value);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(480, 97);
}
@Override
public Dimension getMinimumSize() {
return new Dimension(480, 97);
}
}
private class PanelRenderer extends AbstractCellEditor implements TreeCellEditor, TreeCellRenderer{
/**
*
*/
private static final long serialVersionUID = 1L;
Renderer_Panel component = new Renderer_Panel();
MyTreeNode value;
@Override
public Component getTreeCellEditorComponent(JTree tree,
Object value, boolean isSelected, boolean expanded,
boolean leaf, int row) {
MyTreeNode myNode = ((MyTreeNode)value);
String nodeValue = null;
String prototype = null;
String property = null;
nodeValue = myNode.getValue();
prototype = myNode.getTableName();
property = myNode.getColumnName();
component.setProperty(property);
component.setPrototype(prototype);
component.setValue(nodeValue);
this.value = myNode;
return component;
}
@Override
public Object getCellEditorValue() {
return this.value.getValue();
}
@Override
public boolean isCellEditable(EventObject anEvent) {
if(anEvent instanceof MouseEvent){
MouseEvent mouseEvent = (MouseEvent)anEvent;
if(mouseEvent.getClickCount() == 2){
return true;
}else{
return false;
}
}else{
return false;
}
}
@Override
public Component getTreeCellRendererComponent(JTree tree,
Object value, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
return getTreeCellEditorComponent(tree, value, selected, expanded, leaf, row);
}
}
private class LabelNodeRenderer extends DefaultTreeCellRenderer {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
MyTreeNode myNode = ((MyTreeNode)value);
this.setText(myNode.getValue());
return this;
}
}
private class NodeRenderer implements TreeCellRenderer{
/**
*
*/
private static final long serialVersionUID = 1L;
private LabelNodeRenderer labelRenderer = new LabelNodeRenderer();
private PanelRenderer panelRenderer = new PanelRenderer();
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
Component returnedComponent = null;
if(selected){
returnedComponent = panelRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
}else{
returnedComponent = labelRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
}
returnedComponent.setSize(returnedComponent.getPreferredSize());
return returnedComponent;
}
}
}
}
另外,如果这不是合适的地方,请原谅我,但我抓住机会问是否有一本推荐 Swing 最佳实践的好书?
Swing 的架构师是否在某处记录了他们基于 Swing 设计的推荐解决方案?(我知道我问的太多了)
是否至少有一本食谱包含类似 Kleopatra 在JTree TreeCellRenderer 中发现的建议的建议,提出了关于显示选择颜色:
a) extending a component is dirty design b) mixing calls to super and this is calling for pain (f.i. the infamous color memory in the default table cell renderer)
或解释设计决策,例如让 CellEditorListener 只监听editingCanceled 和editingStopped,而不监听editingStarted(如果我想调整JTable 的单元格大小而不必覆盖JTable.editCellAt,这将很有用)。
提前致谢!