JTree with DefaultTreeModel, how to refresh visually after loading? - java

I have been searching here and found answers to this problem but can't seem to make them work for me. Basicly I have a GUI that contains a JTree inside a JScrollPane:
private void initComponents() {
scroll = new javax.swing.JScrollPane();
nodo padre = new nodo();
modeloArbol modelo = new modeloArbol(padre);
arbol = new arbolNodos(modelo);
I have the following classes:
public class arbolNodos extends JTree implements Serializable{
public arbolNodos(TreeModel newModel) {
public class listenerModeloArbol implements TreeModelListener{
//Overriding treeNodes*(TreeModelEvent e)
public class modeloArbol extends DefaultTreeModel implements Serializable{
public modeloArbol(TreeNode root) {
super(root);
}
public class nodo extends DefaultMutableTreeNode implements Serializable{
I also have 2 buttons that store (botonGuardarArbol) and retrieve (botonCargarArbol) using the XMLEncoder the JTree (they really retrieve the root node of the tree) into a file with the corresponding actionPerformed listeners:
private void botonGuardarArbolActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
try {
XMLEncoder encoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream("C:\\borrar\\presupuesto")));
encoder.writeObject(arbol.getModel().getRoot());
encoder.close();
private void botonCargarArbolActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
try {
XMLDecoder decoder = new XMLDecoder( new BufferedInputStream(new FileInputStream("C:\\borrar\\presupuesto")));
nodo padre = (nodo) decoder.readObject();
modeloArbol modelo = new modeloArbol(padre);
decoder.close();
modelo.setRoot(padre);
arbolNodos arbolNuevo = null;
if( modelo != null ){
arbolNuevo = new arbolNodos(modelo);
arbol = null;
arbol = arbolNuevo;
((DefaultTreeModel)arbol.getModel()).reload();
}
else
arbolNuevo = new arbolNodos();
The thing is, when I retrieve the stored JTree, I know it is well retrieved because I try printing attributes inside all the nodes of the Tree and they are ok, but when I try putting it up in the Swing it doesn't refresh and the view bloks (ie. I can't manipulate anything in the JTree I had before doing loading). I have tried the numerous threads of refresh/update JTree but couldn't fix it. How can I do this?
Thank you

if( modelo != null )
{
arbolNuevo = new arbolNodos(modelo); //
arbol = null;
arbol = arbolNuevo;
((DefaultTreeModel)arbol.getModel()).reload();
}
else
arbolNuevo = new arbolNodos();
Don't keep creating new JTree objects. If you want to change the model then just use:
tree.setModel(...);
Also, why are you extend JTree, DefaultTreeModel, DefaultMutableTreeNode? If for some reason you do need to extend those classes then follow standard Java naming conventions for those classes. Classes start with an upper case character.

Related

Serialization of an object "that has fields reverenced by a SWT class"

I'm kind of stuck with a problem. I do understand the concept of serialization. Nevertheless I'm getting errors when I try to serialize/deserialize (deepCopy) an object:
I have a basic domain objects that hold information (two files):
public class DomainObject implements java.io.Serializable {
private String defaultDescription = "";
private List<Translation> translations;
public DomainObject() {
;
}
public void setTranslations(final List<Translation> newTranslations) {
this.translations = newTranslations;
}
public List<Translation> getTranslations() {
return this.translations;
}
public void setDefaultDescription(final String newDescription) {
this.defaultDescription = newDescription;
}
public String getDefaultDescription() {
return this.defaultDescription;
}
}
public class Translations implements java.io.Serializable {
private String description = "";
public Translation() {
;
}
public void setDescription(final String newDescription) {
this.description = newDescription;
}
public String getDescription() {
return this.description;
}
}
I also have a frame so the user can fill in all the necessary information for this domain object. Since I have multiple domain objects (this example only shows one) with different fields I have different frames for each domain object. Each of these frames includes a "MultiLanguageFrame" which gives the user the ability to add optional translations for this domain object's description.
public class MultiLanguageFrame extends org.eclipse.swt.widgets.Composite {
private List<Translation> translations = new ArrayList<Translation>();
public MultiLanguageFrame(final Composite parent, final int style) {
super(parent, style);
...
}
public List<Translation> getTranslations() {
return translations;
}
}
I deepCopy objects via this method:
...
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.flush();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception t) {
logger.error(deepCopy() error: " + t.getMessage()); //$NON-NLS-1$
throw new RuntimeException("deepCopy() error", t); //$NON-NLS-1$
}
So now to the error:
When i try to do something like this:
MultiLanguageFrame frame = new MultiLanguageFrame(parent, SWT.NONE);
DomainObject dom = new DomainObject();
dom.setDefaultDescription("Testobject");
dom.setTranslations(frame.getTranslations())
deepCopy(dom);
I receive an error telling me that MultiLanguageFrame is not Serializable. Why would Java try to serialize the frame when I only want that DomainObject?
I thought maybe it is because of the reference in frame. So when I add the Serializable-Interface to MultiLanguageFrame and markt the SWT-Components as transient it tells me that no valid constructor was found. I can't add a parameterless constructor because it would logically make no sense and also SWT-Components need a parent to exist.
I'm really stuck with this problem because I do not know how to work around this. Thanks for answers in advance!
I found the solution myself. I'll just post this so others can see it, it might help.
Thanks to #greg-449 who lead the way. I do have an inner class TranslationHelper which extends Translation in MultiLanguageFrame. The purpose of this is so I can save some flags (deleted, changed, new) for Translations without changing Translation itself. When I call frame.getTranslations() I cast the elements from TranslationsHelper to Translation. The instance of the object remains a TranslationHelper though.
Now it all makes sense that MultiLanguageFrame was involved in all of this.

Problems updating JTree changes with JButton

I'm on a project with several people, and my task is to compile a working JTree to show the structure of a set directory. As it is, the JTree shows the correct structure of files and folders when the class starts. However, I can't seem to get it to be able to update when files are added or subtracted (not with the JTree). In my happy ignorance (quite presistant that one) I added a JButton with;
treeModel.reload();
...and hoped that would do the trick. It reloaded the JTree sure enough, but didn't change the file structure, even though several files were added after the class loaded.
And it is thus I place my trust in this community to both point out the source of my issues, and other lackings in semantics and logic. I'm a willing learner.
public class FileTree extends JPanel {
private JTree tree;
private DefaultMutableTreeNode root;
private DefaultTreeModel treeModel;
public FileTree(JPanel jp) {
jp.setLayout(new BorderLayout());
final File directory = getDir();
createTree(directory);
tree = new JTree(treeModel);
tree.setRootVisible(true);
tree.setShowsRootHandles(true);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setCellRenderer(new FileTreeCellRenderer());
tree.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
if(e.getClickCount() == 2) {
Object n = selPath.getLastPathComponent();
String sel = n.toString();
File nodeFile = new File(sel);
if(nodeFile.isDirectory() && (!nodeFile.isHidden())) {
UserWindow.printToLog("doubble-click event on folder: " + nodeFile.getName());
//TODO:stuff happening here
}
if(nodeFile.isFile() && (!nodeFile.isHidden())) {
UserWindow.printToLog("doubble-click event on file: " + nodeFile.getName());
new FileTreeEventHandler(nodeFile);
}
}
}
});
JScrollPane sp = new JScrollPane(tree);
jp.add(sp);
JButton updateButton = new JButton("Update");
updateButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
treeModel.reload();
}
});
jp.add(updateButton, BorderLayout.SOUTH);
setVisible(true);
}
private void createTree(File directory) {
root = new DefaultMutableTreeNode();
treeModel = new DefaultTreeModel(root);
DefaultMutableTreeNode node = new DefaultMutableTreeNode(directory);
root.add(node);
populate(directory, node);
}
private void populate(File directory, DefaultMutableTreeNode node) {
String[] files = directory.list();
for(String file : files) {
File currentFile = new File(directory, file);
addLeaf(node, currentFile);
}
}
private void addLeaf(DefaultMutableTreeNode node, File currentFile) {
if(currentFile.isFile() && !currentFile.isHidden()) {
DefaultMutableTreeNode leafFile = new DefaultMutableTreeNode(currentFile);
node.add(leafFile);
}
if(currentFile.isDirectory() && !currentFile.isHidden()) {
DefaultMutableTreeNode folder = new DefaultMutableTreeNode(currentFile);
node.add(folder);
populate(currentFile, folder);
}
}
private File getDir() {
String path = new File(System.getProperty("user.dir"), "downloaded content").getAbsolutePath();
File dir = new File(path);
if(!dir.exists()) {
dir.mkdirs();
}
return dir;
}
}
treeModel.reload();
Instead of just invoking the above code, I would guess you need to invoke:
createTree(directory);
tree.setModel(treeModel);
to recreate the TreeModel to reflect the new directory structure.
DefaultTreeModel#reload basically states...
Invoke this method if you've modified the TreeNodes upon which this
model depends. The model will notify all of its listeners that the
model has changed below the given node.
This assumes that the model itself has changed.
You have at least two basic course of actions...
You could...
Create a new DefaultTreeModel or
Remove all the nodes from the existing DefaultTreeModel...
And then re-build the model.
If you create a new TreeModel, make sure you set it against the JTree.
This is a little heavy handed, but even if you choose to write the population algorithm so that it checked the existing content of the model, you're still going to have to walk the file structure.
Updating the model could allow you to preserve some of the state of the JTree (ie what nodes are expanded or not, for example)
If you're lucky enough to be using Java 7, you could also take advantage of the File Watcher API, which would allow you to, for example, set each directory node up as a watcher service to monitor it's own content and update itself (add/remove nodes) when changes occur.
Take a look at Watching a Directory for Changes

