How to update tree node size with custom model? - java

I am using custom model in my JTree. When the label of some node changes, I am passing TreeModelEvent to the listeners in the following way:
protected void fireNodeChanged(TreePath path) {
TreeModelEvent evt = new TreeModelEvent(this, path);
for(TreeModelListener listener : listeners) {
listener.treeNodesChanged(evt);
}
}
where path is the path to changed node, including itself.
This causes the title change, but the size of it does not:
Swing truncates the title and adds ellipsis (pointed with red arrow).
How to fix this? Note that I have custom model and can't call nodeChanged, I should emulate it.

Without a snippet there are several guesses to make, but this situation seems familiar to me.
Try adding a renderer to your JTree that performs an updateUI() on the rendering component before returning it:
tree.setCellRenderer(new DefaultTreeRenderer() {
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
Component c = super.getTreeCellRendererComponent(tree, value,
selected, expanded, leaf, row, hasFocus);
// You can do more changes here
((JComponent) c).updateUI();
return c;
}});
About the updating, if "emulating" means implementing it and does what's supposed to do, that's correct. As long as it implements TreeModel and the methods do what's supposed, JTree should do the rest.
The truncation might be related to the fact that trees and tables do not lay their "components" out but use renderers instead to paint them in faked containers.
And, by the way, you can have a look at EventListenerList for a proper collection of event listeners.

Have you implemented addTreeModelListener(TreeModelListener l) in your custom TreeModel implementation? You'll want to call treeNodesChanged(TreeModelEvent e) on any registered listeners when your nodes are modified. Take a look at the source code for DefaultTreeModel for an example (the notification propagates up through the node's parents). Or, just have your custom model extend DefaultTreeModel and save yourself some time re-implementing all the listener stuff.

Related

SwingX JXTable: use ColorHighlighter to color rows based on a "row object"

I'm using JXTable and I know how to do this based on DefaultRenderers for JTable, but I want to know how to do it in a way that's JXTable-friendly based on HighlighterPipeline.
I have a list of objects displayed in a table, and each row represents one object. I would like to color the rows displaying objects of a certain type a different color.
It looks like I should be using ColorHighlighter. But I can't find examples for this, other than the simple highlighters like "color every other row" or some such thing.
I need the row number since there's no such thing as a "row object" in the JTable/TableModel paradigm, but if I can do that, I can easily test a predicate and return true/false to tell the highlighter to kick in or not.
Can someone help me figure out the right direction to get this to work?
never mind, I figured it out. It was just hard to figure out the way to use ComponentAdapter propertly.
JXTable table = ...
final List<Item> itemList = ...
final HighlightPredicate myPredicate = new HighlightPredicate() {
#Override
public boolean isHighlighted(
Component renderer,
ComponentAdapter adapter) {
Item item = itemList.get(adapter.row);
return testItem(item);
}
public boolean testItem(Item item) { ... }
}
ColorHighlighter highlighter = new ColorHighlighter(
myPredicate,
Color.RED, // background color
null); // no change in foreground color
table.addHighlighter(highlighter);

Is there a way to detect if a drop is about to take place on a JTree?

I have a JTree where users can drop elements from other components. When the users hovers over nodes in the tree (during "drop mode") the most near lying node is highlighted. This is achieved in the implementation of TransferHandler.
#Override
public boolean canImport(TransferSupport support) {
//Highlight the most near lying node in the tree as the user drags the
//mouse over nodes in the tree.
support.setShowDropLocation(true);
Each time a new node is selected (also during "drop mode"), this will kick of a TreeSelectionEvent. This in turn will invoke a listener i have created which will query a database for details related to that node.
Now, I am looking for a way to somehow filter out events that is generated from node selections during "drop mode". It is an attempt to limit database calls. Does anyone have any ideas about how I can achieve this?
All input will be highly appreciated!
There is a very indirect method to detect this case. You may register a PropertyChangeListener on the property "dropLocation" with the tree component. This will be called whenever the drop location changes and thus you can set a field dropOn there which you then can read in the TreeSelectionListener.
tree.addPropertyChangeListener("dropLocation", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent pce) {
dropOn = pce.getNewValue() != null;
}
});
tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent tse) {
System.out.println(tse + " dropOn=" + dropOn);
}
});
Note that this does fire a wrong false value for the first time it enters the tree, but all subsequent events then show dropOn = true.

Update ImageIcon of JTable cell

