« TableViewer向けLabelProviderの真実 | トップページ | TableViewerの更新はスレッドに注意せよ »

2004.05.01

TableViewerのホントの利用方法

TableViewerの使い方について,数回に分けて紹介してきた。

  ・TableViewerを使った表コンポーネントの利用
  ・TableViewerのヘッダ列の作成
  ・TableViewerのデータ供給・表示の仕組み
  ・TableViewer向けContentProviderの作成
  ・TableViewer向けLabelProviderの作成
  ・TableViewer向けLabelProviderの真実

実はこれだけでは十分ではなく,「各クラスの役割とかはわかったけど,それで実際にはどう使えばいいの?」というのが正直なところだろう。「ドメインモデルの情報を如何にしてTableViewerに表示させるか」といった話をもう少し付け加えないと,TableViewerの使い方を本当に理解したとは言えない。ドメインモデルの情報をどのようにTableViewerに伝えるか,つまりポイントは「ドメインモデルの情報の変更を如何にTableViewerに反映するか」である。

最初にTableViewerクラスのJavadocを覗いてみる。TableViewerには,行を追加するためのaddメソッドや,行を任意のインデックスに挿入するためのinsertメソッド,行を削除するためのremoveメソッドが備わっていることがわかる。これらのメソッドのJavadocで書かれている記載の中で注目すべき点は2つ。

  「引数で渡したオブジェクトはTableViewerにしか反映されない
  「各メソッドはContentProviderから呼び出されるべきである

まず前者についてだが,TableViewerオブジェクトのaddメソッドに行のオブジェクトを渡すと,とりあえずTableViewerに行が追加されて表示は行われる。しかし,これはあくまで表示だけの話であり,その後refreshメソッドを呼び出してしまうとContentProviderからドメインモデルが再読み込みされるので,追加された行は消えてしまう。

そして後者は,ドメインモデルをTableViewerに対応させる橋渡し的な役割を持つContentProviderからのみしか,TableViewerに対してaddメソッドなどの各種メソッドを呼び出してはいけないということを表している。言い換えると,TableViewerへの表示データの供給は,ContentProviderからのみしか行ってはならないということである。

つまり,表示情報の追加や削除,挿入が「TableViewer → ドメインモデル」という流れではないことが判明する。ではどうするのが正解かというと,「ドメインモデル → ContentProvider → TableViewer」というようにデータが流れるようにしなければならない。ドメインモデルに対して行った追加や削除,挿入がContentProviderに通知され,通知を受け取ったContentProviderはそれをTableViewerに反映させる,という流れにするのである。

例として,ある部署に所属している社員のリストを表示したいとしよう。まず社員のクラスについては「TableViewerのデータ供給・表示の仕組み」で取り上げたEmployeeクラスをそのまま使用する。そして,部署を表すクラスとして,Divisionクラスを新たに作成する。Divisionクラスは,所属する社員のEmployeeオブジェクトを複数内部のコレクションに保持できるようにする。このDivisionクラスがドメインモデルとなり,DivisionオブジェクトがTableViewerオブジェクトにsetInputメソッドでセットされる。さらに,社員が追加された際にその通知を受けることができるように,DivisionListenerインタフェースを準備する。

  public interface DivisionListener {
    public void employeeAdded(Employee employee);
  }

  public class Division {
    private List employeeList = new ArrayList();
    private ListenerList listenerList = new ListenerList();
    public List getEmployeeList() {
      return employeeList;
    }
    public void addEmployee(Employee employee) {
      employeeList.add(employee);
      Object[] listeners = listenerList.getListeners();
      for (int i = 0; i < listeners.length; i++) {
        DivisionListener listener = (DivisionListener)listeners[i];
        listener.employeeAdded(employee);
      }

    }
    public void addDivisionListener(DivisionListener listener) {
      listenerList.add(listener);
    }
    public void removeDivisionListener(DivisionListener listener) {
      listenerList.remove(listener);
    }
  }

DivisionListenerインタフェースでは,社員が追加されたときに呼び出されるemployeeAddedメソッドを定義し,引数として追加された社員を表すEmployeeオブジェクトが渡されるようにしておく。

Divisionクラスでは,社員を表すEmployeeオブジェクトを格納しておくためのemployeeListコレクションを準備し,ContentProviderが社員の一覧を取得できるようにgetEmployeeListメソッドでemployeeListコレクションを返すようにしている。DivisionListenerオブジェクトの格納には,Eclipseプラットフォームがリスナーの管理用に提供してくれているListenerListクラスを使用する。ここでは,addDivisionListenerメソッドおよびremoveDivisionListenerメソッドを準備して,リスナーの登録と削除ができるようにしている。

