Is it possible to detect whether a node is expanded or collapsed from within SwingX's TreeTableModel/AbstractTreeTableModel (specifically the getValueAt(Object node, int index) method)? I need to display different values for a parent node, depending on whether it's expanded or collapsed. (I realize this violates the principle of separating model and view, sorry!)
I know you can check this from the JXTreeTable object, using the standard isExpanded() and isCollapsed() methods, but I need to know this from within the model.
Specifically, I have objects which have multiple versions, and I'd like to use JXTreeTable to support expanding/collapsing the versions. If the object is collapsed, I want the parent node to display a summary of the values across all versions. If it's expanded, I want the parent to display only the values for the current version, (the most important one), and a summary is no longer needed or desired.
Some pseudo-code to give you an idea of what I mean:
getValueAt(Object node, int index) {
if (node.hasChildren()) {
if (node.isExpanded()) { // this is the part I'm not sure how to implement
return node.getCurrentValue(); // eg "Current version: C"
}
else {
return node.getSummaryValue(); // eg "Current version: C; previous versions: A, B"
}
}
else {
return node.getValue(); // eg "Version: B"
}
}
EDIT
Thank you camickr, you're of course right that my question is invalid! (I feel a bit stupid now.)
Would you suggest using a custom TreeCellRenderer, such that it selects which value to display based on the expansion state of the node? That is, have the model provide an object that implements something like this:
public interface ExpansionStateDependentValue {
public Object getDisplayValue(boolean expanded);
}
and then have the TreeCellRenderer use this method (assuming the cell value implements the interface) to display the appropriate value based on the expanded flag in the getTreeCellRendererComponent() method?
but I need to know this from within the model.
A Model can be shared by many View component. That is it could be used by two different JTree Objects. So it is possible that a node code be expanded in one view of the tree but not in the other.
So the answer to your question is that the Model does not contain this information because it is a function of the view. So your approach of trying to do this in the model is not correct.
Related
Let's say I have a class Item. Items have object attributes and collection of other objects attributes:
public class Item
{
//Object attributes
String name;
int id;
Color color;
//Collection of object attributes
List<Parts> parts;
Map<int,Owner> ownersById;
}
I have a fairly simple web application that allows crud operations on these items. This is split up into separate operations:
a page where you can update the simple object attributes (name, id...).
a page where you can edit the collection of parts.
a page where you can edit the map of owners.
Because the server load was getting too high, I implemented a cache in the application which holds the "most recently used item objects" with their simple attributes and their collection attributes.
Whenever an edit is made to the name of an item, I want to do the following do things:
Persist the change to the item's name. This is done by converting the item object to xml (without any collection attributes) and calling a web service named "updateItemData".
Update the current user's cache by updating the relevant item's nme inside the cache. This way the cache stays relevant without having to load the item again after persisting it.
To do this I created the following method:
public void updateItem(Item itemWithoutCollectionData)
{
WebServiceInvoker.updateItemService(itemWithoutCollectionData)
Item cachedItemWithCollectionData = cache.getItemById(itemWithoutCollectionData.getId());
cachedItemWithCollectionData.setName(itemWithoutCollectionData.getName());
cachedItemWithCollectionData.setColor(itemWithoutCollectionData.getColor());
}
This method is very annoying because I have to copy the attributes one by one, because I cannot know beforehand which ones the user just updated. Bugs arised because the objects changed in one place but not in this piece of code. I can also not just do the following: cachedItem = itemWithoutCollectionData; because this would make me lose the collection information which is not present in the itemWithoutCollectionData variable.
Is there way to either:
Perhaps by reflection, to iterate over all the non-collection attributes in a class and thus write the code in a way that it does not matter if future fields are added or removed in the Item class
Find a way so that, if my Item class gains a new attribute, a warning is shown in the class that deals with the caching to signal "hey, you need to update me too!")?
an alternative which might seem a bit overkill: wrap all the non-collection attributes in a class, for example ItemSimpleData and use that object instead of separate attributes. However, this doesn't work well with inheritance. How would you implement this method in the following structure?
classes:
public class BaseItem
{
String name;
int id;
}
public class ColoredItem
{
Color color;
}
There many things that can be done to enhance what you currently have but I am going to point out just two things that may help you with your problem.
Firstly, I am assuming that public void updateItem is a simplified version from your production code. So; make sure this method is thread safe, since it is a common source or problems when it comes to caching.
Secondly, you mentioned that
Perhaps by reflection, to iterate over all the non-collection
attributes in a class and thus write the code in a way that it does
not matter if future fields are added or removed in the Item class.
If I understand the problem correctly; then, you can easily achieve this using BeanUtils.copyProperties() here is an example:
http://www.mkyong.com/java/how-to-use-reflection-to-copy-properties-from-pojo-to-other-java-beans/
I hope it helps.
Cheers,
I have implemented my own ComboBoxModel:
public class MyComboBoxModel extends AbstractListModel<MyType>
implements ComboBoxModel<MyType> {}
Now I obviously need to override public void setSelectedItem(Object item), but the documentation says the following:
The implementation of this method should notify all registered ListDataListeners that the contents have changed.
To do so, I guess I need to use the method AbstractListModel.fireContentsChanged(Object, int, int). The Problem with JComboBox is, that one can set the selected item without it having to be in the list, so when setSelectedItem(Object) is called, I cannot necessarily determine the index of the item in question, since it need not be in the underlying model.
I found an answer to another question (https://stackoverflow.com/a/7077192) which uses fireContentsChanged(item, -1, -1) in this case, but the person did not provide any details to that part of code. Now I am wondering, whether this was the correct way to deal with a changed selected item?
Should I ALWAYS use -1 as both indexes? Should I try and get the real index of an item, if it is actually in the model? Or should I do something entirely different?
I'll write down the whole problem first.
A ring is a collection of items that has a reference to a current item. An operation -- let's call it advance--moves the reference to the next item in the collection. When the reference reaches the last item, the next advance operation will move the reference back to the first item. A ring also has operations to get the current item, add an item, and remove an item. The details of where an item is added and which one is removed are up to you.
Design an ADT(Abstract Data Type) to represent a ring of objects. Specify each operation by stating its purpose, by describing its parameters, and by writing a pseudocode version of its header. Then write a Java interface for a ring's methods. Include javadoc-style comments in your code.
So is it saying the Ring is like a class with operation that can move items by using a reference variable like T = items? And Advance would change T to represent a different item each time it's being called? Something like in UML format....
ADT: Ring
+advance(): T // move T to next item in collection and if T reaches last item, move T back to the first item.
+getCurrItem(): T // return item that T reference.
+addItem(item T): void // add an item in for T, No return.
+removeItem(Item: T): boolean // remove item that T reference and return true | false if it succeed or not.
Am I on the right track or am I supposed to do something else?
That looks like a good start to me. Now you have to work on designing the ADT and how you suppose you will store items and reference the end to the beginning. This is a data abstractions problem, and you can approach the implementation in several ways, but it's up to you to design it in an efficient way.
I have made a JTree and filled it with objects fron an ArrayList.
When I display the contents of the JTree with my GUI, I dont want to see the memory address wherethe object is stored, but a customized String.
for example: I add this object to my tree:
DefaultMutableTreeNode tempnode = new DefaultMutableTreeNode(workspaces.get(i));
And what I see on my GUI is:
package.workspace#1df38f3
I want alternative text instead of
package.workspace#1df38f3
To be displayed.
How can I fix my code to support this?
JTree is going to call the toString function on the items you add and display that. If you can write a toString for your Workspace object then that will fix your problem. If you can't modify the Workspace object then you should create a wrapper object that has the toString you want.
Try to #Override the "toString()" method of your object that is in the ArrayList
class YourObject{
...
#Override
public String toString(){
return "your string formatted here";
}
...
}
Read about TreeCellRenderers and create your own one e.g. extend DefaultTreeCellRenderer. In the method
Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus)
Provide any desired logic
I'd recommend extending JTree and overriding convertValueToText(JTree javadoc). The default implementation is to call toString but you can override it to generate any text you want. No need to wrap all your array objects or override toString for display(I prefer to leave toString for debugging descriptions as opposed to for display text).
As any good book or tutorial on Java teaches you, Learn to override to java.lang.Object.toString()
Read the Java language API and it clearly states that all subclasses should override toString(). Doing so (in your case) makes these Objects ready to be passed (by reference value) to the code that sets the GUI text.
I have a custom DefaultMutableTreeNode class that is designed to support robust connections between many types of data attributes (for me those attributes could be strings, user-defined tags, or timestamps).
As I aggregate data, I'd like to give the user a live preview of the stored data we've seen so far. For efficiency reasons, I'd like to only keep one copy of a particular attribute, that may have many connections to other attributes.
Example: The user-defined tag "LOL" occurs at five different times (represented by TimeStamps). So my JTree (the class that is displaying this information) will have five parent nodes (one for each time that tag occured). Those parent nodes should ALL SHARE ONE INSTANCE of the DefaultMutableTreeNode defined for the "LOL" tag.
Unfortunately, using the add(MutableTreeNode newChild) REMOVES newChild from WHATEVER the current parent node is. That's really too bad, since I want ALL of the parent nodes to have THE SAME child node.
Here is a picture of DOING IT WRONG (Curtis is the author and he should appear FOR ALL THE SHOWS):
How can I accomplish this easily in Java?
Update
I've been looking at the code for DefaultMutableTreeNode.add()... and I'm surprised it works the way it does (comments are mine):
public void add(MutableTreeNode child)
{
if (! allowsChildren)
throw new IllegalStateException();
if (child == null)
throw new IllegalArgumentException();
if (isNodeAncestor(child))
throw new IllegalArgumentException("Cannot add ancestor node.");
// one of these two lines contains the magic that prevents a single "pointer" from being
// a child within MANY DefaultMutableTreeNode Vector<MutableTreeNode> children arrays...
children.add(child); // just adds a pointer to the child to the Vector array?
child.setParent(this); // this just sets the parent for this particular instance
}
If you want easily, you should probably give up on sharing the actual TreeNodes themselves. The whole model is built on the assumption that each node has only one parent. I'd focus instead on designing your custom TreeNode so that multiple nodes can all read their data from the same place, thereby keeping them synced.
I'm not sure it qualifies as easy, but you might look at Creating a Data Model by implementing TreeModel, which "does not require that nodes be represented by DefaultMutableTreeNode objects, or even that nodes implement the TreeNode interface." In addition to the tutorial example, there's a file system example cited here.
Unfortunately, I believe the answer is no. In order to do what you're talking about, you would need to have DefaultMutableTreeNode's internal userObject be a pointer to some String, so that all the corresponding DefaultMutableTreeNode's could point to and share the same String object.
However, you can't call DefaultMutableTreeNode.setUserObject() with any such String pointer, because Java does not have such a concept on the level that C or C++ does. Check out this outstanding blog article on the confusing misconceptions about pass-by-value and pass-by-reference in Java.
Update: Responding to your comment here in the answer space, so I can include a code example. Yes, it's true that Java works with pointers internally... and sometimes you have to clone an object reference to avoid unwanted changes to the original. However, to make a long story short (read the blog article above), this isn't one of those occasions.
public static void main(String[] args) {
// This HashMap is a simplification of your hypothetical collection of values,
// shared by all DefaultMutableTreeNode's
HashMap<String, String> masterObjectCollection = new HashMap<String, String>();
masterObjectCollection.put("testString", "The original string");
// Here's a simplification of some other method elsewhere making changes to
// an object in the master collection
modifyString(masterObjectCollection.get("testString"));
// You're still going to see the original String printed. When you called
// that method, a reference to you object was passed by value... the ultimate
// result being that the original object in you master collection does
// not get changed based on what happens in that other method.
System.out.println(masterObjectCollection.get("testString"));
}
private static void modifyString(String theString) {
theString += "... with its value modified";
}
You might want to check out the JIDE Swing extensions, of which some components are commercial while others are open source and free as in beer. You might find some kind of component that comes closer to accomplishing exactly what you want.