渲染时更改JTree行高调整大小的行为 [英] Change JTree row height resizing behavior when rendering

查看:224
本文介绍了渲染时更改JTree行高调整大小的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在节点为SELECTED时使用包含三个文本字段的自定义TreeCellRenderer,而在节点未选择时使用默认渲染器。
问题是虽然我为面板设置了合适的首选大小和最小大小,但JTree不会更新编辑的行高。
相反,当我使用相同的面板作为编辑器时,它被正确呈现。

I want to use a custom TreeCellRenderer containing three text fields only when a node is SELECTED, while use the default renderer when the node is NOT SELECTED. The problem is that although I've set an appropriate preferred and minimum size for the panel, JTree does not update the edited row height. On the contrary when I use the same panel as an editor, it is rendered correctly.

有人可以解释为什么会发生这种情况吗?

是否有推荐的方法来实现类似于编辑的渲染大小调整行为?

JTree是否提供了直接设置它的方法,或者是否有必要扩展JTree或(更糟)L& F?

注意:
在深入研究 BasicTreeUI.startEditing(TreePath路径,MouseEvent事件)方法之后,我注意到以下几行代码。
他们似乎负责编辑大小调整:

Can someone explain why this is happening?
Is there a recommended way to achieve a rendering resizing behavior similar to that of editing?
Is there a method provided by JTree to set it directly or is it necessary to extend JTree or (worse) L&F?

NOTE: After digging into the BasicTreeUI.startEditing(TreePath path, MouseEvent event) method, I noticed the following lines of code. They seem to be responsible for the edit resizing:

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 ,显示不同的编辑和渲染行为。

Here is a SSCCE showing the different editing and rendering behavior.


  • 如果未选择节点,则使用默认渲染器。

  • 通过在节点上单击一次,选择节点并使用面板渲染器。

  • 通过在节点上单击两次,开始编辑并使用面板编辑器。

正如您所见,面板仅在编辑期间正确呈现。

As you'll see, the panel is rendered correctly only during edit.

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的评论中的建议吗? a href =https://stackoverflow.com/questions/9051463/jtree-treecellrenderer-raising-issue-on-showing-the-selection-color> JTree TreeCellRenderer在显示选择颜色时出现问题:< br>

Also, forgive me if this is not the proper place, but I'm grabbing the chance to ask if there is a good book that recommends best practices for Swing?
Did the architects of Swing documented somewhere the recommended solutions upon which they based their design of Swing?(I know I'm asking too much)
Is there at least a cookbook that contains advice like Kleopatra's comment found in JTree TreeCellRenderer raising issue on showing the selection color:


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只监听editCanceled和editingStopped,但不能用于editStarted(如果我想调整JTable的单元格大小而不必覆盖JTable.editCellAt)。


谢谢我提前!

or explain design decisions such as making the CellEditorListener listen only for editingCanceled and editingStopped, but not for editingStarted (which would be useful in case I want to resize a JTable's cells without having to override the JTable.editCellAt).

Thanks in advance!

推荐答案

一些事实:


  • BasicTreeUI保留节点大小的缓存

  • 没有公共API强制它重新验证该缓存

  • 假定节点大小要求依赖专有的数据,而不是视觉状态,即选择状态的改变不会触发任何内部更新

  • 一旁:渲染器/编辑器内的设置大小无效:无论你做什么, ui会在认为合适时更改它

  • BasicTreeUI keeps a cache of node sizes
  • there's no public api to force it to revalidate that cache
  • node size requirements are assumed to depend exclusively on data, not on visual state, that is change of selection state will not trigger any internal update
  • an aside: setting size inside the renderer/editor has no effect: whatever you do, the ui will change it as it deems appropriate

总的来说,没有办法实现你的要求而不会变脏。基本上,您必须听取选择更改 - 因为渲染器在选定内容与未选择内容中具有不同的大小要求 - 然后尽力使ui的内部缓存无效。基本上有两个选项:

On the whole, there is no way to implement your requirement without going dirty. Basically, you have to listen to selection changes - because the renderer has different size requirement in selected vs. not selected - and then do your best to invalidate the ui's internal cache. Basically two options:


  • 使用反射来访问ui的受保护方法

  • 假模型事件将导致内部重新计算缓存

下面是第一个片段(无法进行第二次工作)快速测试,但依旧记得我做到了......)

Below is a snippet for the first (couldn't make the second work on a quick test, but faintly remember that I did it ...)

protected TreeSelectionListener createReflectiveSelectionListener() {
    TreeSelectionListener l = new TreeSelectionListener() {

        @Override
        public void valueChanged(TreeSelectionEvent e) {
            invalidateLayoutCache();
        }

        protected void invalidateLayoutCache() {
            BasicTreeUI ui = (BasicTreeUI) tree.getUI();
            try {
                Method method = BasicTreeUI.class.getDeclaredMethod("configureLayoutCache");
                method.setAccessible(true);
                method.invoke(ui);
            } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
               e1.printStackTrace();
            }
        }
    };
    return l;
}

刚刚找到第二个 - 与第一个类似的脏级别 - 选项:

Just found the second - similar dirtyness-level as the first - option:

protected TreeSelectionListener createFakeDataEventSelectionListener() {
    TreeSelectionListener l = new TreeSelectionListener() {

        @Override
        public void valueChanged(final TreeSelectionEvent e) {
            fireDataChanged(e.getOldLeadSelectionPath());
            fireDataChanged(e.getNewLeadSelectionPath());
        }

        private void fireDataChanged(TreePath lead) {
            if (lead == null) return;
            DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
            TreeNode last = (TreeNode) lead.getLastPathComponent();
            model.nodeChanged(last);
        }
    };
    return l;
}

这篇关于渲染时更改JTree行高调整大小的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