【问题标题】:JTree cell editor receives mouse clicks differently depending on OSJTree 单元格编辑器根据操作系统接收鼠标点击的方式不同
【发布时间】:2013-03-31 20:14:43
【问题描述】:

我创建了一个树形单元渲染器/编辑器框架,虽然它有点老套,但它在 Windows 和 Linux 上运行良好。下图说明了一个示例设置。

目标是,如果用户点击 图像(数字)1 或 2,则应用程序会响应该点击,但不会选择树行。如果用户单击文本一或两个,应用程序会响应该单击并选择树行。我实现这一点的方式再次有点骇人听闻。基本上,当用户单击树行时,会显示编辑器组件(看起来与渲染器组件相同),并且编辑器组件有一个鼠标侦听器,可以确定用户在行中单击的位置。

这在 Windows/Linux 上工作的事实依赖于我一直认为似乎难以依赖的东西。基本上,如果您单击该行一次,则单击(a)会调出编辑器并(b)激活编辑器组件上的鼠标侦听器。这就是我想要的方式!但是,当您尝试在 Mac OSX(10.6.2 如果重要)上运行该应用程序时,上述脆弱的假设不再适用。任何时候想要与树进行交互,都必须单击两次(一次激活编辑器,再次激活鼠标侦听器)。

下面的 SSCCE 可以重现该行为。当然,如果您没有 OSX,则无法重现 不受欢迎的 行为,但也许您仍然可以推荐一种更聪明的方法来实现我的目标。当您单击树的各个部分时,观察控制台中的 sysout 消息指示正在发生的事情。

哦,SSCCE 引用了这两张图片:

package TreeTest;

import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.EventObject;
import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.CellEditorListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

@SuppressWarnings("serial")
public class TreeTest extends JComponent {

