JavaFX8で業務アプリを作っている際に踏んでしまったバグ JDK-8144681。
私のとった回避策とソースコードをご紹介します。
1. 不具合JDK-8144681 とは?
javafx.scene.control.TableView<S>
において カラムの並び替え(re-ordering) を行った際に発生する不具合。
前提条件
- TableViewのセル複数選択を許可している(SelectionMode.MULTIPLE)
再現手順
- TableViewのデータセルを複数選択する
- ヘッダーセルをドラッグし、カラムの並び替えを行う
→ ドロップした際にArrayIndexOutOfBoundsException
が発生
2. 回避策
JavaFX8のライブラリ内のソースコードのため、根本原因を修正することは厳しい。
ので、例外が発生しないように回避策を立てる必要があります。
- カラムの並び替えを使用不可にする
- カラムの並び替え直前に選択状態を解除する
- カラムの並び替えイベント発生時に選択状態を解除する
今回は「カラムの並び替え直前に選択状態を解除する」を対策として採用しました。
不採用の理由
使用不可
そもそも並び替えしなければ発生しない!
ただ、JavaFX8のTableViewデフォルト動作としてある「並び替え」機能を使用禁止にしてしまうとユーザビリティが落ちてしまう。そのため今回この策は見送った。
イベント発生時に選択状態を解除
調査して分かったのだが、カラム並び替えの時「並び替え」を示すイベントが発生しない。
そのため並び替えをピンポイントで捕まえ、何かのアクションを仕掛けることができなかった
3. ソースコード
ドラッグのイベントは(なぜか)大元のTableViewで発生する。
他のドラッグイベントと区別するために、イベント発生源(target)がヘッダーに属しているかを判定しアクションを行うとする。
// [JDK-8144681] 不具合回避のため reordering直前にselect/focusを解除する
tableView.addEventFilter(MouseEvent.MOUSE_DRAGGED, event -> {
boolean isHeaderNode = false;
// 親ノードを探索し TableColumnHeaderに属しているかを調べる
for (Node node = (Node) event.getTarget() ; ; node = node.getParent()) {
if(node == null || node instanceof TableView) {
break;
}
if(node instanceof TableColumnHeader){
isHeaderNode = true;
break;
}
}
if(! isHeaderNode) return;
tableView.getSelectionModel().clearSelection();
tableView.getFocusModel().focus(-1);
});
まとめ
かなり限定的な条件で発生する不具合ではありますが、JDK8でTableViewを使用すると必ず発生するので対策しておいて損はないと思います。