TreeCellEditor:必须选择cell来编辑,即使ShouldSelectCell返回false。

时间:2022-11-24 12:36:32

I need to use custom cell renderer for my JTree to add some JLabel on each cell. And then allow the user to click on these label without needing to select the cell first.

我需要使用自定义单元渲染器来为我的JTree添加一些JLabel在每个单元格上。然后允许用户单击这些标签,而不需要首先选择单元格。

So, i've created a Renderer which return a JPanel that contains a DefaultTreeCellRenderer and 2 JLabel.

因此,我创建了一个Renderer,它返回一个包含DefaultTreeCellRenderer和2 JLabel的JPanel。

    public class TreeNodeRenderer extends DefaultTreeCellRenderer implements TreeCellRenderer
    {
        private JPanel panel1 = new JPanel();
        private JLabel delete = new JLabel("");
        private JLabel upload = new JLabel("");

        public Component getTreeCellRendererComponent(JTree tree, 
                            Object value,
                boolean selected, boolean expanded, boolean leaf, int row,
                boolean hasFocus) 
        {   
            //
            // DELETE label
            //
            delete.setName("delete");
            delete.setIcon(new ImageIcon("Data/trash.png"));

            //
            // UPLOAD label
            //
            upload.setName("upload");
            upload.setIcon(new ImageIcon("Data/app_up.png"));


            DefaultTreeCellRenderer defaultRenderer = new DefaultTreeCellRenderer();
            Color backgroundSelectionColor = defaultRenderer.getBackgroundSelectionColor();
            Color backgroundNonSelectionColor = defaultRenderer.getBackgroundNonSelectionColor();

            if(selected)
                panel1.setBackground(backgroundSelectionColor);
            else
                panel1.setBackground(backgroundNonSelectionColor);


            component = (DefaultTreeCellRenderer) super.getTreeCellRendererComponent(tree,
                    value, selected, expanded, leaf, row, hasFocus);

            panel1.add(component);
            panel1.add(delete);
            panel1.add(upload);

            return panel1;
        }
    }

Then i've created the editor to allow user to click on these labels thanks to a MouseListener. Everything works well except that the user must select the cell before click on a label. I tried to return "false" with the method "ShouldSelectCell" but it doesn't work.

然后,我创建了一个编辑器,允许用户单击这些标签,这要感谢MouseListener。一切都很好,除了用户必须在点击标签之前选择单元格。我试图用“ShouldSelectCell”方法返回“false”,但它不起作用。

Does someone know why ?

有人知道为什么吗?

Here the editor:

这里编辑器:

public class TreeNodeEditor extends AbstractCellEditor implements TreeCellEditor
{
    private TreeNodeRenderer renderer;

    public TreeNodeEditor(TreeNodeRenderer treeRenderer)
    {
        this.renderer = treeRenderer;

    //change the cursor when it's over a label  renderer.getDeleteButton().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
    renderer.getUploadButton().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));       renderer.getDownloadButton().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));

                 //add labels' mouse listeners 
        addLabelMouseListener(renderer.getDeleteButton());
        addLabelMouseListener(renderer.getUploadButton());
    }

    public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row)
    {
        ...

        return renderer.getTreeCellRendererComponent(
                tree, value, isSelected,
                expanded, leaf, row, true);
    }

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

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


    public boolean stopCellEditing() 
    {
        return super.stopCellEditing();
    }

    public void cancelCellEditing() 
    {
        super.cancelCellEditing();
    }

    public void addCellEditorListener(CellEditorListener l)
    {
        super.addCellEditorListener(l);
    }

    public void removeCellEditorListener(CellEditorListener l) 
    {
        super.removeCellEditorListener(l);
    }

    public Object getCellEditorValue() 
    {
        return null;
    }
}

EDIT - Here a SSCCE:

编辑-这里是一个SSCCE:

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.EventObject;

import javax.swing.AbstractCellEditor;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.CellEditorListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;



public class EditJTreeCell extends JFrame
{
    /**
     * 
     */
    private static final long serialVersionUID = 4745146614430249610L;

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

