<更新記録>
2007年 12月 23日
執筆
2008年 4月 13日
更新

姉妹サイト検索 Web検索


Viewer Framework

Viewer Framework

JFace APIでは、ListTree、のウィジェットの操作を抽象化して、 統一した操作を行う Viewer Framework が用意されています。

Viewer Frameworkは、数々のクラスとインターフェイスから成り立っており、 その構造は複雑です。

しかし、オブジェクティブな思想で設計されており、 慣れてしまえば、直接org.eclipse.widgetsパッケージを扱うよりも、 はるかに簡単に、手際よく、バグを織り込まずに、拡張性高く開発することが可能になります。

慣れるまではややこしいかもしれませんが、 慣れてしまえば、その使いやすさがわかると思います (もうちょっと簡単な構造にできなかったのかなと思います)。

Viewer Frameworkは、ウィジェットの直接操作を、MVC(Model-View-Controller)モデルの各役割に分離することを目的としています。 説明の中で、この用語が出てきますので、覚えておいてください。 ざっくりと簡単にそれぞれの役割を要約するならば、

  • Model = データと、それを扱うロジック
  • View = 表示(ウィジェット)
  • Controller = データと表示を結ぶロジック
というような感じになります。

MVCの説明は、ここちらでうまくされています。

関連するクラスとインターフェイス

Eclipse APIで用意されたクラスやインターフェイスと、それぞれがMVCのどの役割に対応しているのかをリストアップしておきます。

  • Modelに対応:IContentProvider, IBaseLabelProvider
  • Viewに対応:Viewer, ContentViewer, TableViewer
  • Controllerに対応:ViewerSorter, IAction

Modelに対応するクラスやインターフェイスには、getImage(Object)やgetText(Object)といったインターフェイスがあります。 Modelとはデータのことなので、これらのメソッドを使用して、Viewである表示そのものが表示するデータを要求します。

Viewに対応するクラスやインターフェイスには、setInput(Object)メソッドがあります。 このメソッドで指定するのがModelに対応するクラスです。 Viewは、ここでセットされたModelからデータを参照します。

Controllerに対応するクラスやインターフェイスには、IActionインターフェイスがありますが、 IActionインターフェイスにはrun()メソッドがあります。 これらのメソッドは、Viewに対してユーザがイベントを行うと呼び出され、Modelのデータの変更を行ったりします。 その結果、Viewが表示する内容も変更されたりします。

その他、SelectionListenerで用意されている widgetSelected(SelectionEvent)メソッドも、このControllerに当たります。

これらをふまえた上で、今回サンプルを使って説明するViewerクラスにまつわるクラスやインターフェイスの説明をします。

まずは、Viewer Frameworkで登場するクラスとインターフェイスの関係をざっと示します。


                              +--------+
                              | Viewer |
                              +--------+
                                  ^
                                  |
   +---interface------+     +---------------+      +----interface-------+
   | IContentProvider |.....| ContentViewer |......| IBaseLabelProvider |
   +------------------+     +---------------+      +--------------------+
                                  ^
                                  |
                          +------------------+
                          | StructuredViewer |
                          +------------------+<----+
                           ^        ^              |
                           |        |              |
       +--------------------+   +------------+   +-------------+
       | AbstractTreeViewer |   | ListViewer |   | TableViewer |
       +--------------------+   +------------+   +-------------+
          ^            ^
          |            |
    +------------+  +-----------------+
    | TreeViewer |  | TableTreeViewer |
    +------------+  +-----------------+

Viewerクラスは、Viewer Frameworkの核となる部分です。 MVCモデルのうち、Viewer部に相当します。 これを継承するContentViewerクラスもStructuredViewerクラスも、MVCモデルのうちのViewerの役割です(名前からも明白ですが)。

Viewerクラスを継承したContentViewerクラスでは、表示するアイテムと、その表示名を扱います。 IContentProviderインターフェイスとIBaseLabelProviderインターフェイスがその役割を担います。 これらはMVCモデルのうちのModelに相当します。 Modelはデータを表しますが、それをさらに細分化し、IContentProviderはデータそのものを、 IBaseLabelProviderはデータから表示する文字列やイメージを扱います。

StructuredViewerクラスでは、ソーティング、フィルタリングの機能を扱います。 ソーティングはViewerSorterクラス、フィルタリングはViewerFilterクラスで行います。

StructuredViewerクラスのサブクラスとして、ツリー、リスト、テーブル等を扱うためのクラスが定義されています。

サンプル

サンプルの実行例。