    private JFrame frame;
    private DefaultTreeModel treeModel;
    private DefaultMutableTreeNode root;
    private JTree tree;

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(
                UIManager.getSystemLookAndFeelClassName());
        } catch (Throwable e) {
            e.printStackTrace();
        }
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    TreeTest window = new TreeTest();
                    window.frame.setVisible(true);
                    window.frame.requestFocusInWindow();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public TreeTest() {
        initialize();
    }

    private void initialize() {
        frame = new JFrame("Tree Test");
        frame.setBounds(400, 400, 250, 150);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        root = new DefaultMutableTreeNode("root");
        treeModel = new DefaultTreeModel(root);
        tree = new JTree(treeModel);
        tree.setEditable(true);
        tree.getSelectionModel().setSelectionMode(
            TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setRootVisible(false);
        tree.setShowsRootHandles(false);
        tree.setCellRenderer(new TreeRenderer());
        tree.setCellEditor(new TreeEditor());
        tree.putClientProperty("JTree.lineStyle", "None");
        tree.setBackground(Color.white);

        treeModel.insertNodeInto(new DefaultMutableTreeNode("two"), root, 0);
        treeModel.insertNodeInto(new DefaultMutableTreeNode("one"), root, 0);

        TreeNode[] nodes = treeModel.getPathToRoot(root);
        tree.expandPath(new TreePath(nodes));
        tree.addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                System.out.println("SELECTION CHANGED!");
            }
        });

        frame.getContentPane().add(tree);
    }

    public class TreeComponent extends JPanel {

        public TreeComponent(JLabel numIcon, JLabel numText) {
            this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
            this.add(numIcon);
            this.add(numText);
        }
    }

    public class TreeRenderer implements TreeCellRenderer {

        private ImageIcon oneIcon;
        private ImageIcon twoIcon;

        public TreeRenderer() {
            try {
                oneIcon = new ImageIcon(ImageIO.read(
                    new URL("http://i.imgur.com/HtHJkfI.png")));
                twoIcon = new ImageIcon(ImageIO.read(
                    new URL("http://i.imgur.com/w5jAp5c.png")));
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value,
            boolean selected, boolean expanded, boolean leaf, int row,
            boolean hasFocus) {
            JLabel numIcon = new JLabel();
            numIcon.setAlignmentX(JLabel.CENTER_ALIGNMENT);
            numIcon.setAlignmentY(JLabel.CENTER_ALIGNMENT);
            numIcon.setBorder(new EmptyBorder(0, 0, 0, 4));

            JLabel numText = new JLabel();

            TreeComponent comp = new TreeComponent(numIcon, numText);

            String str = (String) ((DefaultMutableTreeNode) value).getUserObject();
            if (str.equals("one")) {
                numIcon.setIcon(oneIcon);
                numText.setText("one");
            } else if (str.equals("two")) {
                numIcon.setIcon(twoIcon);
                numText.setText("two");
            }

            numText.setOpaque(true);
            if (selected) {
                numText.setBackground(new Color(209, 230, 255));
                numText.setBorder(new LineBorder(
                    new Color(132, 172, 221), 1, false));
            } else {
                numText.setBackground(Color.white);
                numText.setBorder(new LineBorder(Color.white, 1, false));
            }
            comp.setFocusable(false);
            comp.setBackground(Color.white);
            return comp;
        }
    }

    public class TreeEditor implements TreeCellEditor {

        private TreeRenderer rend;
        private TreeComponent editorComponent;
        private JTree tree;
        private DefaultTreeModel treeModel;
        private DefaultMutableTreeNode node;
        private String str;

        public TreeEditor() {
            rend = new TreeRenderer();
        }

        @Override
        public Component getTreeCellEditorComponent(
            final JTree tree, final Object value, boolean isSelected,
            boolean expanded, boolean leaf, int row) {
            this.tree = tree;
            treeModel = (DefaultTreeModel) tree.getModel();
            node = (DefaultMutableTreeNode) value;
            Object userObject = node.getUserObject();
            this.str = (String) userObject;

            TreeNode[] nodes = treeModel.getPathToRoot(node);
            final TreePath path = new TreePath(nodes);

            editorComponent = (TreeComponent) rend.getTreeCellRendererComponent(
                tree, value, isSelected, expanded, leaf, row, false);
            editorComponent.addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent e) {
                    TreeEditor.this.stopCellEditing();
                    int x = e.getX();
                    if (x >= 0 && x <= 16) {
                        System.out.println(
                            "you clicked the image for row " + str);
                    } else if (x > 16) {
                        System.out.println(
                            "you clicked the text for row " + str);
                        tree.setSelectionPath(path);
                    }
                }
            });
            return editorComponent;
        }

        @Override
        public boolean isCellEditable(EventObject anEvent) {
            return true;
        }

        @Override
        public boolean shouldSelectCell(EventObject anEvent) {
            return false;
        }

        @Override
        public boolean stopCellEditing() {
            tree.cancelEditing();
            return false;
        }

        @Override
        public Object getCellEditorValue() {
            return null;
        }

        @Override
        public void cancelCellEditing() {
        }

        @Override
        public void addCellEditorListener(CellEditorListener l) {
        }

        @Override
        public void removeCellEditorListener(CellEditorListener l) {
        }
    }
}

【问题讨论】:

  • 无关与否,没有挖掘:您的编辑器实现无效!它不得直接作用于树,它必须在准备好时通知其侦听器。小插曲:不要在 getXXComponent 中创建新组件,不要多次添加侦听器(无论如何,mouseListener 看起来很可疑,因为它与不应该与树交互的树)
  • 好的,只需将您的 cmets 阅读到@trashgod 的答案 - 它不是要成为编辑器 :-) 我看到了问题:渲染器和编辑器之间的区域很滑,有点像不是-an-editor-but-mouse-active 不受支持且难以伪造。在这种情况下,我个人的偏好是不要误用编辑器,而是在树本身上添加控制逻辑......将检查我们在 swingx 孵化器中拥有的内容(不过会半生不熟)
  • @kleopatra 谢谢,我正在尝试查看其他一些选项。为什么说不要在 getXXComponent 中创建新组件?
  • @kleopatra 我想出了一种新方法,它可能仍然有点 hack,但根本不使用编辑器。作为知名的 Swing 大师,我欢迎您的 cmets。 :-) 请参阅here。谢谢。
  • 实际上,我不明白您需要 not 使用普通编辑器:渲染/编辑组件中不同组件表示的所有数据都是您的属性节点的 userObject - 正确实现编辑器并让它完成它的工作,然后一切都会很好地到位,没有任何技巧:-)

标签: java macos swing listener jtree


【解决方案1】:

如何实现复杂的单元格编辑器的简要概述。假设是编辑器的所有交互元素确实确实编辑了节点的 userObject 的属性 - 就像 OP 的完整代码中的情况一样。

合作者

  • 具有多个属性的模拟数据对象
  • 具有多个子级的渲染器,每个子级绑定到数据对象的一个​​属性
  • 使用渲染组件的“实时”实例的编辑器,即根据需要添加侦听器以遵守编辑器的合同

一些代码(显然可用于实际使用,只是为了了解一下:)

public static class ViewProvider extends AbstractCellEditor 
    implements TreeCellEditor, TreeCellRenderer {

    private JCheckBox firstBox;
    private JButton colorButton;
    private JComponent nodePanel;
    private JButton nameButton;

    private Data data;
    private boolean ignore;

    public ViewProvider(boolean asEditor) {
        initComponents();
        if (asEditor)
          installListeners();
    }

    protected void initComponents() {
        nodePanel = new JPanel();
        nodePanel.setOpaque(false);
        firstBox = new JCheckBox();
        colorButton = new JButton();
        // if we need something clickable use something ... clickable :-)
        nameButton = new JButton();
        nameButton.setContentAreaFilled(false);
        nameButton.setOpaque(true);
        nameButton.setBorderPainted(false);
        nameButton.setMargin(new Insets(0, 0, 0, 0));
        nodePanel.add(firstBox);
        nodePanel.add(colorButton);
        nodePanel.add(nameButton);
    }

    protected void installListeners() {
        ActionListener cancel = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                cancelCellEditing();
            }

        };
        nameButton.addActionListener(cancel);
        ActionListener stop = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                stopCellEditing();
            }

        };
        firstBox.addActionListener(stop);
        // Note: code for using a button to trigger opening a dialog
        // is in the tutorial, should replace this
        colorButton.addActionListener(stop);
    }

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value,
            boolean selected, boolean expanded, boolean leaf, int row,
            boolean hasFocus) {
        Data data = (Data) ((DefaultMutableTreeNode) value).getUserObject();
        firstBox.setSelected(data.isVisible);
        colorButton.setBackground(data.color);
        nameButton.setText(data.name);
        nameButton.setBackground(selected ? Color.YELLOW : tree.getBackground());
        nameButton.setFont(tree.getFont());
        return nodePanel;
    }

    @Override
    public Component getTreeCellEditorComponent(JTree tree, Object value,
            boolean isSelected, boolean expanded, boolean leaf, int row) {
        // copy to not fiddle with the original
        data = new Data((Data) ((DefaultMutableTreeNode) value).getUserObject());
        ignore = true;
        getTreeCellRendererComponent(tree, value, isSelected, expanded, leaf, row, false);
        ignore = false;
        return nodePanel;
    }

    @Override
    public Object getCellEditorValue() {
        return data;
    }

    @Override
    public boolean shouldSelectCell(EventObject anEvent) {
        // at this point the editing component is added to the tree
        // and the mouse coordinates still in tree coordinates
        if (anEvent instanceof MouseEvent) {
            MouseEvent me = (MouseEvent) anEvent;
            Point loc = SwingUtilities.convertPoint(me.getComponent(), 
                    me.getPoint(), nodePanel);
            return loc.x >= nameButton.getX();
        }
        return false;
    }

    @Override
    public boolean stopCellEditing() {
        if (ignore) return false;
        // real-world data will have setters
        data.isVisible = firstBox.isSelected();
        return super.stopCellEditing();
    }

    @Override
    public void cancelCellEditing() {
        if (ignore) return;
        data = null;
        super.cancelCellEditing();
    }

}

// simple Data - obviously not for production 
public static class Data {
    boolean isVisible;
    Color color;
    String name;

    public Data(boolean isVisible, Color color,
            String name) {
        this.isVisible = isVisible;
        this.color = color;
        this.name = name;
    }

    /**
     * A copy constructor to allow editors to manipulate its
     * properties without changing the original.
     * 
     * @param original
     */
    public Data(Data original) {
        this.isVisible = original.isVisible;
        this.color = original.color;
        this.name = original.name;
    }
}

// usage:
DefaultMutableTreeNode root = new DefaultMutableTreeNode(
    new Data(true, Color.RED, "someName"));