How do I notify a TreeModel that its underlying model has changed?

I am developing a simulation of the world, where the World is represented by:
public class World {
Turtle turtle;
// .. basic constructors, getters, setters
}
and a Turtle is represented by:
public class Turtle {
List<Turtle> turtles;
// .. basic constructors, getters, setters
}
where a Turtle stands on the backs of its turtles and the World rests on one, main Turtle (who may have turtles underneath it). I want to represent this, in Swing, as a JTree.
So I code up a JTree and a TreeModel:
JTree tree = new JTree(new WorldModel(world.getTurtle());
public class WorldModel implements TreeModel {
private Turtle = null;
public WorldModel(Turtle turtle) {
this.turtle = turtle;
}
#Override
int getChildCount(Object object) {
return ((Turtle) object).getNumTurtles();
}
#Override
Object getChild(Object parent, int index) {
return ((Turtle) object).getTurtle(index);
}
// etc., you get the overbearing point
}
This works splendidly up until the point at which I change the Turtle I initially passed in to the TreeModel's constructor:
world.getTurtle().removeAllTurtleChildren();
How do I update my JList to reflect this change? Do I have to create a new TreeModel and set it again?
(I would like to stay away from DefaultTreeModel, if possible.)
add api that supports modifying the structure to the model:
public class WorldModel implements TreeModel {
...
public void removeAllChildren(Turtle parent) {
if (parent.getChildCount() == 0) return;
Turtle[] children = parent.getChildren();
int[] locations = new int[children.length());
for(int loc = 0; loc < locations.length; loc++) {
locations[i] = i;
}
parent.removeAllChildren();
List<Turtle> path = new ArrayList<>();
while (parent != null) {
path.add(0, parent);
parent = parent.getParent();
}
TreeModelEvent event = new TreeModelEvent(this, path, locations, children);
// for each listener
listener.treeNodesRemoved(event);
}
}
As you see, it's quite some work - you might reconsider not using DefaultTreeModel :-)

JTree update nodes without collapsing

I have a Java SE 7 application that needs to have the JTree nodes updated. From the tutorial given by Oracle using this thread, there's no given hint on how I could update the label (displayed text of the node on the Tree) on code. Currently I am using DefaultTreeModel as the model of my JTree and DefaultMutableTreeNode as the nodes of the said Tree.
To further detail about the application I am working on, I am developing a chat facility having the contact(s) displayed with their availability status (whether Online, Offline, etc.) per account.
The question is, how can I update the displayed text of a particular node without (at most) removing it from it's parent and adding it on it's designated index. Like a DefaultMutableTreeNode.setText("<new label>")?
UPDATE : January 20, 2013
Redefined the question for clarifications.
Perhaps if you use 'nodeChanged()' instead of 'reload()' you will get the effect you desire.
There are a bunch of methods on the DefaultTreeModel class that cause various parts of the tree to be changed and redrawn. There are also other methods on DefaultTreeModel that only cause redrawing to take place.
You mentioned 'reload(node)' and commented that it causes the tree to collapse when you call it. 'reload' causes the entire sub-tree to be completely redrawn starting at that node. (But if that node isn't visible, it changes nothing.) That is called a 'structure change'.
'insertNodeInto()' and 'removeNodeFromParent()' modify the tree structure by adding or removing the node and then redrawing.
I think 'nodeChanged()' is the one you need since it just notifies the model that something changed in the node that will cause it to display differently. Perhaps the displayable text is now different than it was. Perhaps you changed the user object in the node. That's when you call 'nodeChanged()' on a node.
You should try 'nodeChanged()' in place of the 'reload()' call in your own code that was collapsing and in the example program vels4j provided. This might take care of the problem.
Note that there are also two other families of methods on the DefaultTreeModel that are used in other cases:
These methods work with the tree nodes and use the tree path to define where the change took place. They do not change the data structures underlying the tree but notify the model that something changed so it can notify the listeners that actually redraw things or otherwise respond to changes.
nodesWereInserted()
nodesWereRemovde()
nodesChanged()
nodeStructureChanged()
There are also a set of fire...() methods that are used internally to the DefaultTreeModel and any sub-classes you may create. They merely notify any listeners that something changed. Notice that they are protected.
May this simple and executable program help you to resolve your issue.
public class JTreeDemo extends JPanel
implements Runnable {
private JTree tree;
private DefaultTreeModel treeModel ;
private Random rnd = new Random();
private List<User> userList;
public JTreeDemo() {
super( );
//Create the nodes.
DefaultMutableTreeNode top =
new DefaultMutableTreeNode("Users");
treeModel = new DefaultTreeModel(top);
createNodes(top);
//Create a tree that allows one selection at a time.
tree = new JTree(treeModel);
tree.getSelectionModel().setSelectionMode
(TreeSelectionModel.SINGLE_TREE_SELECTION);
//Create the scroll pane and add the tree to it.
JScrollPane treeView = new JScrollPane(tree);
//Add the split pane to this panel.
add(treeView);
}
public String getRandomStatus() {
int nextInt = rnd.nextInt(100);
if( nextInt%2==0) {
return "Online";
} else {
return "Offline";
}
}
#Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
int nextInt = rnd.nextInt(10);
User user = userList.get(nextInt);
user.setStatus(getRandomStatus());
treeModel.nodeChanged(user);
} catch (InterruptedException ex) {
// handle it if necessary
}
}
}
private class User extends DefaultMutableTreeNode {
public String userName;
public String status;
public User(String name) {
userName = name;
}
public void setStatus(String status) {
this.status = status;
}
public String getStatus() {
return status;
}
#Override
public String toString() {
String color = status.equals("Online") ? "Green" : "Red";
return "<html><b color='"+color+"'>"+
userName +"-"+status +"</b></html>";
}
}
private void createNodes(DefaultMutableTreeNode top) {
userList = new ArrayList() ;
for(int i=0;i<10;i++) {
User u1 = new User("User " + (i+1));
u1.setStatus("Online");
top.add(u1);
userList.add(u1);
}
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("TreeDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Add content to the window.
JTreeDemo jTreeDemo = new JTreeDemo();
frame.add(jTreeDemo);
frame.pack();
frame.setVisible(true);
// update status randomly
Thread thread = new Thread(jTreeDemo);
thread.start();
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
I've added a Thread to update Status randomly, hope you can modify base on your need.
Output :
Edit:
1. Based on suggestion I've removed reload(node) and added tree model reload.
It's easy if nodes contains objects which are unique in the tree and have implemented method equals and hashCode (for example you show strings or object with unique ID from database). First of all you iterate over all expanded nodes and save objects from the nodes in a set. Then you perform update of the model. After update you iterate over all nodes and if they are in the set you expand the node in the tree.
If nodes are not unique - you need to save in the set the complete tree path (for example as list) and check it after update to expand the nodes.
If objects has neither equals nor hashCode (both these methods must be implemented) - this variant cannot be used.
Just for the record (I voted for Lee Meador), DefaultTreeModel#nodeChanged(javax.swing.tree.TreeNode) is the way to go:
public class TestFrame extends JFrame {
public TestFrame() {
//create gui with simple jtree (and DefaultTreeModel)
JButton changeBtn = new JButton();
final JTree jTree = new JTree();
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
changeBtn.setText("update selected node");
getContentPane().add(changeBtn, java.awt.BorderLayout.PAGE_END);
DefaultMutableTreeNode treeNode1 = new DefaultMutableTreeNode("root");
DefaultMutableTreeNode treeNode2 = new DefaultMutableTreeNode("blue");
treeNode1.add(treeNode2);
treeNode2 = new DefaultMutableTreeNode("violet");
DefaultMutableTreeNode treeNode3 = new DefaultMutableTreeNode("red");
treeNode2.add(treeNode3);
treeNode3 = new DefaultMutableTreeNode("yellow");
treeNode2.add(treeNode3);
treeNode1.add(treeNode2);
jTree.setModel(new DefaultTreeModel(treeNode1));
getContentPane().add(jTree, BorderLayout.CENTER);
pack();
//add listener to button, to change selected node on button click
changeBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
DefaultMutableTreeNode dmt = (DefaultMutableTreeNode)jTree.getSelectionPath().getLastPathComponent();
//update content/representation of selected node
dmt.setUserObject("My update: " + new Date());
//nodeChanged
((DefaultTreeModel) jTree.getModel()).nodeChanged(dmt);
}
});
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new TestFrame().setVisible(true);
}
});
}
}

