【问题标题】:JTree convertValueToText return is truncated when with changed更改时 JTree convertValueToText 返回被截断
【发布时间】:2012-05-09 14:10:29
【问题描述】:

我有一个实现 convertValueToText 的自定义 JTree 实现。这个实现依赖于一些全局状态。如果返回的字符串比以前返回的字符串长(实际上我认为以像素为单位触发它更宽),则文本将被截断并用“...”填充。当重绘是由(取消)选择元素或树上的repaint 引起的时,这是正确的。

这样实现convertValueToText是否有效? (我知道树显示不会自动更新)。

如何摆脱“...”并强制使用当前文本值正确绘制所有元素?

更新2:

显然我违反了 Swings 相当严格的踩踏政策 (http://docs.oracle.com/javase/6/docs/api/javax/swing/package-summary.html)。进行更新:

        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                for (int row = 0; row < tree.getRowCount(); row++) {
                    ((DefaultTreeModel) tree.getModel())
                            .nodeChanged((TreeNode) tree.getPathForRow(row)
                                    .getLastPathComponent());
                }
            }
        });

似乎工作正常。

更新:

这是一个最小的例子:

import java.util.Enumeration;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;

public class WeirdTreeFrame extends JFrame {
    public class WeirdTree extends JTree {
        public WeirdTree(TreeNode root) {
            super(root);
        }

        @Override
        public String convertValueToText(Object value, boolean selected,
                boolean expanded, boolean leaf, int row, boolean hasFocus) {
            return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
        }
    }

    public class WeirdNode implements TreeNode {
        protected WeirdNode parent;
        protected WeirdNode children[];
        protected int dephth;
        protected String name;
        protected String names[] = {"Foo", "Bar", "Ohh"};
        public long superCrazyNumber;

        public WeirdNode(WeirdNode parent, String name) {
            this.parent = parent; 
            this.name = name;
            if (parent != null) {
                dephth = parent.dephth + 1;
            } else {
                dephth = 0;
            }
            if (dephth < 10) {
                children = new WeirdNode[3];
                for (int i = 0; i < 3; i++) {
                    children[i] = new WeirdNode(this, name + names[i]);
                }
            }
        }

        @Override
        public TreeNode getChildAt(int childIndex) {
            if (childIndex >= getChildCount()) return null;
            return children[childIndex];
        }

        @Override
        public int getChildCount() {
            return (children != null) ? children.length : 0;
        }

        @Override
        public TreeNode getParent() {
            return parent;
        }

        @Override
        public int getIndex(TreeNode node) {
            for (int i = 0; i < children.length; i++) {
                if (children[i] == node) return i;
            }
            throw new RuntimeException();
        }

        @Override
        public boolean getAllowsChildren() {
            return true;
        }

        @Override
        public boolean isLeaf() {
            return getChildCount() == 0;
        }

        @Override
        public Enumeration children() {
            return new Enumeration<TreeNode>() {
                int nextIdx = 0;
                @Override
                public boolean hasMoreElements() {
                    return nextIdx < getChildCount();
                }

                @Override
                public TreeNode nextElement() {
                    return getChildAt(nextIdx++);
                }
            };
        }

        @Override
        public String toString() {
            return "[" + dephth + "]" + name + "@" + superCrazyNumber;
        }
    }

    public WeirdNode root;
    private WeirdTree tree;

    public WeirdTreeFrame() {
        root = new WeirdNode(null, "Blubb");
        tree = new WeirdTree(root);
        add(tree);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(500,500);
        setVisible(true);
    }

    public void update() {
        for (int row = 0; row < tree.getRowCount(); row++) {
            ((DefaultTreeModel) tree.getModel()).nodeChanged((TreeNode)tree.getPathForRow(row).getLastPathComponent());
        }
    }


    public static void main(String[] args) {
        WeirdTreeFrame frame = new WeirdTreeFrame();
        Random rnd = new Random(41);

        for (long i = 0; ; i++) {
            for (WeirdNode node = frame.root; node != null; node = (WeirdNode)node.getChildAt(rnd.nextInt(3))) {
                node.superCrazyNumber++;
            }
            if (i % 1e7 == 0) {
                System.out.println("Update: " + i);
                frame.update();
            }
        }
    }
}

使用update() 方法,我尝试触发适当的事件以确保正确更新所有可见节点。正如您所看到的,一些计算在计算过程中并行且周期性地发生,我想更新树标签(而不是结构)。

update() 方法的问题是某些节点的标签完全错误,如您在附图中所见(根节点应为“[0]...”,对于根节点应为“[n]..”第n级)

我认为这是某种竞争条件。

【问题讨论】:

  • 如需更具体的指导,请编辑您的问题以包含一个sscce,以展示您所描述的问题。
  • 就像“Swing 相当严格的线程策略”的旁注。您永远不会发现窗口工具包不是单线程的,并且没有与 Swing 相同的线程限制。 AWT 是我所知道的唯一一个试图允许从多线程更新 UI 组件的工具包,而巨大的失败并不能描述这种努力。

标签: java swing jtree


【解决方案1】:

DefaultTreeCellRendererJLabel,它添加了省略号以方便使用。您可以通过调整此example 中的框架大小来查看效果。有几种可能的选择:

  • 为封闭容器使用合适的布局;该示例使用GridLayout

  • 为渲染器使用可以水平滚动的组件,例如JTextField

  • 添加更新相邻文本组件的TreeSelectionListener

【讨论】:

    【解决方案2】:

    这可能意味着您正在更新树而没有告诉它您更改了某些内容,因此当它重绘时它只是重绘它而不重新计算布局。您必须从模型中调用 DefaultTreeModel.nodeChanged(node) 来通知 JTree 您实际修改了某些内容,然后它将处理重新计算节点的大小。或者使用适当的 TreeModelListener 接口调用来通知 Tree 发生了变化。如果你这样做,它将正确地重新布局该节点。

    如果您正确地通知树 JLabel 正在重绘自己,但它没有再次执行布局来计算新字符串需要多少空间。如果你 revalidate() 它(而不是 repaint())它应该再次执行布局,它会根据新字符串再次计算其首选大小并调整自身大小以包围整个字符串。

    main() 方法中操作树然后调用 update 的部分违反了摆动线程规则,因为在构造 WeirdFrame 时,它​​是通过调用 setVisible() 实现的。这意味着您不能触摸它或用于从除 Swing Event Dispatcher 线程之外的任何线程中绘制的任何模型。与是否实现TreeNode无关。

    最简单的解决方法是将其推迟到事件调度线程。像这样:

    public void update( final WeirdNode node, final long nextSuperCrazyNumber ) {
        SwingUtilities.invokeLater( new Runnable() {
            public void run() {
                node.superCrazyNumber = nextSuperCrazyNumber;
                ((DefaultTreeModel) tree.getModel()).nodeChanged(node);
            }
        });
    }
    
    public static void main(String[] args) {
        WeirdTreeFrame frame = new WeirdTreeFrame();
        Random rnd = new Random(41);
    
        for (long i = 0; ; i++) {
            for (WeirdNode node = frame.root; node != null; node  =(WeirdNode)node.getChildAt(rnd.nextInt(3))) {
                long superCrazyNumber = node.superCrazyNumber;
                frame.update( node, superCrazyNumber++ );
            }
        }
    }
    

    现在请注意主线程是如何不受约束地运行的,但它并没有直接操作 WeirdNode 中包含的数据。请记住,调用 WeirdNode 的 toString() 方法来呈现要显示的字符串,因此在 SwingThread 上调用它。如果您从另一个线程写入 WeirdNode.superCrazyNumber,则您违反了线程规则。主线程获取副本对其进行一些计算,然后使用 SwingUtilities.invokeLater() 而不是 invokeAndWait() 将更新发布回 Swing 线程。你不需要 invokeAndWait 除非是非常有限的情况,它有可能导致锁定,所以最好避免它。您的 UI 不必每秒钟都反映模型,它只需要最终反映它,因此 invokeLater 是您的朋友。这里的关键是我没有像之前的代码那样在主线程上进行写操作,而且我当然可以在各个节点上调用更新,而无需求助于shotgun更新。

    【讨论】:

    • 我尝试对所有可见节点使用nodeChanged,但这会产生一些奇怪的错误标签。我用可重现的示例代码更新了描述。
    • 您提供的示例仍然违反 Swing 规则。通过 main() 运行的线程不是 Swing 事件调度线程。所以一旦 WeirdFrame 被构造并在屏幕上实现(setVisible(true))。您无法从该主线程中触摸它。这可能解释了你的例子中的奇怪之处。您可以将该代码包装在 SwingUtilities.invokeLater() 中并在 UI 线程上运行它以使其再次安全。或者通过 InputMap/ActionMaps 从按钮或键盘操作触发添加组件。
    • 您也不需要更新每个可见节点。它会起作用,但会导致很多不必要的树刷新。所以这有点像用霰弹枪打苍蝇一样杀戮。最好只在实际更改的节点上调用 nodeChanged()。如果将子节点添加到节点,则调用 nodeChanged() 并传递添加的子节点的父节点。不是新添加的孩子。儿童与改名在技术上存在不同的事件。
    • 你的意思是node.superCrazyNumber++违反了Swing规则,因为node实现了TreeNode?实际上,这是一个复杂的、长期持续的、潜在的并行计算——将其放入 GUI 线程不是一个选项。但是,我可以保证实现的 TreeNode 函数的结果一致——唯一实际变化的是toString/convertValueToText。没有即时的结构性变化(我担心这会是一个问题)。
    • 我的想法是在我的算法树结构对象中实现TreeNode,以避免在算法计算时一遍又一遍地复制。由于每秒有数百万次更改,因此在每次更改时更新节点是不可行的。因此,我偶尔会进行霰弹枪更新。我希望现在说得通。感谢您的 cmets!
    【解决方案3】:

    改变节点调用方法后nodeChanged.

    ((DefaultTreeModel)tree).getModel().nodeChanged(node);
    

    【讨论】:

      猜你喜欢
      • 2013-12-22
      • 2012-07-23
      • 1970-01-01
      • 2018-03-23
      • 1970-01-01
      • 1970-01-01
      • 2012-05-28
      • 1970-01-01
      • 2018-07-22
      相关资源
      最近更新 更多