root.add(new DefaultMutableTreeNode(new Data(true, Color.GREEN, "other")));
root.add(new DefaultMutableTreeNode(new Data(false, Color.BLUE, "whatagain")));
root.add(new DefaultMutableTreeNode(new Data(false, Color.YELLOW, "dummy")));

JTree tree = new JTree(root);
tree.setCellRenderer(new ViewProvider(false));
tree.setCellEditor(new ViewProvider(true));
tree.setEditable(true);

【讨论】:

  • @mKorbel 是吗?不记得了,现在对我来说没有多大意义:-)
  • +1,它似乎确实有效,尽管我回应 @mKorbel 的乐趣,您现在支持使用编辑器来实现此行为。 :-) 更重要的是,我不知道为什么您的代码在 OSX 上有效,但我在 OP 中的代码却没有。我似乎无法确定您所做的不同之处,即可以使用相同的单击来(a)启动编辑器和(b)使用编辑器。有什么想法吗?
  • @The111 每个按钮对应于一个布尔标志或 GPSObject 的颜色并对其进行更改,这称为 ... 编辑 :-) 没什么特别的,只是按照编辑的规则进行操作:初始触发器(如:应该是,早期版本中存在错误)在添加后传递到编辑器中。
  • 实际上,我不太喜欢@mKorbel,有偏见的我会怀疑是误解 - 在渲染器/编辑器中 使用 JPanel 没问题,而 扩展任何具有新角色的 JSomething 渲染器/编辑器是 :-) 所以如果有人有我的链接来反对前者,请告诉我,应该删除
  • @The111 没问题 :-) 再说一遍:您的编辑器实现是 invalid - 它可能会产生任何不可预测(阅读:意外)的结果
【解决方案2】:

我不确定我是否理解这个要求,但下面的示例添加了一个方便的键绑定,并且可以与here 所示的任一单击 方法一起使用。根据kleopatra 的建议,我已选择覆盖canEditImmediately(),但要遵守她关于可用性的警告。

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.EventObject;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.DefaultTreeCellRenderer;

/**
 * @see https://stackoverflow.com/a/15738813/230513
 * @see https://stackoverflow.com/q/15625424/230513
 */
public class Test {

    private static Icon one;
    private static Icon two;

    private void display() {
        JFrame f = new JFrame("Test");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JTree tree = new JTree();
        for (int i = 0; i < tree.getRowCount(); i++) {
            tree.expandRow(i);
        }
        final TreeRenderer renderer = new TreeRenderer();
        tree.setCellRenderer(renderer);
        tree.setCellEditor(new TreeEditor(tree, renderer));
        tree.setEditable(true);
        tree.getInputMap().put(
            KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "startEditing");
        f.add(new JScrollPane(tree));
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static class TreeRenderer extends DefaultTreeCellRenderer {

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value,
            boolean sel, boolean exp, boolean leaf, int row, boolean hasFocus) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
            String s = node.getUserObject().toString();
            if ("colors".equals(s)) {
                setOpenIcon(one);
                setClosedIcon(one);
            } else if ("sports".equals(s)) {
                setOpenIcon(two);
                setClosedIcon(two);
            } else {
                setOpenIcon(getDefaultOpenIcon());
                setClosedIcon(getDefaultClosedIcon());
            }
            super.getTreeCellRendererComponent(
                tree, value, sel, exp, leaf, row, hasFocus);
            return this;
        }
    }

    private static class TreeEditor extends DefaultTreeCellEditor {

        public TreeEditor(JTree tree, DefaultTreeCellRenderer renderer) {
            super(tree, renderer);
        }

        @Override
        public Component getTreeCellEditorComponent(JTree tree, Object value,
            boolean isSelected, boolean exp, boolean leaf, int row) {
            Component c = super.getTreeCellEditorComponent(
                tree, value, isSelected, exp, leaf, row);
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
            String s = node.getUserObject().toString();
            if ("colors".equals(s)) {
                editingIcon = one;
            } else if ("sports".equals(s)) {
                editingIcon = two;
            }
            return c;
        }

        @Override
        protected boolean canEditImmediately(EventObject event) {
            if ((event instanceof MouseEvent)
                && SwingUtilities.isLeftMouseButton((MouseEvent) event)) {
                MouseEvent me = (MouseEvent) event;

                return ((me.getClickCount() >= 1)
                    && inHitRegion(me.getX(), me.getY()));
            }
            return (event == null);
        }
    }

    public static void main(String[] args) throws Exception {
        one = new ImageIcon(ImageIO.read(
            new URL("http://i.imgur.com/HtHJkfI.png")));
        two = new ImageIcon(ImageIO.read(
            new URL("http://i.imgur.com/w5jAp5c.png")));
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Test().display();
            }
        });
    }
}

