如果解析失败,取消表格单元格编辑的规范方法
Posted
技术标签:
【中文标题】如果解析失败,取消表格单元格编辑的规范方法【英文标题】:Canonical way to cancel a table cell edit if parse fails 【发布时间】:2020-07-05 09:19:28 【问题描述】:编辑:
在 James_D 找到 this answer 后,我第一次投票决定关闭,它在 TextField
上设置了 TextFormatter
。但首先我发现(在TableView
上下文中)方法TextFieldTableCell.forTableColumn()
在开始编辑时实际上并没有绘制TextField
,而是绘制LabeledText
,它不是TextInputControl
的子类,因此没有setTextFormatter()
。
其次,我想要一些以熟悉的方式起作用的东西。我可能在我的回答中提出了“规范”的解决方案:让其他人判断。
这是TableView
中的TableColumn
(都是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类:
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
的String
时,NumberFormatException
毫不奇怪会被抛出。但我也发现单元格中的数字随后被设置为 0,这可能不是预期的结果。
但上面的解决方案让我觉得很笨拙。
我查看了ageCol
和ageCol.cellFactory
(因为它们可以从catch
块内部访问),但没有看到更好更明显的东西。我还可以看到,可以轻松获得Callback
(ageCol.cellFactory
),但调用它需要参数cdf
,即CellDataFeatures
实例,您必须再次将其存储在某个地方。
我确信 Swing 涉及某种验证器机制:即在可以从编辑器组件传输值之前(通过一些委托或其他东西),可以覆盖一些验证机制。但是这个IntegerStringConverter
似乎起到了验证器的作用,虽然它似乎没有提供任何方法来在验证失败时恢复到现有(“旧”)值。
有没有比我上面展示的更简单的机制?
【问题讨论】:
NumberFormatException 实际上是验证。我不明白为什么您的解决方案“笨拙”,对我来说看起来不错。 GUI 框架不必存储您以前的值(无论如何它必须记住多远?),我也不记得 Swing 这样做了。 这是小家伙:docs.oracle.com/javase/9/docs/api/javax/swing/… ...shouldYieldFocus
,所有的 mullarkey... 响铃?
NumberFormatException 是您的验证。
你是反对者吗?例如,正如shouldYieldFocus
所建议的那样,InputVerifier 做了诸如防止焦点留下无效值之类的事情,如果有人这样配置的话。可能是在 JavaFX 中没有同等级别的复杂处理,但这个问题是完全有效的,而且我发现的机制是垃圾、笨重。毫无疑问。
啊,是的,真正的答案是这是一个“可能的骗局”,而不是应该被否决,是的,当问题很好并且没有给出解释时,这让我很恼火。由(还有谁)James_D 提供的正确解决方案:***.com/a/45079741/595305。我认为我们根本不干预StringConverter
类。 PS如何处理焦点应该是可配置的。
【参考方案1】:
编辑 在 kleopatra 的宝贵见解之后,NB 得到了改进。Edit2 在意识到最好的办法是使用现有的默认编辑器并对其进行调整后彻底检修。
我想我会举一个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;
然后创建一个新的编辑器单元格类,它与TextFieldTreeTableCell
(TreeTableCell
的子类)完全相同,默认情况下用于为TreeTableView
的表格单元格创建一个编辑器。但是,您不能真正继承 TextFieldTreeTableCell
,例如,它的基本字段 textField
是 private
。
所以你从源代码*中完整复制代码(只有大约 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);
NB 不要费心去查找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
。但是,使用ValidatingLocalDateStringConverter
的valid
字段覆盖一些事情以防止这种情况发生(即强制您输入有效日期或取消编辑,例如通过 Escape)是微不足道的:
@Override
void commitEdit( LocalDate newDueDate )
if( getConverter().valid )
super.commitEdit( newDueDate );
* 我在网上找不到这个。我从 javafx 源 .jar 文件 javafx-controls-11.0.2-sources.jar 中提取
【讨论】:
几个 cmets (顺便说一句,这种混合的任何东西,groovy?和纯 java 很难阅读 - 在答案中不是一个好主意;),没有特别的顺序:a)您不想在每个 startEdit 中重新创建 textField b) 只有在超级将单元格切换到编辑状态时才真正开始编辑 c) 如果您有 getString(item) 始终使用它 d) 不要覆盖那些不要添加任何东西 e) 在对显示的字符串进行更改之前触发 lambda (Java) 哪个 lambda?并且听起来更像是您使用次优格式化程序 - 它可以配置为执行您需要的任何操作;) 作为个人的旁注——在学习一个新的框架/工具包时,比较新旧之间的点点滴滴根本没有帮助,它们没有可比性。去过那里:)要么坚持旧的 - 并尝试根据需要进行改进 - 要么全心全意地切换到涉及尽可能“忘记”旧方式的新方式。 感谢您的建设性言论。当我有时间并应用您的建议时,我会将其放入 Java 中。重新切换:明智的方法......如果非常困难。请记住,3 天前我对 JavaFX 属性或工厂几乎一无所知,因此处于挣扎阶段。此外,没有其他人对看似相当普遍的问题提出答案(我也没有找到答案),而且可能不是 Swing 特定的(只有经验会教我!)。以上是关于如果解析失败,取消表格单元格编辑的规范方法的主要内容,如果未能解决你的问题,请参考以下文章