我的类中有两个变量:金额和该金额的最大可能值。
我需要一个能够接收任何数字的 TableCell,只要它不超过每个项目中存在的最大值。 我还需要这两个值用于其他东西,例如实现自定义 StringConverter、自定义 TextField 等。
但是,我无法访问 setCellFactory() 中的项目值!!!
这或多或少是我所做的:
public class Item {
private SimpleIntegerProperty amount;
private SimpleIntegerProperty max; // maximum
Item(int max,int amount){
this.max = new SimpleIntegerProperty(max);
this.amount = new SimpleIntegerProperty(amount);
}
public static TableView<Item> getTable(){
TableColumn<Item,Number> colAmnt = new TableColumn<Item, Number>("column");
colAmnt.setCellValueFactory(c -> c.getValue().amount); // I CAN access the item object (c.getValue()) here!
colAmnt.setCellFactory(d->{
// But I can't access it from here!!!
return new MaxNumberTableCell();
});
// unimportant stuff
colAmnt.setOnEditCommit((TableColumn.CellEditEvent<Item, Number> t) -> {
Item o = ((Item) t.getTableView().getItems().get(
t.getTablePosition().getRow()));
o.amount.setValue(t.getNewValue().intValue());
});
TableView<Item> t = new TableView<Item>();
t.getColumns().add(colAmnt);
t.setEditable(true);
return t;
}
public int getMax() {
return max.get();
}
}
我尝试通过扩展 TableCell 类来获取值,如下所示,但我收到 NullPointerException。
class MaxNumberTableCell extends TableCell<Item,Number>{
public MaxNumberTableCell(){
super();
// Can't access from here either
int a = this.getTableRow().getItem().getMax(); // NullPointerException
// tldr: I need the value of a !
}
}
编辑:如果您想要一个最小的工作示例,这里是Main
public class Main extends Application{
@Override
public void start(Stage stage) {
TableView<Item> t = Item.getTable();
ObservableList<Item> ol = FXCollections.observableArrayList();
ol.add(new Item(5,1));
t.setItems(ol);
Scene scene = new Scene(t);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
看起来您正在使用 TableView
构建可编辑的
属性表。由于模型中的每个
Item
可能具有不同的边界,因此您在 max
中编辑时需要访问项目的 TableCell
属性。您当前的方法似乎很尴尬,复制了模型中的现有属性。相反,聚合将其边界作为BoundedValue
的值,并让模型包含相应的属性:
private final ObjectProperty<BoundedValue> quantity;
在下面的示例中,金额和分数列各自具有相同的单元格值工厂:
quantityProperty()
。相比之下,每列都有一个自定义单元格工厂,用于显示感兴趣的方面:以百分比显示的可编辑量或不可编辑分数。无论哪种情况,工厂的 TableCell
都可以访问 BoundedValue
的任何所需字段。
展望未来,您将需要处理
NumberFormatException
抛出的任何 Integer.valueOf()
或考虑使用 TextFormatter
,参见 here。相关示例 here 和 here 说明了使用颜色来突出显示单元格状态,此 example 检查使用 Spinner
作为 TableCell
。
import java.text.NumberFormat;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.stage.Stage;
import javafx.util.StringConverter;
/**
* @see https://stackoverflow.com/a/71542867/230513
* @see https://stackoverflow.com/q/45200797/230513
* @see https://stackoverflow.com/q/41702771/230513
*/
public class BoundedValueTest extends Application {
private static class BoundedValue {
private final Integer value;
private final Integer min;
private final Integer max;
public BoundedValue(int value, int min, int max) {
if (value < min || value > max) {
throw new IllegalArgumentException("Value out of bounds.");
}
this.value = value;
this.min = min;
this.max = max;
}
}
private static class Item {
private final ObjectProperty<BoundedValue> quantity;
Item(int quantity, int min, int max) {
this.quantity = new SimpleObjectProperty<>(
new BoundedValue(quantity, min, max));
}
private ObjectProperty<BoundedValue> quantityProperty() {
return quantity;
}
}
private TableCell<Item, BoundedValue> createQuantityCell() {
TextFieldTableCell<Item, BoundedValue> cell = new TextFieldTableCell<>();
cell.setConverter(new StringConverter<BoundedValue>() {
@Override
public BoundedValue fromString(String string) {
int min = cell.getItem().min;
int max = cell.getItem().max;
//TODO NumberFormatException vs TextFormatter
int value = Integer.valueOf(string);
if (value < min || value > max) {
return cell.getItem();
} else {
return new BoundedValue(value, min, max);
}
}
@Override
public String toString(BoundedValue item) {
return item == null ? "" : item.value.toString();
}
});
return cell;
}
private TableCell<Item, BoundedValue> createFractionCell() {
NumberFormat f = NumberFormat.getPercentInstance();
TextFieldTableCell<Item, BoundedValue> cell = new TextFieldTableCell<>(
new StringConverter<BoundedValue>() {
@Override
public String toString(BoundedValue t) {
return f.format((double) t.value / t.max);
}
@Override
public BoundedValue fromString(String string) {
return null;
}
});
return cell;
}
@Override
public void start(Stage stage) {
ObservableList<Item> list = FXCollections.observableArrayList();
for (int i = 0; i < 10; i++) {
list.add(new Item(i, 0, 10));
}
TableView<Item> table = new TableView<>(list);
TableColumn<Item, BoundedValue> amountCol = new TableColumn<>("Amount");
amountCol.setCellValueFactory(cd -> cd.getValue().quantityProperty());
amountCol.setCellFactory(tc -> createQuantityCell());
table.getColumns().add(amountCol);
TableColumn<Item, BoundedValue> fractionCol = new TableColumn<>("Fraction");
fractionCol.setCellValueFactory(cd -> cd.getValue().quantityProperty());
fractionCol.setCellFactory(tc -> createFractionCell());
fractionCol.setEditable(false);
table.getColumns().add(fractionCol);
table.setEditable(true);
Scene scene = new Scene(table);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
无论如何,我可以创建一个额外的字段并绑定到
amount
和 max
。虽然我觉得有点丑,但效果很完美。
我要开放这个问题,这样也许有人可以提出一种更优雅的方式,也许不需要额外的变量。
public class Item {
private SimpleIntegerProperty amount;
private SimpleIntegerProperty max;
private SimpleObjectProperty<Integer[]> fraction;
// I have changed the order here
Item(int amount,int max){
this.max = new SimpleIntegerProperty(max);
this.amount = new SimpleIntegerProperty(amount);
// Extra variable
this.fraction = new SimpleObjectProperty<Integer[]>();
this.fraction.bind(Bindings.createObjectBinding(()->{
return new Integer[] {this.amount.get(),this.max.get()};
}, this.amount,this.max));
}
public static TableView<Item> getTable(){
TableColumn<Item,Integer[]> colAmnt = new TableColumn<Item, Integer[]>("column");
colAmnt.setCellValueFactory(c -> c.getValue().fraction);
// setCellFactory with fraction variable
colAmnt.setCellFactory(d->new MaxNumberTableCell());
colAmnt.setOnEditCommit((TableColumn.CellEditEvent<Item, Integer[]> t) -> {
Item o = ((Item) t.getTableView().getItems().get(
t.getTablePosition().getRow()));
o.amount.setValue(t.getNewValue()[0]);
});
TableView<Item> t = new TableView<Item>();
t.getColumns().add(colAmnt);
t.setEditable(true);
return t;
}
void setFraction(int amount, int max){
this.max.set(max);
this.amount.set(amount);
}
}
class MaxNumberTableCell extends TableCell<Item,Integer[]>{
@Override
public void updateItem(Integer[] item,boolean empty) {
super.updateItem(item, empty);
if (item == null) {
setText(null);
setGraphic(null);
} else {
// I can acess both values and further modify if I want
setText(item[0]+"/"+item[1]);
setGraphic(null);
}
}
}