レベル: 中級 Adrian Emmenis (van@vanemmenis.com), Independent consultant
2003年 2月 14日 今回の記事では、A. O. Van Emmenis氏が、このシリーズの第1回のサンプルを引き続き使用して、コンテンツ・プロバイダーとラベル・プロバイダーを見栄え良く変更し、JFaceのビューアーでソートやフィルタリングを実行する方法について説明します。ウィンドウにステータス行を追加する方法や、サンプルの2つのビューアーにアイコンを追加する方法とともに、JFaceイメージ・レジストリーを使用したシステム・リソースの保存方法についても説明します。
インストール上の注意点
シリーズ第1回では、JFaceのアプリケーション・ウィンドウをサブクラス化し、ツリー・ビューアーとテーブル・ビューアーを使用してフォルダーとファイルを表示する例から説明を開始しました。今回は、前回の例でカバーできなかった部分に手を加えて整理し、ウィンドウにステータス行を追加します。大きな変更点は、2つのビューアーにアイコンを追加して、JFaceのイメージ・レジストリーを理解することです。そして最後に、ビューアーでソートとフィルタリングを使用して、前回よりもさらに本物に近い「ファイル・エクスプローラー」を作成します。
インストール上の注意点
この記事のサンプル・ソース・コードをダウンロードできます。このソース・コードを実行するには、以下のシステム設定が必要です。
- Windows 2000
- Ecipse, Stable Build M3 (2002年11月15日)
- Eclipseのインストール場所 - C:\eclipse-2.1.0
読者のシステムでプログラムが正しく動作するよう、以降は名前とファイルの区切り文字を自由に変更してください。
作成と実行の手順
クラスパスに次のJARファイルが必要です。
C:\eclipse-2.1.0\plugins\org.eclipse.jface_2.1.0\jface.jar
C:\eclipse-2.1.0\plugins\org.eclipse.runtime_2.1.0\runtime.jar
C:\eclipse-2.1.0\plugins\org.eclipse.swt.win32_2.1.0\ws\win32\swt.jar
C:\eclipse-2.1.0\plugins\org.eclipse.ui.workbench_2.1.0\workbench.jar
C:\eclipse-2.1.0\plugins\org.eclipse.core.runtime_2.1.0\runtime.jar
|
実行時にJava VM (仮想マシン) が、使用するGUIの正しい共有ライブラリーを選択するよう、次の引数を使用して実行してください。
-Djava.library.path=C:\eclipse-2.1.0\plugins\org.eclipse.swt.win32_2.1.0\os\win32\x86\ |
最後に、サンプル・アプリケーションでアイコンを含むgifファイルを見つけられるよう、icons フォルダーを含むフォルダーからプログラムを実行してください。
第1回のサンプルからの続き
前回の記事で、Explorerアプリケーションは最後に図1のようになりました。
図1. Explorer (バージョン4)
左側のペインには、ツリー・ビューアーによってフォルダーとファイルが表示されています。左側のペインでフォルダーを選択すると、右側のペインのテーブル・ビューアーにそのフォルダーに含まれるファイルが表示されます。それではまず、ウィンドウ・タイトルを設定するところから整理を始めましょう。
ウィンドウ・タイトルの設定
ここでも、やはりJFaceはSWTを隠蔽しません。ウィンドウの基盤となっているSWT Shellウィジェットに対してタイトルを設定する必要があります。
JFaceウィンドウでそのSWT Shellを取得するにはgetShell() を使用し、タイトルを設定するにはsetText() を使用します。したがって、ExplorerのcreateContents() メソッドの内容は次のようになります。
getShell().setText("JFace File Explorer");
現在テーブル・ビューには列が1つあります。今度はファイル・サイズをバイトで表示するもう1つの列を追加してみましょう。この列のテキストは右揃えにします。このコードは最初の列を作ったコードと似ているので、ここでは省略します。
これで列が2つになったので、今度はテーブル・ビューアーの選択形式をFULL に設定して、選択されたときに行全体がハイライト表示されるようにします。コードは次のようになります。
new TableViewer(sash_form, SWT.BORDER| SWT.FULL_SELECTION);
アプリケーション・ウィンドウへのステータス行の追加
 |
