TableViewer?とは

TableViewer?とは、JFaceのレイヤが提供するビューワクラスです。いわゆる表形式のデータを表示するためのウィジェットです。SWTのTableクラスのラッパになっていて、データを表示する機能やDrag&Dropやソート機構など、いろいろ便利な機能を提供してくれます。

pic01.png

ViewPart?に配置する

ビューに配置するには、ViewpartのcreatePartControl?(Compsite parent)内で

Composite container = new Composite(parent, SWT.NONE);
container.setLayout(new FillLayout());
viewer = new TableViewer(container, SWT.FULL_SELECTION | SWT.BORDER
               | SWT.H_SCROLL | SWT.V_SCROLL);

とするだけです。

カラムを設定する

次にTableViewer?にカラム(列)を定義していきます。基本的にTableViewer?からSWTのTableクラスを取得して

table = viewer.getTable();
TableColumn tc1 = new TableColumn(table, SWT.NONE, 0);
TableColumn tc2 = new TableColumn(table, SWT.NONE, 1);

としていけばよいのですが、EclipseのTaskViewのソースに非常に汎用的なコーディングがあったので、それに沿ってやってみたいと思います。

ViewPart?のフィールドに定義

private final ColumnPixelData[] DEFAULT_COLUMN_LAYOUTS = {
    new ColumnPixelData(200), new ColumnPixelData(200),
    new ColumnPixelData(200) };
private final IField[] VISIBLE_FIELDS = { new TitleField(),
    new SummaryField(), new URLField() };

ColumnPixelData?はそのカラムがリサイズ可能かとか、大きさはいくつかなどの情報を指定します。ちなみにColumnPixelDataのJavaDocを下に載せときました。

IFieldはヘッダ名やヘッダに表示するアイコンの情報、またビューワの各カラムにオブジェクトが渡されたときにオブジェクトを文字列に変換する変換処理、などを備えた(自作の*1)インタフェースです。後に出てくるので詳細は割愛します。。

public interface IField {
  String getColumnHeaderText();
  Image getColumnHeaderImage();
  String getValue(Object obj);
  Image getImage(Object obj);
}

これを各カラムに沿った形でOverrideすればOKです。

次に ViewPart?#createPartControl?で以下のinitTalbeメソッドを呼び出します。

private void initTable(Viewer viewer){
 Table table = viewer.getTable();
 TableLayout layout = new TableLayout();
 table.setLayout(layout);
 table.setHeaderVisible(true); // ヘッダは表示する
 table.setLinesVisible(true);  // ラインは表示する
 
 ColumnLayoutData[] columnLayouts = getColumnLayouts();
 
 for (int i = 0; i < columnLayouts.length; i++) {
   layout.addColumnData(columnLayouts[i]);
   TableColumn tc = new TableColumn(table, SWT.NONE, i);
   tc.setText(VISIBLE_FIELDS[i].getColumnHeaderText());
   tc.setImage(VISIBLE_FIELDS[i].getColumnHeaderImage());
 }
}
private ColumnLayoutData[] getColumnLayouts() {
  ColumnPixelData[] result = new ColumnPixelData[DEFAULT_COLUMN_LAYOUTS.length];
  for (int i = 0; i < DEFAULT_COLUMN_LAYOUTS.length; i++) {
    int width = DEFAULT_COLUMN_LAYOUTS[i].width;
    //////////// ※ /////////////////
    result[i] = new ColumnPixelData(width);
  }
  return result;
}

これでTableにTableColumn?がセットされました。getColumnLayouts?についてですが、なかで新しいColumnPixelData?を作成して、んで配列ColumnPixelData?[]を返しています。このままだとフィールドと同じモノが返るのでわけわかんないのですが、実は※のところに処理があります。いまんところは、あまり気にせずにいきましょう。後述します。

ドメインオブジェクトと2つのProvider

ここまででカラムが3つあるTableViewer?が構築されました。さてこのテーブルに値を表示させていくのですが、JFaceレイヤのTableViewer?では

  • 表示したいデータを持つドメインオブジェクト
  • ドメインオブジェクトとTableViewer?の橋渡しをするContentProvider?
  • TableViewer?に値を表示するためのLabelProvider?

という3種類のオブジェクトが重要です。

たとえば、ドメインオブジェクトを

