问题
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.
So, i've created a Renderer which return a JPanel that contains a 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'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.
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:
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:
starting an edit on mouseEnter is a valid solution :-)
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
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
- 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
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:
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.
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:
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.
来源:https://stackoverflow.com/questions/8119718/treecelleditor-must-select-cell-to-edit-even-if-shouldselectcell-return-false