Changing JTree Node Icon dynamically - java

I have implemented a JTree using the DefaultTreeModel.I am storing Message type objects in the tree where I set the node icons initially according to a direction variable value of the Message. I can do this successfully using the below code.
tcBuilderTree = new JTree(treeModel);
tcBuilderTree.getSelectionModel().setSelectionMode
(TreeSelectionModel.SINGLE_TREE_SELECTION);
tcBuilderTree.setCellRenderer(new DefaultTreeCellRenderer(){
public Component getTreeCellRendererComponent(JTree tree,
Object value,
boolean selected,
boolean expanded,
boolean isLeaf,
int row,
boolean focused) {
Component c = super.getTreeCellRendererComponent(tree, value,
selected, expanded, isLeaf, row, focused);
if(value instanceof CustomMessageTreeNode){
setIcon(((CustomMessageTreeNode)value).
getMessage().getMessageDirectionIcon());
}
return c;
}
});
//implementation of getMessageDirectionIcon()
public ImageIcon getMessageDirectionIcon(){
ImageIcon icon=null;
URL messageIconUrl;
if(msgDirection.equalsIgnoreCase("In")){
messageIconUrl = ToolBar.class.getResource
("/icons/arrowRight.png");
icon=new ImageIcon(messageIconUrl);
}
else if(msgDirection.equalsIgnoreCase("Out")){
messageIconUrl = ToolBar.class.getResource
("/icons/arrowLeft.png");
icon=new ImageIcon(messageIconUrl);
}
return icon;
}
Dynamically I want to let the user select the node and change its direction so that the node icon gets changed. I'm doing it through the foloowing code.
public void toggleDirection(){
CustomMessageTreeNode currentSelectedNode =
(CustomMessageTreeNode) tcBuilderTree.getLastSelectedPathComponent();
Message toggleMessage=currentSelectedNode.getMessage();
tcBuilderTree.setCellRenderer(new DefaultTreeCellRenderer(){
#Override
public Component getTreeCellRendererComponent(JTree tree,
Object value,
boolean selected,
boolean expanded,
boolean isLeaf,
int row,
boolean focused) {
Component c = super.getTreeCellRendererComponent(tree, value,
selected, expanded, isLeaf, row, focused);
if(value instanceof CustomMessageTreeNode){
String currentDirection=toggleMessage.getMsgDirection();
if(currentDirection.equalsIgnoreCase("In")){
toggleMessage.setMsgDirection("Out");
System.out.println("Direction changed as : Out");
setIcon(toggleMessage.getMessageDirectionIcon());
return c;
}
if(currentDirection.equalsIgnoreCase("Out")){
toggleMessage.setMsgDirection("In");
System.out.println("Direction changed as : In");
setIcon(toggleMessage.getMessageDirectionIcon());
return c;
}
}
return c;
}
});
}
This is my CustomMessageNode class.
public class CustomMessageTreeNode extends DefaultMutableTreeNode{
private Message message;
public CustomMessageTreeNode(String msgName){
super(msgName);
}
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
}
Once I change the direction by selecting the node the icon gets changed. But the issue happens whenever I click on some other node or somewhere in my JFrame the icon keeps on changing the direction. I debugged and made sure that the toggleDirection() function is called only once when I click the node and said to change the direction.
Please let me know why this happens and is there a better way to do this.What is the reason for the node icon to keep on changing when the user changes it once.

Related

I already have a checkbox in a Java Swing TreeNode. But how do I make it checkable?

Note: I am NOT asking how to put a checkbox in a JTree - previously, a confused moderator thought this is what I was asking. I already have the checkbox in the tree. I am asking what class or method controls the checkability of the checkbox...
In order to get a checkbox inside a Tree node, I read that you had to make a checkbox renderer, so I made one:
class CheckboxCellRenderer implements TreeCellRenderer {
final static Logger logger = LoggerFactory.getLogger(CheckboxCellRenderer.class);
JLabel firstNameLabel = new JLabel(" ");
JPanel renderer = new JPanel();
JCheckBox checkbox;
DefaultTreeCellRenderer defaultRenderer = new DefaultTreeCellRenderer();
public CheckboxCellRenderer() {
super();
checkbox = new JCheckBox(firstNameLabel.getText(), false);
renderer.add(checkbox);
renderer.add(firstNameLabel);
}
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
boolean expanded, boolean leaf, int row, boolean hasFocus) {
Component returnValue = null;
firstNameLabel.setText(value.toString());
if ((value != null) && (value instanceof DefaultMutableTreeNode)) {
Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
renderer.setEnabled(tree.isEnabled());
if(((DefaultMutableTreeNode) value).getLevel()==1){
returnValue = renderer;
}
}
if (returnValue == null) {
returnValue = defaultRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
}
return returnValue;
}
}
This creates the checkbox in the tree node. But for some reason this makes the checkbox uncheckable, and nothing happens when I click it. Why does creating the checkbox in the renderer "break" the checkbox? How do I make the checkbox checkable (i.e. it gets checked when I click the checkbox, and unchecked when I click it again)?
In order to be make a checkbox "checkable", it looks like you need an editor class. This can be demonstrated by commenting out the line "tree.setCellEditor(new CheckBoxNodeEditor(tree));" in the "CheckBox Node Tree Sample" that Abra posted, which makes the example "uncheckable".