public intarface Model{
  getSummary();
  getURL();
  getTitle();
}

とします。このオブジェクトがたとえば10個(たとえば配列models)あったときに、それをTableViewer?で一行に一オブジェクト表示したいときは、直接TableViewer?#setInput(models);としてしまえばOKです。後は二つのプロバイダが何とかしてくれます。

ContentProvider?についてですが、実はドメインオブジェクトが配列やListの場合に関しては、JFaceでArrayContentProvider?というプロバイダを提供してくれています。よってcreatePartControl?

 viewer.setContentProvider(new ArrayContentProvider());

で終わりです。これで各行に一つのModelオブジェクトを割り当ててくれます。

つぎにLabelProvider?です。LabelProvider?ContentProvider?が各行に割り当ててくれたオブジェクト(Modelでした)を、各カラムになんと表示するか指定します。

以下のように定義しました。

class ViewLabelProvider extends LabelProvider implements
    ITableLabelProvider {
  private final IField[] fields;
  public ViewLabelProvider(IField[] fields) {
    this.fields = fields;
  }↑ViewPartに定義したIFieldです

  public String getColumnText(Object obj, int index) {
    if (obj instanceof Model) { ↑indexは左から何番目のカラムか
       ↑ ContentProviderが配列から取り出して渡してくれる
      Modelelement = (Model) obj;
      return fields[index].getValue(element);
    }
    return "";
  }

  public Image getColumnImage(Object element, int columnIndex) {
    return null; <-イメージを使いたい場合には何かを返す
  }
}

このクラスを作っておいて、

viewer.setLabelProvider(new ViewLabelProvider(VISIBLE_FIELDS));

としておけばOKです。

IFieldの効能についてですが、このようなクラスがないとLabelProvider?の中で、一列目だったらmodel#getTitle,二列目だったらmodel#getSummaryなどとswitchが出てきそうですが、それをポリモフィズムで何とかしてるわけです。

まとめると

  • フィールドの
    ColumnPixelData[] DEFAULT_COLUMN_LAYOUTS;
    IField[] VISIBLE_FIELDS;

TableViewer?を構築

  • TableViewer?の行単位のデータを生成するContentProvider?を定義(ArrayContentProvider?で十分の場合もある)
  • 行単位のデータを実際に各カラムに表示するためのLabelProvider?を定義。その中でIFieldを有効に使う

という感じですね。

参考
プラグイン開発のTIPS集/GooglePlugin/検索結果一覧を表示するビューの作成

IMemmentoを使った、カラム幅を保存してリストアする

org.eclipse.ui.IViewPartの 設定の保存とリストア にまとめたのですが、IMementoインタフェースを用いることで、ビューを閉じたときの各カラムのカラム幅を覚えておいて、次回そのビューを開くときに、閉じたときのカラム幅を再現することができます*2

やってみましょう。EclipseにはあらかじめViewを開いたときに呼ばれる

public void init(IViewSite site, IMemento memento);

というメソッドがあるので、それを以下のように実装しておきます。

public void init(IViewSite site, IMemento memento) throws PartInitException {
  this.memento = memento;
  super.init(site, memento);
}

Viewが開くときに渡されてきた引数のmementoはフィールドに持っておきます。

逆に、ビューを閉じるときには

public void saveState(IMemento memento);

が呼ばれるので、そのメソッドを以下のように実装しておきます。

public void saveState(IMemento memento) {
  super.saveState(memento);
  saveColumnWidth(memento);
}

このメソッドの引数のmementoは、次にこのViewを開いたときにinitで渡されてくるmementoなので、このmementoのなかに次回再現したい情報をセットしておけばよいと言うわけです。上では、savaColumnWidth?てメソッドの中で、以下のように実装しておきます。

private void saveColumnWidth(IMemento memento) {
  TableColumn[] columns = viewer.getTable().getColumns();
  for (int i = 0; i < columns.length; i++) {
    memento.putInteger(TAG_COLUMN_WIDTH + i, columns[i].getWidth());
  }                      ↑適当な文字列           ↑現在のカラム幅
}

現在の各カラム幅を「TAG_COLUMN_WIDTH + i」というキー値でmementoにセットしていっています。このキー値を使って、次回ビューを開いたときはキー値で取り出される(前回の)カラム幅をTableにセットしていきます。

さて、先ほど後述するからとりあえず消しておく、といっていた、

