如何设置可访问两个特定字段的 CellFactory

问题描述 投票:0回答:2

我的类中有两个变量:金额和该金额的最大可能值。

我需要一个能够接收任何数字的 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);
    }
}
java javafx tableview
2个回答
2
投票

看起来您正在使用 TableView 构建可编辑的

属性表
。由于模型中的每个
Item
可能具有不同的边界,因此您在
max
中编辑时需要访问项目的
TableCell
属性。您当前的方法似乎很尴尬,复制了模型中的现有属性。相反,聚合将其边界作为
BoundedValue
的值,并让模型包含相应的属性:

private final ObjectProperty<BoundedValue> quantity;

在下面的示例中,金额和分数列各自具有相同的单元格值工厂:

quantityProperty()
。相比之下,每列都有一个自定义单元格工厂,用于显示感兴趣的方面:以百分比显示的可编辑量或不可编辑分数。无论哪种情况,工厂的
TableCell
都可以访问
BoundedValue
的任何所需字段。

展望未来,您将需要处理

NumberFormatException
抛出的任何
Integer.valueOf()
或考虑使用
TextFormatter
,参见 here。相关示例 herehere 说明了使用颜色来突出显示单元格状态,此 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);
    }
}

0
投票

无论如何,我可以创建一个额外的字段并绑定到

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);
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.