TreeCellEditor: you must select a cell to edit, even if ShouldSelectCell returns false

I need to use my own cell rendering for my JTree to add JLabel to each cell. And then let the user click these labels without having to select a cell first.

So, I created a Renderer that returns a JPanel that contains the DefaultTreeCellRenderer and 2 JLabel.

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 created an editor to allow the user to click on these labels thanks to MouseListener. Everything works well, except that the user must select a cell before clicking on the shortcut. I tried to return "false" using the "ShouldSelectCell" method, but it does not work.

Does anyone know why?

Here's the editor:

 public class TreeNodeEditor extends AbstractCellEditor implements TreeCellEditor { private TreeNodeRenderer renderer; public TreeNodeEditor(TreeNodeRenderer treeRenderer) { this.renderer = treeRenderer; //change the cursor when it 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 is 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; } } 
+1
java editor swing jtree
source share
3 answers

starting editing on mouseEnter is a valid solution :-)

Your editor, on the other hand, is not a valid version: he cannot help but notify his listener if editing is completed due to internal events (like clicking on a button on any of the buttons). The following is an example of practical achievement of both your goal and having the right implementation.

some comments:

  • if you want something like a button. use the button: otherwise users may be confused.
  • in your editor, set the action on the buttons as needed
  • make all the basic settings of the panel (for example, adding its children) to the constructor)
  • to start editing / detection of the pressed button, resend the event received in shouldSelect. Do this in SwingUtilities.invokeLater to make sure all pending event events (in the tree) are ready
  • Do not change the node tree inside the editor: a) these changes will not be able to notify the model b) will be canceled by the default behavior for editing the default tree DefaultTreeTable will reset the userObject tree using the Value editor made in valueForPathChanged: to implement custom behavior, override this method in the model

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 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; } } 
+5
source share

Finally, I solved my problem using MouseMotionListener and the method: myTree.startEditingAtPath(path) . A node is now in edit mode when the cursor is over it.

 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 anyone has a better idea, please let me know.

+1
source share

I think you need to add mouse listeners inside the treeNodeRenderer itself. It is likely that the mouselistener is added only after entering the "edit mode", and the editor is placed in the cell.

0
source share

All Articles