TornadoFX中FlowPane如何实现类似ListCellFragment的模型绑定渲染?
I'm working with TornadoFX and want to replicate the ListCellFragment functionality for a FlowPane—specifically, rendering each cell with multiple controls and SVG drawings, bound to a shapeItem model. Right now, my code uses a simple button:
class LibraryView : View("Current Library") { val shapeLibraryViewModel : LibraryViewModel by inject() override val root = anchorpane{ flowpane { bindChildren(shapeLibraryViewModel.libraryItemsProperty){ shapeItem -> button(shapeItem.nameProperty) } } } }
Since there's no pre-built component for FlowPane like ListCellFragment, I'm wondering if I need to create a similar custom component, or if there's a lighter-weight way to achieve this?
Great question! TornadoFX doesn't have a direct equivalent to ListCellFragment for FlowPane, but you don't need to build a full custom component from scratch—there are two lightweight approaches that work perfectly for this scenario.
Option 1: Reusable Fragment for Cells
If you want a cell layout you can reuse across multiple views, create a small Fragment that encapsulates your cell's UI and binds directly to the shapeItem model:
// Define the reusable cell fragment class ShapeCellFragment(private val shapeItem: ShapeItem) : Fragment() { override val root = vbox(alignment = Pos.CENTER, spacing = 4.0, styleClass = arrayOf("shape-cell")) { // SVG drawing (replace with your actual SVG path logic) svgview("/assets/svg/${shapeItem.svgFileNameProperty.value}") { fitWidth = 60.0 fitHeight = 60.0 } // Label bound to the shape's name property label(shapeItem.nameProperty) { styleClass.add("shape-name") } // Action buttons tied to the shape item hbox(spacing = 4.0) { button("Edit") { action { /* Add your edit logic for this shapeItem */ } } button("Delete") { action { shapeLibraryViewModel.remove(shapeItem) } } } } } // Update your LibraryView to use the fragment class LibraryView : View("Current Library") { val shapeLibraryViewModel : LibraryViewModel by inject() override val root = anchorpane{ flowpane(spacing = 10.0, padding = Insets(10.0)) { bindChildren(shapeLibraryViewModel.libraryItemsProperty){ shapeItem -> add(ShapeCellFragment(shapeItem).root) } } } }
Option 2: Inline Component Builder (Quick & Simple)
If you only need this cell layout once, you can build all the components directly inside the bindChildren lambda without a separate fragment:
class LibraryView : View("Current Library") { val shapeLibraryViewModel : LibraryViewModel by inject() override val root = anchorpane{ flowpane(spacing = 10.0, padding = Insets(10.0)) { bindChildren(shapeLibraryViewModel.libraryItemsProperty){ shapeItem -> vbox(alignment = Pos.CENTER, spacing = 4.0, styleClass = arrayOf("shape-cell")) { // SVG drawing svgview("/assets/svg/${shapeItem.svgFileNameProperty.value}") { fitWidth = 60.0 fitHeight = 60.0 } // Bound name label label(shapeItem.nameProperty) // Action buttons hbox(spacing = 4.0) { button("Edit") { action { /* Edit logic here */ } } button("Delete") { action { shapeLibraryViewModel.remove(shapeItem) } } } } } } } }
Key Takeaways
- Both approaches let you bind directly to
shapeItemobservable properties, just likeListCellFragmentdoes for lists. - Use the
Fragmentoption for reusability, and the inline approach for one-off use cases to keep things concise. - Add CSS styles (like
shape-cellorshape-name) to polish the appearance of your cells.
内容的提问来源于stack exchange,提问作者DanielMcQ

