简体   繁体   English

如果解析失败,取消表格单元格编辑的规范方法

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

Edit :编辑
I first voted to close as a duplicate after finding this answer by James_D, which sets a TextFormatter on a TextField .在 James_D 找到这个答案后,我首先投票关闭作为副本,它在TextField上设置了一个TextFormatter But then firstly I found that (in a TableView context) the method TextFieldTableCell.forTableColumn() does not in fact draw a TextField when it starts editing, but instead a LabeledText , which does not subclass TextInputControl , and therefore does not have setTextFormatter() .但首先我发现(在TableView上下文中)方法TextFieldTableCell.forTableColumn()在开始编辑时实际上并没有绘制TextField ,而是一个LabeledText ,它没有LabeledText TextInputControl ,因此没有setTextFormatter() .
Secondly, I wanted something which acted in a familiar sort of way.其次,我想要一些以熟悉的方式行事的东西。 I may have produced the "canonical" solution in my answer: let others judge.我可能已经在我的回答中产生了“规范”的解决方案:让别人来判断。


This is a TableColumn in a TableView (all Groovy):这是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
        }
    }
})

Excerpt from class Person:摘自类人:

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
    }
    ...

Without the start-edit Handler , when I enter a String which can't be parsed as an Integer NumberFormatException not surprisingly gets thrown.如果没有 start-edit Handler ,当我输入一个无法解析为Integer NumberFormatExceptionString ,毫不奇怪地抛出。 But I also find that the number in the cell then gets set to 0, which is likely not to be the desired outcome.但我也发现单元格中的数字随后被设置为 0,这可能不是预期的结果。

But the above strikes me as a pretty clunky solution.但以上给我的印象是一个非常笨拙的解决方案。

I had a look at ageCol , and ageCol.cellFactory (as these are accessible from inside the catch block) but couldn't see anything better and obvious.我查看了ageColageCol.cellFactory (因为它们可以从catch块内部访问)但看不到任何更好和明显的东西。 I can also see that one can easily obtain the Callback ( ageCol.cellFactory ), but calling it would require the parameter cdf , ie the CellDataFeatures instance, which again you'd have to store somewhere.我还可以看到,可以轻松获得CallbackageCol.cellFactory ),但调用它需要参数cdf ,即CellDataFeatures实例,您必须再次将其存储在某处。

I'm sure a validator mechanism of some kind was involved with Swing: ie before a value could be transferred from the editor component (via some delegate or something), it was possible to override some validating mechanism.我确信 Swing 涉及某种类型的验证器机制:即在可以从编辑器组件(通过某些委托或其他方式)传输值之前,可以覆盖某些验证机制。 But this IntegerStringConverter seems to function as a validator, although doesn't seem to provide any way to revert to the existing ("old") value if validation fails.但是这个IntegerStringConverter似乎起到了验证器的作用,尽管如果验证失败,它似乎没有提供任何方法来恢复到现有(“旧”)值。

Is there a less clunky mechanism than the one I've shown above?有没有比我上面展示的更笨拙的机制?

Edit编辑
NB improved after kleopatra's valuable insights. NB 在 kleopatra 的宝贵见解之后有所改进。
Edit2编辑2
Overhauled completely after realising that the best thing is to use the existing default editor and tweak it.在意识到最好的办法是使用现有的默认编辑器并对其进行调整后,彻底进行了大修。


I thought I'd give an example with a LocalDate , slightly more fun than Integer .我想我会举一个LocalDate的例子,比Integer稍微有趣一点。 Given the following class:鉴于以下类:

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;
}

Then you create a new editor cell class, which is exactly the same as TextFieldTreeTableCell (subclass of TreeTableCell ), which is used by default to create an editor for a TreeTableView 's table cell.然后创建一个新的编辑器单元格类,它与TextFieldTreeTableCellTreeTableCell子类)完全相同,默认情况下使用它为TreeTableView的表格单元格创建编辑器。 However, you can't really subclass TextFieldTreeTableCell as, for example, its essential field textField is private .但是,您不能真正继承TextFieldTreeTableCell子类,例如,它的基本字段textFieldprivate

So you copy the code in full from the source* (only about 30 lines), and you call it所以你从源代码中完整复制代码*(只有大约 30 行),然后调用它

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

You then have to create a new StringConverter class, subclassing LocalDateStringConverter .然后,您必须创建一个新的StringConverter类, LocalDateStringConverter The reason for subclassing is that if you don't do that it is impossible to catch the DateTimeParseException thrown by fromString() when an invalid date is received: if you use LocalDateStringConverter the JavaFX framework unfortunately catches it, without any frames in the stack trace involving your own code.子类化的原因是,如果您不这样做,则无法在收到无效日期时捕获fromString()抛出的DateTimeParseException :如果您使用LocalDateStringConverter ,JavaFX 框架会不幸地捕获它,堆栈跟踪中没有任何帧涉及您自己的代码。 So you do this:所以你这样做:

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;
    }
}

Back in your DueDateEditor class you then rewrite the startEdit method as follows.回到您的DueDateEditor类,然后按如下方式重写startEdit方法。 NB, as with the TextFieldTreeTableCell class, textField is actually created lazily, when you first edit.注意,与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);
    }
}

NB don't bother trying to look up CellUtils : this is package-private, the package in question being javafx.scene.control.cell.注意不要费心去查找CellUtils :这是包私有的,有问题的包是 javafx.scene.control.cell。

To set things up you do this:要进行设置,请执行以下操作:

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

... the result is a nice, reactive editor cell: when containing an invalid date (acceptable pattern yyyy-mm-dd ; see other LocalDate.parse() variant for other formats) the background is red, otherwise normal. ... 结果是一个不错的反应式编辑器单元格:当包含无效日期(可接受的模式yyyy-mm-dd ;请参阅其他LocalDate.parse()其他格式的变体)时,背景为红色,否则为正常。 Entering with a valid date works seamlessly.输入有效日期可以无缝工作。 You can also enter an empty String , which is returned as a null LocalDate .您还可以输入一个空String ,它作为null LocalDate返回。

With the above, pressing Enter with an invalid date sets the date to null .使用上述方法,在无效日期下按 Enter 会将日期设置为null But overriding things to prevent this happening (ie forcing you to enter a valid date, or cancel the edit, eg by Escape) is trivial, using the ValidatingLocalDateStringConverter 's valid field:但是,使用ValidatingLocalDateStringConvertervalid字段覆盖防止这种情况发生的事情(即强制您输入有效日期,或取消编辑,例如通过 Escape)是微不足道的:

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

* I couldn't find this online. * 我在网上找不到这个。 I extracted from the javafx source .jar file javafx-controls-11.0.2-sources.jar我从 javafx 源 .jar 文件中提取了 javafx-controls-11.0.2-sources.jar

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM