JavaFX TreeViewのカスタマイズ - その2：TreeCellの作成

[前回](http://qiita.com/sipadan2003/items/71ddbf0259ce89ba5243 "前回")に続いて、今回は独自のTreeCellを作ります。

TreeViewのTreeCellでラベルの編集を可能にするには、TreeView.setEditable(true)を実行するだけではダメで、ラベルを編集できるようにTreeCellを自前で作る必要もあります。今回はラベルを編集できるTreeCellを作ることにします。

編集していない状態のTreeCellは、下図のように左側から順にStackPane、ImageView、LabeledTextが貼り付けられています。StackPaneが▲を、ImageViewがアイコンを、LabeledText（非公開クラス）がラベルを表示します。

![TreeCellの構成](https://qiita-image-store.s3.amazonaws.com/0/33023/2e2d770d-34c4-3fdf-749d-14c123b40c1e.gif "TreeCellの構成")

[JavaFX Documentation](http://docs.oracle.com/javafx/ "JavaFX Documentation")の[Tree View](http://docs.oracle.com/javafx/2/ui_controls/tree-view.htm "Tree View")に、TreeCellの作成方法が書いてあります。そこのExample 13-3を見てください。

```java:startEdit()から抜粋
if (textField == null) {
    createTextField();
}
setText(null);
setGraphic(textField);
```

TextFieldTreeCellImplクラスにstartEdit()がありますが、そこでsetText(null)でラベルを消してsetGraphic(textField)でImageViewをTextFieldに置き換えています。つまり、ラベルを一時的に非表示にして、TextFieldで編集ができるようにしているわけです。
しかし、ImageViewをそのままTextFieldに置き換えているので、ラベル編集中は下図のようにアイコンがありません。ちょっとナンセンスです(^^;

![編集中のTreeCell](http://docs.oracle.com/javafx/2/ui_controls/img/tree-view-hr2.png "編集中のTreeCell")

では、どうすればいいのかというと、setGraphic()ではTextFieldだけでなくImageViewも一緒に貼り付ければいいのです。下図がそのイメージです。LabeledTextはラベルを消しているので、幅が無い状態です。

![編集中のTreeCellの構成](https://qiita-image-store.s3.amazonaws.com/0/33023/facb1b6d-4e77-e1b6-59a5-e8e49c651bbb.gif "編集中のTreeCellの構成")

では、TreeCellの派生クラスを作ってみます。次はその全コードです。

```java:TreeCellImpl.java
package jfxtreeview;

import javafx.scene.control.TreeCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

/**
 *TreeCellです。
 */
final class TreeCellImpl extends TreeCell<TreeItemData> {
	private static final Image GROUP_IMAGE = createImage("folder.gif");
	private static final Image ITEM_IMAGE = createImage("file.gif");
	//TreeViewコントローラ
	private final TreeViewController controller;
	//編集用グラフィック
	private TreeCellGraph graph;

	/**
	 *コンストラクタです。
	 */
	TreeCellImpl(TreeViewController controller){
		this.controller = controller;
	}

	@Override
	public void startEdit() {
		super.startEdit();
		//編集用Graphicを生成する
		if(this.graph == null){
			this.graph = new TreeCellGraph(this.controller, this, createImageView(getItem()));
		}
		//編集開始前の処理をTreeCellGraphに委譲
		this.graph.startEdit();
		//編集時はLabeledTextにラベルを表示させない
		setText(null);
		setGraphic(this.graph);
	}

	@Override
	public void cancelEdit(){
		super.cancelEdit();
		//アイコンとラベルを元に戻す
		setText(getItem().getName());
		setGraphic(createImageView(getItem()));
	}

	@Override
	public void updateItem(TreeItemData data, boolean empty){
		super.updateItem(data, empty);
		if(empty){
			//空の場合は、ラベルもアイコンも表示させない
			setText(null);
			setGraphic(null);
		}else if(isEditing()){
			//編集時はLabeledTextにラベルを表示させない
			setText(null);
			setGraphic(this.graph);
		} else {
			//通常の表示
			setText(data.getName());
			setGraphic(createImageView(data));
		}
	}

	/**
	 *画像をロードしてImageを生成します。
	 */
	private static Image createImage(String fname){
		return new Image(TreeCellImpl.class.getResourceAsStream(fname));
	}

	/**
	 *データの種類に応じたImageViewを生成します。
	 */
	private static ImageView createImageView(TreeItemData data){
		final Image img = (data.getType() == TreeItemData.Type.GROUP) ?
			GROUP_IMAGE : ITEM_IMAGE;
		return new ImageView(img);
	}
}
```

以下、実装内容を説明します。

```java:TreeCellImplのクラス宣言
class TreeCellImpl extends TreeCell<TreeItemData> {
```

TreeCellの派生クラスとして、TreeCellImplを定義します。
[前回](http://qiita.com/sipadan2003/items/71ddbf0259ce89ba5243 "前回")紹介したTreeItemDataをTreeItemのデータとして扱うので、TreeItemDataをジェネリクスで指定しています。

```java:TreeCellImplのフィールド

	private static final Image GROUP_IMAGE = createImage("folder.gif");
	private static final Image ITEM_IMAGE = createImage("file.gif");
	//TreeViewコントローラ
	private final TreeViewController controller;
	//編集用グラフィック
	private TreeCellGraph graph;
```

TreeCellImplには4つのフィールドがあります。

+ GROUP_IMAGEとITEM_IMAGEは、フォルダとファイルのアイコンイメージです。staticフィールドなので、最初のTreeCellImplを生成するときにだけアイコンをロードします。一度ロードしたアイコンはすべてのTreeCellImplのインスタンスで共有します。
+ controllerは、TreeViewを制御するコントローラです。TreeViewを制御するものですが、改めて紹介します。
+ graphは、冒頭で説明したとおり、ラベルを編集するときにTextFieldだけでなくアイコンも一緒に表示するための部品です。少々実装が複雑なので、別クラスTreeCellGraphで定義することにします。これも改めて紹介します。

```java:TreeCellImplのコールバックメソッド
	@Override
	public void startEdit() {
		super.startEdit();
		//編集用Graphicを生成する
		if(this.graph == null){
			this.graph = new TreeCellGraph(this.controller, this, createImageView(getItem()));
		}
		//編集開始前の処理をTreeCellGraphに委譲
		this.graph.startEdit();
		//編集時はLabeledTextにラベルを表示させない
		setText(null);
		setGraphic(this.graph);
	}

	@Override
	public void cancelEdit(){
		super.cancelEdit();
		//アイコンとラベルを元に戻す
		setText(getItem().getName());
		setGraphic(createImageView(getItem()));
	}

	@Override
	public void updateItem(TreeItemData data, boolean empty){
		super.updateItem(data, empty);
		if(empty){
			//空の場合は、ラベルもアイコンも表示させない
			setText(null);
			setGraphic(null);
		}else if(isEditing()){
			//編集時はLabeledTextにラベルを表示させない
			setText(null);
			setGraphic(this.graph);
		} else {
			//通常の表示
			setText(data.getName());
			setGraphic(createImageView(data));
		}
	}
```

startEdit()、cancelEdit()、updateItem()はコールバックメソッドでもあります。それぞれ、次のタイミングで呼び出されます。

+ startEdit(): 編集開始するときに呼び出される
+ cancelEdit(): 編集をキャンセルするときに呼び出される
+ updateItem(): TreeCellの再描画が必要なときに呼び出される

これらのメソッドをオーバーライドして、TreeCellの編集状態などに応じてラベルとアイコンの表示を切り替えるために、setText()とsetGraphic()を呼び出す処理を追加しているわけです。
TreeCellImplの実装は以上です。

次回は、ImageViewとTextField両方を並べて表示するTextCellGraphクラスを紹介します。