木難しいのはツリー (3) [木のモデル: TreeModel]
2003-03-09
JTreeについての第3回目です。前回は、TreeNodeとTreePathの関係や、TreeNodeの中身とその表示のコントロールなどについて紹介しました。今回は、木構造を格納するモデルについてお話しします。
「木のモデル」では、TreeModelインタフェースやそのデフォルトの実装であるDefaultTreeModelについて見ていきます。「作ってみましょ」では、XMLのDOMをベースにして、オリジナルのモデルを作るということを行ってみます。
JTreeは、他のSwingのコンポーネントと同様に、MVC(Model-View-Controller)と呼ばれる構造をとっています。木の構造のデータを格納しているモデルには、TreeModelというインタフェースを通してアクセスします。枝の構造自体は、ノード(node:
節)の親子関係として、前回お話ししたTreeNodeに格納されています。この点では、TreeNodeは、モデルの一部であると言えます。TreeModelは、ノードの中でルート(root:
根)のノードへの参照を保持し、ノードを調べるメソッドや、木構造の変化を通知するリスナーの追加、削除を行うメソッドなどが用意されています。
通常、JTreeを使用している分には、TreeModelにアクセスすることはそれほど無いかもしれません。しかしながら、ノードの変更を行う場合には、頻繁にメソッドを使用することがあります。以下のようなメソッドを知っていれば、TreeModelとDefaultTreeModelを使うには事足りるでしょう。
| TreeModelのメソッド |
| getRoot() |
ルート(root: 根)のノード(node: 節)を取得する。 |
| DefaultTreeModelのメソッド |
| getPathToRoot() |
引数のTreeNodeからルートまでのTreeNodeの配列を取得する。 |
| nodeChanged() |
TreeNodeの表示方法を変更した際に、それを反映させる。 |
| nodeStructureChanged() |
TreeNodeの構造を変更した際に、それを反映させる。 |
| reload() |
TreeNode全体を変更した際に、それを反映させる。 |
| setRoot() |
ルートのノードを指定する。 |
getChildrenなど、TreeModelのメソッドには、TreeNodeを引数にとるものがいくつかあります。このようなメソッドの多くは、同じ機能をTreeNodeやMutableTreeNode自体が持っています。TreeModelのメソッドは、JTreeがデータモデルにアクセスする際に使うためのものであると言えます。実際にプログラムを書く際には、このようなメソッドは、TreeNodeに対して直接呼び出しはしても、TreeModelに対して呼び出すことはほとんどないでしょう。そのような理由から、そういったメソッドは、上の表には載せていません。
TreeModelを実装したクラスのインスタンスを取得するには、いくつかの方法があります。
- JTreeから、
getModelメソッドで取り出す
- 直接、コンストラクタを呼び出してインスタンスを生成する
前者のようにJTreeから取り出す場合、デフォルトでは、TreeModelは、DefaultTreeModelのインスタンスになります。オリジナルのTreeModelを作成した場合には、後者を行い、JTreeのコンストラクタに渡すか、JTreeのsetModelメソッドを呼び出すことになります。
JTreeのデータモデルが、TreeModelとTreeNodeからなることにすると、TreeNodeに手を加えるだけで、独自のモデルを作ったと言えることになります。ただ、それではちょっと物足りないので、こだわってTreeModelの方も作ってみることにしましょう。
通常、JTreeを使っている分には、独自のTreeModelを作る必要はほとんど無いでしょう。JTreeは、多くのクラスが組み合わさって出来ていますから、TreeModelをいじるより、実際に処理を行う個々のクラスに手を加えた方が細かいことができます。データを木構造を持たせて格納するだけなら、前回お話ししたとおり、DefaultMutableTreeNodeがObject型への参照を格納できますから、デフォルトの実装で十分です。
TreeModelを実装したクラスを独自に作る必要がある場合を考えてみると、以下のように言えるのではないでしょうか。
- 別のツリー構造をしたオブジェクトでデータを保持しており、標準のDefaultTreeModelを使うと2重管理になる
- TreeNodeが共通に持つデータの管理をさせるという役割を積極的に担わせる
独自のTreeModelを作る題材として、XMLのDOM(Document
Object Model)をJTreeで表示するためのデータモデルを考えてみることにします。XMLのファイルをIEで開くと、ツリーで表示されます。これをJTreeで表示することを考えてみます。
DOMでは、ドキュメント(Document)をルートにして、各々のエレメント(Element)が親子関係を持った木構造をしています。これをJTreeで扱うために、わざわざバラバラにして、TreeNodeに格納し直すことはしたくありません。そのようにすると、DOMとTreeNodeが2重管理になり、DOMに変更が加えられた場合にそれをTreeNodeに反映させる仕組みが必要になってしまいます。
DOM自体をどこに格納しておくかということと、DOMとTreeNodeを一元的に管理することを考えて、以下のようなクラスを作成してみました。memo11ex1.javaを実行すると、これらのクラスを使って、entry.xml、bookmark.dtdを読み込み、JTreeでDOMを表示します。
XMLTreeModelでは、DOMの他に、ノードを格納するMap型のコレクションクラスを持ちます。このコレクションには、DOMのNode(org.w3c.dom.Node)のオブジェクトをキーにして、XMLTreeNodeのインスタンスを格納します。XMLTreeNodeは、userObjectとしてDOMのNodeを保持しています。TreeNode間の親子関係は直接は持っておらず、親または子のノードを取得する際には、userObjectのNodeから親または、子のNodeを取得し、それをキーにコレクションを探します。木構造はDOMで一元管理するわけです。
001:...
002:public class XMLTreeModel implements TreeModel {
003: private Document doc;
004: private Map elms;
005: ...
006: public Object getRoot() {
007: Object
ret = elms.get(
doc );
008: if(
ret == null ) {
009: ret
= new
XMLTreeNode( doc, elms );
010: elms.put(
doc, ret );
011: }
012: return
ret;
013: }
014: ...
015:}
|
| XMLTreeModel.java |
DOMの木構造を保持するXMLTreeModel |
docがDOMの最上位のエレメント、Documentへの参照を保持します。elmsがTreeNodeを格納するコレクションクラスへの参照です。XMLTreeModelでは、コレクションクラスとして、HashMapクラスを使用するように、コンストラクタの中で初期化しています。getRootメソッドでは、007で最初にコレクションからDocumentをキーに登録されたTreeNodeオブジェクトを探します。TreeNodeが見つからない場合、009でDocumentをuserObjectに格納するXMLTreeNodeを生成します。010では、生成したXMLTreeNodeをDocumentをキーにコレクションに登録しておきます。これにより、次回はコレクションを探せば、今回生成したXMLTreeNodeを取得できるわけです。(プログラム中の色のついた部分にマウスを合わせると注釈が表示されます。)
XMLTreeModelのコンストラクタは、引数としてxmlファイルへのパスをStirngで渡すものと、org.w3c.dom.Documentを渡すものの2種類を用意してあります。前者の場合、妥当なXML文書を要求するように作ってあるため、DTDが必要になります。XMLTreeModelは、実際に使おうと思ったら、いくつか改良の余地があります。マルチスレッドのことは一切考えていませんし、DOMに変更があった場合への対処もあります。いろいろ、試してみるといいのではないでしょうか。
MVC(Model-View-Controller)に基づいた構造になっているオブジェクトを最初に使ってみたときに、その便利さに驚いたものでした。他の人が作ったプログラムに、自分の作ったコードを追加するような場合に、いわゆるモジュール間の独立性がものをいいます。今回と見てきたように、JTreeでModelを構成するTreeModelとTreeNodeに手を加えるだけで、これだけのことができます。あまりの自由度の高さのため、戸惑う部分も多々あるかと思いますが、そこは少しずつ、とにかく使ってみることが理解への早道といえます。

|