I am creating my first JTable that requires me to create a custom AbstractTableModel, TableCellEditor, and DefaultTableCellRenderer. Given that I have not needed to create these before, I've made some significant progress in getting my table to behave as desired.
However, I am getting overwhelmed with all the different methods I am overriding, and am spinning my wheels trying to figure out how to modify the ImageIcon of a particular cell. The cell must contain a JLabel, as it needs both an ImageIcon as well as a text string. I can already set the initial ImageIcon (although I am probably doing it incorrectly), but I can't set an updated ImageIcon. Nothing fails, but no change is made.
In a general sense, what is the best way to get and set an icon to a JLabel cell of a JTable, assuming all of these models, editors, and renderers have already been instantiated?
My model has already been defined to return JLabel.class for these cells, if you're wondering, and I also do a fireTableCellUpdated(row, col) once the change has supposedly been made. If I do a System.out.println(getIcon()) before and after the update, I can even see the source has changed.
Here is some of the code (updated with URL/ImageIcon fix in place):
class MonitorTable extends JTable {
MonitorTableModel model = new MonitorTableModel(rows, columnNames);
setModel(model);
...
public void setIconAt(ImageIcon icon, int row, int col) {
model.setIconAt(icon, row, col);
} // End setIconAt(ImageIcon, int, int)
...
class MonitorTableModel extends AbstractTableModel {
...
public void setIconAt(ImageIcon icon, int row, int col) {
StatusTableCellRenderer cell =
(StatusTableCellRenderer)getColumnModel().getColumn(col).getCellRenderer().
getTableCellRendererComponent(myTableObject, null, false, false, row, col);
System.out.println(cell.getIcon()); // Shows initial icon source
cell.setIcon(icon);
fireTableCellUpdated(row, col); // Should update the table
System.out.println(cell.getIcon()); // Shows new icon source
System.out.println("Cell updated");
} // End setIconAt(ImageIcon, int, int)
} // End class MonitorTableModel
public class StatusTableCellRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int col) {
setIcon(imgGray);
setText((String)value);
return this;
} // End getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)
} // End class StatusTableCellRenderer
} // End class MonitorTable
My model has already been defined to return JLabel.class for these cells,
But according to the code in your renderer you expect a String value in these cells:
setText((String)value);
I don't like your setIcon() method. I would not pass in the URL. I would pass in the Icon. Maybe you have a problem that the icon has not been read into memory at the time the cell is rendered.
what is the best way to get and set an icon to a JLabel cell of a JTable,
You should not store a JLable in the TableModel. It is expensive to store Swing components in the model, that is why Swing components use renderers. Instead you store a custom Object like "LabelInfo" which contains two properties, the text and the Icon. Then your custom renderer will extend the default renderer and invoke super.getTableCellRendererComponent(). You can then access your object and rest the text/icon properties of the renderer. You should not be creating objects in the renderer.
Now when you want to change something in the model you can do:
LabelInfo info = (LabelInfo)table.getValueAt(row, column);
info.setIcon(...);
table.setValueAt(info, row, column);
Thats all you need. There is not custom code to repaint the cell or anything because that is already built intothe setValueAt(...) method. of your table model.
Edit: a simple example for using a custom Object in the TableModel.
1) to add the object to the model you do something like:
LabelInfo info = new LabelInfo("some Text", yourIcon);
table.setValueAt(info, row, column);
2) the code for your custom renderer would be:
class LabelInfoRenderer extends DefaultTableCellRenderer
{
#Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
LableInfo info = (LabelInfo)value;
setIcon( info.getIcon() );
return this;
}
}
Call the fireTableDataChanged from your model.
Try call the repaint method from JLabel too.
The better way to do it, is implement a CellRenderer, with returns a JPanel and make the draw method in the paintComponent. You can load a BufferedImage instead of a ImageIcon and use it to draw in the JPanel.
Try change you Renderer to:
public class StatusTableCellRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int col) {
JLabel comp = new JLabel(new ImageIcon(imgGray));
comp.setText((String)value);
return comp;
} // End getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)
}
You are doing some mess, this is not the right way to work with the TableModel, you should return the URL for the image in the getValueAt(int row, int col), after it, register the CellRenderer to correspond to cells with URL.class. The renderer is automatically called from the JTable, you don't need to extends JTable either, you have only to implement the Renderer and the Model.
The setIconAt should only call the setValueAt and put your URL at the column, the renderer take care of the rest.
I fixed this by changing setIcon(imgGray) to if (getIcon() == null) setIcon(imgGray);.
The issue is my getTableCellRendererComponent method was setting the icon to imgGray every time. Apparently my setIconAt method, which calls getTableCellRendererComponent, was being overridden, even though the "new" icon value was processed after the "old" value was (re)set.
I ended up removing all my setIcon methods and moved the relevant logic into my StatusTableCellRenderer class. That way I pass the value of the cell and let the renderer do the icon setting based on that value. It makes more sense this way, and works beautifully. I have confirmed that initial setting and all subsequent updates are performing as expected.
The logic of setting the icon is pretty simple -- set the predefined icon based on certain predefined threshold values.
double val;
if (getIcon() == null) setIcon(imgGray); // Initialize
if ((value == null) || (value == "")) {
val = 0;
} else {
val = Double.parseDouble(value.toString());
} // End if
if (val <= THRESHOLD1) {
setIcon(icon1);
} else if (val <= THRESHOLD2) {
setIcon(icon2);
...
} // End if
setText(value.toString());
I was very concerned about suggestions to make brand new objects to use, when the default JLabel was exactly what I needed. It was both unnecessary and a potential performance hit to the JTable. Thank you all for your insight and assistance. This was driving me batty!

