1

我是一个关于 GUI 编程的新手,也许我的问题有一个非常简单的解决方案。我正在尝试实现一个 Java Swing GUI,它充当树状数据结构的编辑器。GUI分为三个部分:

  1. 窗口左侧四分之一的树查看器显示树结构数据。

  2. 右上角的大区域显示包含文本字段、表格等的编辑器。树结构中的每种不同类型的对象都有自己的编辑器,当在树查看器中选择它时会显示该编辑器。

  3. 右下方区域显示了一个控制台查看器。它用于显示有关特定操作的消息。

我正在努力遵守模型与程序中树查看器/编辑器中的可视化的严格分离。因此,我创建了一个 DefaultTreeModel (MyTreeModel) 子类的实例,它存储对业务数据的引用,以及一个 JTree 子类的实例,它提供了树结构的可视化表示。

我正在尝试实现使用 Action 类修改数据的功能。任何动作(如 CreateNode、RenameNode、DeleteNode)都在它自己的动作类(AbstractAction 的子类)中实现。这些操作在树查看器的上下文菜单和应用程序“编辑”菜单中使用。但我也想在 GUI 的编辑器部分重用其中的一些,例如 RenameNode 操作。在这里,我目前被困住了。

树查看器显示一个图标以及树中每个节点的名称。除其他内容外,相应的编辑器还包含一个显示关联节点名称的 JTextField。

我知道我可以使用 setAction 方法将操作对象附加到 JMenu、JPopupMenu 甚至 JTextField 对象。我这样做了,现在我在程序的“编辑”菜单中有一个“重命名”菜单条目,在与代表树查看器的 JTree 关联的弹出菜单中,显示节点名称的 JTextField 也附加了此操作。

为了更改树节点的“名称”属性,我创建了类 RenameNode 作为 AbstractAction 的子类。如前所述,名称显示在树查看器中的每个节点上,并且该操作只是使该文本可编辑。执行此操作的代码如下所示(在 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());
    }
}

需要这些 if 语句才能使操作正常工作:

-- 弹出菜单(第一个 if 语句;这里打开弹出菜单时鼠标下的对象可编辑)

-- 应用程序的菜单(第二个 if 语句;这里当前选择的树节点 -- 如果有的话 -- 可编辑)。

嗯,这工作正常,原则上但实际上节点的重命名不是通过 RenameAction 类的 actionPerformed(ActionEvent ev) 方法中的代码完成的。节点名称的真正更改是在树的 MyTreeModel 类中的 valueForPathChanged() 方法中执行的,该方法被覆盖如下:

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);
    }

[...]

}

我完全不知道如何在这里正确应用动作的概念。更糟糕的是执行重命名操作更改 JTextField 对象中的文本时的情况。目前我不知道如何以干净的方式实现它。JTextField 应与树模型结构中的单个节点作为其模型关联,并且附加的操作应修改该模型,并且当此模型更改时,树查看器需要得到通知以更新树查看器中相应节点的名称。

我假设 MyNode 类(它一直是 DefaultMutableTreeNode 的子类)必须实现接口 Document 并且 RenameAction 类必须修改它,然后必须发出一个事件来通知显示改变节点。

底线:我必须承认我还没有完全理解如何正确实现要在 GUI 中的多个位置使用的操作,并且我不完全理解如何实现可以被多个 GUI 对象使用的模型(在我的例子中是一个 JTree 和一个 JTextField)。可能这一切都很简单......

提前感谢您的帮助!

