JTree nodes won't get visually selected - java

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.

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".

Changing JTree Node Icon dynamically

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.

TableCellRenderer sets color to many cells and not just one

I have a JTable, that I want I want to be able to change the color of a single cell that is clicked on.
Here is a simplified version of my code:
public class TableFrame extends JFrame {
public TableFrame() {
JTable table = new JTable(8, 8);
table.setGridColor(Color.BLACK);
table.setDefaultRenderer(CustomCellRenderer.class, new CustomCellRenderer());
getContentPane().add(table);
}
public class CustomCellRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JLabel l = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (hasFocus) {
l.setBackground(Color.red);
l.setText("Hello");
}
return l;
}
}
}
When I click a certain cell, I expect it to change color to red and add "Hello" to it. It changes the text, but for some weird reason, it changes the color of all the cells after it? And when I click an uncolored cell, it does the same, but not always in an organised way if that makes sense? Like, it won't color all the cells after it, but maybe some that are just above and leave others blank..
It's really weird and makes no sense whatsoever. What is happening??
Having dug around the DefaultTableCellRenderer class a bit, when you call setBackground on the JLabel component, which is backing the DefaultTableCellRenderer, it is storing the value you use...
public void setBackground(Color c) {
super.setBackground(c);
unselectedBackground = c;
}
When the cell is painted again, it's this value (unselectedBackground) which is been used to repaint the cell in "default" mode...
if (isSelected) {
//...
} else {
Color background = unselectedBackground != null
? unselectedBackground
: table.getBackground();
if (background == null || background instanceof javax.swing.plaf.UIResource) {
Color alternateColor = DefaultLookup.getColor(this, ui, "Table.alternateRowColor");
if (alternateColor != null && row % 2 != 0) {
background = alternateColor;
}
}
super.setForeground(unselectedForeground != null
? unselectedForeground
: table.getForeground());
super.setBackground(background);
}
This means, the moment you use setBackground and pass it Color.RED, the DefaultTableCellRenderer assumes that this becomes the default color for ALL unselected cells.
The only choice you have is to reset the background color manually, for example...
public class CustomCellRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JLabel l = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (hasFocus) {
l.setBackground(Color.red);
l.setText("Hello");
} else if (!isSelected) {
l.setBackground(table.getBackground());
}
return l;
}
}
Also, you should really be using something more like...
table.setDefaultRenderer(Object.class, new CustomCellRenderer());
to register the cell renderer, as it's the Class type returned by TableModel#getColumnClass which determines which cell renderer is used ;)
Since the OP only wants help with the rendered and not with the data... here goes (assuming there is a function called hasBeenClicked(row,column) method available to determine whether the cell has been visited yet.
public class CustomCellRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JLabel l = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (hasBeenClicked(row,column)) {
l.setBackground(Color.red);
l.setText("Hello");
} else {
// reset the label to white background
l.setBackground(Color.white);
l.setText("Hello");
}
return l;
}
}
}
Also note that the registration of the renderer should be
table.setDefaultRenderer(Object.class, new CustomCellRenderer());
Since we want all columns to have this renderer (renderers are registered against the class of the column in the model).
I tested with the below as the hasBeenClicked method.
public boolean hasBeenClicked(int row, int column){
return (row%2==0 && column%2==0);
}
Just implement your own tracking of whether a cell has been clicked or not and you should be good to go. Remember that you should not use the renderer to track the clicks, use some kind of listener instead.

JTable render Boolean as JToggleButton

Hi I am able to render the Boolean column as a JToggleButton but if I keep the button pressed, I am seeing the checkbox instead of the button.
TableColumnModel tcm = smartAlertsTable.getColumnModel();
TableColumn tc = tcm.getColumn( Index of the boolean column);
tc.setCellRenderer(new ActiveAlertRenderer());
where ActiveAlertRenderer is
public class ActiveAlertRenderer extends JToggleButton implements
TableCellRenderer
{
public ActiveAlertRenderer()
{
super();
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
boolean isActive = ((Boolean) value).booleanValue();
if (isActive)
{
this.setText("Armed");
this.setSelected(false);
value = Boolean.TRUE;
}
else
{
this.setText("Triggered");
this.setSelected(true);
value = Boolean.FALSE;
}
return this;
}
}
How do I prevent the checkbox from appearing?
JTable uses a checkbox as a default renderer and editor for boolean columns. You provided your own renderer but the editor remains default. This is the checkbox that you see when you edit the cell. You'd have to provide a custom editor (implementation of TableCellEditor) in a similar fashion you did the renderer. You can set it up using JTable.setDefaultEditor() or TableColumn.setCellRenderer.
See Concepts: Editors and Renderers in How to Use Tables tutorial for more details.
Try like this:
public class ActiveAlertRenderer extends DefaultTableRenderer {
private JToggleButton toggleButton = new JToggleButton();
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (column==INDEX_OF_TOGGLE_BUTTON_COLUMN) {
boolean active = (Boolean) argValue;
if (active) {
toggleButton.setText("Armed");
} else {
toggleButton.setText("Triggered");
}
toggleButton.setSelected(active);
return toggleButton;
}
else {
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
}
}

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.

Categories