ステータス行
ステータス行は、実際にはSWT複合コントロールで、他のコントロールを含むことのできるStatusLineManager クラスに依存します。
一般に、ステータス行はステータス情報を表示する読み取り専用のコントロールですが、一時的なプログレス・モニターを表示することもできます。
ステータス行は、表示する標準メッセージやエラー・メッセージがあることを認識します。ApplicationWindow のsetMessage(String) メソッドは、実際にはgetStatusLineManager().setMessage(String) への単なるショートカットにすぎません。
|
|
ステータス行も、アプリケーション・ウィンドウのオプション・コンポーネントの1つです。ステータス行は、ウィンドウでSWTウィジェットが作成される前に、addStatusLine() を使用して作成しておく必要があります。
ここでは、テーブル・ビューで選択された項目の数をステータス行に表示してみましょう。テーブル・ビューの選択内容が変更されるたびにステータス行を更新し、選択された項目数を表示します。コードはリスト1のようになります。
リスト1. Explorer -- ステータス行へのテキストの設定
tbv.addSelectionChangedListener(new ISelectionChangedListener()
{
public void selectionChanged(SelectionChangedEvent event)
{
IStructuredSelection selection =
(IStructuredSelection) event.getSelection();
setStatus("Number of items selected is " + selection.size());
}
});
|
デフォルトでは、テーブル・ビューアーは単一選択モードになっています。これを複数選択に変更します。そのためには、次のように、テーブル・ビューアーの作成時に複数選択のオプションをスタイル引数のビットとして (文字どおり) 追加します。
new TableViewer(sash_form, SWT.BORDER| SWT.FULL_SELECTION | SWT.MULTI);
これらをすべてまとめると、リスト2のようになります。
リスト2. Explorer (バージョン5)
import java.io.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.*;
import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.widgets.*;
public class Explorer extends ApplicationWindow
{
public Explorer()
{
super(null);
addStatusLine();
}
protected Control createContents(Composite parent)
{
getShell().setText("JFace File Explorer");
SashForm sash_form = new SashForm(parent, SWT.HORIZONTAL | SWT.NULL);
TreeViewer tv = new TreeViewer(sash_form);
tv.setContentProvider(new FileTreeContentProvider());
tv.setLabelProvider(new FileTreeLabelProvider());
tv.setInput(new File("C://"));
final TableViewer tbv =
new TableViewer(sash_form, SWT.BORDER| SWT.FULL_SELECTION | SWT.MULTI);
tbv.setContentProvider(new FileTableContentProvider());
tbv.setLabelProvider(new FileTableLabelProvider());
TableColumn column = new TableColumn(tbv.getTable(), SWT.LEFT);
column.setText("Name");
column.setWidth(200);
column = new TableColumn(tbv.getTable(), SWT.RIGHT);
column.setText("Size");
column.setWidth(100);
tbv.getTable().setHeaderVisible(true);
tv.addSelectionChangedListener(new ISelectionChangedListener()
{
public void selectionChanged(SelectionChangedEvent event)
{
IStructuredSelection selection =
(IStructuredSelection) event.getSelection();
Object selected_file = selection.getFirstElement();
tbv.setInput(selected_file);
}
});
tbv.addSelectionChangedListener(new ISelectionChangedListener()
{
public void selectionChanged(SelectionChangedEvent event)
{
IStructuredSelection selection =
(IStructuredSelection) event.getSelection();
setStatus("Number of items selected is " + selection.size());
}
});
return sash_form;
}
public static void main(String[] args)
{
Explorer w = new Explorer();
w.setBlockOnOpen(true);
w.open();
Display.getCurrent().dispose();
}
}
|
このコードを実行すると、図2のようになります。
図2. Explorer (バージョン5)
お気付きだと思いますが、サイズ列の表示はこの時点では正しくありません。これはすぐに修正します。
アイコンとイメージ
SWTでは、Image オブジェクトを使用してアイコンを表します (Image クラスの詳細については、記事後半の参考文献を参照してください)。イメージの表示が可能なSWTウィジェットを直接作成している場合は、setImage(Image) を使用してイメージを設定します。
さて、イメージに関しては問題が1つあります。それは、イメージが制限のあるリソースだということです。イメージは、外部OSリソースへの参照を持つ比較的重いオブジェクトです (図3)。オペレーティング・システムによっては、同時に使用できるイメージの数が厳しく制限されている場合があります。そのため、イメージを使用するときには、このような制限に注意しなければなりません。
図3. イメージ・ディスクリプター、イメージ、およびオペレーティング・システムのリソース
イメージによって使用されたリソースは、最終的にはアプリケーションが終了すると解放されますが、オペレーティング・システムによっては、その後も共有ライブラリーでリソースが保持されたままになる問題が発生する可能性があります。これにはさまざまな理由がありますが、Javaガーベッジ・コレクターが必ずこのような問題を解決してくれると考えることはできません。SWT (Standard Widget Toolkit) の詳細については、参考文献を参照してください。
幸いなことに、イメージはウィジェット間で共有可能であり、SWTのImageには、使用したリソースを解放するdispose() メソッドが提供されています。
ウィジェット間でイメージを共有できるため、SWTは、あるウィジェットが存在しなくなっても (ウィンドウがクローズされても) 自動的にそのイメージを破棄しないデザインになっています。ただし、JFaceでは、ウィンドウやビューアーのような大きなUIオブジェクトでイメージを破棄できる場合、それをユーザーに通知してくれます。作成したイメージを廃棄するかどうかはユーザーが決定できます。
実際には、ほとんどのアプリケーションではgifやjpegといった標準フォーマットのデータを含むファイルやデータベースからアイコンを取得します。JFaceではImageDescriptor を使用してアイコンを取得します (参考文献を参照)。ImageDescriptorは、イメージ自体を保存するわけではなく、特定のイメージを必要に応じて作成する軽量オブジェクトです。このクラスには多くのサブクラスがあり、さまざまなソースからイメージを作成できるようになっています。
今回の例では、アイコンをicons フォルダーの .gifファイルに保存して、ImageDescriptor の次のファクトリー・メソッドを使用します。
public static ImageDescriptor createFromURL(URL url)
TableViewerは、要素のテキストを取得するときと同じ方法で要素のアイコンを取得します。つまり、ラベル・プロバイダーを使用します。そのためには、ITableLabelProvider で次のメソッドを実装する必要があります。
public Image getColumnImage(Object element, int columnIndex)
イメージの共有と破棄の管理を簡単にするには、ImageRegistry (参考文献を参照) を使用します。
JFaceイメージ・レジストリー
費用のかかる共有リソースの管理は、ソフトウェア・エンジニアリングにおける典型的な問題でした。JFaceは、この典型的な問題に典型的なソリューションで対処します。それは、イメージとイメージ・ディスクリプターをキャッシュする、集中管理された共有レジストリーの提供です。
ユーザー・コードで必要とするイメージ・ディスクリプターを取得し、それらをイメージ・レジストリーに追加して、それぞれにキーでインデックスを付けます。イメージが必要になったときには、このキーを使ってレジストリーからそのイメージを取り出します。
イメージ・レジストリーは、トップレベルのDisplayが破棄された場合に、自身のイメージを破棄します。もっと頻繁にイメージを破棄する必要がある場合は、複数のイメージ・レジストリーを作成して、必要に応じて直接イメージを破棄する必要があります。詳細については、Eclipse Webサイトのイメージの使用に関する記事を参照してください (参考文献を参照)。
このサンプルでは多くのアイコンを必要としないので、イメージ・レジストリーを1つだけ使用することにします。まずは、テーブル・ビューアーから見ていきましょう。
ファイル・テーブル・ビューアーへのアイコンの追加
\file.gifというファイルからイメージ・ディスクリプターを作成するには、以下のようにします。
image_descriptor = ImageDescriptor.createFromURL(new URL("icons/file.gif"));
イメージ・ディスクリプターを取得したら、イメージ・レジストリーに保存します。この例では、キーとして文字列 "file" を使用します。
image_registry.put("file", image_descriptor);
その後、このイメージをもう一度取得するには、次のコードを使用します。
image = image_registry.get("file");
URLのコンストラクターはチェック例外をスローします。この例ではハード・コーディングしたURLを使用するので、URLの作成を何らかのユーティリティー・コードでラップして、チェック例外を実行時例外にします。
また、これらのイメージをサンプル・コード全体で共有したいので、イメージ・レジストリーは、グローバル・アクセスが可能な場所に作成する必要があります。
今度は、リスト3のようにユーティリティー・クラスを作成します。
リスト3. Util (バージョン1)
import import java.net.*;
import org.eclipse.jface.resource.*;
public class Util
{
private static ImageRegistry image_registry;
public static URL newURL(String url_name)
{
try
{
return new URL(url_name);
}
catch (MalformedURLException e)
{
throw new RuntimeException("Malformed URL " + url_name, e);
}
}
public static ImageRegistry getImageRegistry()
{
if (image_registry == null)
{
image_registry = new ImageRegistry();
image_registry.put(
"folder",
ImageDescriptor.createFromURL(newURL("file:icons/folder.gif")));
image_registry.put(
"file",
ImageDescriptor.createFromURL(newURL("file:icons/file.gif")));
}
return image_registry;
}
} |
getImageRegistry() メソッドは、単純にイメージ・レジストリーを作成し、2つのイメージ・ディスクリプターを追加しています。ここではFileTableLabelProvider を変更し、要素がファイルであるかフォルダーであるかによって、適切なイメージが返されるようにします。リスト4にそのコードを示します。
リスト4. FileTableLabelProvider (バージョン2)
import java.io.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.graphics.*;
public class FileTableLabelProvider implements ITableLabelProvider
{
public String getColumnText(Object element, int column_index)
{
if (column_index == 0)
{
return ((File) element).getName();
}
if (column_index == 1)
{
return "" + ((File) element).length();
}
return "";
}
public void addListener(ILabelProviderListener ilabelproviderlistener)
{
}
public void dispose()
{
}
public boolean isLabelProperty(Object obj, String s)
{
return false;
}
public void removeListener(ILabelProviderListener ilabelproviderlistener)
{
}
public Image getColumnImage(Object element, int column_index)
{
if (column_index != 0)
{
return null;
}
if (((File) element).isDirectory())
{
return Util.getImageRegistry().get("folder");
}
else
{
return Util.getImageRegistry().get("file");
}
}
}
|
テーブルには現在2つの列が存在する (前の手順でサイズ列を追加した) ため、getColumnText(Object,int) にも手を加えて正しく動作するようにしなければなりません。
これで、図4のような画面が表示されます。
図4. Explorer (バージョン6)
これでテーブル・ビューアーの方はなかなか見栄えがよくなってきました。それでは、今度はイメージを使用するようFileTreeLabelProvider をアップグレードしていきましょう。
リスト5. FileTreeLabelProvider (バージョン2)
import java.io.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.graphics.*;
public class FileTreeLabelProvider extends LabelProvider
{
public String getText(Object element)
{
return ((File) element).getName();
}
public Image getImage(Object element)
{
if (((File) element).isDirectory())
{
return Util.getImageRegistry().get("folder");
}
else
{
return Util.getImageRegistry().get("file");
}
}
}
|
図5のような画面が表示されます。
図5. Explorer (バージョン7)
これで、ファイルかフォルダーかを簡単に見分けられるようになりました。テーブル・ビューでは、デフォルトのソート・アルゴリズムによってファイルとフォルダーがアルファベット順に並べられています。しかし、フォルダーとファイルが一緒にソートされています。また、ツリー・ビューにも、フォルダーとファイルの両方が表示されています。今度はこれを修正しましょう。
ソートとフィルタリング
ビューアーの項目をソートするには、ViewerSorter を使用します。
ViewerSorter (参考文献を参照) は、サブクラス化を目的としたクラスです。ビューアーは、ViewerSorterを使用して、自身の要素を2段階でソートします。まず最初に、要素のカテゴリーを要求します。これによって整数値が返されるので、ビューアーは要素をカテゴリー番号でソートし、昇順のカテゴリー・グループを作成します。
public int category(Object element)
次に、各カテゴリー内でViewerSorter のcompare() メソッドを使用してソートを実行します。このメソッドは、標準のJavaクラスComparator のcompare() メソッドに似ています。
public int compare(Viewer viewer, Object element1, Object element2)
デフォルトでは、ラベル・プロバイダーによって返された文字列を使用して、大文字と小文字を区別せずにソートが行われます。
それでは、テーブル・ビューアーでcategoryを実装してみましょう。コードはリスト6のようになります。
リスト6. FileSorter (バージョン1)
import java.io.*;
import org.eclipse.jface.viewers.*;
public class FileSorter extends ViewerSorter
{
public int category(Object element)
{
return ((File) element).isDirectory() ? 0 : 1;
}
} |
これによって、フォルダーがファイルより先にソートされます。
ツリー・ビューアーでは、フォルダーだけを表示するようにします。これは、ViewerFilter (参考文献を参照) を使用して実行できます。ソーターと同様に、フィルターもサブクラス化することを目的に設計されています。ここでは、各要素を調べて、それを表示すべき場合にtrueを返すselect() メソッドを実装する必要があります。この例ではフォルダーだけがフィルターを通過するようにしたいので、リスト7のようにAllowOnlyFoldersFilter() メソッドを使用します。
リスト7. AllowOnlyFoldersFilter (バージョン1)
import java.io.*;
import org.eclipse.jface.viewers.*;
public class AllowOnlyFoldersFilter extends ViewerFilter
{
public boolean select(Viewer viewer, Object parent, Object element)
{
return ((File) element).isDirectory();
}
}
|
フィルタリング対象の要素が3番目の引数になっていることに注意してください。ここでは、ビューアーと親にもアクセスしなければならない場合を考慮して、これらも引数として取ります。
さて、あとはリスト8のように、これらのクラスのインスタンスをビューアーに付加するだけです。
リスト8. Explorer (バージョン8)
import java.io.*;
import org.eclipse.jface.action.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.*;
import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.widgets.*;
public class Explorer extends ApplicationWindow
{
...
protected Control createContents(Composite parent)
{
getShell().setText("JFace File Explorer");
SashForm sash_form = new SashForm(parent, SWT.HORIZONTAL | SWT.NULL);
TreeViewer tv = new TreeViewer(sash_form);
tv.setContentProvider(new FileTreeContentProvider());
tv.setLabelProvider(new FileTreeLabelProvider());
tv.setInput(new File("C://"));
tv.addFilter(new AllowOnlyFoldersFilter());
final TableViewer tbv =
new TableViewer(sash_form, SWT.BORDER| SWT.FULL_SELECTION | SWT.MULTI);
tbv.setContentProvider(new FileTableContentProvider());
tbv.setLabelProvider(new FileTableLabelProvider());
tbv.setSorter(new FileSorter());
...
} |
メソッドの名前からも予想できるように、ビューアーは同時に複数のフィルターを持つことができますが、ソーターは1つしか持つことができません。
この時点でExplorerを実行すると、図6のようになります。
図6. Explorer (バージョン8)
まとめ
どうでしたか。これらのアイコンによって、少し格好がついてきたのではないでしょうか。今回は、ウィンドウで何を表示しているのかを示すウィンドウ・タイトルと、選択した項目数を表示する便利なステータス行を新たに追加し、フィルタリングとソートを実行してファイルとフォルダーをきれいに分類しました。これで、ずいぶん本物らしいファイル・エクスプローラーになってきました。
ただし、見た目はきれいになってきたものの、現時点ではたいした機能はありません。この3回シリーズの最後の記事では、メニューとアクションを追加して、この機能面に手を加えます。次回は、バー・メニュー、ツールバー、およびポップアップ・メニューの作成方法を説明します。これらのメニューでJFaceの優れたユーティリティーを使って、プログラムの起動やシステム・クリップボードへのアクセスを行うサンプルをいくつか作成するとともに、リスナーを使用してメニュー項目をコンテキスト依存にする方法についても説明します。
参考文献
著者について  | 
|  | A. O. Van Emmenis氏は、Java/J2EEのトレーニングとコンサルティングを専門とする、イギリス、ケンブリッジを基盤に活躍する独立系コンサルタントです。Van氏は、これまで20年にわたってソフトウェア業界に関わってきました。CAD業界においてSmalltalkを使用したオブジェクトの操作からキャリアを開始し、現在は主にJavaを扱っています。彼は、特にアジャイル・メソッドとGUIの設計に関心を寄せています。Vanの連絡先は、van@vanemmenis.com です。 |
記事の評価
|