そしてメインの社員を追加するためのaddEmployeeメソッドの登場である。ここでは,まず引数で受け取ったEmployeeオブジェクトをemployeeListコレクションに追加している。社員のコレクションへの追加後,listenerListオブジェクトから登録されているリスナーオブジェクトを順次取り出し,それぞれのemployeeAddedメソッドを呼び出すことで観測者に社員が追加されたことを通知する。これにより,ドメインモデルの内容の変化を外部に通知できるようになった。

そして,DivisionオブジェクトをドメインモデルとするDivisionContentProviderクラスの作成だが,ドメインモデルの内容変化通知を受け取ってTableViewerにそれを反映するために,DivisionListenerインタフェースを実装する

  public class DivisionContentProvider
      implements IStructuredContentProvider, DivisionListener {
    private TableViewer viewer;
    public Object[] getElements(Object inputElement) {
      return ((Division)inputElement).getEmployeeList().toArray();
    }
    public void dispose() {
    }
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
      this.viewer = (TableViewer)viewer;
      if (oldInput != null)
        ((Division)oldInput).removeDivisionListener(this);
      if (newInput != null)
        ((Division)newInput).addDivisionListener(this);
    }
    public void employeeAdded(Employee employee) {
      if (viewer != null)
        viewer.add(employee);
    }
  }

まず,ドメインモデルから行のオブジェクトを返却するgetElementsメソッドは,引数で渡されるDivisionオブジェクトのgetEmployeeListメソッドを呼び出してEmployeeオブジェクトが格納されているコレクションを取得し,そのtoArrayメソッドを呼び出して配列に変換し,その結果を返却している。ここは「TableViewer向けContentProviderの作成」のときとほぼ同じなので問題ないだろう。disposeメソッドに記載する処理は,今回も特になし。

そして「TableViewer向けContentProviderの作成」のときは何も処理を記述しなかったinputChangedメソッドが,ここでは非常に重要である。まず,引数で受け取ったTableViewerオブジェクトをフィールドに保持するようにしている。フィールドに保持されたTableViewerオブジェクトは,ドメインモデルに変更があった際にその反映先となる。

ちょっと説明の順番が前後するが,第3引数で渡されてくるnewInputオブジェクトをDivision型にキャストし,addDivisionListenerメソッドに自分自身を渡してドメインモデルの内容変化の観測者として登録している。これにより,このDivisionContentProviderオブジェクトはsetInputメソッドでセットされたドメインモデルの変更通知を受け取ることができるようになる。これとは反対に,setInputメソッドで新たなドメインモデルがセットされるまで使用されていた第2引数で渡されるoldInputオブジェクト,すなわち古いドメインモデルに対しては,removeDivisionListenerメソッドを呼び出して自分自身を観測者から解除する。

あとはDivisionListenerインタフェースに規定されているemployeeAddedメソッドを実装する。引数で渡されてくる追加された社員のEmployeeオブジェクトを,予めフィールドで保持しておいたTableViewerオブジェクトのaddメソッドに渡すことによって,TableViewer上に反映している。

以上のコーディングにより,Divisionオブジェクト,すなわちドメインモデルに対する社員の追加という行為が,シームレスにTableViewerに反映されるようになった。上記は社員の追加だけだが,社員の削除などに関しても同様の実装方法となる。使い方的には,TableViewerを使用するビューのクラス内で,

  List employeeList = ...; // ドメインオブジェクト作成
  TableViewer viewer = ...; // TableViewerオブジェクト作成
  // ヘッダ列の作成
  viewer.setContentProvider(
    new DivisionContentProvider());
  viewer.setLabelProvider(new EmployeeLabelProvider());

  Division division = ...;
  viewer.setInput(division);

としておいて,任意の場所で,

  Division division = ...;
  Employee employee = new Employee("よういちろう", 29);
  division.addEmployee(employee);

とすることにより,自動的にTableViewerに追加される,というわけである。まとめると,

  ・ContentProviderはドメインモデルの内容変化の観測者となり,内容変化をTableViewerに反映する。
  ・そのためには,ドメインモデルがその内容変化を外部に通知する機構を持つ必要がある。

ということである。

・・・うーん,一番長い記事になっちゃったかも。

|

« TableViewer向けLabelProviderの真実 | トップページ | TableViewerの更新はスレッドに注意せよ »

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/12631/519965

この記事へのトラックバック一覧です: TableViewerのホントの利用方法:

« TableViewer向けLabelProviderの真実 | トップページ | TableViewerの更新はスレッドに注意せよ »