TornadoFX Как создать MDI со списком дочерних моделей окон?

У меня есть следующие компоненты:

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 и иметь доступ к модели для каждого ребенка.

Я пытаюсь сделать это с помощью openInternalWindow, но я не могу создать несколько дочерних экземпляров, и мне приходится вручную управлять списком - это плохо.

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 способом?


person Wi-Al    schedule 04.01.2017    source источник
comment
Вам нужен доступ к реальным фрагментам или вам просто нужен список моделей, которые они содержат, и этот список должен обновляться при открытии / закрытии редакторов фрагментов? Должен ли каждый InstrumentFragment управлять своим собственным / отдельным инструментом? PS: Никогда не создавайте экземпляр фрагмента напрямую, используйте find, inject или this += InstrumentFragment.   -  person Edvin Syse    schedule 04.01.2017
comment
Мне нужен доступ к списку моделей (конечно, я также могу получить ссылку на модель, если у меня будет список фрагментов - мне наиболее важно чистое и элегантное решение), и я хочу обновить этот список после открытия / закрытия фрагмента редакторы. Каждый IntrumentFragment знает только о своей модели (или имеет ссылку на родительский объект - почему бы и нет?).   -  person Wi-Al    schedule 04.01.2017


Ответы (1)


В этом примере я буду использовать модель представления и область видимости, чтобы отслеживать элемент для каждого редактора инструментов. Нам нужно убедиться, что инструменты уникальны, чтобы мы могли удалить их из списка при закрытии редактора. Я создал объект домена 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()
    }
}

Нам нужна модель представления, которую мы можем внедрить в редактор инструментов. Мы позаботимся о том, чтобы модель представления по умолчанию содержала новый инструмент. Он содержит фасад для свойства name, чтобы мы могли привязать его к полю ввода редактора.

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

Fragment имеет обратные вызовы для onDock и onUndock, которые можно использовать для отслеживания модели для этого фрагмента. Мы можем использовать события, чтобы сигнализировать об этом. Объявите следующие события:

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>()

В классе init основного представления мы подпишемся на созданные нами события и изменим наш список:

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

Действие «New Instrument» откроет новый InstrumentEditor в новом Scope, чтобы мы могли внедрить в него модель представления и получить экземпляр, уникальный для этого редактора.

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

К сожалению, мы не можем использовать openInternalWindow, поскольку в настоящее время он поддерживает только одно внутреннее окно за раз. Поэтому вместо этого я использовал openWindow.

Если вы хотите закрыть редактор из действия, вы можете вызвать closeModal() из любого места внутри фрагмента.

Я включил полный пример приложения с TableView, который показывает открытые в данный момент инструменты. Это будет выглядеть как на картинке ниже. Обратите внимание, что вам нужно нажать «Сохранить», прежде чем изменения будут удалены из модели и отображены в таблице. образец приложения

Я надеюсь, что это то, что вы ищете, или что вы, по крайней мере, можете изменить его в соответствии со своим вариантом использования на основе этого образца.

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

}
person Edvin Syse    schedule 04.01.2017
comment
Спасибо, Эдвин! Это то, что я хочу! Я никогда не использовал функциональность Scope и EventBus. Надо учиться. - person Wi-Al; 04.01.2017
comment
Рад это слышать :) Мы добавили в руководство Scope и EventBus, если вы хотите прочитать об этом там: gitbook.com/book/edvin/tornadofx-guide/details - person Edvin Syse; 04.01.2017