    public EditJTreeCell()
    {
        super("Sample");
        root = new DefaultMutableTreeNode("Root folder");
        treeModel = new DefaultTreeModel(root);
        tree = new JTree(treeModel);

        TreeNodeRenderer renderer = new TreeNodeRenderer();
        tree.setCellRenderer(renderer);
        tree.setCellEditor(new TreeNodeEditor());
        tree.setEditable(true);

        //tree creation
        DefaultMutableTreeNode folder = new DefaultMutableTreeNode("folder1");
        DefaultMutableTreeNode file = new DefaultMutableTreeNode("file1");
        folder.add(file);
        file = new DefaultMutableTreeNode("file2");
        folder.add(file);
        root.add(folder);
        folder = new DefaultMutableTreeNode("folder2");
        file = new DefaultMutableTreeNode("file1");
        folder.add(file);
        file = new DefaultMutableTreeNode("file2");
        folder.add(file);
        file = new DefaultMutableTreeNode("file3");
        folder.add(file);
        root.add(folder);

        this.setSize(400, 800);
        this.add(tree);
        this.setVisible(true);
    }

    public static void main(String[] args) 
    {
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
        new EditJTreeCell();
    }
}

class TreeNodeRenderer extends DefaultTreeCellRenderer implements TreeCellRenderer
{
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    private JPanel panel1 = new JPanel();
    private JLabel delete = new JLabel("DELETE");
    private JLabel upload = new JLabel("UPLOAD");

    public Component getTreeCellRendererComponent(JTree tree, Object value,
            boolean selected, boolean expanded, boolean leaf, int row,
            boolean hasFocus) 
    {   
        //
        // DELETE label
        //
        delete.setName("delete");
        delete.setIcon(new ImageIcon("trash.png"));
        //addLabelMouseListener(delete);
        //
        // UPLOAD label
        //
        upload.setName("upload");
        upload.setIcon(new ImageIcon("app_up.png"));
        //addLabelMouseListener(upload);


        DefaultTreeCellRenderer defaultRenderer = new DefaultTreeCellRenderer();
        Color backgroundSelectionColor = defaultRenderer.getBackgroundSelectionColor();
        Color backgroundNonSelectionColor = defaultRenderer.getBackgroundNonSelectionColor();

        DefaultTreeCellRenderer component = (DefaultTreeCellRenderer) super.getTreeCellRendererComponent(tree,
                value, selected, expanded, leaf, row, hasFocus);

        if(selected)
        {   
            panel1.setBackground(backgroundSelectionColor);
        }
        else
        {
            panel1.setBackground(backgroundNonSelectionColor);
        }

        panel1.add(component);
        panel1.add(delete);
        panel1.add(upload);

        return panel1;
    }
}

class TreeNodeEditor extends AbstractCellEditor implements TreeCellEditor
{
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private JLabel button1;
    private JLabel button2;
    private JPanel panel1;
    private DefaultMutableTreeNode node = null;
    private DefaultTreeCellRenderer defaultRenderer;


    public TreeNodeEditor()
    {
        super();
        panel1 = new JPanel();
        defaultRenderer = new DefaultTreeCellRenderer();
        button1 = new JLabel("DELETE");
        button1.setOpaque(true);
        button1.setIcon(new ImageIcon("trash.png"));
        button1.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        button1.addMouseListener(new MouseListener() 
        {
            public void mouseClicked(MouseEvent arg0) {
                System.out.println("Delete clicked");
            }
            public void mouseEntered(MouseEvent arg0) {}
            public void mouseExited(MouseEvent arg0) {}
            public void mousePressed(MouseEvent arg0) {}
            public void mouseReleased(MouseEvent arg0) {}

        });
        button2 = new JLabel("UPLOAD");
        button2.setOpaque(true);
        button2.setIcon(new ImageIcon("app_up.png"));
        button2.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        button2.addMouseListener(new MouseListener() 
        {
            public void mouseClicked(MouseEvent arg0) {
                System.out.println("Upload clicked");
            }
            public void mouseEntered(MouseEvent arg0) {}
            public void mouseExited(MouseEvent arg0) {}
            public void mousePressed(MouseEvent arg0) {}
            public void mouseReleased(MouseEvent arg0) {}

        });
    }


