簡體   English   中英

如果解析失敗,取消表格單元格編輯的規范方法

[英]Canonical way to cancel a table cell edit if parse fails

編輯
在 James_D 找到這個答案后,我首先投票關閉作為副本,它在TextField上設置了一個TextFormatter 但首先我發現(在TableView上下文中)方法TextFieldTableCell.forTableColumn()在開始編輯時實際上並沒有繪制TextField ,而是一個LabeledText ,它沒有LabeledText TextInputControl ,因此沒有setTextFormatter() .
其次,我想要一些以熟悉的方式行事的東西。 我可能已經在我的回答中產生了“規范”的解決方案:讓別人來判斷。


這是TableViewTableColumn (所有 Groovy):

TableColumn<Person, String> ageCol = new TableColumn("Age")
ageCol.cellValueFactory = { cdf -> cdf.value.ageProperty() }

int oldAgeValue
ageCol.onEditStart = new EventHandler(){
    @Override
    public void handle( Event event) {
        oldAgeValue = event.oldValue
    }
}
ageCol.cellFactory = TextFieldTableCell.forTableColumn(new IntegerStringConverter() {
    @Override
    public Integer fromString(String value) {
        try {
            return super.fromString(value)
        }
        catch ( NumberFormatException e) {
            // inform user by some means...
            println "string could not be parsed as integer..."
            // ... and cancel the edit
            return oldAgeValue
        }
    }
})

摘自類人:

public class Person {
    private IntegerProperty age;
    public void setAge(Integer value) { ageProperty().set(value) }
    public Integer getAge() { return ageProperty().get() }
    public IntegerProperty ageProperty() {
        if (age == null) age = new SimpleIntegerProperty(this, "age")
        return age
    }
    ...

如果沒有 start-edit Handler ,當我輸入一個無法解析為Integer NumberFormatExceptionString ,毫不奇怪地拋出。 但我也發現單元格中的數字隨后被設置為 0,這可能不是預期的結果。

但以上給我的印象是一個非常笨拙的解決方案。

我查看了ageColageCol.cellFactory (因為它們可以從catch塊內部訪問)但看不到任何更好和明顯的東西。 我還可以看到,可以輕松獲得CallbackageCol.cellFactory ),但調用它需要參數cdf ,即CellDataFeatures實例,您必須再次將其存儲在某處。

我確信 Swing 涉及某種類型的驗證器機制:即在可以從編輯器組件(通過某些委托或其他方式)傳輸值之前,可以覆蓋某些驗證機制。 但是這個IntegerStringConverter似乎起到了驗證器的作用,盡管如果驗證失敗,它似乎沒有提供任何方法來恢復到現有(“舊”)值。

有沒有比我上面展示的更笨拙的機制?

編輯
NB 在 kleopatra 的寶貴見解之后有所改進。
編輯2
在意識到最好的辦法是使用現有的默認編輯器並對其進行調整后,徹底進行了大修。


我想我會舉一個LocalDate的例子,比Integer稍微有趣一點。 鑒於以下類:

class Person(){ 
...
private ObjectProperty<LocalDate> dueDate;
public void setDueDate(LocalDate value) {
    dueDateProperty().set(value);
}
public LocalDate getDueDate() {
    return (LocalDate) dueDateProperty().get();
}
public ObjectProperty dueDateProperty() {
    if (dueDate == null) dueDate = new SimpleObjectProperty(this, "dueDate");
    return dueDate;
}

然后創建一個新的編輯器單元格類,它與TextFieldTreeTableCellTreeTableCell子類)完全相同,默認情況下使用它為TreeTableView的表格單元格創建編輯器。 但是,您不能真正繼承TextFieldTreeTableCell子類,例如,它的基本字段textFieldprivate

所以你從源代碼中完整復制代碼*(只有大約 30 行),然后調用它

class DueDateEditor extends TreeTableCell<Person, LocalDate> { 
    ...

然后,您必須創建一個新的StringConverter類, LocalDateStringConverter 子類化的原因是,如果您不這樣做,則無法在收到無效日期時捕獲fromString()拋出的DateTimeParseException :如果您使用LocalDateStringConverter ,JavaFX 框架會不幸地捕獲它,堆棧跟蹤中沒有任何幀涉及您自己的代碼。 所以你這樣做:

class ValidatingLocalDateStringConverter extends LocalDateStringConverter {
    boolean valid;
    LocalDate fromString(String value) {
        valid = true;
        if (value.isBlank()) return null;
        try {
            return LocalDate.parse(value);
        } catch (Exception e) {
            valid = false;
        }
        return null;
    }
}

回到您的DueDateEditor類,然后按如下方式重寫startEdit方法。 注意,與TextFieldTreeTableCell類一樣, textField實際上是在您第一次編輯時延遲創建的。

@Override
void startEdit() {
    if (! isEditable()
            || ! getTreeTableView().isEditable()
            || ! getTableColumn().isEditable()) {
        return;
    }
    super.startEdit();

    if (isEditing()) {
        if (textField == null) {
            textField = CellUtils.createTextField(this, getConverter());

            // this code added by me
            ValidatingLocalDateStringConverter converter = getConverter();
            Callable bindingFunc = new Callable(){
                @Override
                Object call() throws Exception {
                    // NB the return value from this is "captured" by the editor
                    converter.fromString( textField.getText() );
                    return converter.valid? '' : "-fx-background-color: red;";
                }
            }
            def stringBinding = Bindings.createStringBinding( bindingFunc, textField.textProperty() );
            textField.styleProperty().bind( stringBinding );


        }
        CellUtils.startEdit(this, getConverter(), null, null, textField);
    }
}

注意不要費心去查找CellUtils :這是包私有的,有問題的包是 javafx.scene.control.cell。

要進行設置,請執行以下操作:

Callback<TreeTableColumn, TreeTableCell> dueDateCellFactory =
        new Callback<TreeTableColumn, TreeTableCell>() {
            public TreeTableCell call(TreeTableColumn p) {
                return new DueDateEditor( new ValidatingLocalDateStringConverter() );
            }
        }
dueDateColumn.setCellFactory(dueDateCellFactory);

... 結果是一個不錯的反應式編輯器單元格:當包含無效日期(可接受的模式yyyy-mm-dd ;請參閱其他LocalDate.parse()其他格式的變體)時,背景為紅色,否則為正常。 輸入有效日期可以無縫工作。 您還可以輸入一個空String ,它作為null LocalDate返回。

使用上述方法,在無效日期下按 Enter 會將日期設置為null 但是,使用ValidatingLocalDateStringConvertervalid字段覆蓋防止這種情況發生的事情(即強制您輸入有效日期,或取消編輯,例如通過 Escape)是微不足道的:

@Override
void commitEdit( LocalDate newDueDate ){
    if( getConverter().valid )
        super.commitEdit( newDueDate );
}

* 我在網上找不到這個。 我從 javafx 源 .jar 文件中提取了 javafx-controls-11.0.2-sources.jar

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM