JavaFXのTreeViewでアニメーションしてみる
この記事は2014年のJavaFX Advent Calendarの10日目の記事です。
前日の記事はorekyuuさんの"StringConverterとBinding"です。
明日の記事はYucchi_jpさんの"JavaFX の標準機能だけでシンプルな 3D トイピアノをつくろう"です。
TreeViewの各アイテムにチェックボックスを付けるやり方は、
Using JavaFX UI Controls: Tree View | JavaFX 2 Tutorials and Documentation
に書かれていますが、これをWindowsのエクスプローラーあるいはOSXのFinderのように使い、ディレクトリーツリーの各アイテムごとにチェックボックスとファイルに対するタスクの進捗状況を示すアイコンを表示してみたいと思い、いろいろと試行錯誤してみました。まあ、なんとか目的は達成しましたが、いろいろと躓いたので、ここで公開したいと思います。未解決の問題もありますが。
これを使い、WAVファイルからMP3へ変換するアプリを作成してみました。というか、変換するアプリを作りたいけど、どうせならJavaFXで作ってみようか、ということですね。
JavaFXのTreeViewでアニメーションしてみる - YouTube
(追記:YouTubeの動画に差し替えました)
ソース一式はここtoruwest/javafx-treeview-sample2 · GitHubにおいてあります。
追加のライブラリとして、lombok.jarが必要です。
Mp3ConverterApplication.javaがランチャーになります。
なお、ffmpegがインストールされているのを前提にしています(ConvertWorker.javaに記述)。それと、WAVフォーマットのファイルがないと変換できず、進捗の様子もわかりません。
これではすぐには試せない人もいるので、これらの条件を満たしていなくても使えるTreeViewProgressDemoApplication.javaというのも用意しました。
課題と対策
(1)変換開始前、変換中、変換終了後でボタンの状態を変えたい。JavaFXをあまり理解していないため、ここに一番時間がかかりましたが、何とか思うような動きが実現できました。例えば、変換開始時のPlatform.runLater()によるボタンへの変更が、変換終了後の
ボタンへの変更とほぼ同時に立て続けに行われたりしていました。
(2)traverseTreeForExpanding()メソッドで、ツリー上の各アイテムのチェック状態をトラバースして調べたかったが、TreeItem.getChildren()がTreeItemを返すようになっているため、自作のFileTreeItemに代入しようとするとコンパイルエラーになる。
対策:参考にしたソースがTreeItem<String>となっていたのを、TreeItem<ExtraProperty(自分で造ったクラス)>に変え、
このクラスにisSelectedというフィールドとgetter/setterを設けることにより、
TreeItemによりトラバースして各ノードのチェック状態を調べられるようになった。
(3)当初、task内のforループで複数のファイルを処理していたのですが、各ファイルごとに結果に応じてonSucceeded(), onFailed(), onCancelled()が呼ばれると思っていたら間違いで、全ての処理が終わってから一度だけ呼ばれます。当たり前か。
forループ内でtaskを生成し、各taskでは一つのファイルだけを処理するように直しました。
また、onFailed()を呼び出すために、何かメソッドを呼べばいいのかと思っていたら、
次のようにあえて例外を投げる方法しか見つかりませんでした。
throw new IllegalStateException();
(4)ツリー上のファイルのステータス(変換前、変換中、変換終了)を示すアイコンのリペイントができなかった。
対策:以下のように、親ノード(フォルダー)をいったん閉じた直後に展開することにより代用できた。
node.getParent().setExtended(false);
node.getParent().setExtended(true);
(5)変換中を示すためにアイコンをぐるぐる回したい。
対策:FileTreeItemという自作クラス内にsetStatusIconAnimated() というメソッドを用意し、変換開始時に呼び出すようにした。
JavaFXとアナログ時計 - ソフトウェアエンジニアリング - Torutk が参考になりました。ありがとうございます。
未解決の課題
(1)ボタンの制御を行うのに、bindを使っていないので、改善の余地がある。
(2)チェックボックスを無効(編集不可)にできない。
(3)ディレクトリー選択ダイアログDirectoryChooserで
、「NewFolder」というボタンを非表示にできない。OSXの場合だと「NewFolder」となるのですが、これを例えば「新しいフォルダ」に変える手段もわかりませんでした。
感想
(1)なぜsetEnabled()ではなくてsetDisable()?
(2)repaint()がないのでいろいろとhackしなくてはならない。JavaFXはそういうものだと書かれていた。(JavaFXはretained mode で、Swingはimmediate mode)
StackOverflow.comでTableViewをrepaintする方法についていろいろと議論されています。
java - JavaFX 2.1 TableView refresh items - Stack Overflow
JavaFX 2 TableView how to update cell when object is changed - Stack Overflow
(3)米オラクルのホームページでもドキュメントが不十分だと思う(個人的感想)
stackoverflow.comが役だった。
最後に
アイコン素材はhttp://findicons.com/、 http://www.free-icon.org、 http://www.shutterstock.comにあるのを使わせていただきました。