Javafx:如何在 TreeTableView 中实现三态复选框
Javafx: How to implement 3-state checkboxes inside a TreeTableView
我正在尝试在 javafx 中实现 TreeTableView,其中第一列包含字符串值,第三列将呈现为三态复选框。
使用以下 MCVE,我可以获得一棵树 table,但是复选框中的 none 选项仍然存在 parent collapse/expand 或 table 的调整大小.
MCVE
Class A 是 parent.
Class B 扩展了 A 并且是 child.
Class C 表示第 2 列,(呈现为复选框)
Class MVCECheckBox 构建树table 并显示它。
A.java
package mcve.checkbox;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* @author returncode13
*/
public class A {
StringProperty name=new SimpleStringProperty();
C check=new C();
List<A> children;
public StringProperty getName() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public C getCheck() {
return check;
}
public void setCheck(C check) {
this.check = check;
}
public StringProperty nameProperty(){
return name;
}
public List<A> getChildren() {
return children;
}
public void setChildren(List<A> children) {
this.children = children;
}
}
B.java
package mcve.checkbox;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* @author returncode13
*/
public class B extends A{
StringProperty name=new SimpleStringProperty();
C Check=new C();
@Override
public StringProperty getName() {
return name;
}
@Override
public void setName(String name) {
this.name.set(name);
}
@Override
public C getCheck() {
return Check;
}
@Override
public void setCheck(C Check) {
this.Check = Check;
}
@Override
public StringProperty nameProperty(){
return name;
}
}
C.java
package mcve.checkbox;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
*
* @author returncode13
*/
public class C {
BooleanProperty checkUncheck=new SimpleBooleanProperty();
BooleanProperty indeterminate=new SimpleBooleanProperty();
public BooleanProperty getCheckUncheck() {
return checkUncheck;
}
public void setCheckUncheck(BooleanProperty checkUncheck) {
this.checkUncheck = checkUncheck;
}
public BooleanProperty getIndeterminate() {
return indeterminate;
}
public void setIndeterminate(BooleanProperty indeterminate) {
this.indeterminate = indeterminate;
}
public BooleanProperty checkUncheckProperty(){
return checkUncheck;
}
public BooleanProperty indeterminateProperty(){
return indeterminate;
}
}
MCVECheckBox.java
package mcve.checkbox;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
*
* @author returncode13
*/
public class MCVECheckBox extends Application {
A selectedItem;
private TreeTableView<A> treetable=new TreeTableView<>();
@Override
public void start(Stage primaryStage) {
//setting up parents (A) and children (B)
A a1=new A();
a1.setName("A1");
List<A> A1Children=new ArrayList();
B b11=new B();
b11.setName("B11");
B b12=new B();
b12.setName("B12");
A1Children.add(b11);
A1Children.add(b12);
a1.setChildren(A1Children);
A a2=new A();
a2.setName("A2");
List<A> A2Children=new ArrayList();
B b21=new B();
b21.setName("B21");
B b22=new B();
b22.setName("B22");
A2Children.add(b21);
A2Children.add(b22);
a2.setChildren(A2Children);
//tree columns . first one holds strings
TreeTableColumn<A,String> name=new TreeTableColumn<>("Name");
name.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
//2nd tree columns. rendered as checkboxes. boolean values
TreeTableColumn<A,Boolean> checks=new TreeTableColumn<>("Checks");
checks.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<A, Boolean>, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> call(TreeTableColumn.CellDataFeatures<A, Boolean> param) {
A a=param.getValue().getValue();
SimpleBooleanProperty checkUncheck=new SimpleBooleanProperty();
SimpleBooleanProperty indeterminate=new SimpleBooleanProperty();
checkUncheck=(SimpleBooleanProperty) a.getCheck().getCheckUncheck();
indeterminate=(SimpleBooleanProperty) a.getCheck().getIndeterminate();
//to do: set parents status based on children status.
if(indeterminate.get()){
return indeterminate;
}else{
return checkUncheck;
}
}
});
checks.setCellFactory(new Callback<TreeTableColumn<A, Boolean>, TreeTableCell<A, Boolean>>() {
@Override
public TreeTableCell<A, Boolean> call(TreeTableColumn<A, Boolean> param) {
return new CheckBoxCell();
}
});
//building the tree;
TreeItem<A> a1item=new TreeItem<>(a1);
TreeItem<A> b11item=new TreeItem<>(b11);
TreeItem<A> b12item=new TreeItem<>(b12);
a1item.getChildren().add(b11item);
a1item.getChildren().add(b12item);
TreeItem<A> a2item=new TreeItem<>(a2);
TreeItem<A> b21item=new TreeItem<>(b21);
TreeItem<A> b22item=new TreeItem<>(b22);
a2item.getChildren().add(b21item);
a2item.getChildren().add(b22item);
TreeItem<A> root=new TreeItem<>();
root.getChildren().add(a1item);
root.getChildren().add(a2item);
treetable.getColumns().addAll(name,checks);
treetable.setRoot(root);
treetable.setShowRoot(false);
treetable.setEditable(true);
// StackPane rootSp = new StackPane();
Scene scene = new Scene(treetable, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
//to render checkboxes in treetable
private class CheckBoxCell extends TreeTableCell<A, Boolean> {
CheckBox checkbox;
public CheckBoxCell() {
checkbox=new CheckBox();
checkbox.setAllowIndeterminate(true);
checkbox.selectedProperty().addListener((obs,wasSelected,isNowSelected) -> {
if(isNowSelected){
selectedItem=getTreeTableRow().getItem();
}
});
}
@Override
public void updateItem(Boolean b,boolean empty){
super.updateItem(b, empty);
if(empty){
setGraphic(null);
}else{
checkbox.setSelected(b);
setGraphic(checkbox);
}
}
}
}
我之前曾尝试使用 CheckTreeTableCell 在第二列设置单元格工厂,但很快发现 CheckTreeTableCell 不支持三态(选中、取消选中、不确定)复选框。
之后我尝试实现上面的代码。尽管我能够引入三态复选框,但我无法让它们的状态持续存在。每次 parent 为 collapsed/expanded 时,对其 children 进行的检查将被取消选择。
感谢您在确定修复方面提供的任何帮助。
我现在可以通过对发布的 MCVE 进行以下修改来实现三态复选框,它现在是一个完整的工作示例。
A.java (parent class)
package com.mycompany.yetanothercheckbox;
import java.util.Iterator;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* @author returncode13
*/
public class A {
private StringProperty name=new SimpleStringProperty();
C check=new C();
List<A> children;
final boolean isLeaf=false;
final boolean isParent=true;
public boolean updateParent=false;
public boolean updateChildren=false;
public boolean isLeaf() {
return isLeaf;
}
public boolean isParent() {
return isParent;
}
public StringProperty getName() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public C getCheck() {
return check;
}
public void setCheck(C check) {
this.check = check;
for (Iterator<A> iterator = children.iterator(); iterator.hasNext();) {
A next = iterator.next();
next.setCheck(check);
}
}
public StringProperty nameProperty(){
return name;
}
public List<A> getChildren() {
return children;
}
public void setChildren(List<A> children) {
this.children = children;
}
public A getParent() {
return this;
}
}
B.java (child class)
package com.mycompany.yetanothercheckbox;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* @author returncode13
*/
public class B extends A{
private StringProperty name=new SimpleStringProperty();
C Check=new C();
final boolean isLeaf=true;
final boolean isParent=false;
public boolean updateParent=false;
public boolean updateChildren=false;
A parent;
public A getParent() {
return parent;
}
public void setParent(A parent) {
this.parent = parent;
}
@Override
public boolean isLeaf() {
return isLeaf;
}
@Override
public boolean isParent() {
return isParent;
}
@Override
public StringProperty getName() {
return name;
}
@Override
public void setName(String name) {
this.name.set(name);
}
@Override
public C getCheck() {
return Check;
}
@Override
public void setCheck(C Check) {
this.Check = Check;
}
@Override
public StringProperty nameProperty(){
return name;
}
@Override
public List<A> getChildren() {
return parent.getChildren();
}
}
C.java(保持检查状态)
package com.mycompany.yetanothercheckbox;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
*
* @author returncode13
*/
public class C {
BooleanProperty checkUncheck=new SimpleBooleanProperty();
BooleanProperty indeterminate=new SimpleBooleanProperty();
public BooleanProperty getCheckUncheck() {
return checkUncheck;
}
public void setCheckUncheck(BooleanProperty checkUncheck) {
this.checkUncheck = checkUncheck;
}
public BooleanProperty getIndeterminate() {
return indeterminate;
}
public void setIndeterminate(BooleanProperty indeterminate) {
this.indeterminate = indeterminate;
}
public BooleanProperty checkUncheckProperty(){
return checkUncheck;
}
public BooleanProperty indeterminateProperty(){
return indeterminate;
}
}
ThreeStateCheckBoxTreeTableCell.java(树的 3state 复选框 table)
package com.mycompany.yetanothercheckbox;
import java.util.List;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.input.MouseEvent;
/**
*
* @author returncode13
*/
//to render checkboxes in treetable
public class ThreeStateCheckBoxTreeTableCell extends TreeTableCell<A, Boolean> {
A selectedItem;
CheckBox checkbox;
TreeTableColumn<A,Boolean> param;
/*private static boolean updateParent=false;
private static boolean updateChildren=false;*/
public ThreeStateCheckBoxTreeTableCell(TreeTableColumn<A,Boolean> param) {
checkbox=new CheckBox();
this.param=param;
checkbox.setAllowIndeterminate(true);
checkbox.selectedProperty().addListener((obs,wasSelected,isNowSelected) -> {
int sel=getTreeTableRow().getIndex();
selectedItem=this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getCheckUncheck().set(isNowSelected);
selectedItem.getCheck().getIndeterminate().set(false);
//ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
});
checkbox.indeterminateProperty().addListener((obx,ol,newV)->{
int sel=getTreeTableRow().getIndex();
selectedItem=this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getIndeterminate().set(newV);
//ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
});
checkbox.setOnMouseClicked(new EventHandler<MouseEvent>(){
@Override
public void handle(MouseEvent event) {
int sel=getTreeTableRow().getIndex();
selectedItem=ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
if(selectedItem.isParent()){
selectedItem.updateChildren=true;
for(A child:selectedItem.getChildren()){
child.updateParent=false;
}
updateDownwards();
}
if(selectedItem.isLeaf()){
selectedItem.getParent().updateChildren=false;
selectedItem.updateParent=true;
updateUpWards();
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
});
}
@Override
public void updateItem(Boolean b,boolean empty){
super.updateItem(b, empty);
if(empty){
setGraphic(null);
}else{
if(b==null){
checkbox.setIndeterminate(true);
}
else{
checkbox.setIndeterminate(false);
checkbox.setSelected(b);
}
setGraphic(checkbox);
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
private void updateUpWards(){
if(selectedItem.updateParent){
List<A> children=selectedItem.getChildren();
int indeterminateCount=0;
int selectedCount=0;
A parent=selectedItem.getParent();
for(A child:children){
indeterminateCount+=child.getCheck().getIndeterminate().get()?1:0;
selectedCount+=child.getCheck().getCheckUncheck().get()?1:0;
}
if(indeterminateCount>0) {
parent.getCheck().getIndeterminate().set(true);
}
else if(indeterminateCount==0 && selectedCount==children.size()){
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(true);
}else{
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(false);
}
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
private void updateDownwards(){
List<A> children=selectedItem.getChildren();
if(selectedItem.isParent() && selectedItem.updateChildren ){
for(A child:children){
child.getCheck().getCheckUncheck().set(selectedItem.getCheck().getCheckUncheck().get());
child.getCheck().getIndeterminate().set(selectedItem.getCheck().getIndeterminate().get());
}
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
}
MainApp.java(作为 POC 申请)
package com.mycompany.yetanothercheckbox;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.stage.Stage;
import javafx.util.Callback;
public class MainApp extends Application {
private TreeTableView<A> treetable=new TreeTableView<>();
@Override
public void start(Stage primaryStage) {
//setting up parents (A) and children (B)
A a1=new A();
a1.setName("A1");
List<A> A1Children=new ArrayList();
B b11=new B();
b11.setName("B11");
B b12=new B();
b12.setName("B12");
b11.setParent(a1);
b12.setParent(a1);
A1Children.add(b11);
A1Children.add(b12);
a1.setChildren(A1Children);
A a2=new A();
a2.setName("A2");
List<A> A2Children=new ArrayList();
B b21=new B();
b21.setName("B21");
B b22=new B();
b22.setName("B22");
b21.setParent(a2);
b22.setParent(a2);
A2Children.add(b21);
A2Children.add(b22);
a2.setChildren(A2Children);
//tree columns . first one holds strings
TreeTableColumn<A,String> name=new TreeTableColumn<>("Name");
name.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
//2nd tree columns. rendered as checkboxes. boolean values
TreeTableColumn<A,Boolean> checks=new TreeTableColumn<>("Checks");
checks.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<A, Boolean>, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> call(TreeTableColumn.CellDataFeatures<A, Boolean> param) {
A a=param.getValue().getValue();
SimpleBooleanProperty checkUncheck=new SimpleBooleanProperty();
SimpleBooleanProperty indeterminate=new SimpleBooleanProperty();
checkUncheck.bindBidirectional(a.getCheck().getCheckUncheck());
indeterminate.bindBidirectional(a.getCheck().getIndeterminate());
checkUncheck.addListener(new ChangeListener<Boolean>(){
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
a.getCheck().indeterminateProperty().set(false);
a.getCheck().checkUncheckProperty().set(newValue);
}
});
indeterminate.addListener(new ChangeListener<Boolean>(){
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
a.getCheck().indeterminateProperty().set(newValue);
}
});
if(indeterminate.get()){
return null;
}else{
return checkUncheck;
}
}
});
checks.setCellFactory(new Callback<TreeTableColumn<A, Boolean>, TreeTableCell<A, Boolean>>() {
@Override
public TreeTableCell<A, Boolean> call(TreeTableColumn<A, Boolean> param) {
return new ThreeStateCheckBoxTreeTableCell(param);
}
});
//building the tree;
TreeItem<A> a1item=new TreeItem<>(a1);
TreeItem<A> b11item=new TreeItem<>(b11);
TreeItem<A> b12item=new TreeItem<>(b12);
a1item.getChildren().add(b11item);
a1item.getChildren().add(b12item);
TreeItem<A> a2item=new TreeItem<>(a2);
TreeItem<A> b21item=new TreeItem<>(b21);
TreeItem<A> b22item=new TreeItem<>(b22);
a2item.getChildren().add(b21item);
a2item.getChildren().add(b22item);
TreeItem<A> root=new TreeItem<>();
root.getChildren().add(a1item);
root.getChildren().add(a2item);
treetable.getColumns().addAll(name,checks);
treetable.setRoot(root);
treetable.setShowRoot(false);
treetable.setEditable(true);
// StackPane rootSp = new StackPane();
Scene scene = new Scene(treetable, 300, 250);
primaryStage.setTitle("CheckBoxTreeTable Example");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
非常感谢您提供这段很棒的代码,它让您深入了解树项 table 单元格(
不存在三态复选框这一事实
对于那些将以此 post 为食的人,我在 class '' 上做了一些小的改动。现在看起来像这样:
package com.mycompany.yetanothercheckbox;
import java.util.List;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
/**
*
* @author returncode13
*/
// to render checkboxes in treetable
public class ThreeStateCheckBoxTreeTableCell extends TreeTableCell<A, Boolean> {
A selectedItem;
CheckBox checkbox;
TreeTableColumn<A, Boolean> param;
/*
* private static boolean updateParent=false;
* private static boolean updateChildren=false;
*/
public ThreeStateCheckBoxTreeTableCell(TreeTableColumn<A, Boolean> param) {
checkbox = new CheckBox();
this.param = param;
checkbox.setAllowIndeterminate(true);
checkbox.selectedProperty().addListener((obs,
oldSelectedVal,
newSelectedVal) -> {
int sel = getTreeTableRow().getIndex();
selectedItem = this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getCheckUncheck().set(newSelectedVal);
selectedItem.getCheck().getIndeterminate().set(false);
});
checkbox.indeterminateProperty().addListener((obx,
ol,
newV) -> {
int sel = getTreeTableRow().getIndex();
selectedItem = this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getIndeterminate().set(newV);
});
checkbox.setOnMouseClicked(event -> {
int sel = getTreeTableRow().getIndex();
selectedItem = this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
if (selectedItem.isParent()) {
selectedItem.updateChildren = true;
for (A child : selectedItem.getChildren()) { child.updateParent = false; }
updateDownwards();
}
if (selectedItem.isLeaf()) {
selectedItem.getParent().updateChildren = false;
selectedItem.updateParent = true;
updateUpWards();
}
});
}
@Override
public void updateItem(Boolean b,
boolean empty) {
super.updateItem(b, empty);
if (empty) {
setGraphic(null);
} else {
if (b == null) {
checkbox.setIndeterminate(true);
} else {
checkbox.setIndeterminate(false);
checkbox.setSelected(b);
}
setGraphic(checkbox);
}
}
private void updateUpWards() {
if (selectedItem.updateParent) {
List<A> children = selectedItem.getChildren();
int indeterminateCount = 0;
int selectedCount = 0;
A parent = selectedItem.getParent();
for (A child : children) {
indeterminateCount += child.getCheck().getIndeterminate().get() ? 1 : 0;
selectedCount += child.getCheck().getCheckUncheck().get() ? 1 : 0;
}
if (indeterminateCount > 0) {
parent.getCheck().getIndeterminate().set(true);
} else if (indeterminateCount == 0
&& selectedCount == children.size()) {
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(true);
} else {
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(false);
}
}
this.param.getTreeTableView().refresh();
}
private void updateDownwards() {
List<A> children = selectedItem.getChildren();
if (selectedItem.isParent()
&& selectedItem.updateChildren) {
for (A child : children) {
child.getCheck().getCheckUncheck().set(selectedItem.getCheck().getCheckUncheck().get());
child.getCheck().getIndeterminate().set(selectedItem.getCheck().getIndeterminate().get());
}
}
this.param.getTreeTableView().refresh();
}
}
我正在尝试在 javafx 中实现 TreeTableView,其中第一列包含字符串值,第三列将呈现为三态复选框。
使用以下 MCVE,我可以获得一棵树 table,但是复选框中的 none 选项仍然存在 parent collapse/expand 或 table 的调整大小.
MCVE
Class A 是 parent.
Class B 扩展了 A 并且是 child.
Class C 表示第 2 列,(呈现为复选框)
Class MVCECheckBox 构建树table 并显示它。
A.java
package mcve.checkbox;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* @author returncode13
*/
public class A {
StringProperty name=new SimpleStringProperty();
C check=new C();
List<A> children;
public StringProperty getName() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public C getCheck() {
return check;
}
public void setCheck(C check) {
this.check = check;
}
public StringProperty nameProperty(){
return name;
}
public List<A> getChildren() {
return children;
}
public void setChildren(List<A> children) {
this.children = children;
}
}
B.java
package mcve.checkbox;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* @author returncode13
*/
public class B extends A{
StringProperty name=new SimpleStringProperty();
C Check=new C();
@Override
public StringProperty getName() {
return name;
}
@Override
public void setName(String name) {
this.name.set(name);
}
@Override
public C getCheck() {
return Check;
}
@Override
public void setCheck(C Check) {
this.Check = Check;
}
@Override
public StringProperty nameProperty(){
return name;
}
}
C.java
package mcve.checkbox;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
*
* @author returncode13
*/
public class C {
BooleanProperty checkUncheck=new SimpleBooleanProperty();
BooleanProperty indeterminate=new SimpleBooleanProperty();
public BooleanProperty getCheckUncheck() {
return checkUncheck;
}
public void setCheckUncheck(BooleanProperty checkUncheck) {
this.checkUncheck = checkUncheck;
}
public BooleanProperty getIndeterminate() {
return indeterminate;
}
public void setIndeterminate(BooleanProperty indeterminate) {
this.indeterminate = indeterminate;
}
public BooleanProperty checkUncheckProperty(){
return checkUncheck;
}
public BooleanProperty indeterminateProperty(){
return indeterminate;
}
}
MCVECheckBox.java
package mcve.checkbox;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
*
* @author returncode13
*/
public class MCVECheckBox extends Application {
A selectedItem;
private TreeTableView<A> treetable=new TreeTableView<>();
@Override
public void start(Stage primaryStage) {
//setting up parents (A) and children (B)
A a1=new A();
a1.setName("A1");
List<A> A1Children=new ArrayList();
B b11=new B();
b11.setName("B11");
B b12=new B();
b12.setName("B12");
A1Children.add(b11);
A1Children.add(b12);
a1.setChildren(A1Children);
A a2=new A();
a2.setName("A2");
List<A> A2Children=new ArrayList();
B b21=new B();
b21.setName("B21");
B b22=new B();
b22.setName("B22");
A2Children.add(b21);
A2Children.add(b22);
a2.setChildren(A2Children);
//tree columns . first one holds strings
TreeTableColumn<A,String> name=new TreeTableColumn<>("Name");
name.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
//2nd tree columns. rendered as checkboxes. boolean values
TreeTableColumn<A,Boolean> checks=new TreeTableColumn<>("Checks");
checks.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<A, Boolean>, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> call(TreeTableColumn.CellDataFeatures<A, Boolean> param) {
A a=param.getValue().getValue();
SimpleBooleanProperty checkUncheck=new SimpleBooleanProperty();
SimpleBooleanProperty indeterminate=new SimpleBooleanProperty();
checkUncheck=(SimpleBooleanProperty) a.getCheck().getCheckUncheck();
indeterminate=(SimpleBooleanProperty) a.getCheck().getIndeterminate();
//to do: set parents status based on children status.
if(indeterminate.get()){
return indeterminate;
}else{
return checkUncheck;
}
}
});
checks.setCellFactory(new Callback<TreeTableColumn<A, Boolean>, TreeTableCell<A, Boolean>>() {
@Override
public TreeTableCell<A, Boolean> call(TreeTableColumn<A, Boolean> param) {
return new CheckBoxCell();
}
});
//building the tree;
TreeItem<A> a1item=new TreeItem<>(a1);
TreeItem<A> b11item=new TreeItem<>(b11);
TreeItem<A> b12item=new TreeItem<>(b12);
a1item.getChildren().add(b11item);
a1item.getChildren().add(b12item);
TreeItem<A> a2item=new TreeItem<>(a2);
TreeItem<A> b21item=new TreeItem<>(b21);
TreeItem<A> b22item=new TreeItem<>(b22);
a2item.getChildren().add(b21item);
a2item.getChildren().add(b22item);
TreeItem<A> root=new TreeItem<>();
root.getChildren().add(a1item);
root.getChildren().add(a2item);
treetable.getColumns().addAll(name,checks);
treetable.setRoot(root);
treetable.setShowRoot(false);
treetable.setEditable(true);
// StackPane rootSp = new StackPane();
Scene scene = new Scene(treetable, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
//to render checkboxes in treetable
private class CheckBoxCell extends TreeTableCell<A, Boolean> {
CheckBox checkbox;
public CheckBoxCell() {
checkbox=new CheckBox();
checkbox.setAllowIndeterminate(true);
checkbox.selectedProperty().addListener((obs,wasSelected,isNowSelected) -> {
if(isNowSelected){
selectedItem=getTreeTableRow().getItem();
}
});
}
@Override
public void updateItem(Boolean b,boolean empty){
super.updateItem(b, empty);
if(empty){
setGraphic(null);
}else{
checkbox.setSelected(b);
setGraphic(checkbox);
}
}
}
}
我之前曾尝试使用 CheckTreeTableCell 在第二列设置单元格工厂,但很快发现 CheckTreeTableCell 不支持三态(选中、取消选中、不确定)复选框。
之后我尝试实现上面的代码。尽管我能够引入三态复选框,但我无法让它们的状态持续存在。每次 parent 为 collapsed/expanded 时,对其 children 进行的检查将被取消选择。
感谢您在确定修复方面提供的任何帮助。
我现在可以通过对发布的 MCVE 进行以下修改来实现三态复选框,它现在是一个完整的工作示例。
A.java (parent class)
package com.mycompany.yetanothercheckbox;
import java.util.Iterator;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* @author returncode13
*/
public class A {
private StringProperty name=new SimpleStringProperty();
C check=new C();
List<A> children;
final boolean isLeaf=false;
final boolean isParent=true;
public boolean updateParent=false;
public boolean updateChildren=false;
public boolean isLeaf() {
return isLeaf;
}
public boolean isParent() {
return isParent;
}
public StringProperty getName() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public C getCheck() {
return check;
}
public void setCheck(C check) {
this.check = check;
for (Iterator<A> iterator = children.iterator(); iterator.hasNext();) {
A next = iterator.next();
next.setCheck(check);
}
}
public StringProperty nameProperty(){
return name;
}
public List<A> getChildren() {
return children;
}
public void setChildren(List<A> children) {
this.children = children;
}
public A getParent() {
return this;
}
}
B.java (child class)
package com.mycompany.yetanothercheckbox;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* @author returncode13
*/
public class B extends A{
private StringProperty name=new SimpleStringProperty();
C Check=new C();
final boolean isLeaf=true;
final boolean isParent=false;
public boolean updateParent=false;
public boolean updateChildren=false;
A parent;
public A getParent() {
return parent;
}
public void setParent(A parent) {
this.parent = parent;
}
@Override
public boolean isLeaf() {
return isLeaf;
}
@Override
public boolean isParent() {
return isParent;
}
@Override
public StringProperty getName() {
return name;
}
@Override
public void setName(String name) {
this.name.set(name);
}
@Override
public C getCheck() {
return Check;
}
@Override
public void setCheck(C Check) {
this.Check = Check;
}
@Override
public StringProperty nameProperty(){
return name;
}
@Override
public List<A> getChildren() {
return parent.getChildren();
}
}
C.java(保持检查状态)
package com.mycompany.yetanothercheckbox;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
*
* @author returncode13
*/
public class C {
BooleanProperty checkUncheck=new SimpleBooleanProperty();
BooleanProperty indeterminate=new SimpleBooleanProperty();
public BooleanProperty getCheckUncheck() {
return checkUncheck;
}
public void setCheckUncheck(BooleanProperty checkUncheck) {
this.checkUncheck = checkUncheck;
}
public BooleanProperty getIndeterminate() {
return indeterminate;
}
public void setIndeterminate(BooleanProperty indeterminate) {
this.indeterminate = indeterminate;
}
public BooleanProperty checkUncheckProperty(){
return checkUncheck;
}
public BooleanProperty indeterminateProperty(){
return indeterminate;
}
}
ThreeStateCheckBoxTreeTableCell.java(树的 3state 复选框 table)
package com.mycompany.yetanothercheckbox;
import java.util.List;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.input.MouseEvent;
/**
*
* @author returncode13
*/
//to render checkboxes in treetable
public class ThreeStateCheckBoxTreeTableCell extends TreeTableCell<A, Boolean> {
A selectedItem;
CheckBox checkbox;
TreeTableColumn<A,Boolean> param;
/*private static boolean updateParent=false;
private static boolean updateChildren=false;*/
public ThreeStateCheckBoxTreeTableCell(TreeTableColumn<A,Boolean> param) {
checkbox=new CheckBox();
this.param=param;
checkbox.setAllowIndeterminate(true);
checkbox.selectedProperty().addListener((obs,wasSelected,isNowSelected) -> {
int sel=getTreeTableRow().getIndex();
selectedItem=this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getCheckUncheck().set(isNowSelected);
selectedItem.getCheck().getIndeterminate().set(false);
//ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
});
checkbox.indeterminateProperty().addListener((obx,ol,newV)->{
int sel=getTreeTableRow().getIndex();
selectedItem=this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getIndeterminate().set(newV);
//ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
});
checkbox.setOnMouseClicked(new EventHandler<MouseEvent>(){
@Override
public void handle(MouseEvent event) {
int sel=getTreeTableRow().getIndex();
selectedItem=ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
if(selectedItem.isParent()){
selectedItem.updateChildren=true;
for(A child:selectedItem.getChildren()){
child.updateParent=false;
}
updateDownwards();
}
if(selectedItem.isLeaf()){
selectedItem.getParent().updateChildren=false;
selectedItem.updateParent=true;
updateUpWards();
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
});
}
@Override
public void updateItem(Boolean b,boolean empty){
super.updateItem(b, empty);
if(empty){
setGraphic(null);
}else{
if(b==null){
checkbox.setIndeterminate(true);
}
else{
checkbox.setIndeterminate(false);
checkbox.setSelected(b);
}
setGraphic(checkbox);
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
private void updateUpWards(){
if(selectedItem.updateParent){
List<A> children=selectedItem.getChildren();
int indeterminateCount=0;
int selectedCount=0;
A parent=selectedItem.getParent();
for(A child:children){
indeterminateCount+=child.getCheck().getIndeterminate().get()?1:0;
selectedCount+=child.getCheck().getCheckUncheck().get()?1:0;
}
if(indeterminateCount>0) {
parent.getCheck().getIndeterminate().set(true);
}
else if(indeterminateCount==0 && selectedCount==children.size()){
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(true);
}else{
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(false);
}
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
private void updateDownwards(){
List<A> children=selectedItem.getChildren();
if(selectedItem.isParent() && selectedItem.updateChildren ){
for(A child:children){
child.getCheck().getCheckUncheck().set(selectedItem.getCheck().getCheckUncheck().get());
child.getCheck().getIndeterminate().set(selectedItem.getCheck().getIndeterminate().get());
}
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
}
MainApp.java(作为 POC 申请)
package com.mycompany.yetanothercheckbox;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.stage.Stage;
import javafx.util.Callback;
public class MainApp extends Application {
private TreeTableView<A> treetable=new TreeTableView<>();
@Override
public void start(Stage primaryStage) {
//setting up parents (A) and children (B)
A a1=new A();
a1.setName("A1");
List<A> A1Children=new ArrayList();
B b11=new B();
b11.setName("B11");
B b12=new B();
b12.setName("B12");
b11.setParent(a1);
b12.setParent(a1);
A1Children.add(b11);
A1Children.add(b12);
a1.setChildren(A1Children);
A a2=new A();
a2.setName("A2");
List<A> A2Children=new ArrayList();
B b21=new B();
b21.setName("B21");
B b22=new B();
b22.setName("B22");
b21.setParent(a2);
b22.setParent(a2);
A2Children.add(b21);
A2Children.add(b22);
a2.setChildren(A2Children);
//tree columns . first one holds strings
TreeTableColumn<A,String> name=new TreeTableColumn<>("Name");
name.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
//2nd tree columns. rendered as checkboxes. boolean values
TreeTableColumn<A,Boolean> checks=new TreeTableColumn<>("Checks");
checks.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<A, Boolean>, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> call(TreeTableColumn.CellDataFeatures<A, Boolean> param) {
A a=param.getValue().getValue();
SimpleBooleanProperty checkUncheck=new SimpleBooleanProperty();
SimpleBooleanProperty indeterminate=new SimpleBooleanProperty();
checkUncheck.bindBidirectional(a.getCheck().getCheckUncheck());
indeterminate.bindBidirectional(a.getCheck().getIndeterminate());
checkUncheck.addListener(new ChangeListener<Boolean>(){
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
a.getCheck().indeterminateProperty().set(false);
a.getCheck().checkUncheckProperty().set(newValue);
}
});
indeterminate.addListener(new ChangeListener<Boolean>(){
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
a.getCheck().indeterminateProperty().set(newValue);
}
});
if(indeterminate.get()){
return null;
}else{
return checkUncheck;
}
}
});
checks.setCellFactory(new Callback<TreeTableColumn<A, Boolean>, TreeTableCell<A, Boolean>>() {
@Override
public TreeTableCell<A, Boolean> call(TreeTableColumn<A, Boolean> param) {
return new ThreeStateCheckBoxTreeTableCell(param);
}
});
//building the tree;
TreeItem<A> a1item=new TreeItem<>(a1);
TreeItem<A> b11item=new TreeItem<>(b11);
TreeItem<A> b12item=new TreeItem<>(b12);
a1item.getChildren().add(b11item);
a1item.getChildren().add(b12item);
TreeItem<A> a2item=new TreeItem<>(a2);
TreeItem<A> b21item=new TreeItem<>(b21);
TreeItem<A> b22item=new TreeItem<>(b22);
a2item.getChildren().add(b21item);
a2item.getChildren().add(b22item);
TreeItem<A> root=new TreeItem<>();
root.getChildren().add(a1item);
root.getChildren().add(a2item);
treetable.getColumns().addAll(name,checks);
treetable.setRoot(root);
treetable.setShowRoot(false);
treetable.setEditable(true);
// StackPane rootSp = new StackPane();
Scene scene = new Scene(treetable, 300, 250);
primaryStage.setTitle("CheckBoxTreeTable Example");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
非常感谢您提供这段很棒的代码,它让您深入了解树项 table 单元格(
不存在三态复选框这一事实对于那些将以此 post 为食的人,我在 class '' 上做了一些小的改动。现在看起来像这样:
package com.mycompany.yetanothercheckbox;
import java.util.List;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
/**
*
* @author returncode13
*/
// to render checkboxes in treetable
public class ThreeStateCheckBoxTreeTableCell extends TreeTableCell<A, Boolean> {
A selectedItem;
CheckBox checkbox;
TreeTableColumn<A, Boolean> param;
/*
* private static boolean updateParent=false;
* private static boolean updateChildren=false;
*/
public ThreeStateCheckBoxTreeTableCell(TreeTableColumn<A, Boolean> param) {
checkbox = new CheckBox();
this.param = param;
checkbox.setAllowIndeterminate(true);
checkbox.selectedProperty().addListener((obs,
oldSelectedVal,
newSelectedVal) -> {
int sel = getTreeTableRow().getIndex();
selectedItem = this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getCheckUncheck().set(newSelectedVal);
selectedItem.getCheck().getIndeterminate().set(false);
});
checkbox.indeterminateProperty().addListener((obx,
ol,
newV) -> {
int sel = getTreeTableRow().getIndex();
selectedItem = this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getIndeterminate().set(newV);
});
checkbox.setOnMouseClicked(event -> {
int sel = getTreeTableRow().getIndex();
selectedItem = this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
if (selectedItem.isParent()) {
selectedItem.updateChildren = true;
for (A child : selectedItem.getChildren()) { child.updateParent = false; }
updateDownwards();
}
if (selectedItem.isLeaf()) {
selectedItem.getParent().updateChildren = false;
selectedItem.updateParent = true;
updateUpWards();
}
});
}
@Override
public void updateItem(Boolean b,
boolean empty) {
super.updateItem(b, empty);
if (empty) {
setGraphic(null);
} else {
if (b == null) {
checkbox.setIndeterminate(true);
} else {
checkbox.setIndeterminate(false);
checkbox.setSelected(b);
}
setGraphic(checkbox);
}
}
private void updateUpWards() {
if (selectedItem.updateParent) {
List<A> children = selectedItem.getChildren();
int indeterminateCount = 0;
int selectedCount = 0;
A parent = selectedItem.getParent();
for (A child : children) {
indeterminateCount += child.getCheck().getIndeterminate().get() ? 1 : 0;
selectedCount += child.getCheck().getCheckUncheck().get() ? 1 : 0;
}
if (indeterminateCount > 0) {
parent.getCheck().getIndeterminate().set(true);
} else if (indeterminateCount == 0
&& selectedCount == children.size()) {
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(true);
} else {
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(false);
}
}
this.param.getTreeTableView().refresh();
}
private void updateDownwards() {
List<A> children = selectedItem.getChildren();
if (selectedItem.isParent()
&& selectedItem.updateChildren) {
for (A child : children) {
child.getCheck().getCheckUncheck().set(selectedItem.getCheck().getCheckUncheck().get());
child.getCheck().getIndeterminate().set(selectedItem.getCheck().getIndeterminate().get());
}
}
this.param.getTreeTableView().refresh();
}
}