解析失败时取消表格单元格编辑的规范方法 [英] Canonical way to cancel a table cell edit if parse fails

查看:78
本文介绍了解析失败时取消表格单元格编辑的规范方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

修改:
在找到James_D的此答案后,我首先投票关闭了副本,该答案在TextField上设置了TextFormatter .但是首先,我发现(在TableView上下文中)方法TextFieldTableCell.forTableColumn()在开始编辑时实际上并没有绘制TextField,而是一个LabeledText,它没有成为TextInputControl的子类,因此没有setTextFormatter().
其次,我想要某种以一种熟悉的方式起作用的东西.我可能在回答中提出了规范"解决方案:让其他人来判断.

Edit:
I first voted to close as a duplicate after finding this answer by James_D, which sets a TextFormatter on a TextField. 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().
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.

这是TableView(全部为Groovy)中的TableColumn:

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

Person类的节选:

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

没有开始编辑Handler的情况下,当我输入不能解析为Integer NumberFormatExceptionString时,就不会意外地抛出该错误.但是我还发现该单元格中的数字然后被设置为0,这可能不是期望的结果.

Without the start-edit Handler, when I enter a String which can't be parsed as an Integer NumberFormatException not surprisingly gets thrown. But I also find that the number in the cell then gets set to 0, which is likely not to be the desired outcome.

但是以上这些让我印象深刻,这是一个很笨拙的解决方案.

But the above strikes me as a pretty clunky solution.

我看过ageColageCol.cellFactory(因为可以从catch块内部访问它们),但是看不到任何更好和明显的东西.我还可以看到人们可以轻松获取Callback(ageCol.cellFactory),但是调用它需要参数cdf,即CellDataFeatures实例,您又必须将其存储在某个地方.

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. I can also see that one can easily obtain the Callback (ageCol.cellFactory), but calling it would require the parameter cdf, i.e. the CellDataFeatures instance, which again you'd have to store somewhere.

我确定Swing涉及某种验证器机制:即,在可以从编辑器组件(通过某些委托或某些方式)传输值之前,可以覆盖某些验证机制.但这IntegerStringConverter似乎可以充当验证器,尽管似乎没有提供任何方法来在验证失败时恢复到现有(旧")值.

I'm sure a validator mechanism of some kind was involved with Swing: i.e. before a value could be transferred from the editor component (via some delegate or something), it was possible to override some validating mechanism. 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.

是否有比上面显示的笨拙的机制?

Is there a less clunky mechanism than the one I've shown above?

推荐答案

编辑
在kleopatra的宝贵见解之后,NB有所改善.
Edit2
意识到最好的办法是使用现有的默认编辑器并对其进行调整,从而彻底地进行了彻底的检查.

Edit
NB improved after kleopatra's valuable insights.
Edit2
Overhauled completely after realising that the best thing is to use the existing default editor and tweak it.

我以为我会举一个LocalDate的例子,比Integer稍微有趣些.给定以下课程:

I thought I'd give an example with a LocalDate, slightly more fun than 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;
}

然后创建一个新的编辑器单元格类,该类与TextFieldTreeTableCell(TreeTableCell的子类)完全相同,默认情况下,该类用于创建TreeTableView的表单元格的编辑器.但是,您不能真正地将TextFieldTreeTableCell子类化,例如,它的必填字段textFieldprivate.

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. However, you can't really subclass TextFieldTreeTableCell as, for example, its essential field textField is private.

因此,您可以从源代码*(仅约30行)中完整复制代码,然后将其称为

So you copy the code in full from the source* (only about 30 lines), and you call it

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

然后,您必须创建一个新的StringConverter类,并继承LocalDateStringConverter.子类化的原因是,如果不这样做,则在接收到无效日期时无法捕获fromString()抛出的DateTimeParseException:如果使用LocalDateStringConverter,则JavaFX框架不幸地捕获了它,没有任何框架在涉及您自己的代码的堆栈跟踪中.因此,您可以这样做:

You then have to create a new StringConverter class, subclassing 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. 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;
    }
}

返回您的DueDateEditor类,然后按如下所示重写startEdit方法.注意,与TextFieldTreeTableCell类一样,textField实际上是在您第一次编辑时被延迟创建的.

Back in your DueDateEditor class you then rewrite the startEdit method as follows. NB, as with the TextFieldTreeTableCell class, textField is actually created lazily, when you first edit.

@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不必费心尝试查找CellUtils:这是程序包专用的,有问题的程序包是javafx.scene.control.cell.

NB don't bother trying to look up CellUtils: this is package-private, the package in question being 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);

...结果是一个很好的反应式编辑器单元:当包含无效的日期(可接受的模式yyyy-mm-dd;有关其他格式的其他LocalDate.parse()变体)时,背景为红色,否则为正常.输入有效日期可以无缝进行.您还可以输入一个空的String,它以null LocalDate的形式返回.

... 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. Entering with a valid date works seamlessly. You can also enter an empty String, which is returned as a null LocalDate.

使用上面的命令,按Enter并输入无效的日期,将日期设置为null.但是,使用ValidatingLocalDateStringConvertervalid字段来压倒一切,以防止发生这种情况(例如,迫使您输入有效日期或取消编辑,例如通过Escape)是微不足道的.

With the above, pressing Enter with an invalid date sets the date to null. But overriding things to prevent this happening (i.e. forcing you to enter a valid date, or cancel the edit, e.g. by Escape) is trivial, using the ValidatingLocalDateStringConverter's valid field:

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


*我在网上找不到.我从javafx源.jar文件中提取了javafx-controls-11.0.2-sources.jar


* I couldn't find this online. I extracted from the javafx source .jar file javafx-controls-11.0.2-sources.jar

这篇关于解析失败时取消表格单元格编辑的规范方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