Java Serializable Issue

I'm trying to serialize my model inside my program. The model is named "ImageModel" and it implements Serializable. This model also contains another custom object named "Perspective" which is also implementing Serializable. I have a static utility class that serializes my model and reads it. This code has been tested in the main method with an "ImageModel" and everything is working perfectly.
But when I try to use it in the actual program I'm running into an issue. The "ImageModel" class is declared in my system class, named "MainWindow" which extends JFrame and is the link between most of the different classes. For some reason, I can't serialize the model doing something like MainWindow.getModel(). The compiler argues that my "EventFactory" is not serializable. This class is also declared in "MainWindow", but I'm not even understanding why Java wants to serialize it, I'm under the impression that java is not just trying to serialize the model, but also the GUI.
Here are segments of code :
My model:
public class ImageModel extends Observable implements Cloneable, Serializable {
private String path;
private ArrayList<Observer> observers;
private ArrayList<Perspective> perspectives;
private int numPerspectives;
private Perspective selectedPerspective;
...
}
The perspective class:
public class Perspective implements Serializable {
private ImageModel image;
private int degreeOfRotation;
private Point topLeftPoint;
private int zoomPercentage;
private int height;
private int width;
...
}
The actual GUI that declares the model and other elements:
public class MainWindow extends JFrame {
private final int GRID_ROWS = 0;
private final int GRID_COLUMNS = 2;
private final int NUM_PERSPECTIVE = 3;
private JPanel mainPane;
private ArrayList<View> perspectiveList;
private ImageModel imageModel;
private EventFactory eventFactory;
private JMenu menu;
private JToolBar toolBar;
...
}
The main method:
MainWindow mw = new MainWindow();
/*
* Does NOT work:
* ImageModel imageModel= mw.getImageModel();
* Utility.serializeModel(imageModel); //Crashes
*
* Works:
*
* ImageModel imageModel= new ImageModel();
* Utility.serializeModel(imageModel);
*
*/
Here are my two utility functions in case you need them :
public static void serializeModel(ImageModel imageModel)
{
String filename = "TEST.ser";
FileOutputStream fos = null;
ObjectOutputStream out = null;
try
{
fos = new FileOutputStream(filename);
out = new ObjectOutputStream(fos);
out.writeObject(imageModel);
out.close();
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
public static ImageModel restoreModel(String filename)
{
ImageModel imageModel = null;
FileInputStream fis = null;
ObjectInputStream in = null;
try
{
fis = new FileInputStream(filename);
in = new ObjectInputStream(fis);
imageModel = (ImageModel)in.readObject();
in.close();
}
catch(IOException ex)
{
ex.printStackTrace();
}
catch(ClassNotFoundException ex)
{
ex.printStackTrace();
}
return imageModel;
}
Here's the STACK_TRACE of the error I'm receiving when working on the actual use case:
http://pastie.org/3008549
So yeah, like I'm saying, it's like if Java was trying to serialize other stuff around the model.
I'm guessing EventFactory is somehow making it's way into ImageModel's fields. Maybe indirectly linked from an Observer. Perhaps you should clear that list before attempting to serialise or set that field as transient.

Categories