那么给出的答案对于解释如何将动作与 JTrees 一起使用非常有帮助。但还有一点我想讨论。在我的 GUI 中,我有一个业务数据的树表示与数据的编辑器相结合(树位于窗口的左四分之一处,旁边是一个节点类型特定的编辑器)。所有节点都有可以更改的名称。并且,编辑器包含一个文本字段(使用 JTextField 实现),其中显示了节点的名称并且也可以进行编辑。我的不确定性如下: JTextField 允许为它分配一个动作对象和一个模型。实际上,该模型将是已在 JTree 中查看的节点对象。我认为应该有一种方法可以使用 JTree 中使用的相同模型对象,也可以作为编辑器中 JTextField 的模型,并在那里重用 Action 类。关于模型的重用,我认为我的模型类 MyTreeNode 也必须实现 Document 接口,对吗?当启动节点特定的编辑器时,我必须使用其 setDocument() 方法将 JTree 中当前选择的节点与 JTextField 对象相关联。最后,我的 RenameNodeAction 必须更改 JTextField 的节点名称。所以我的中心点是:让一个模型显示在多个视图中,并在要重命名节点的任何地方重用一个 RenameAction。这有意义吗?我的想法是如何实现的?关于模型的重用,我认为我的模型类 MyTreeNode 也必须实现 Document 接口,对吗?当启动节点特定的编辑器时,我必须使用其 setDocument() 方法将 JTree 中当前选择的节点与 JTextField 对象相关联。最后,我的 RenameNodeAction 必须更改 JTextField 的节点名称。所以我的中心点是:让一个模型显示在多个视图中,并在要重命名节点的任何地方重用一个 RenameAction。这有意义吗?我的想法是如何实现的?关于模型的重用,我认为我的模型类 MyTreeNode 也必须实现 Document 接口,对吗?当启动节点特定的编辑器时,我必须使用其 setDocument() 方法将 JTree 中当前选择的节点与 JTextField 对象相关联。最后,我的 RenameNodeAction 必须更改 JTextField 的节点名称。所以我的中心点是:让一个模型显示在多个视图中,并在要重命名节点的任何地方重用一个 RenameAction。这有意义吗?我的想法是如何实现的?d 必须使用其 setDocument() 方法将 JTree 中当前选择的节点与 JTextField 对象相关联。最后,我的 RenameNodeAction 必须更改 JTextField 的节点名称。所以我的中心点是:让一个模型显示在多个视图中,并在要重命名节点的任何地方重用一个 RenameAction。这有意义吗?我的想法是如何实现的?d 必须使用其 setDocument() 方法将 JTree 中当前选择的节点与 JTextField 对象相关联。最后,我的 RenameNodeAction 必须更改 JTextField 的节点名称。所以我的中心点是:让一个模型显示在多个视图中,并在要重命名节点的任何地方重用一个 RenameAction。这有意义吗?我的想法是如何实现的?

4

3 回答 3

3

超出 Stackoverflow 范围的应用程序设计完整指南。相反,从如何使用树TreeIconDemo中显示的示例开始。请注意它如何添加 a以更新附近的. 现在,将另一个添加到不同的视图中,看看如何更新新视图。您还可以从这个相关的答案中获得一些见解。TreeSelectionListenertreeJEditorPaneTreeSelectionListener

附录:从这个例子开始,你可以做如下的事情。更改选择会更新textField以显示所选节点的名称。编辑节点(通常是F2)或textField更改所选节点的名称。

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);
            }
        }
    }
});
于 2012-07-24T21:28:59.503 回答
1

不完全确定,但我认为你正在寻找这样的东西

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);
    }

}

然后当你初始化你的树时添加

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);

这将使您的树编辑与您的字段同步。

顺便说一句,我喜欢你很好地遵守 MVC。而且,你的设计听起来很复杂。您可能想重新考虑一些功能,使其更精简。

于 2012-07-24T23:17:30.843 回答
1

哇,好详细啊!谢谢!

JTable好的,我对's(插入/删除行)做了类似的事情。

本质上,您想要Action一个可以引用 a 的JTree. 然后Action可以将其提供给您的文件菜单、弹出菜单和分配给击键的事件。如果你超级高效地编写代码,可以共享相同的操作,但这不是必要的要求。

你想要做的是让视图和模型做他们设计的事情,你的行动只是启动过程的催化剂。

所以基本上你可以做类似的事情:

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);
        }

    }
}

为了让它更高级一点,您可以将 a 附加TreeSelectionListener到提供的树并enabled根据选择更改操作的状态。

因此,当没有选择任何内容时,您将禁用该操作,如果所选内容不可编辑,您将禁用选择等。

这意味着(正如您正确地尝试实现的那样)是实现此目标的代码是集中的和可重用的。JButtons您可以对文件菜单、工具栏按钮、弹出菜单和击键应用相同的操作(相同的实例或多个实例),并确保为每个执行相同的代码。

于 2012-07-24T21:19:34.903 回答