Viewer Frameworkの構造を解説するにあたって、ListViewerクラスを例に話を進めていきます。 inputRoot変数は、好きな任意のディレクトリに変更してください。 サンプルでは、私のPC上の、とあるディレクトリのパスになっています。
ListViewerDemonstrate.java
import java.io.File;
import org.eclipse.jface.viewers.ListViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class ListViewerDemonstrate extends Composite {

	// 任意のディレクトリ
	private static final File inputRoot = new File(
		"C:\\Users\\root\\Documents\\kimama\\src\\coolJava\\swt_jface");
	
	public ListViewerDemonstrate(Composite parent) {
		super(parent, SWT.NONE);
		setLayout(new FillLayout());
		
		ListViewer listViewer = new ListViewer(this, SWT.NONE);
		
		FileLabelProvider labelProvider = new FileLabelProvider();
		listViewer.setLabelProvider(labelProvider);
		
		FileContentProvider contentProvider = new FileContentProvider();
		listViewer.setContentProvider(contentProvider);
		
		// フィルターを追加
		//DirectoryFilter filter = new DirectoryFilter();
		//listViewer.addFilter(filter);
		
		// コンペレーターを追加
		//UpdateTimeComparator comparator = new UpdateTimeComparator();
		//listViewer.setComparator(comparator);
		
		listViewer.setInput(inputRoot);
		
		setSize(400, 400);
	}
	
	public static void main(String[] args) {
		Display display = new Display();
		Shell shell = new Shell(display);
		
		ListViewerDemonstrate composit = new ListViewerDemonstrate(shell);
		
		shell.pack();
		shell.open();
		
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) {
				display.sleep();
			}
		}
		display.dispose();
	}
}
ListViewerDemonstrateクラスは、ListViewerを作成しているメインクラスです。 まずは、ListViewerオブジェクトを作成し、setLabelProviderメソッドやsetContentProviderメソッド、 setInputメソッドが呼ばれているなぁという程度の理解で結構です。 ここで、FileLabelProviderクラス、FileContentProviderクラスが登場していますが、これらのクラスはこれから解説しながら作成します。

IContentProvider

                        +-----interface----+
                        | IContentProvider |<---------------+
                        +------------------+                |
                           ^        ^                       |
                           |        |                       |
+---------interface----------+ +--------interface-----+ +interface-+
| IStructuredContentProvider | | ITreeContentProvider | |  その他  |
+----------------------------+ +----------------------+ +----------+
IContentProviderインターフェイスは、ContentViewer#setContentProviderメソッドで指定します。 IContentProviderインターフェイスは、Viewerが要素を表示するための操作のフレームワークを提供します。 IContentProvider#setContentProviderメソッドでは、ただ単にIContentProviderを実装したクラスのオブジェクトを指定すればよいかというと、 そうではありません。
      +--------+
      | Viewer |
      +--------+
          ^
          |
   +---------------+
   | ContentViewer |
   +---------------+
          ^
          |
  +------------------+
  | StructuredViewer |
  +------------------+
          ^
          |
 +--------------------+
 | AbstractListViewer |
 +--------------------+
          ^
          |
     +------------+
     | ListViewer |
     +------------+
例えば、ListViewerは、StructuredViewerクラスを継承しているので、 それに指定するIContentProvider実装クラスは、IStructuredContentProviderインターフェイスを指定しなければなりません。 サンプルListViewerDemonstrate.javaで登場するFileContentProviderクラスは、IStructuredContentProviderインターフェイスの実装クラスです。
FileContentProvider.java
import java.io.File;

import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.Viewer;

public class FileContentProvider implements IStructuredContentProvider {
	public Object[] getElements(Object inputElement) {
		return ((File)inputElement).listFiles();
	}
	
	public void dispose() {
		// 何もしない
	}
	
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		// 何もしない
	}
}
FileContentProviderクラスは、表示すべきファイル要素を取り出します。 ContentViewer#setInputメソッドで指定されたオブジェクトは、 IStructuredContentProvider#getElementsメソッドのパラメータにinputElementとして渡されます。 このinputElementとして渡されたパラメータを、適切に型キャストして、そこからリストに表示するのに必要な要素を取り出しています。 今回のサンプルの場合、Fileオブジェクトを扱っているので、ディレクトリに格納されているファイルを取り出すために、 listFilesメソッドを呼び出しています。

IBaseLabelProvider

FileContentProviderクラスでは、Viewerが表示する要素をgetElementsメソッドによりオブジェクト配列として算出しましたが、 それらの要素をどうリストに表示するのかという処理は記述しませんでした。

IBaseLabelProviderインターフェイスとそのサブインターフェイス、実装クラスでは、 実際にツリーやリストに表示するアイテム1つ1つのテキストやラベルを決定するという仕事を行います。

               +-------interface----+
               | IBaseLabelProvider |
               +--------------------+
                  ^            ^
                  |            |
    +----interface---+      +-------interface-----+
    | ILabelProvider |      | ITableLabelProvider |
    +----------------+      +---------------------+
             ^                         ^
             |                         |
    +---------------+       +--------------------+
    | LabelProvider |       | TableLabelProvider |
    +---------------+       +--------------------+
FileLabelProvider.java
import java.io.File;

