質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JavaFX

JavaFXとは、Java仮想マシン上で動作するリッチインターネットアプリケーション (RIA) のGUIライブラリです。Swingとは異なり、FXMLと呼ばれる XMLとCSSを併用してデザインを記述します。

Q&A

解決済

1回答

3157閲覧

TableViewで特定の属性を持つ行のみCheckBoxを表示したい

XCUBE

総合スコア101

JavaFX

JavaFXとは、Java仮想マシン上で動作するリッチインターネットアプリケーション (RIA) のGUIライブラリです。Swingとは異なり、FXMLと呼ばれる XMLとCSSを併用してデザインを記述します。

0グッド

0クリップ

投稿2017/12/28 02:28

表題の通りなのですが、TableViewにCheckBox付の列を表示するようにしていますが、データを構築する際に特定の属性を持つものだけCheckBoxを表示したいとやっているのですが、思うようにうまくいきません。

一部抜粋していますが、やり方として次のようなコードを書いています。

Java

1 2Callback<TableColumn<Parson, Boolean>, TableCell<Parson, Boolean>> callBackCheck = new Callback<TableColumn<Parson, Boolean>, TableCell<Parson, Boolean>>(){ 3 @Override 4 public TableCell<Parson,Boolean> call( TableColumn<Parson,Boolean> param ) { 5 return new CheckBoxTableCell<Parson, Boolean>() { 6 @Override 7 public void updateItem(Boolean item, boolean empty) { 8 super.updateItem(item, empty); 9 if (!empty && item != null) { 10 TableRow row = getTableRow(); 11 Parson selitem = (Parson)row.getItem(); 12 setVisible((selitem.getDataType() != 1)); // 属性により非表示にする 13 } 14 } 15 }; 16 } 17}; 18 19202122ObservableList<TableColumn<Parson, ?>> cols = tableView.getColumns(); 23TableColumn checkCol = cols.get(0); 24checkCol.setCellValueFactory(new PropertyValueFactory<>("view")); 25checkCol.setCellFactory(CheckBoxTableCell.forTableColumn((TableColumn<Parson, Boolean>)checkCol)); 26checkCol.setCellFactory(callBackCheck); 27

表示するTableViewのデータがスクロールしないほど少ない場合は問題ないのですが、データを増やしてスクロールさせてみるとCheckBoxが表示されているところとされてないところはあるのですが、属性と関係なく表示されたりされなかったりとなってうまくいきません。

そもそもやり方が間違っているのかもしれないと思い質問させていただきました。
よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KSwordOfHaste

2017/12/28 03:12

アイテムのdataTypeプロパティーはテーブル表示中に編集したり変化することがあるでしょうか?
XCUBE

2017/12/28 03:15

ありがとうございます。dataTypeは最初に表示してから変化することはないです。
KSwordOfHaste

2017/12/28 04:12

あ・・・すみません、トンチンカンな質問してしまいました。
guest

回答1

0

ベストアンサー

訂正:最初の回答は間違っていたようです。申し訳ありません。
CellのvisibilityはTableViewやCellの基底クラスの方の制御の影響を強く受けるようで、せっかくsetVisible(false)にしても直後にsetVisible(true)にされてしまうことがあるようです。多分Cell領域がTableViewの可視領域に入るとそのような動きになるのではないでしょうか(あくまで想像です)。

本件はどのような対処にするのがよいのか自分には少々難しいです。CheckBoxTableCellのようなカスタムセルの場合、setGraphic()やsetVisible()によって外観を制御しているのですが実際にどのような制御を行っているかは必ずしも明文化されてないと思います。下手に派生クラス側で制御しようとすると基底クラスに予期せぬ制御をされてしまう可能性がある以上、基底クラスの実装まで踏み込んでみないと確実な方法がわかりません。しかし本来隠蔽されているはずの実装に基づき実装するのは危険ですよね・・・。公開されている仕様のみでうまく制御できる方法が思いつけばそれはそれでよいのですが・・・

一応それらしく動くあまり乱暴ではなさそうな方法があったので一応参考までにコードを載せてみます。しかし充分にCheckBoxTableCellの振る舞いを調べた上での実装ではないのでこの実装でよいかどうかはCheckBoxTableCellがどのタイミングでsetGraphicを呼び出すかをよく調べた方がよいと思います。また、こうした対処の常でJDKのバージョンが変わる際に再度検証が必要かも知れません。JavaFXはJDK8時点では充分安定しておらずJDK9で大規模なリファクタリングが行われているようですので要注意と思います。

java

1Callback<TableColumn<Parson, Boolean>, TableCell<Parson, Boolean>> cb2 = tc -> new CheckBoxTableCell<Parson, Boolean>() { 2 @Override 3 public void updateItem(Boolean item, boolean empty) { 4 super.updateItem(item, empty); 5 updateVisiblity(); 6 } 7 8 @Override 9 public void updateIndex(int i) { 10 super.updateIndex(i); 11 updateVisiblity(); 12 } 13 14 private void updateVisiblity() { 15 int index = getIndex(); 16 boolean visibility = index != -1 && 17 getItem() != null && 18 getTableView().getItems().get(index).getDataType() != 1; 19 if (!visibility) 20 setGraphic(null); 21 } 22}; 23... 24checkCol.setCellFactory(cb2);

以下最初の回答

そもそもやり方が間違っているのかもしれない

個人的にはアプローチはよいと思います。ただCellの振る舞いについて注意が必要な点がありそこにはまってしまったのが原因だと思います。

###原因
Cellは特定のアイテムに結び付いているのではなく特定のプロパティの「値」に結び付いているだけなので、表示対象のBoolean値が変化した場合ならupdateItemが呼び出されますが、そうでない限りは例え別のインスタンスのプロパティーに再割り当てされたとしてもupdateItemは起動されないのです。

これは自分も全く同じことで過去にはまったのですが、ご質問を拝見したときすぐに気づけませんでした。自分にとってそれほどに勘違いしやすい仕様に思えますが、よーく考えてみると妥当な仕様だと納得した覚えがあります。表示内容は基本的に「表示しようとしている値に依存する」という考え方がベースのため「別のインスタンスに切り替わったからといってupdateItemを呼び出す必要はない」という設計なのでしょう。

###対処
updateItemによって状態を変化させるのではなく、表示対象のアイテムが切り替わったタイミングで変化させるという方法が考えられると思います。例えばソート順を変化させないならこのCellが置かれているTableRowの変化タイミングを検知すればそれができると思います。

Java

1return new CheckBoxTableCell<Parson, Boolean>() { 2 { 3 tableRowProperty().addListener((observable, oldRow, newRow) -> { 4 Parson item = (Parson)newRow.getItem(); 5 setVisible(item.getDataType() != 1); 6 }); 7 } 8};

しかしソート順が変化した場合はどうなるでしょうか・・・やってみてないので実験してみるとよいと思います。ちゃんと調べてないのですが最新のJDK8ではソート順が変化してもtableRowとアイテムの対応関係は変化しないのではないかと思います。

ただJDK8の途中でTableViewのソートの実装が変わったという情報を見たことがあるので念のためtableRowプロパティーとtableRowプロパティーのitemプロパティーの両方を監視した方がよいのかも知れません。(その点は自信ないので確認した方がよいと思います)
もしそこまで配慮するなら以下のようにできます。

Java

1return new CheckBoxTableCell<Parson, Boolean>() { 2 ChangeListener<Parson> itemListener = (observable, oldItem, newItem) -> { 3 // newItemに従ってセルの状態を変更する処理 4 Parson item = (Parson)newRow.getItem(); 5 setVisible(item.getDataType() != 1); 6 }; 7 { // Cellインスタンスの初期化ブロック(無名クラスにはコンストラクターが書けないので...) 8 tableRowProperty().addListener((observable, oldRow, newRow) -> { 9 if (oldRow != null) oldRow.itemProperty().removeListener(itemListener); 10 if (newRow != null) newRow.itemProperty().addListener(itemListener); 11 }); 12 } 13};

(ご質問のコードではlambda式ではなく無名クラスが使われていたりしますね。でもJavaFXなのでJava8前提でよい気がします。そこでlambda式を用いたコード例にしました)

投稿2017/12/28 04:48

編集2017/12/28 09:51
KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

XCUBE

2017/12/28 05:51 編集

ご回答ありがとうございます。 投降後に色々試行錯誤していました。 質問にも書いていましたか、スクロールしないような恰好で表示すれば問題なかったので、一旦TableViewを非表示にしてHeightを極端に高くしてデータを投入後にまたHeightを元に戻すという姑息な手段で逃げようとしましたが、ご指摘の通りソート時に表示がおかしくなりました。 初歩的な質問で申し訳ないのですが、ご回答いただいたラムダ式を使用した場合、CheckBoxをどのように参照すればよいかわかりません。 CheckBox.setVisible(newItem.getDataType() != 1); で表示/非表示をしたいのです。
KSwordOfHaste

2017/12/28 06:00 編集

無名クラス内での書き方と同じです。最初のコード例でtableRowPropertyのChangeListenerをラムダ式で表現していますが{}の中身は同じですよね?後のコード例で // newItemに従ってセルの状態を変更する処理 とコメントになっている部分に同じコードを書けばよいです。 --- はっきりさせるために解答のコードに上記を反映しておきました。
XCUBE

2017/12/28 06:33

ほんとうにありかどうございます。 教えていただいたもので以下のようなコードに変更してみましたが thisが親クラス(実行クラス)を指してしまいnewItemもParsonを指すようです。 何かお気づきの点などご指摘いただいたら幸いです。 Callback<TableColumn<Parson, Boolean>, TableCell<Parson, Boolean>> callBackCheck = new Callback<TableColumn<Parson, Boolean>, TableCell<Parson, Boolean>>(){ @Override public TableCell<Parson,Boolean> call( TableColumn<Parson,Boolean> param ) { return new CheckBoxTableCell<Parson, Boolean>() { ChangeListener<Parson> itemListener = (observable, oldItem, newItem) -> { // newItemに従ってセルの状態を変更する処理 Parson item = (Parson)newItem.getItem(); setVisible(item.getDataType() != 1); }; { // Cellインスタンスの初期化ブロック(無名クラスにはコンストラクターが書けないので...) tableRowProperty().addListener((observable, oldRow, newRow) -> { if (oldRow != null) oldRow.itemProperty().removeListener(itemListener); if (newRow != null) newRow.itemProperty().addListener(itemListener); }); } }; } };
XCUBE

2017/12/28 07:06 編集

thisが親クラスになるのは私の勘違いでした。すいません。 ご教示いただいたコードを元に再度確認し、ソートをしてみたのですが、非表示にはできませんでした。 試しにソート時に setVisible(false); System.out.println(isVisible()); で属性に関係なくすべて非表示にしたところ非表示にはならず、ログもすべてtrueになっていました。 一応updateItemでは非表示にできましたが、ソート後の動作がうまくいかず、ソートのイベントでのsetVisible(false)も効かずといった感じです。 もう少し試行錯誤してみます。
KSwordOfHaste

2017/12/28 07:57 編集

すみません、実際にやってみたのですが上のコードは間違ってる点がありました。また他に配慮不足の点もあるようで少し試したのですがすんなり動いてくれません>< 自分でも改めて調べてみます。ちなみに自分はJDK9でやっているのですがXCUBEさんはJava1.8.0_152とかですか?できるなら合わせた方がよいかも知れませんので。
XCUBE

2017/12/28 08:12

ありがとうございます。 Javaのバージョンは1.8.0_151の32bit版です(お客様の環境の32bitなもので…)。 KSwordOfHasteさんの貴重なお時間を費やすのは気が引けます・・・ 気持ちだけでもほんとうにうれしいです。
KSwordOfHaste

2017/12/28 08:34

JDK9とJDK8ではTableViewまわりでもいくつも変更があるようなのでJDK8でやってみることにします。なんとなく原因がわかりました。visibleはTableViewがセルが見えるようになったときに制御するためのもののようなのでこれを自前で制御するのはよくなさそうな感じです。上の回答が間違っている気がしますので、自分でももう少し調べさせてください。 ちなみに、お気遣いは無用です。そもそも自分の回答が間違っていたのですし、こういう問題は自分にとっても勉強になります。
XCUBE

2018/01/05 01:36

検証いただきましてありがとうございます。 本件では特定の行のCheckBoxを変更できないようにするのが本来の目的の一つでもありますが、 setVisible(false);が効かないのであればsetDisable(true);で代用すれば、その目的は一応達成できます。 これによりㇾのON/OFFはできなくなるのですが、見た目がグレーアウトにならず、利用者にON/OFFができるように見えてしまうという問題があります。 □の枠線の色を透明もしくは背景色と同じ色に変えることができれば、疑似的にsetVisible(false);にできると思うので、ちょっとその方法を模索してみます。 大変ありがとうございました。心より感謝いたします。
XCUBE

2018/01/05 03:32 編集

上記、コメントでsetDisable(true);を代用するとしましたが、それでも並び替えるとうまくいきませんでした。 ですが、結果的にsetVisible(false);でもなくsetDisable(true);でもなくsetEditable(false);で解決しました。 見た目を非表示にするため、非表示にしたいcheckboxにIdを設定し、そのIdに対して外枠、背景などを透明にしてstyleをcssに記述しました。 その結果、疑似的ではありますが、非表示にすることができましたが、ひとつ疑問ができました。 以下のように疑似要素として disabled を付加しないとstyleが適用できませんでした。 setDisable(true);にはしていないのですが、setEditable(false);も内部的にはDisable扱いなんだなということで理解することにしました。 #Id:disabled {   -fx-background-color: transparent;   -fx-border-color: transparent;   -fx-fill: transparent; }
KSwordOfHaste

2018/01/05 03:40

なるほど!CheckBoxTableCellの実装を気にするよりはXCUBEさんの採られた方法の方がずっと適切だと思いました。なお、そのようにするのでしたら疑似クラスを設定してもよい気がします。Cellに対して cell.getStyleClass().add("hidden") cell.getStyleClass().remove("hidden") とかやるわけです。独自のスタイルクラスなのでJavaFXランタイムがそれを勝手に変更する心配はないですし・・・css上も .hidden { ... } といった感じに書けますね。IDを振る必要は特にないのでは?
XCUBE

2018/01/05 04:51

アドバイスありがとうございます。 TableViewクラスの不具合なのか私のやり方が悪いのか.hiddenはうまくいきませんでした。 私としてもあまりIdは付けたくなかったのですが・・・該当するcellに直接3つのstyleを追加するか迷った結果Idを付けることにしました。 hiddenという疑似クラスがあるということが判ったことだけでも収穫です。
KSwordOfHaste

2018/01/05 04:56 編集

いえ、hiddenという疑似クラスは標準にはありません。アプリケーションで独自に定義するものです。それっぽい名前として"hidden"にしたまでです。 --- もとい・・・標準にありました!申し訳ないですがhiddenではなく独自の疑似クラス名を使った方がよいと思います。
XCUBE

2018/01/05 07:50

そうやって独自のセレクタを追加できるんですか、知りませんでした。 今までIDもしくは標準のセレクタで対応していました。 そういえば暫くやっていないですがHTMLを記述するときもIDとCLASSを使う方法がありましたのを思い出しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問