1

I'm a total newbee regarding GUI programming and maybe my problem has a quite simple solution. I'm trying to implement a Java Swing GUI that serves as an editor for a tree like data structure. The GUI is divided into three parts:

  1. A tree viewer in the left quarter of the window displays the tree structured data.

  2. The large upper right area displays editors containing text fields, tables and the like. Each different kind of object in the tree structure has its own editor which is shown when it is selected in the tree viewer.

  3. The lower right area shows a console viewer. It is used to display messages about specific actions.

I'm trying hard to obey a strict separation of the model from its visualization in the tree viewers/editors in my program. Therefore I created an instance of a sub-class of DefaultTreeModel (MyTreeModel) which stores references to the business data and an instance of a sub-class of JTree that provides the visual representation of the tree structure.

I'm attempting to implement functionality that modifies the data using Action classes. Any action (like CreateNode, RenameNode, DeleteNode) is implemented in it's own action class (sub-class of AbstractAction). The actions are used in the tree viewer's context menu and in the applications "Edit" menu. But I also want to reuse some of them in the editor parts of the GUI, e.g. the RenameNode action. And here I'm currently stuck.

The tree viewer displays an icon along with the name for every node in the tree. And the respective editor contains, among other stuff, also a JTextField that shows the associated node's name.

I know that I can attach action objects to JMenu, JPopupMenu and even to JTextField objects using the setAction method. I did so and now I have a "Rename" menue entry in the program's "Edit" menue, in the popup menue associated with the JTree that represents the tree viewer and the JTextField showing the node name also has this action attached to it.

To change the "Name" attribute of a tree node, I created the class RenameNode as a sub-class of AbstractAction. As already mentioned the name is displayed at each node in the tree viewer and the action simply makes this text editable. The code doing this looks as follows (in the class RenameNode):

public void actionPerformed(ActionEvent ev) {
    // "mouseOverPath" is the Treepath were the mouse was placed on
    // when the popup menu was opened
    if (tree.existsMouseOverPath()) {
        tree.startEditingAtPath(tree.mouseOverPath);
    } else if (tree.getSelectionCount() != 0) {
        tree.startEditingAtPath(tree.getSelectionPath());
    }
}

These if statements are needed to make the action work properly from:

-- the popup menu (first if statement; here the object which was under the mouse when the popup menu is opened is made editable)

-- the application's menu (second if statement; here the tree node that is currently selected -- if any -- is made editable).

Well, this works fine, in principle but in fact the renaming of the node is not done through the code in the RenameAction class's actionPerformed(ActionEvent ev) method. The real change of the node's name is executed in the tree's MyTreeModel class in the method valueForPathChanged() which is overridden as follows:

public class MyTreeModel extends DefaultTreeModel {


[...]

    @Override
    public void valueForPathChanged(TreePath path, Object newValue) {
    final MyTreeNode aNode = (MyTreeNode)path.getLastPathComponent();
    if (newValue instanceof String) {
        ((MyNode) aNode.getUserObject()).setName((String) newValue);
    } else {
        aNode.setUserObject(newValue);
    }
        nodeChanged(aNode);
    }

[...]

}

I have absolutely no clue how the concept of actions could properly be applied here. Even worse is the situation with the rename operation when it is performed changing the text in the JTextField object. At the moment I don't know how to implement that in a clean way. The JTextField should get associated with a single node from the tree model structure as its model and the attached action should modify that model and when this model is changed the tree viewer would need to get notified to update the respective node's name in the tree viewer.

I assume that the MyNode class (which is alrady a sub-class of DefaultMutableTreeNode) would have to implement the interface Document and the RenameAction class would have to modify it and then an event would have to be issued to notify the tree viewer that displays the changed node.

Bottom line: I must admit that I did not yet completely understand how to properly implement an action that is to be used in multiple places in a GUI and I don't completely understand how to implement a model that can be used by multiple GUI objects (in my case a JTree and a JTextField). Possibly all that is quite simple ...

