i've got a problem with a custom TreeCellRenderer.
the overridable method getTreeCellRendererComponent is executed about 4 time when i click on a node. Does anybody have an idea about this?
Thanks by advance.
Simon
my code:
#Override
public Component getTreeCellRendererComponent(JTree tree, Object p_value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
super.getTreeCellRendererComponent(tree, p_value, selected, expanded,
leaf, row, hasFocus);
if (row != -1) {
DefaultMutableTreeNode aNode = (DefaultMutableTreeNode) p_value;
TreePath treePath;
treePath = tree.getPathForRow(row);
if (treePath == null) {
return this;
}
// int pathCount = treePath.getPathCount();
JLabel label = (JLabel) this;
if (aNode.getUserObject() instanceof MlCompteMail) {
traiteNomCompte((MlCompteMail) aNode.getUserObject(), label);
// JTreeFactory treeFact = new JTreeFactory();
// treeFact.refreshNode(treePath);
return this;
} else if (aNode.getUserObject() instanceof MlDossier) {
traiteNomDossier((MlDossier) aNode.getUserObject(), label, leaf);
// JTreeFactory treeFact = new JTreeFactory();
// treeFact.refreshNode(treePath);
return this;
}
}
return this;
}
/**
* #param value
* #param treePath
* #param label
* #param p_leaf
*/
private void traiteNomDossier(MlDossier p_dossier, JLabel label,
boolean p_leaf) {
String nomDossier = p_dossier.getNomDossier();
if (nomDossier.equals(EnDossierBase.BROUILLON.getLib())) {
label.setIcon(IconeTreeFactory.getBrouillon());
} else if (nomDossier.equals(EnDossierBase.CORBEILLE.getLib())) {
label.setIcon(IconeTreeFactory.getCorbeille());
} else if (nomDossier.equals(EnDossierBase.ENVOYES.getLib())) {
label.setIcon(IconeTreeFactory.getEnvoye());
} else if (nomDossier.equals(EnDossierBase.RECEPTION.getLib())) {
label.setIcon(IconeTreeFactory.getReception());
} else if (nomDossier.equals(EnDossierBase.SPAM.getLib())) {
label.setIcon(IconeTreeFactory.getSpam());
} else if (p_leaf) {
label.setIcon(IconeTreeFactory.getDossierFerme());
} else {
label.setIcon(IconeTreeFactory.getDossierOuvert());
}
int unreadMess = p_dossier.getUnreadMessCount();
if (unreadMess > 0) {
label.setText(nomDossier + " (" + unreadMess + ")");
label.setFont(Fontfactory.getTREE_FONT_GRAS());
} else {
label.setText(nomDossier);
label.setFont(Fontfactory.getTREE_FONT_PLAIN());
}
return;
}
// }
// }
/**
* #param value
* #param label
*/
private void traiteNomCompte(MlCompteMail p_compteMail, JLabel label) {
int unreadMess = p_compteMail.getUreadMessCount();
if (unreadMess > 0) {
label
.setText(p_compteMail.getNomCompte() + " (" + unreadMess
+ ")");
label.setFont(Fontfactory.getTREE_FONT_GRAS());
} else {
label.setText(p_compteMail.getNomCompte());
label.setFont(Fontfactory.getTREE_FONT_PLAIN());
}
label.setIcon(IconeTreeFactory.getDossierOuvert());
}
That's probably not a problem; the method is called whenever the cell needs to be painted. You don't have the luxury of controlling that.
So design the method to be very efficient and make sure it doesn't have side effects, and you will have no problem.
i found a way to speed up the decoration of my jtree,
the jtree use the method "toString()" to paint a node so i have override the method in my two kind of node, so, in my customTreeCellRenderer, i just call the toString method on my userObject and that work.
Thanks for your help.
Related
Can someone show an example of a TreeCellEditor that can update multiple nodes on a single edit? From what I can tell, GetCellEditorValue() will only update a single node.
My editor currently takes in the selected Nodes, compares them and displays differing values as while displaying the other values that the nodes have in common.
My Constructor which initializes "myDevice".
public DeviceEditor(Collection<DefaultMutableTreeNode> nodes) throws NoSuchFieldException {
System.out.println("CREATING NEW EDITOR \n");
this.nodes = nodes;
ObjectMatcher matcher = new ObjectMatcher();
try {
myDevice = matcher.match(nodes, DefaultDevice.CREATE_MULTIVALUE_DEFAULTDEVICE(), new DefaultDevice());
//System.out.println("Device= " + myDevice.getAddress() + " " + myDevice.getHostName());
} catch (Exception e) {
System.out.println(e);
}
initComponents();
}
The methods of TreeCellEditor
#Override
public Component getTreeCellEditorComponent(JTree jtree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
// this.tree = jtree;
return this;
}
#Override
public Object getCellEditorValue() {
//This value is what is populated into the JTree
return this.myDevice;
}
#Override
public boolean isCellEditable(EventObject eo) {
//should be true only if it's a leaf.
return true;
}
#Override
public boolean shouldSelectCell(EventObject eo) {
//Don't Select
return false;
}
#Override
public boolean stopCellEditing() {
try {
System.out.println("\n Cell Editing Stopped");
//update myDevice
if (!this.addressField.getText().equals(DefaultDevice.MULTIVALUE)) {
myDevice.setAddress(this.addressField.getText());
}
myDevice.setDeviceType(this.deviceTypeField.getText());
myDevice.setLocation(this.locationField.getText());
myDevice.setSerialNumber(this.serialField.getText());
myDevice.setUser(this.userField.getText());
myDevice.setPassword(new String(this.passwordField.getPassword()));
myDevice.setVendor(this.vendorField.getText());
myDevice.setModel(this.modelField.getText());
myDevice.setOS(this.osField.getText());
myDevice.setDescription(this.descriptionField.getText());
myDevice.setVersion(this.versionField.getText());
myDevice.setDeviceType(this.deviceTypeField.getText());
myDevice.setDisplayHostName(this.hostNameCheckBox.isSelected());
myDevice.setDisplayIPV4Address(this.ipV4checkBox.isSelected());
myDevice.setDisplayIPV6Address(this.ipV6CheckBox.isSelected());
DeviceEditor.UPDATE_DEVICES(nodes, myDevice);
return true;
} catch (IPConverter.InvalidIPException ex) {
Exceptions.printStackTrace(ex);
return false;
}
}
/* when i am clicking the tree node (EX:WEBLOGIC is selected) the node icon disappears,but the other icons(Non selected) are coming.please help me out to solve this issue.This is a swing based program*/
class ColorRenderer extends DefaultTreeCellRenderer
{
public ColorRenderer()
{
super();
}
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)
{
try
{
Component cell = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
RTGTreeNode treeNode = (RTGTreeNode) value;
GraphUniqueKeyDTO graphUniqueKeyDTO = treeNode.getGraphUniqueKeyDTO();
/*Default Icon not needed.*/
setIcon(null);
if (graphUniqueKeyDTO == null)
{
return cell;
}
String nodeName = treeNode.toString();
if (!leaf)
{
cell.setFont(NSColor.mediumPlainFont());
if (selected)
{
cell.setForeground(Color.black);
cell.setBackground(new Color(128, 0, 0));
}
else
{
Color color = treeNode.getNodeColor();
if (treeNode.getTreeViewToolTip() != null)
nodeName = treeNode.getTreeViewToolTip();
openIcon = treeNode.getImgIcon();
if(openIcon!=null){
setIcon(openIcon);
setLeafIcon(openIcon);
}
if(color == null)
cell.setForeground(NSColor.leftPaneGroupTitleColor());
else
cell.setForeground(color);
}
}
else
{
cell.setFont(NSColor.smallPlainFont());
if (selected)
{
cell.setForeground(Color.black);
cell.setBackground(new Color(128, 0, 0));
}
else
{
cell.setForeground(NSColor.leftPaneGraphTitleColor());
}
}
setToolTipText(nodeName);
JLabel currentCell = (JLabel) cell;
currentCell.setHorizontalAlignment(JLabel.CENTER);
return cell;
}
catch (Exception ex)
{
Log.errorLog("ColorRenderer", "getTreeCellRendererComponent", "", "", "Exception - " + ex);
return null;
}
}
A node in a tree can have a custom icon but most skins have an additional folder like icon next to those nodes in a tree that contain children. I think you mean the Folder icon next to the node disappears when you click on it. If the tree asks the model if it has children and the model returns true then the icon is displayed. When you click on the node and the children are not present the tree removes the icon.
This will happen if you did not implement the getChildren and isLeaf method or implemented isLeaf incorrectly. The isLeaf method tells the JTree UI to draw or not draw the folder icon. Also make sure setAsksAllowsChildren() is set the the correct value for your needs and getChildCount() returns the correct value for each node.
I've got a JTree with icons on some of the nodes within the tree. They appear and work fine but when I select a node with a icon, the renderer does not render the entire node selected but appears to have an offset applied to it, as if it thinks the icon is still to the left of the node as below:
The code for the renderer (which extends DefaultTreeCellRenderer) is below:
public ProfileTreeRenderer() {
super.setLeafIcon(null);
super.setClosedIcon(null);
super.setOpenIcon(null);
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
Component c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
if (c instanceof JLabel) {
JLabel label = (JLabel) c;
label.setHorizontalTextPosition(SwingConstants.LEADING);
}
if(sel && !hasFocus) {
setBackgroundSelectionColor(UIManager.getColor("Panel.background"));
setTextSelectionColor(UIManager.getColor("Panel.foreground"));
} else {
setTextSelectionColor(UIManager.getColor("Tree.selectionForeground"));
setBackgroundSelectionColor(UIManager.getColor("Tree.selectionBackground"));
}
if (value instanceof ProfileNode) {
ProfileNode node = (ProfileNode) value;
if (node.isUsed() && !sel) {
c.setForeground(Color.GRAY);
}
if (node.getIcon() != null) {
setIcon(node.getIcon());
}
}
}
I cannot see why the renderer would apply this offset, so can anyone offer a way to get the node fully selected with the icon? The SSCCE code for the tree itself is below.
public class Example extends JDialog {
public Example() {
JTree tree = new JTree(createModel());
tree.setCellRenderer(new ProfileTreeRenderer());
setLayout(new BorderLayout());
add(tree, BorderLayout.CENTER);
}
private TreeModel createModel() {
ProfileNode root = new ProfileNode("Profiles");
ProfileNode userA = new ProfileNode("Example User A");
ProfileNode userB = new ProfileNode("Example User B");
// You'll need to subsitute your own 16x16 icons here
userA.setIcon(ImageSet.USER_ICON);
userB.setIcon(ImageSet.USER_ICON);
root.add(userA);
root.add(userB);
return new DefaultTreeModel(root);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Example().setVisible(true);
}
});
}
}
The ProfileNode class:
public class ProfileNode extends DefaultMutableTreeNode {
#Getter private String labelDisplay;
#Getter #Setter private ImageIcon icon;
#Getter #Setter private boolean isUsed = false;
public ProfileNode(String labelDisplay) {
this.labelDisplay = labelDisplay;
}
#Override
public String toString() {
return labelDisplay;
}
}
Thanks in advance.
The problem is that the DefaultTreeCellRenderer uses its icon property exclusively for the open/leaf/close icons: it assumes that - if the icon != null - it's at the start of the component (even if it isn't) and adjusts the selection accordingly. You need to re-adjust ... or use SwingX renderers :-)
Something like:
JXTree tree = new JXTree();
tree.expandAll();
IconValue iv = new IconValue() {
Icon icon = XTestUtils.loadDefaultIcon("green-orb.png");
#Override
public Icon getIcon(Object value) {
return value.toString().contains("s") ? icon : null;
}
};
StringValue converter = new MappedValue(StringValues.TO_STRING, iv);
WrappingProvider provider = new WrappingProvider(IconValues.NONE, converter);
// hacking around missing api ...
LabelProvider wrappee = (LabelProvider) provider.getWrappee();
wrappee.getRendererComponent(null).setHorizontalTextPosition(JLabel.LEADING);
TreeCellRenderer r = new DefaultTreeRenderer(provider);
tree.setCellRenderer(r);
I need to render Two type of nodes in a single tree.
Parent nodes and Leaf nodes.
I also need to edit both of them .
The CheckBoxNodeRender is as follows :
public JCheckBox leafRenderer;
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
Component returnValue;
if (leaf) {
String stringValue =
tree.convertValueToText(value, selected, expanded, leaf,
row, false);
if (selected) {
leafRenderer.setForeground(selectionForeground);
leafRenderer.setBackground(selectionBackground);
} else {
leafRenderer.setForeground(textForeground);
leafRenderer.setBackground(textBackground);
}
if ((value != null) && (value instanceof DefaultMutableTreeNode)) {
Object userObject =
((DefaultMutableTreeNode) value).getUserObject();
if (userObject instanceof CheckBoxNode) {
CheckBoxNode node = (CheckBoxNode) userObject;
leafRenderer.setText(node.getText());
System.err.println("Leaf Value = "+node.getText());
leafRenderer.setSelected(node.isSelected());
}
}
returnValue = leafRenderer;
}
else {
// For the Parent Node
leafRenderer.setText(value.toString());
leafRenderer.setSelected(selected);
returnValue = leafRenderer;
}
return returnValue;
}
And the Editor is as follows :
public boolean isCellEditable(EventObject event) {
//All cells are editable
return true;
}
public Component getTreeCellEditorComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row) {
Component editor =
renderer.getTreeCellRendererComponent(tree, value,
true, expanded, leaf, row, true);
ItemListener itemListener = new ItemListener() {
public void itemStateChanged(ItemEvent itemEvent) {
if (stopCellEditing()) {
fireEditingStopped();
}
}
};
if (editor instanceof JCheckBox) {
((JCheckBox) editor).addItemListener(itemListener);
}
return editor;
}
I have been facing a unique problem here .
When i select the parent node and select some other node , the value of the other node gets set for the parent node.
Any leads ?
What mistake am i doing here ?
It looks like you have your customized TreeCellEditor reusing components returned from your customized TreeCellRenderer, is that correct? That seems like a bad practice to me. The default JLabel-based implementation of TreeCellRenderer uses a single JLabel for rendering a large tree - so even if the tree has 1,000 nodes, only one JLabel instance is needed. If you're reusing this component when displaying your editor, this will result in graphical anomalies.
My recommendation would be to change your TreeCellEditor implementation to return a different component than the one being used for your TreeCellRenderer.
If you have JTable set with table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) and then you click drag on a row that is not already selected, it starts selecting multiple rows. We don't want that behavior. We want it so if you click on a node, even if it's not already selected, it will start dragging it.
We do need the multi select mode on, so setting it to single select (which does result in the behavior we want) is not an option.
Update: At this point, it appears it will require some type of ugly hack since the logic is in a private method BasicTableUI$Handler.canStartDrag
I found one possible solution. You bracket the mouse listeners so you can lie to the call to isCellSelected during the canStartDrag call.
Subclass JTable (or in my case, JXTreeTable). In the constructor call this:
private void setupSelectionDragHack()
{
// Bracket the other mouse listeners so we may inject our lie
final MouseListener[] ls = getMouseListeners();
for (final MouseListener l : ls)
{
removeMouseListener(l);
}
addMouseListener(new MouseAdapter()
{
#Override
public void mousePressed(final MouseEvent e)
{
// NOTE: it might not be necessary to check the row, but... I figure it's safer maybe?
mousingRow = rowAtPoint(e.getPoint());
mousingInProgress = true;
}
});
for (final MouseListener l : ls)
{
addMouseListener(l);
}
addMouseListener(new MouseAdapter()
{
#Override
public void mousePressed(final MouseEvent e)
{
mousingInProgress = false;
}
});
}
And then you'll need this:
#Override
public boolean isCellSelected(final int row, final int column)
{
if (mousingInProgress && row == mousingRow)
{
// Only lie to the canStartDrag caller. We tell the truth to everyone else.
final StackTraceElement[] elms = Thread.currentThread().getStackTrace();
for (int i = 0; i < 3; i++)
{
if (elms[i].getMethodName().equals("canStartDrag"))
{
return mousingInProgress;
}
}
}
return super.isCellSelected(row, column);
}
It's an ugly hack in many ways, but... for now it seems to work.
Unfortunately none of the other answers worked for me.
So I made my own hack/fix for the problem (I'm posting it here for others with the same problem):
public class SFixTable extends JTable {
private static final long serialVersionUID = 1082882838948078289L;
boolean pressed = false;
int currSRow = -100;
public SFixTable(TableModel dm) {
super(dm);
}
public SFixTable() {
super();
}
public SFixTable(Vector<?> rowData, Vector<?> columnNames) {
super(rowData, columnNames);
}
#Override
protected void processMouseEvent(MouseEvent e) {
int row = rowAtPoint(e.getPoint());
int col = columnAtPoint(e.getPoint());
if (SwingUtilities.isLeftMouseButton(e) && !e.isShiftDown() && !e.isControlDown()) {
boolean isDragRelease = (e.getID() == MouseEvent.MOUSE_RELEASED) && row != currSRow;
boolean isStartClick = (e.getID() == MouseEvent.MOUSE_PRESSED);
if (row >= 0 && col >= 0) {
if (isStartClick) {
super.changeSelection(row, col, false, false);
} else if (isDragRelease) {
super.changeSelection(currSRow, col, false, false);
}
}
pressed = (e.getID() == MouseEvent.MOUSE_PRESSED);
if (pressed) {
currSRow = row;
} else {
currSRow = -100;
}
}
super.processMouseEvent(e);
}
#Override
public boolean isCellSelected(int row, int col) {
return (pressed)? (row == currSRow) : super.isCellSelected(row, col);
}
}
It's a bug:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6349223
and as you already assumed, it requires some ugly hack. Here's one (not from me, but from a user Aephyr on old sun forums which didn't survive the migration to OTN)
table = new JTable() {
// fix for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6349223
// requirement is the option to turn off drag-selection if dragEnabled
// fix posted in sun dev forum by Aephyr
// http://forums.sun.com/thread.jspa?threadID=5436355&tstart=0
private boolean pressed;
#Override
protected void processMouseEvent(MouseEvent e) {
pressed = e.getID() == MouseEvent.MOUSE_PRESSED;
if (pressed && !e.isShiftDown() && !e.isControlDown())
clearSelection();
try {
super.processMouseEvent(e);
} finally {
pressed = false;
}
}
#Override
public boolean isCellSelected(int row, int col) {
return pressed ? true : super.isCellSelected(row, col);
}
};
Similar to kleopatra's answer, but this seems to handle a few issues with the previous one -- you can control-click to both add and remove items from a multiple selection, and you can successfully drag a multi-select group. I've tested this only with an ETable/Outline from NetBeans, but should work with a regular JTable.
table = new JTable() {
private boolean inPress = false;
#Override protected void processMouseEvent(MouseEvent e) {
inPress = e.getID() == MouseEvent.MOUSE_PRESSED && e.getButton() == MouseEvent.BUTTON1 && !e.isShiftDown() && !e.isControlDown();
try {
super.processMouseEvent(e);
} finally {
inPress = false;
}
}
#Override public boolean isCellSelected(int row, int col) {
boolean selected = super.isCellSelected(row, col);
if (inPress) {
if (!selected)
clearSelection();
return true;
}
return selected;
}
};
If what you are looking for is to drag an unselected row in a single selection JTable, setting the table's selection model to SINGLE_SELECTION mode is not enough, you also have to set the column model's selection mode.
JTable table = new JTable();
table.getSelectionModel()
.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.getColumnModel().getSelectionModel()
.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);