    public Component getTreeCellEditorComponent(JTree tree, Object value, 
            boolean isSelected, boolean expanded, boolean leaf, int row)
    {
        //in order to do some actions on a node
        if(value instanceof DefaultMutableTreeNode)
        {
            node = (DefaultMutableTreeNode) value;
        }


         defaultRenderer.getTreeCellRendererComponent(tree,
                    value, isSelected, expanded, leaf, row, true);

        panel1.add(defaultRenderer);
        panel1.add(button1);
        panel1.add(button2);
        return panel1;
    }

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

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


    public boolean stopCellEditing() 
    {
        return super.stopCellEditing();
    }

    public void cancelCellEditing() 
    {
        super.cancelCellEditing();
    }

    public void addCellEditorListener(CellEditorListener l)
    {
        super.addCellEditorListener(l);
    }

    public void removeCellEditorListener(CellEditorListener l) 
    {
        super.removeCellEditorListener(l);
    }

    public Object getCellEditorValue() 
    {
        return null;
    }
}

3 个解决方案

#1


5  

starting an edit on mouseEnter is a valid solution :-)

在mouseEnter开始编辑是一个有效的解决方案:-)

Your editor, on the other hand is not a valid implementation: it fails on not notifying its listener if the edit is terminated due to internal events (as f.i. clicking on any of the buttons) Below is an example of how-to achieve both your goal and have a valid implementation

另一方面,您的编辑器并不是有效的实现:如果编辑由于内部事件(如f.i.单击任何一个按钮)而终止,那么它就不会通知它的侦听器,这是如何实现您的目标和实现有效实现的示例。

a couple of comments:

一些评论:

  • if you want something like a button .. use a button: otherwise users might be confused
  • 如果你想要一个按钮。使用按钮:否则用户可能会感到困惑。
  • in your editor, set an action to the buttons as needed
  • 在编辑器中,根据需要对按钮设置一个操作。
  • do all basic panel config (like adding its children) in the constructor)
  • 在构造函数中执行所有基本面板配置(比如添加其子元素)
  • to start editing/detect which button is clicked, re-dispatch the event received in shouldSelect. Do it in a SwingUtilities.invokeLater to make sure any internally pending events (in the tree) are ready
  • 要开始编辑/检测哪个按钮被单击,重新分派在shouldSelect中接收的事件。在SwingUtilities中做。invokeLater确保任何内部挂起事件(在树中)都准备好了。
  • do not change the tree node inside the editor: a) those changes will fail to notify the model b) will be overruled by the tree's default editing termination behaviour. DefaultTreeTable will reset the userObject of the tree with editorValue, that's done in valueForPathChanged: to implement custom behaviour, override that method in the model
  • 不要更改编辑器内的树节点:a)这些更改将无法通知模型b)将被树的默认编辑终止行为所推翻。DefaultTreeTable将重新设置树的userObject,它具有编辑器值,这是在valueForPathChanged中完成的:为了实现自定义行为,在模型中重写该方法。

in code:

在代码:

static class TreeNodeEditor extends AbstractCellEditor implements TreeCellEditor {
    private static final long serialVersionUID = 1L;
    private JButton button1;
    private JButton button2;
    private JPanel panel1;
    // JW: do not modify the node inside the editor 
    //        private DefaultMutableTreeNode node = null;
    private DefaultTreeCellRenderer defaultRenderer;

    private Object editorValue;

    public TreeNodeEditor() {
        super();
        panel1 = new JPanel();
        defaultRenderer = new DefaultTreeCellRenderer();
        button1 = new JButton("DELETE");
        button1.setOpaque(true);
        button1.setIcon(new ImageIcon("trash.png"));
        button1.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        button2 = new JButton("UPLOAD");
        button2.setOpaque(true);
        button2.setIcon(new ImageIcon("app_up.png"));
        button2.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        button2.setAction(createAction("upload", "UPLOAD"));
        button1.setAction(createAction("delete", "DELETE"));
        panel1.add(defaultRenderer);
        panel1.add(button1);
        panel1.add(button2);
    }