import org.eclipse.jface.viewers.LabelProvider;

public class FileLabelProvider extends LabelProvider {
	public String getText(Object element) {
		return ((File)element).getName();
	}
}
IBaseLabelProviderインターフェイスのサブインターフェイスとして、 ILabelProviderインターフェイスとITableLabelProviderインターフェイスがありますが、 TableViewerクラス以外では、ILabelProviderインターフェイスを使用します。 ただし、ILabelProviderとIBaseLabelProviderのすべてのメソッドを実装するのは効率的でないので、 デフォルトの実装のLabelProviderというクラスが用意されています。 FileLabelProviderクラスでは、ファイルの名前を取り出すために、getTextメソッドをオーバーライドしています。

setInput

setContentProviderメソッドとsetLabelProviderメソッドを呼び出したら、最後にsetInputメソッドを呼び出します。 これに指定したオブジェクトは、フレームワークを経由して、さまざまな場所で利用されます。 サンプルではFileオブジェクトを使用していますので、setContentProviderメソッドやsetLabelProviderメソッドで指定したオブジェクトは、 いずれもFileオブジェクトに対する操作を行うクラスでなければなりません。

setInputメソッドに対して整合性の取れていないクラスをsetContentProviderメソッドやsetLabelProviderメソッドに指定すると、 型キャストがうまく行われずにすぐにエラーとなってしまいます(この部分がViewer Frameworkの欠点かなと思います)。

フィルタリングとソーティング

フィルタリング機能は、ViewerFilterクラスでそのフレームワークが提供されています。 ViewerFilterクラスを継承してオーバーライドする必要があるメソッドは、selectメソッドです。

DirectoryFilter.java
import java.io.File;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;

public class DirectoryFilter extends ViewerFilter {
	public boolean select(Viewer viewer, Object parentElement, Object element) {
		return !((File)element).isDirectory();
	}
}

ソーティング機能は、ViewerComparatorクラスでそのフレームワークが提供されています。 ViewerComparatorクラスのサブクラスは、compareメソッドをオーバーライドします。

UpdateTimeComparator
import java.io.File;

import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;

public class UpdateTimeComparator extends ViewerComparator {
	public int compare(Viewer viewer, Object e1, Object e2 ) {
		long result = ((File)e1).lastModified() - ((File)e2).lastModified();
		return new Long(result).intValue();
	}
}

DirectoryFilterとUpdateTimeComparatorを使用することで、ディレクトリを除いたファイルのみを表示し、 ファイルは日付順にソートされます。 この結果を分かりやすくするために、先ほどのサンプルで作成したFileLabelProviderクラスを、少し改変します。

FileLabelProvider.java
import java.io.File;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.eclipse.jface.viewers.LabelProvider;

public class FileLabelProvider extends LabelProvider {
	public String getText(Object element) {
		File file = ((File)element);
		GregorianCalendar gCalendar = new GregorianCalendar();
		gCalendar.setTime(new Date(file.lastModified()));
		
		return file.getName() + 
		" " + gCalendar.get(Calendar.YEAR) + "年" + 
		gCalendar.get(Calendar.MONTH) + "月" + 
		gCalendar.get(Calendar.DATE) + "日更新";
	}
}
これで、ファイルの更新日もリストに表示されるようになりました。

そしてメインをこれに対応して書き換えます。

import java.io.File;
import org.eclipse.jface.viewers.ListViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class ListViewerDemonstrate extends Composite {

	// 任意のディレクトリ
	private static final File inputRoot = new File(
		"C:\\Users\\root\\Documents\\kimama\\src\\coolJava\\swt_jface");
	
	public ListViewerDemonstrate(Composite parent) {
		super(parent, SWT.NONE);
		setLayout(new FillLayout());
		
		ListViewer listViewer = new ListViewer(this, SWT.NONE);
		
		FileLabelProvider labelProvider = new FileLabelProvider();
		listViewer.setLabelProvider(labelProvider);
		
		FileContentProvider contentProvider = new FileContentProvider();
		listViewer.setContentProvider(contentProvider);
		
		// フィルターを追加
		DirectoryFilter filter = new DirectoryFilter();
		listViewer.addFilter(filter);
		
		// コンペレーターを追加
		UpdateTimeComparator comparator = new UpdateTimeComparator();
		listViewer.setComparator(comparator);
		
		listViewer.setInput(inputRoot);
		
		setSize(400, 400);
	}
	
	public static void main(String[] args) {
		Display display = new Display();
		Shell shell = new Shell(display);
		
		ListViewerDemonstrate composit = new ListViewerDemonstrate(shell);
		
		shell.pack();
		shell.open();
		
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) {
				display.sleep();
			}
		}
		display.dispose();
	}
}
すべてディレクトリでないファイルのみが表示されて、日付も昇順にソートされています。


Powered by VeryEasyCMS