【讨论】:

  • 一如既往地感谢您的输入,垃圾神。但是,我不认为我正在寻找的答案在那里。也许我应该更好地解释我的要求,因为我能理解你的困惑。检查我的实际应用程序here。按 CTRL+N 创建一个新文件,然后使用左侧的树进行操作。在我的应用程序中,用户实际上从未编辑树,他只是将它用于交互。树行中的每个图标都有不同的作用,除非用户单击文本,否则该行不应选中
  • 我正在使用编辑器,这无疑是不合逻辑的(因为实际上没有进行任何编辑),因为我想不出任何其他方法来实现我的目标!如果树渲染器给了我一个渲染真实组件的选项,那么我可以将我的点击侦听器附加到它们。但事实并非如此,所以我将它们附加到编辑器上,它会出现并通过相同的点击被点击,然后自行消失,对用户来说都是透明的。但是在 OSX 上,这需要每次点击两次。我相信您使用的是 Mac,因此也许您可以提供更好的见解。希望详细说明有所帮助!
  • 抱歉,您正试图用一个不必要的编辑器来击败 UI 委托——这是一种注定失败的方法。为什么不看看Outline,它使添加行组件变得容易。它适用于任何TreeModel
  • 同意,从来不需要编辑器。我想出了一种新方法,它可能仍然有点小技巧,但根本不使用编辑器。我会欢迎你的cmets。请参阅here。谢谢。
【解决方案3】:

我想出了一些可以创建我想要的行为并且根本不使用TreeCellEditor 的东西。相反,该树是不可编辑的,并使用 JTree 的自定义扩展名进行实例化,其中 processMouseEvent 被覆盖。我有这个想法here

它似乎工作得很好,但它仍然有点 hacky(它会循环计算以确定树单元的开始位置,因为这可能会根据缩进而有所不同)。此外,我几乎禁用了mouseClickedmouseReleased 类型的事件,并且仅使用mousePressed 事件来控制JTreeMod。不知道这是否会在以后咬我或者是不好的做法,但我不喜欢我的自定义代码在所有事件中连续运行 3 次。我还无法在非 Windows 操作系统上进行测试。

这里是点击后的控制台输出,串联 (1) 图片一 (2) 文字一 (3) 图片二 (4) 文字二。同样,这完美地实现了我想要的行为。

you clicked the image for row 1. this was detected, but no selection will happen!
you clicked the text for row 1. this was detected, and selection WILL happen!
SELECTION CHANGED!
you clicked the image for row 2. this was detected, but no selection will happen!
you clicked the text for row 2. this was detected, and selection WILL happen!
SELECTION CHANGED!

这是新的 SSCCE:

package TreeTest;

import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.MouseEvent;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

@SuppressWarnings("serial")
public class TreeTest2 extends JComponent {

