TornadoFX中TableView与详情编辑器数据同步问题及最佳实践咨询
Hey there! Let's tackle your problem step by step—first fixing the TableView update issue, then reviewing whether your current setup follows TornadoFX best practices.
Why the TableView Isn't Updating
The core issue here is that your Person data class uses plain var properties instead of JavaFX ObservableValue types (like StringProperty). TornadoFX's TableView relies on either:
- Observable properties in your model to detect in-place value changes, or
- Changes to the underlying
ObservableList(like adding/removing/replacing elements) to trigger row refreshes.
When you call model.commit() in your PersonEditor, it updates the properties of the original Person instance—but since those properties aren't observable, the TableView has no way to know the values changed. It only watches for structural changes to the personlist itself.
Best Solutions
Option 1: Trigger List Update After Commit (No Model Changes)
Since you want to keep your model free of JavaFX dependencies, this is the cleanest approach. After committing changes in the editor, replace the modified Person instance in the ObservableList to trigger a refresh:
- Pass a save callback to your editor:
class PersonEditor(private val person: Person, private val onSave: (Person) -> Unit) : Fragment("Edit Person") { val model = PersonModel(person) override val root = form { fieldset("Person Details") { field("Last Name") { textfield(model.name) } field("First Name") { textfield(model.firstname) } // Add fields for the `complex: Stuff` property as needed } buttonbar { button("Save") { action { if (model.commit()) { onSave(person) // Pass the updated instance back close() } } } button("Cancel") { action { close() } } } } } // Your ItemViewModel implementation class PersonModel(person: Person) : ItemViewModel<Person>(person) { val name = bind(Person::name) val firstname = bind(Person::firstname) // Bind `complex` properties if needed, or create a nested ViewModel for Stuff }
- Update the ObservableList in your main view:
// In your TableView-containing view private val personlist = mutableListOf<Person>().observable() // When opening the editor (e.g., on Enter key press) fun openEditor() { val selectedPerson = personlist.selectedItem ?: return find<PersonEditor>(mapOf( PersonEditor::person to selectedPerson, PersonEditor::onSave to { updatedPerson -> val index = personlist.indexOf(selectedPerson) if (index != -1) { // Replace the instance in the list to trigger TableView refresh personlist[index] = updatedPerson } } )).openModal() }
This works because replacing an element in an ObservableList fires a change event, which the TableView detects and uses to refresh the corresponding row.
Option 2: Use a Mapper to Sync ViewModel and List (Advanced)
If you want to avoid manual list updates, you can set up a listener on your ItemViewModel to sync changes back to the list automatically. This is useful if you have frequent edits:
class PersonModel(person: Person, private val list: ObservableList<Person>) : ItemViewModel<Person>(person) { val name = bind(Person::name) val firstname = bind(Person::firstname) init { // Listen for commit events and update the list onCommit { val index = list.indexOf(item) if (index != -1) { list[index] = item } } } }
Then pass personlist to the PersonModel when creating it in the editor.
Are You Following Best Practices?
Your current setup is mostly aligned with TornadoFX and clean architecture principles—here's the breakdown:
- ✅ Model independence: Keeping
Personfree of JavaFX dependencies is great for separation of concerns. Your model can be reused in non-UI contexts (like serialization/deserialization) without issues. - ✅ ObservableList for data: Using
mutableListOf<Person>().observable()is the recommended way to manage data in TornadoFX, as it integrates seamlessly withTableView. - ✅ ItemViewModel for editing: Using
ItemViewModelto handle form state and validation is a core TornadoFX best practice—it keeps your UI code clean and separates editing logic from the view.
Minor Improvements to Consider
- Handle null selection: Add a check for
personlist.selectedItembefore opening the editor to avoid crashes if no row is selected. - Nested ViewModel for
Stuff: IfStuffhas its own properties, create a nestedItemViewModelfor it inPersonModelto simplify form binding and validation. - Validation: Add validation rules to your
PersonModel(e.g., non-empty name) usingvalidatorblocks to improve user experience.
内容的提问来源于stack exchange,提问作者IARI