private ColumnLayoutData[] getColumnLayouts() {
  ColumnPixelData[] result = new ColumnPixelData[DEFAULT_COLUMN_LAYOUTS.length];
  for (int i = 0; i < DEFAULT_COLUMN_LAYOUTS.length; i++) {
    int width = DEFAULT_COLUMN_LAYOUTS[i].width;
    //////////// ※ /////////////////
    result[i] = new ColumnPixelData(width);
  }
  return result;
}

に修正をかけていきます。これは、カラム幅を保持したクラス、ColumnLayoutData?を取得するメソッドですが、これを、フィールドのmementoから値がとれる場合はそっちを使用する、という処理を追加していきます。※の箇所を以下のように変更します。

private ColumnLayoutData[] getColumnLayouts(ColumnPixelData[] data) {
  ColumnPixelData[] result = new ColumnPixelData[data.length];
  for (int i = 0; i < data.length; i++) {
    int width = data[i].width;
    if (memento != null) {
      // メメントから、前回の幅を取得している
      Integer widthInt = memento.getInteger(TAG_COLUMN_WIDTH + i);
      if (widthInt != null && widthInt.intValue() > 0) {
        width = widthInt.intValue();
      }
    }
    result[i] = new ColumnPixelData(width);
  }
  return result;
}

これで、あらかじめ、初回のデフォルトのカラム幅はフィールドに定義しておいたけど、ユーザが使用してmementoにカラム幅が保存されているときには、そっちのカラム幅を使用するよというコーディングができあがりました。

ソート処理

保存処理

DnD

TIP集

どのカラムをクリックしてもその行を選択状態にする

viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
               | SWT.V_SCROLL|SWT.FULL_SELECTION);

SWT.FULL_SELECTION がポイントです*3

プロパティ値を設定する。

String[] properties = new String[] { "text", "check", "color", "combo" };
// カラム・プロパティの設定
viewer.setColumnProperties(properties);

ICellModifier#getValue(Object element, String property)

のproperty値になります。

ICellModifier?などについては編集機能付きテーブルとはをご参照ください。

参考1 ColumnPixelData?JavaDoc?コメント

/**
 * Creates a resizable column width of the given number of pixels.
 *
 * @param widthInPixels the width of column in pixels
 */
public ColumnPixelData(int widthInPixels) {
  this(widthInPixels, true, false);
}

/**
 * Creates a column width of the given number of pixels.
 *
 * @param widthInPixels the width of column in pixels
 * @param resizable <code>true</code> if the column is resizable,
 *   and <code>false</code> if size of the column is fixed
 */
public ColumnPixelData(int widthInPixels, boolean resizable) {
this(widthInPixels, resizable, false);
}

/**
 * Creates a column width of the given number of pixels.
 * 
 * @param widthInPixels
 *      the width of column in pixels
 * @param resizable
 *      <code>true</code> if the column is resizable, and
 *      <code>false</code> if size of the column is fixed
 * @param addTrim
 *      <code>true</code> to allocate extra width to the column to
 *      account for trim taken by the column itself,
 *      <code>false</code> to use the given width exactly
 * @since 3.1
 */
public ColumnPixelData(int widthInPixels, boolean resizable, boolean addTrim) {
  super(resizable);
  Assert.isTrue(widthInPixels >= 0);
  this.width = widthInPixels;
this.addTrim = addTrim;
}

各カラムの背景色や文字の色、フォントなどを設定する

いろいろサイトを探して、みつかんねえなあ直接Tableをいじるしかねーかなと思ってたら、Eclipseをのソースを調べていて、見つけました。TableViewer?などにセットするLabelProvider?にITableColorProvider?、ITableFontProvider?をimplementsさせればよいのでした。しかし、これくらいの日本語情報がないとなると、、、、。。がんばんないとね。


この記事は

選択肢 投票
おもしろかった 21  
そうでもない 0  

現在のアクセス:31439


*1 TaskView?のソースでは、internalなインタフェースでした。だから自分で作っちゃおう
*2 ちなみにIMementoって変な名前はデザインパタンのメメントパターンのことですね。
*3 クリックに反応して選択した行を変えようと思って、いろんなリスナを探してしまった

添付ファイル: filepic01.png 1553件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-04-27 (金) 10:16:12 (485d)