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.
Related
Javadoc for Object.toString() says:
Returns a string representation of the object. In general, the
toString method returns a string that "textually represents" this
object. The result should be a concise but informative representation
that is easy for a person to read. It is recommended that all
subclasses override this method. The toString method for class Object
returns a string consisting of the name of the class of which the
object is an instance, the at-sign character '#', and the unsigned
hexadecimal representation of the hash code of the object. In other
words, this method returns a string equal to the value of:
getClass().getName() + '#' + Integer.toHexString(hashCode())
Therefore, I am questioning myself if I'm abusing this when I am overriding toString() of a class Foo just so that a ComboBox control (doesn't matter it's JavaFX or Swing control) can render a list of Foo correctly without me doing anything explicit in the ComboBox.
class Foo {
private String name;
public final String getName() { return name; }
public final void setName(String n) { name = n; }
#Override
public String toString() {
return name;
}
}
List<Foo> foos = getFoos(); // Get list of Foos from somewhere
ComboBox comboBox = ....; // Some ComboBox control
comboBox.addItems(foos); // Add the list to the ComboBox
The default implementation of toString() that most IDE generates are JSON representation of the class. I am suspecting this allows easy serialization/deserialization of class objects. Although I do not need to serialize or deserialize through this method, but I am definitely breaking this possibility by overriding like this.
Edit
I've read the comments of many people, as well as the answer given by VGR. I can see that the general consensus is that there is no hard and fast rule on this. I see that it is generally agreed that toString() is better left for debugging purposes.
The biggest reason why I am doing this "hack" is because I have ComboBox in quite a number of different places. Of course the indisputably correct and clean way is to use a "renderer" to do this job, but it is causing a significant amount of code duplication.
I will mull over this for a few days before I decide which is the best way to do it. Thanks a lot for the bunch of comments and answer.
For Swing, you definitely need to override the toString method of some class. That’s how Swing model objects work. (Do not use cell renderers to convert a value to String; that will break accessibility, keyboard navigation, and built-in sorting of JTables.)
Usually you’d want to separate your display logic from your data, such as by creating a wrapper class, but since you are just returning the raw value of a property (as opposed to formatting it or concatenating it with other information), what you’re doing is fine.
JavaFX is another matter: Controls use cell factory callback objects to determine what text appears in a cell, so a data class probably should not override toString with a display value in a JavaFX application.
I’m not familiar with IDEs’ auto-generated toString methods, but converting an object to JSON in a toString method is a horrible idea in my opinion. Swing model objects should have a toString method that returns a human-readable form of the object; most other classes should return a one-line string with a summary of the object’s state (which does not have to be every property in the object), like getClass().getName() + "[" + getName() + "]".
In summary, you are not abusing toString. For Swing, you’re doing the right thing. For JavaFX, it’s better to return a string useful for debugging.
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.
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 am making a JTable that uses an arrayList for data population. I have two sets of data in the arrayList. one for one type of table and another for another type of table.
What I am hoping to do is overload the getValueAt method on the abstractTableModel so that it takes in an argument to which set of data I want.
Is this possible or should I think about this a different way?
Nobody stops you from overloading the method. It's just that the JTable code won't call your new method. You will have to overwrite the regular public Object getValueAt(int rowIndex, int columnIndex) and call the other method from inside it, based on your business logic.
you can do so, however the problem is that whoever in Swing components is calling this method currently would not call the oveloaded one :)
It would not be too much usefull.
From my point of view, you have basically 2 options, as you need to present the specific data in 2 different table types:
either I'd go for 2 different table models and keep all the data separated
or the other approach might be to have some specific flag (new field) on model, that would indicate the table you use it in. This one could be set by setter or directly in constructor if you know which type you'd go for.
However the 1.st one would be a recommended way from my point of view.
There's nothing wrong with the accepted answer, but also consider a second TableModel that shares a reference to the given List with the first. A single JTable could display either model by simply invoking setModel().
In this exmaple, EnvDataModel obtains a its data via System.getenv(). A PropDataModel might obtain a its data via System.getProperties(). Both share access to System.
TableModel first = new EnvDataModel();
TableModel second = new PropDataModel();
JTable table = new JTable(fisrt);
...
table.setModel(second);
Ok, so I am working on a homework assignment, and I am using SWING to make a GUI for a Java project, and I am running into troubles with JList.
I have a customer object that I have made and set attributes to, and I want to add the object into a TreeMap. I want to hook up the Treemap so that any and all objects that are in the map will populate (the name attribute anyway) inside of the JList.
I have done a lot of looking around, and am seeing a lot about coding these things from scratch but little dealing with Swing implementation. I put my customer object into my map and then I would like for my JList to reflect the map's contents, but I don't know how to hook it up.
customers.put(c.getName(), c);
this.customerList.(What can I do here? add Customer object?? I can't find what I need);
Thanks for your help!!!
You need to create a custom list model that returns objects to put in each row of a JList. TreeMap can't be accessed with an index, so you'll need something else. So the general idea is this: (from JList javadoc):
ListModel bigData = new AbstractListModel() {
ArrayList customers;
public int getSize() { return customers.size() }
public Object getElementAt(int index) { return customers.get(index); }
};
JList bigDataList = new JList(bigData);
this way when you update your collection, just call revalidate() or repaint() on the JList and it will update its contents too.
so I am working on a homework
assignment
So what exactly is the assignment about? You've given us your attempted solution, but since we don't know the actual requirement we can't tell if you are on the right track or not.
Are you forced to use a TreeMap to store the objects? Because this is not a good collection to use for your ListModel since you can't access the objects directly.
Or is the assignment simply about displaying data from an object in a JList? If so then you can use the DefaultListModel. All you need to do is override the toString() method of you custom object to have the "name attribute" show in the list.