1

私は GUI プログラミングに関してまったくの初心者であり、おそらく私の問題には非常に単純な解決策があります。データ構造のようなツリーのエディターとして機能する Java Swing GUI を実装しようとしています。GUI は次の 3 つの部分に分かれています。

  1. ウィンドウの左 4 分の 1 にあるツリー ビューアには、ツリー構造のデータが表示されます。

  2. 右上の大きな領域には、テキスト フィールド、テーブルなどを含むエディタが表示されます。ツリー構造内のさまざまな種類のオブジェクトには、ツリー ビューアで選択したときに表示される独自のエディタがあります。

  3. 右下の領域には、コンソール ビューアーが表示されます。特定のアクションに関するメッセージを表示するために使用されます。

私は、プログラムのツリー ビューアー/エディターでモデルを視覚化から厳密に分離することに懸命に取り組んでいます。したがって、ビジネス データへの参照を格納する DefaultTreeModel (MyTreeModel) のサブクラスのインスタンスと、ツリー構造の視覚的表現を提供する JTree のサブクラスのインスタンスを作成しました。

Action クラスを使用してデータを変更する機能を実装しようとしています。すべてのアクション (CreateNode、RenameNode、DeleteNode など) は、独自のアクション クラス (AbstractAction のサブクラス) に実装されます。アクションは、ツリー ビューアのコンテキスト メニューとアプリケーションの [編集] メニューで使用されます。しかし、RenameNode アクションなど、GUI のエディター部分で再利用したいものもあります。そして、ここで私は現在立ち往生しています。

ツリー ビューアには、ツリー内のすべてのノードの名前とともにアイコンが表示されます。また、それぞれのエディタには、関連するノードの名前を示す 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 ステートメント。ここでは、ポップアップ メニューを開いたときにマウスの下にあったオブジェクトを編集可能にします)

-- アプリケーションのメニュー (2 番目の if ステートメント。ここでは、現在選択されているツリー ノード (存在する場合) が編集可能になります)。

これは原理的には問題なく動作しますが、実際にはノードの名前変更は RenameAction クラスの actionPerformed(ActionEvent ev) メソッドのコードでは行われません。ノードの名前の実際の変更は、次のようにオーバーライドされるメソッド valueForPathChanged() のツリーの MyTreeModel クラスで実行されます。

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)。おそらくすべてが非常に単純です...

助けてくれてありがとう!

与えられた回答は、アクションを JTree と一緒に使用する方法を説明するのに非常に役立ちました。しかし、もう 1 つ議論したい点があります。私の GUI には、ビジネス データのツリー表示と、データ用のエディター (ウィンドウの左側の 4 分の 1 にあるツリーと、その横にあるノード タイプ固有のエディター) が組み合わされています。すべてのノードには変更可能な名前があります。また、エディタには、ノードの名前が表示され、編集可能なテキスト フィールド (JTextField で実装) が含まれています。ここでの私の不確実性は次のとおりです。JTextField を使用すると、アクション オブジェクトとモデルをそれに割り当てることができます。実際には、モデルは JTree ですでに表示されているノード オブジェクトになります。JTree で使用されているのと同じモデル オブジェクトをエディターの JTextField のモデルとして使用し、そこで Action クラスを再利用する方法が必要だと思います。モデルの再利用に関しては、私のモデル クラス MyTreeNode も Document インターフェイスを実装する必要があると思いますよね?また、ノード固有のエディタを起動するときは、setDocument() メソッドを使用して、JTree で現在選択されているノードを JTextField オブジェクトに関連付ける必要があります。最後に、RenameNodeAction で JTextField のノードの名前を変更する必要があります。したがって、私の中心的なポイントは、1 つのモデルを複数のビューに表示し、ノードの名前を変更するあらゆる場所で 1 つの RenameAction を再利用することです。これは理にかなっており、これを実現する方法は私の考えですか? モデルの再利用に関しては、私のモデル クラス MyTreeNode も Document インターフェイスを実装する必要があると思いますよね?また、ノード固有のエディタを起動するときは、setDocument() メソッドを使用して、JTree で現在選択されているノードを JTextField オブジェクトに関連付ける必要があります。最後に、RenameNodeAction で JTextField のノードの名前を変更する必要があります。したがって、私の中心的なポイントは、1 つのモデルを複数のビューに表示し、ノードの名前を変更するあらゆる場所で 1 つの RenameAction を再利用することです。これは理にかなっており、これを実現する方法は私の考えですか? モデルの再利用に関しては、私のモデル クラス MyTreeNode も Document インターフェイスを実装する必要があると思いますよね?また、ノード固有のエディタを起動するときは、setDocument() メソッドを使用して、JTree で現在選択されているノードを JTextField オブジェクトに関連付ける必要があります。最後に、RenameNodeAction で JTextField のノードの名前を変更する必要があります。したがって、私の中心的なポイントは、1 つのモデルを複数のビューに表示し、ノードの名前を変更するあらゆる場所で 1 つの RenameAction を再利用することです。これは理にかなっており、これを実現する方法は私の考えですか? setDocument() メソッドを使用して、JTree で現在選択されているノードを JTextField オブジェクトに関連付ける必要があります。最後に、RenameNodeAction で JTextField のノードの名前を変更する必要があります。したがって、私の中心的なポイントは、1 つのモデルを複数のビューに表示し、ノードの名前を変更するあらゆる場所で 1 つの RenameAction を再利用することです。これは理にかなっており、これを実現する方法は私の考えですか? setDocument() メソッドを使用して、JTree で現在選択されているノードを JTextField オブジェクトに関連付ける必要があります。最後に、RenameNodeAction で JTextField のノードの名前を変更する必要があります。したがって、私の中心的なポイントは、1 つのモデルを複数のビューに表示し、ノードの名前を変更するあらゆる場所で 1 つの RenameAction を再利用することです。これは理にかなっており、これを実現する方法は私の考えですか?

4

3 に答える 3

3

Stackoverflow の範囲を超えたアプリケーション設計の完全なガイドです。代わりに、How to Use Trees にTreeIconDemo示されている例から始めてください。近くの を更新するために に を追加する方法に注目してください。次に、別のビューを別のビューに追加して、新しいビューも更新する方法を確認します。この関連する回答から洞察を得ることもできます。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

うわー、それは多くの詳細です!ありがとう!

さて、私はJTable's(行の挿入/削除)と同様のことをします。

基本的にAction、への参照をとることができるが必要です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);
        }

    }
}

もう少し高度にするためTreeSelectionListenerに、提供されたツリーにをアタッチenabledし、選択に基づいてアクションの状態を変更することができます。

したがって、何も選択されていない場合はアクションを無効にし、選択したものが編集可能でない場合は選択を無効にします。

これが意味することは(あなたが正しく達成しようとしているように)、これを達成するためのコードは一元化されて再利用可能であるということです。同じアクション(同じインスタンスまたは複数のインスタンス)をファイルメニュー、ツールバーボタン、JButtonsポップアップメニュー、およびキーストロークに適用して、それぞれに対して同じコードが実行されていることを確認できます。

于 2012-07-24T21:19:34.903 に答える
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 に答える