Thanks in advance for any help!

Well the answers given were quite helpful in explaining how actions can be used together with JTrees. But there is one more point I'd like to discuss. In my GUI I have a tree representation of my business data combined with editors for the data (the tree located in the left quarter of the window and aside of it a node type specific editor). All the nodes have names that can be changed. And, the editors contain a text field (implemented with a JTextField) in which the node's name is displayed and which can be edited too. My uncertainty here is as follows: A JTextField allows to assign an action object as well as a model to it. Actually the model would be a node object already viewed in the JTree. I think there should be a way to use the same model object used in the JTree also as a model for the JTextField in the editor and also reuse the Action class there. Regarding the reuse of the model I think my model class MyTreeNode will have to implement the Document interface too, correct? And when bringing up the node specific editor I'd have to associate the node currently selected in the JTree with the JTextField object using its setDocument() method. Finally my RenameNodeAction would have to perform the change of the JTextField's node's name. So my central point is: making one model being displayd in multiple views and the reuse of just one RenameAction everywhere where a node is to be renamed. Does this make sense and is my idea how this must be accomplished feasible?

apatwork
  • 71
  • 1
  • 8
  • For better guidance, please edit your question to include an [sscce](http://sscce.org/) that exhibits the specific problem you encounter. – trashgod Jul 24 '12 at 21:30
  • How are you storing your JTextFields? – Eva Jul 24 '12 at 21:44
  • Sorry for not providing an [sscce](http://sscce.org).I feared it would have been to much code... – apatwork Jul 25 '12 at 10:27
  • Thanks, Eva, for pointing to the JTextFields. I'll discuss the in a comment to one of the answers in more detail. – apatwork Jul 25 '12 at 10:29

3 Answers3

3

A complete guide to application design in beyond the scope of Stackoverflow. Instead, start with the example TreeIconDemo shown in How to Use Trees. Notice how it adds a TreeSelectionListener to the tree in order to update a nearby JEditorPane. Now, add another TreeSelectionListener to a different view to see how you could update the new view, too. You might also get some insight from this related answer.

Addendum: Starting from this example, you can do something like the following. Changing the selection updates the textField to show the selected node's name. Editing either the node (typically F2) or the textField changes the selected node's name.

private JTextField textField = new JTextField(10);
...
final DefaultTreeModel treeModel = new DefaultTreeModel(root);
tree = new JTree(treeModel);
tree.addTreeSelectionListener(new TreeSelectionListener() {

    @Override
    public void valueChanged(TreeSelectionEvent e) {
        TreePath path = e.getNewLeadSelectionPath();
        if (path != null) {
            DefaultMutableTreeNode node =
                (DefaultMutableTreeNode) path.getLastPathComponent();
            if (node.isLeaf()) {
                Resource user = (Resource) node.getUserObject();
                textField.setText(user.toString());
            } else {
                textField.setText("");
            }
        }
    }
});
textField.addActionListener(new AbstractAction("edit") {

    @Override
    public void actionPerformed(ActionEvent e) {
        TreePath path = tree.getSelectionPath();
        if (path != null) {
            DefaultMutableTreeNode node =
                (DefaultMutableTreeNode) path.getLastPathComponent();
            if (node.isLeaf()) {
                String s = textField.getText();
                Resource user = (Resource) node.getUserObject();
                user.setName(s);
                treeModel.reload(node);
            }
        }
    }
});
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • I don't think this person is asking for a complete guide to application design. The application seems designed quite well to me, adhering to MVC principles. The question, at least to my understanding, is how to associate a Java `Action` with a `JTree`/`TreeModel`. Perhaps the question title should be edited to reflect the real question. – Eva Jul 24 '12 at 21:33
  • @Eva: Good point; without something more specific, it's hard to offer a better example. user1549299: see also [`Action`](http://docs.oracle.com/javase/tutorial/uiswing/misc/action.html). – trashgod Jul 24 '12 at 21:37
  • I have added some more information to the initial article on my problems with using one model for multiple views which hopefully makes my areas of insufficient understanding a bit more clear ... – apatwork Jul 25 '12 at 16:17
1

Wow, that's a lot of detail! Thanks!

Okay, I do something similar with JTable's (insert/delete rows).

Essentially you want an Action that can take a reference to a JTree. This Action can then be supplied to your File menus, Popup Menus and event assigned to key strokes. If you write the code super efficiently, the same action can be shared, but this is not an essential requirement.

What you want to do, is to let the view and model do what they are designed to do, you action is simply the catalyst for getting the process started.

So basically you could do something like:

public class RenameNodeAction extends AbstractAction {

    private JTree tree;
    public RenameNodeAction(JTree tree) {
        this.tree = tree;

        // Initialise action as you require...
    }

    // Access to the tree, provide mostly so you can extend the action
    public JTree getTree() {
        return tree;
    }

    public void actionPerformed(ActionEvent evt) {

        JTree tree = getTree();
        TreePath path = tree.getSelectionPath();
        if (path != null && tree.isPathEditable(path)) {
            tree.startEditingAtPath(path);
        }

    }
}

To make it a little more advanced, you could attach a TreeSelectionListener to the supplied tree and change the enabled state of the action based on the selection.

So when nothing is selected, you would disable the action, if the selected wasn't editable, you would disable the selection, etc.

What this means (as you have correctly trying to achieve) is that the code to achieve this is centralised and reusable. You can apply the same action (the same instance or multiple instances) to File menus, tool bar buttons, JButtons, popup menus and key strokes and be assured that the same code is executing for each.

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
1

Not exactly sure, but I think you're looking for something like this

public class RenameNode extends AbstractAction
        implements TreeSelectionListener, CellEditorListener { 

    // Replace with whatever you're storing your text fields with
    private FieldStorage fields;

    private JTree tree;

    public RenameNode(FieldStorage fields, JTree tree) {
        this.fields = fields;
        this.tree = tree;
    }

    public void actionPerformed(ActionEvent ev) {
        // "mouseOverPath" is the Treepath were the mouse was placed on
        // when the popup menu was opened
        if (tree.existsMouseOverPath()) {
            tree.startEditingAtPath(tree.mouseOverPath);
        } else if (tree.getSelectionCount() != 0) {
            tree.startEditingAtPath(tree.getSelectionPath());
        }
    }

    public void valueChanged(TreeSelectionEvent e) {
        tree.startEditingAtPath(tree.getSelectionPath());
        setFieldText();
    }

    public void editingCanceled(ChangeEvent e) {
        // empty
    }

    public void editingStopped(ChangeEvent e) {
        setFieldText();
    }

    private void setFieldText() {
        MyTreeNode node = (MyTreeNode) tree.getSelectionPath()
                .getLastPathComponent();
        String text = node.getUserObject().toString();
        fields.getFieldFor(node).setText(text);
    }

}

Then when you're initializing your tree add

RenameNode renameNodeAction = new RenameNode(fields, tree);
tree.addTreeSelectionListener(renameNodeAction);

// editor is your TreeCellEditor. Can be the DefaultTreeCellEditor
// if you haven't already made your own
editor.addCellEditorListener(renameNodeAction);
tree.setCellEditor(editor);
tree.setEditable(true);

This will synchronize your tree edits with your fields.

BTW, I like that you're adhering to MVC really well. But also, your design sounds really complicated. You might want to rethink some of the features to something more streamlined.

Eva
  • 4,397
  • 5
  • 43
  • 65
  • Thanks a lot for the nice piece of code that now -- in contrast to mine -- really contains the implementation of the rename operation as a whole, i.e. storing the changed name in the associated model object. – apatwork Jul 25 '12 at 11:41