Tree node icon dissappears when i click on it

/* 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.

JTree nodes won't get visually selected

Somehow I am not able to enable a "select highlight" for my JTree nodes.
I am working with a custom cell renderer in my project (which most likely causes this problem).
This is the full renderer class code:
protected class ProfessionTreeCellRenderer extends DefaultTreeCellRenderer {
private final JLabel label;
public ProfessionTreeCellRenderer() {
label = new JLabel();
setBackgroundSelectionColor(Color.BLUE);
setOpaque(true);
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
Object o = ((DefaultMutableTreeNode) value).getUserObject();
if (o instanceof Profession) {
Profession profession = (Profession) o;
label.setIcon(profession.getIcon());
label.setText(profession.getDisplayName());
} else if(o instanceof NPC) {
label.setIcon(QuestEdit.getIcon("npc"));
label.setText(((NPC) o).getName());
} else {
label.setIcon(null);
label.setText("" + value);
}
return label;
}
}
I searched on stackoverflow and other sites for possible solutions, found the "setOpaque" method - no change at all.
I am sure that it has to do something with the custom renderer, since the highlight is working perfectly fine in another project of mine.
Edit:
Removing the JLabel and adding those lines worked for me:
this.selected = selected;
this.hasFocus = hasFocus;
if (selected) {
super.setBackground(getBackgroundSelectionColor());
setForeground(getTextSelectionColor());
} else {
super.setBackground(getBackgroundNonSelectionColor());
setForeground(getTextNonSelectionColor());
}
DefaultTreeCellRenderer extends JLabel, so try configuring this instead of label, then returning this.
After overriding the method as below :
getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus)
Create a JLabel instance with this code snippet:
JLabel label=(JLabel)super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
And then override the method public Color getBackgroundSelectionColor() to your own color.

Two Type of Nodes in a Single Check Box Node Tree

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.

How to mark JTable cell input as invalid?

If I take a JTable and specify a column's classtype on it's model as follows:
DefaultTableModel model = new DefaultTableModel(columnNames, 100) {
#Override
public Class<?> getColumnClass(int columnIndex) {
return Integer.class;
}};
Then whenever a user tries to enter a double value into the table, Swing automatically rejects the input and sets the cell's outline to red.
I want the same effect to occur when someone enters a 'negative or 0' input to the cell. I've got this:
#Override
public void setValueAt(Object val, int rowIndex, int columnIndex) {
if (val instanceof Number && ((Number) val).doubleValue() > 0) {
super.setValueAt(val, rowIndex, columnIndex);
}
}
}
This prevents the cell from accepting any non-positive values, but it doesn't set the color to red and leave the cell as editable.
I tried looking into how JTable's doing the rejection by default, but I can't seem to find it.
How can I make it reject the non-positive input the same way it rejects the non-Integer input?
The private static class JTable.GenericEditor uses introspection to catch exceptions raised by constructing specific Number subclasses with invalid String values. If you don't need such generic behavior, consider creating PositiveIntegerCellEditor as a subclass of DefaultCellEditor. Your stopCellEditing() method would be correspondingly simpler.
Addendum: Updated to use RIGHT alignment and common error code.
Addendum: See also Using an Editor to Validate User-Entered Text.
private static class PositiveIntegerCellEditor extends DefaultCellEditor {
private static final Border red = new LineBorder(Color.red);
private static final Border black = new LineBorder(Color.black);
private JTextField textField;
public PositiveIntegerCellEditor(JTextField textField) {
super(textField);
this.textField = textField;
this.textField.setHorizontalAlignment(JTextField.RIGHT);
}
#Override
public boolean stopCellEditing() {
try {
int v = Integer.valueOf(textField.getText());
if (v < 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
textField.setBorder(red);
return false;
}
return super.stopCellEditing();
}
#Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
textField.setBorder(black);
return super.getTableCellEditorComponent(
table, value, isSelected, row, column);
}
}
I figured it out. Override the DefaultCellEditor and return false / set the border to red if the number given is not positive.
Unfortunately, since JTable.GenericEditor is static w/ default scope, I'm unable to override the GenericEditor to provide this functionality and have to re-implement it w/ a few tweaks, unless someone has a better way of doing this, which I'd like to hear.
#SuppressWarnings("serial")
class PositiveNumericCellEditor extends DefaultCellEditor {
Class[] argTypes = new Class[]{String.class};
java.lang.reflect.Constructor constructor;
Object value;
public PositiveNumericCellEditor() {
super(new JTextField());
getComponent().setName("Table.editor");
((JTextField)getComponent()).setHorizontalAlignment(JTextField.RIGHT);
}
public boolean stopCellEditing() {
String s = (String)super.getCellEditorValue();
if ("".equals(s)) {
if (constructor.getDeclaringClass() == String.class) {
value = s;
}
super.stopCellEditing();
}
try {
value = constructor.newInstance(new Object[]{s});
if (value instanceof Number && ((Number) value).doubleValue() > 0)
{
return super.stopCellEditing();
} else {
throw new RuntimeException("Input must be a positive number.");
}
}
catch (Exception e) {
((JComponent)getComponent()).setBorder(new LineBorder(Color.red));
return false;
}
}
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected,
int row, int column) {
this.value = null;
((JComponent)getComponent()).setBorder(new LineBorder(Color.black));
try {
Class type = table.getColumnClass(column);
if (type == Object.class) {
type = String.class;
}
constructor = type.getConstructor(argTypes);
}
catch (Exception e) {
return null;
}
return super.getTableCellEditorComponent(table, value, isSelected, row, column);
}
public Object getCellEditorValue() {
return value;
}
}
This code is a small improvement of the accepted answer. If the
user does not enter any value, clicking on another cell should
allow him to select another cell. The accepted solution does not
allow this.
#Override
public boolean stopCellEditing() {
String text = field.getText();
if ("".equals(text)) {
return super.stopCellEditing();
}
try {
int v = Integer.valueOf(text);
if (v < 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
field.setBorder(redBorder);
return false;
}
return super.stopCellEditing();
}
This solution checks for empty text. In case of an empty text, we call the stopCellEditing() method.
So first I created an analogy to make this topic easier to be understood.
We have a pen(editor). This pen will need some ink(The component that the editor use, an example of a component is JTextField,JComboBox and so on) to write.
Then this is a special pen when we want to write something using the pen, we speak(typing behavior in the GUI) to tell it to write something(write in the model). Before writing it out, the program in this pen will evaluate whether the word is valid(which being set in stopCellEditing() method), then it writes the words out on paper(model).
Would like to explain #trashgod's answer since I have spent 4 hours on the DefaultCellEditor Section.
//first, we create a new class which inherit DefaultCellEditor
private static class PositiveIntegerCellEditor extends DefaultCellEditor {
//create 2 constant to be used when input is invalid and valid
private static final Border red = new LineBorder(Color.red);
private static final Border black = new LineBorder(Color.black);
private JTextField textField;
//construct a `PositiveIntegerCellEditor` object
//which use JTextField when this constructor is called
public PositiveIntegerCellEditor(JTextField textField) {
super(textField);
this.textField = textField;
this.textField.setHorizontalAlignment(JTextField.RIGHT);
}
//basically stopCellEditing() being called to stop the editing mode
//but here we override it so it will evaluate the input before
//stop the editing mode
#Override
public boolean stopCellEditing() {
try {
int v = Integer.valueOf(textField.getText());
if (v < 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
textField.setBorder(red);
return false;
}
//if no exception thrown,call the normal stopCellEditing()
return super.stopCellEditing();
}
//we override the getTableCellEditorComponent method so that
//at the back end when getTableCellEditorComponent method is
//called to render the input,
//set the color of the border of the JTextField back to black
#Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
textField.setBorder(black);
return super.getTableCellEditorComponent(
table, value, isSelected, row, column);
}
}
Lastly, use this line of code in your class that initialise JTable to set your DefaultCellEditor
table.setDefaultEditor(Object.class,new PositiveIntegerCellEditor(new JTextField()));
The Object.class means which type of column class you wish to apply the editor
(Which part of paper you want to use that pen. It can be Integer.class,Double.class and other class).
Then we pass new JTextField() in PositiveIntegerCellEditor() constructor(Decide which type of ink you wish to use).
If anything that I misunderstood please tell me. Hope this helps!

Categories