    private Action createAction(final String actionCommand, String display) {
        Action action = new AbstractAction(display) {

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

        };
        return action;

    }
    /**
     * @param actionCommand
     */
    protected void stopEditing(String actionCommand) {
        editorValue = actionCommand;
        stopCellEditing();
    }

    @Override
    public Component getTreeCellEditorComponent(JTree tree, Object value,
            boolean isSelected, boolean expanded, boolean leaf, int row) {
        // in order to do some actions on a node
        //            if (value instanceof DefaultMutableTreeNode) {
        //                node = (DefaultMutableTreeNode) value;
        //            }

        defaultRenderer.getTreeCellRendererComponent(tree, value,
                isSelected, expanded, leaf, row, true);

        return panel1;
    }

    /**
     * 
     */
    private void reset() {
        editorValue = null;
    }

    /**
     * At this point in time the component is added to the tree (not documented!) but
     * tree's internal cleanup might not yet be ready
     */ 
    @Override
    public boolean shouldSelectCell(EventObject anEvent) {
        reset();
        if (anEvent instanceof MouseEvent) {
            redirect((MouseEvent) anEvent);
        }
        return false;
    }

    private void redirect(final MouseEvent anEvent) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MouseEvent ev = SwingUtilities.convertMouseEvent(anEvent.getComponent(), anEvent, panel1);
                panel1.dispatchEvent(ev);

            }
        });
    }

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

#2


1  

Finally, i solved my problem with a MouseMotionListener and the method : myTree.startEditingAtPath(path). A node is now in editing mode when the cursor is over it.

最后,我用一个MouseMotionListener和这个方法解决了我的问题:myTree.startEditingAtPath(path)。当游标在上面时,节点现在处于编辑模式。

tree.addMouseMotionListener(new MouseMotionListener() {
            public void mouseMoved(MouseEvent e) 
            {
                if (tree.getRowForLocation(e.getX(), e.getY()) != -1)
                {
                    tree.startEditingAtPath(tree.getPathForLocation(e.getX(), e.getY()));
                }               
            }
            public void mouseDragged(MouseEvent e) {}
        });

However, if someone has a better idea, please let me know.

但是,如果有人有更好的主意,请告诉我。

#3


0  

I think you need to add the mouse listeners inside the treeNodeRenderer itself. It is likely that the mouselistener is only getting added after you enter 'edit mode' and editor is put into the cell.

我认为您需要在treeNodeRenderer本身中添加鼠标监听器。在输入“编辑模式”和编辑器放入单元格之后,mouselistener可能只会被添加。

#1


5  

starting an edit on mouseEnter is a valid solution :-)

在mouseEnter开始编辑是一个有效的解决方案:-)

Your editor, on the other hand is not a valid implementation: it fails on not notifying its listener if the edit is terminated due to internal events (as f.i. clicking on any of the buttons) Below is an example of how-to achieve both your goal and have a valid implementation

另一方面,您的编辑器并不是有效的实现:如果编辑由于内部事件(如f.i.单击任何一个按钮)而终止,那么它就不会通知它的侦听器,这是如何实现您的目标和实现有效实现的示例。

a couple of comments:

一些评论:

  • if you want something like a button .. use a button: otherwise users might be confused
  • 如果你想要一个按钮。使用按钮:否则用户可能会感到困惑。
  • in your editor, set an action to the buttons as needed
  • 在编辑器中,根据需要对按钮设置一个操作。
  • do all basic panel config (like adding its children) in the constructor)
  • 在构造函数中执行所有基本面板配置(比如添加其子元素)
  • to start editing/detect which button is clicked, re-dispatch the event received in shouldSelect. Do it in a SwingUtilities.invokeLater to make sure any internally pending events (in the tree) are ready
  • 要开始编辑/检测哪个按钮被单击,重新分派在shouldSelect中接收的事件。在SwingUtilities中做。invokeLater确保任何内部挂起事件(在树中)都准备好了。
  • do not change the tree node inside the editor: a) those changes will fail to notify the model b) will be overruled by the tree's default editing termination behaviour. DefaultTreeTable will reset the userObject of the tree with editorValue, that's done in valueForPathChanged: to implement custom behaviour, override that method in the model
  • 不要更改编辑器内的树节点:a)这些更改将无法通知模型b)将被树的默认编辑终止行为所推翻。DefaultTreeTable将重新设置树的userObject,它具有编辑器值,这是在valueForPathChanged中完成的:为了实现自定义行为,在模型中重写该方法。

