TornadoFX 如何使用 child windows 模型列表创建 MDI?

TornadoFX How to create MDI with list of child windows models?

我有以下组件:

class ChildModel:ViewModel() { //or it may be an POJO, it does not matter
  val value ....      
} 

class ParentView: View() {
  ...
  //Maybe this should be implemented into ParentViewModel   
  val childrenList:List<ChildModel>

  fun addFragmentAsChild() {
    //should:
    // 1. display fragment within ParentView
    // 2. add fragment into modelList (or fragmentList - it does not matter -  important to have access to the model of every child )  
  }

  fun deleteFragmentAsChild() {
    //should destroy child and remove item from childrenList   
    //should work also on manual closing 
  }         
}

class ChildFragment: Fragment() {
  val model = ChildModel()      
...
}

总结:我想创建 MDI 并可以访问每个 child 的模型。

我尝试在 "openInternalWindow" 的帮助下执行此操作,但我无法创建多个 child 实例并且我必须手动管理列表 - 这很糟糕。

class InstrumentsView: View() {
  override val root = BorderPane()
  val instrumentList = ArrayList<InstrumentFragment>()

  init {
    with(root){
      top = menubar {
        menu("Tools") {
          menuitem("Add instrument", "Shortcut+A") {
            val newFragment = InstrumentFragment()
            instrumentList.add(newFragment)
            println(instrumentList.size)
            openInternalWindow(newFragment, modal = false)
          }

        }
      }
    }
  }
}

如何正确使用 tornadofx 方法?

在此示例中,我将使用视图模型和范围界定来跟踪每个仪器编辑器的项目。我们需要确保乐器是唯一的,以便我们可以在编辑器关闭时将它们从列表中删除。我创建了一个具有 ID 和名称的 Instrument 域对象:

class Instrument {
    val idProperty = SimpleObjectProperty<UUID>(UUID.randomUUID())
    var id by idProperty

    val nameProperty = SimpleStringProperty()
    var name by nameProperty

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other?.javaClass != javaClass) return false

        other as Instrument

        if (id != other.id) return false

        return true
    }

    override fun hashCode(): Int {
        return id.hashCode()
    }
}

我们想要一个可以在仪器编辑器中注入的视图模型。我们将确保视图模型默认包含一个新仪器。它包含名称 属性 的外观,因此我们可以将它绑定到编辑器输入字段。

class InstrumentModel: ItemViewModel<Instrument>() {
    init {
        item = Instrument()
        item.name = "New instrument"
    }
    val name = bind { item?.nameProperty }
}

A Fragment 具有 onDockonUndock 的回调,可用于跟踪该片段的模型。我们可以使用事件来表示这一点。声明以下事件:

class InstrumentAdded(val instrument: Instrument) : FXEvent()
class InstrumentRemoved(val instrument: Instrument) : FXEvent()

覆盖 InstrumentFragment 中的停靠回调以触发这些事件:

override fun onDock() {
    fire(InstrumentAdded(model.item))
}

override fun onUndock() {
    fire(InstrumentRemoved(model.item))
}

现在我们将在主视图中保留工具列表,InstrumentsView。这也可以在 Controller.

val instruments = FXCollections.observableArrayList<Instrument>()

在主视图的初始化 class 中,我们将订阅我们创建的事件并修改我们的列表:

subscribe<InstrumentAdded> {
    instruments.add(it.instrument)
}
subscribe<InstrumentRemoved> {
    instruments.remove(it.instrument)
}

"New Instrument" 操作将在新的 Scope 中打开一个新的 InstrumentEditor,因此我们可以将视图模型注入其中并获得该编辑器独有的实例。

menuitem("Add instrument", "Shortcut+A") {
    find<InstrumentFragment>(Scope()).openWindow()
}

遗憾的是,我们无法使用 openInternalWindow,因为它目前一次仅支持一个内部 window。因此我改用 openWindow

如果您想通过操作关闭编辑器,可以从片段内的任何位置调用 closeModal()

我已经包含了一个完整的示例应用程序,其中包含一个显示当前打开的工具的 TableView。它将如下图所示。请注意,在更改从模型中刷新并在 table 中可见之前,您需要点击保存。

我希望这就是您正在寻找的,或者您至少可以根据此示例修改它以适合您的用例。

import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections
import tornadofx.*
import java.util.*

class Instrument {
    val idProperty = SimpleObjectProperty<UUID>(UUID.randomUUID())
    var id by idProperty

    val nameProperty = SimpleStringProperty()
    var name by nameProperty

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other?.javaClass != javaClass) return false

        other as Instrument

        if (id != other.id) return false

        return true
    }

    override fun hashCode(): Int {
        return id.hashCode()
    }
}

class InstrumentModel : ItemViewModel<Instrument>() {
    init {
        item = Instrument()
        item.name = "New instrument"
    }

    val name = bind { item?.nameProperty }
}

class InstrumentAdded(val instrument: Instrument) : FXEvent()
class InstrumentRemoved(val instrument: Instrument) : FXEvent()

class InstrumentFragment : Fragment("Instrument Editor") {
    val model: InstrumentModel by inject()

    override val root = form {
        prefWidth = 300.0
        fieldset("Edit instrument") {
            field("Name") {
                textfield(model.name)
            }
        }
        button("Save") {
            setOnAction {
                model.commit()
            }
        }
    }

    override fun onDock() {
        fire(InstrumentAdded(model.item))
    }

    override fun onUndock() {
        fire(InstrumentRemoved(model.item))
    }
}

class InstrumentsView : View() {
    val instruments = FXCollections.observableArrayList<Instrument>()

    override val root = borderpane {
        setPrefSize(400.0, 300.0)
        top {
            menubar {
                menu("Tools") {
                    menuitem("Add instrument", "Shortcut+A") {
                        find<InstrumentFragment>(Scope()).openWindow()
                    }
                }
            }
        }
        center {
            tableview(instruments) {
                column("Name", Instrument::nameProperty)
                columnResizePolicy = SmartResize.POLICY
            }
        }
    }

    init {
        subscribe<InstrumentAdded> {
            instruments.add(it.instrument)
        }
        subscribe<InstrumentRemoved> {
            instruments.remove(it.instrument)
        }
    }

}