manipulate jtree node via references seems not working (swing)

I have the following problem (this is related to my post blink a tree node):
I have a custom cell renderer.
In some part of my code I create a new DefaultMutableTreeNode and store it in a list
public static List<DefaultMutableTreeNode> nodes = new ArrayList<DefaultMutableTreeNode>()
//in some time
DefaultMutableTreeNode aNode = new DefaultMutableTreeNode("SomeValue");
nodes.add(node);
In my cell renderer I do:
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
DefaultMutableTreeNode n = (DefaultMutableTreeNode)value;
if(nodes.contains(n)){
//set background to red
}
}
At this point nodes has a node but the code never goes in the if branch.
Why? I can not understand since I already stored it in the arraylist. Do I get a different reference?
Also I created a swing timer:
Timer t = new Timer(400, new ActionListener(){
public void actionPerformed(ActionEvent evt) {
if(nodes.size == 0)
return;
TreePath p = new TreePath(nodes.get(0));
Rectangle r = tree.getPathBounds(p);
tree.repaint(r);
}
});
But I get a NPE in tree.getPathBounds.
I can not understand why. Can't I manipulate DefaultMutableNodes I stored in my list this way? What am I doing wrong in my thinking?
Note: If I simply call repaint(); in the timer and in the cell renderer I loop over the nodes to see if it displays the sametext with the node I have stored, what I want I get the blinking, works
Thanks
Actually TreePath is a list of objects... path from tree root to the node. If you create a path from the single node the path exists in the tree only if the node is root of the tree.
I woul recommend to use TreeSelectionEvent public TreePath[] getPaths() method. The method provides actual paths.
I don't think DefaultMutableTreeNode defines an equals method, so it might not find the match in your List of nodes. Try storing and searching for the user object or extends DefaultMutableTreeNode and define equals.

ListCellRenderer not Firing Events on Child Components

The following ListCellRenderer does not receive click events on the nested ComboBoxes. Do I need to enable something?
class FilterCellRenderer implements ListCellRenderer {
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Filter filter = (Filter)value;
JPanel filterPanel = new JPanel();
FlowLayout layout = new FlowLayout();
layout.setAlignment(FlowLayout.LEFT);
filterPanel.setLayout(layout);
filterPanel.add(new JLabel(filter.getLabel()));
final List<Object> options = filter.getOptions();
if (options.size() > 1) {
JComboBox optionCombo = new JComboBox(new AbstractComboBoxModel() {
public int getSize() {
return options.size();
}
public Object getElementAt(int index) {
return options.get(index);
}
});
optionCombo.setSelectedItem(filter.getValue());
filterPanel.add(optionCombo);
}
if (isSelected) {
filterPanel.setBackground(list.getSelectionBackground());
filterPanel.setForeground(list.getSelectionForeground());
}
return filterPanel;
}
}
Renderer components in swing work like "rubber stamps" -they are just used to render/paint a value and are not added to the parent container in the usual way (just think how a single component could be added in multiple places!).
It sounds like you may want an editor rather than a renderer (an editor is a fully-fledged component, added in one place at any given time). Failing that you will have to install the MouseListener on the JList instead.
Since I didn't need to select rows, I ended up just dynamically adding and elements to a JPanel with a custom layout. Allowed for full component behaviour without having to hack a table.
It's a little bit tricky this. I believe you need to replace the JList with a single column JTable. Then set a table cell editor as well as renderer. IIRC, there might be a problem losing the first click (which gets used to select that cell edited).
Also it's a very good idea to reuse the components between each call to getCellRendererComponent. The components are used as a stamp and then discarded. Performance will suck massively if they are recreated each time.

Categories

Resources