in code:

在代码:

static class TreeNodeEditor extends AbstractCellEditor implements TreeCellEditor {
    private static final long serialVersionUID = 1L;
    private JButton button1;
    private JButton button2;
    private JPanel panel1;
    // JW: do not modify the node inside the editor 
    //        private DefaultMutableTreeNode node = null;
    private DefaultTreeCellRenderer defaultRenderer;

    private Object editorValue;

    public TreeNodeEditor() {
        super();
        panel1 = new JPanel();
        defaultRenderer = new DefaultTreeCellRenderer();
        button1 = new JButton("DELETE");
        button1.setOpaque(true);
        button1.setIcon(new ImageIcon("trash.png"));
        button1.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        button2 = new JButton("UPLOAD");
        button2.setOpaque(true);
        button2.setIcon(new ImageIcon("app_up.png"));
        button2.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        button2.setAction(createAction("upload", "UPLOAD"));
        button1.setAction(createAction("delete", "DELETE"));
        panel1.add(defaultRenderer);
        panel1.add(button1);
        panel1.add(button2);
    }

    private Action createAction(final String actionCommand, String display) {
        Action action = new AbstractAction(display) {

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

        };
        return action;

    }
    /**
     * @param actionCommand
     */
    protected void stopEditing(String actionCommand) {
        editorValue = actionCommand;
        stopCellEditing();
    }

    @Override
    public Component getTreeCellEditorComponent(JTree tree, Object value,
            boolean isSelected, boolean expanded, boolean leaf, int row) {
        // in order to do some actions on a node
        //            if (value instanceof DefaultMutableTreeNode) {
        //                node = (DefaultMutableTreeNode) value;
        //            }

        defaultRenderer.getTreeCellRendererComponent(tree, value,
                isSelected, expanded, leaf, row, true);

        return panel1;
    }

    /**
     * 
     */
    private void reset() {
        editorValue = null;
    }

    /**
     * At this point in time the component is added to the tree (not documented!) but
     * tree's internal cleanup might not yet be ready
     */ 
    @Override
    public boolean shouldSelectCell(EventObject anEvent) {
        reset();
        if (anEvent instanceof MouseEvent) {
            redirect((MouseEvent) anEvent);
        }
        return false;
    }

    private void redirect(final MouseEvent anEvent) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MouseEvent ev = SwingUtilities.convertMouseEvent(anEvent.getComponent(), anEvent, panel1);
                panel1.dispatchEvent(ev);

            }
        });
    }

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

#2


1  

Finally, i solved my problem with a MouseMotionListener and the method : myTree.startEditingAtPath(path). A node is now in editing mode when the cursor is over it.

最后,我用一个MouseMotionListener和这个方法解决了我的问题:myTree.startEditingAtPath(path)。当游标在上面时,节点现在处于编辑模式。

tree.addMouseMotionListener(new MouseMotionListener() {
            public void mouseMoved(MouseEvent e) 
            {
                if (tree.getRowForLocation(e.getX(), e.getY()) != -1)
                {
                    tree.startEditingAtPath(tree.getPathForLocation(e.getX(), e.getY()));
                }               
            }
            public void mouseDragged(MouseEvent e) {}
        });

However, if someone has a better idea, please let me know.

但是,如果有人有更好的主意,请告诉我。

#3


0  

I think you need to add the mouse listeners inside the treeNodeRenderer itself. It is likely that the mouselistener is only getting added after you enter 'edit mode' and editor is put into the cell.

我认为您需要在treeNodeRenderer本身中添加鼠标监听器。在输入“编辑模式”和编辑器放入单元格之后,mouselistener可能只会被添加。