    private JFrame frame;
    private DefaultTreeModel treeModel;
    private DefaultMutableTreeNode root;
    private JTreeMod tree;

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(
                UIManager.getSystemLookAndFeelClassName());
        } catch (Throwable e) {
            e.printStackTrace();
        }
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    TreeTest2 window = new TreeTest2();
                    window.frame.setVisible(true);
                    window.frame.requestFocusInWindow();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public TreeTest2() {
        initialize();
    }

    private void initialize() {
        frame = new JFrame("Tree Test");
        frame.setBounds(400, 400, 250, 150);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        root = new DefaultMutableTreeNode("root");
        treeModel = new DefaultTreeModel(root);
        tree = new JTreeMod(treeModel);
        tree.setEditable(false);
        tree.getSelectionModel().setSelectionMode(
            TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setRootVisible(false);
        tree.setShowsRootHandles(true);
        tree.setCellRenderer(new TreeRenderer());
        tree.putClientProperty("JTree.lineStyle", "None");
        tree.setBackground(Color.white);

        DefaultMutableTreeNode one = new DefaultMutableTreeNode("one");
        DefaultMutableTreeNode two = new DefaultMutableTreeNode("two");

        treeModel.insertNodeInto(one, root, 0);
        treeModel.insertNodeInto(two, one, 0);

        TreeNode[] nodes = treeModel.getPathToRoot(root);
        tree.expandPath(new TreePath(nodes));
        nodes = treeModel.getPathToRoot(one);
        tree.expandPath(new TreePath(nodes));
        tree.addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                System.out.println("SELECTION CHANGED!");
            }
        });

        frame.getContentPane().add(tree);
    }

    public class TreeRenderer implements TreeCellRenderer {

        private ImageIcon oneIcon;
        private ImageIcon twoIcon;

        public TreeRenderer() {
            try {
                oneIcon = new ImageIcon(ImageIO.read(
                        new URL("http://i.imgur.com/HtHJkfI.png")));
                twoIcon = new ImageIcon(ImageIO.read(
                        new URL("http://i.imgur.com/w5jAp5c.png")));
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value,
            boolean selected, boolean expanded, boolean leaf, int row,
            boolean hasFocus) {
            JLabel numIcon = new JLabel();
            numIcon.setAlignmentX(JLabel.CENTER_ALIGNMENT);
            numIcon.setAlignmentY(JLabel.CENTER_ALIGNMENT);
            numIcon.setBorder(new EmptyBorder(0, 0, 0, 4));

            JLabel numText = new JLabel();

            JPanel comp = new JPanel();
            comp.setLayout(new BoxLayout(comp, BoxLayout.X_AXIS));
            comp.add(numIcon);
            comp.add(numText);

            String str = (String) ((DefaultMutableTreeNode) value).getUserObject();
            if (str.equals("one")) {
                numIcon.setIcon(oneIcon);
                numText.setText("one");
            } else if (str.equals("two")) {
                numIcon.setIcon(twoIcon);
                numText.setText("two");
            }

            numText.setOpaque(true);
            if (selected) {
                numText.setBackground(new Color(209, 230, 255));
                numText.setBorder(new LineBorder(
                    new Color(132, 172, 221), 1, false));
            } else {
                numText.setBackground(Color.white);
                numText.setBorder(new LineBorder(Color.white, 1, false));
            }
            comp.setFocusable(false);
            comp.setBackground(Color.white);
            return comp;
        }
    }

    public class JTreeMod extends JTree {
        public JTreeMod(DefaultTreeModel treeModel) {
            super(treeModel);
        }

        @Override
        protected void processMouseEvent(MouseEvent e) {
            int type = e.getID();
            if (type == MouseEvent.MOUSE_CLICKED || type == MouseEvent.MOUSE_RELEASED) {
                // do nothing
            } else if (type == MouseEvent.MOUSE_PRESSED) {
                int x = e.getX();
                int y = e.getY();

                int row = this.getRowForLocation(x, y);
                if (row == -1) {
                    super.processMouseEvent(e);
                    return;
                }

                int xOffset = x;
                int row1 = row;
                while (row1 == row) {
                    xOffset--;
                    row1 = this.getRowForLocation(xOffset, y);
                }
                xOffset++;

                if (x - xOffset <= 16) {
                    System.out.println("you clicked the image for row " + (row + 1) +
                            ". this was detected, but no selection will happen!");
                    return;
                } else {
                    System.out.println("you clicked the text for row " + (row + 1) + 
                            ". this was detected, and selection WILL happen!");
                    super.processMouseEvent(e);
                }
            } else {
                super.processMouseEvent(e);
            }
        }
    }
}

【讨论】:

  • +1 表示sscce,尽管我认为它突出了这种方法的尴尬。在Outline 中,这些将是您的RowModel 中的普通渲染器(可能还有编辑器)。
  • @trashgod 谢谢,我会尽快调查Outline,但同时这个解决方案会让我度过难关。
猜你喜欢
  • 2016-05-15
  • 2013-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-20
  • 2011-03-26
  • 2014-04-24
相关资源
最近更新 更多