【JavaFx教程】第二部分:Model 和 TableView

图片 14

JavaFX 是Java中用来构建图形应用程序的新的标准库, 但非常多程序员反之亦然始终如一在利用Swing以致AWT(额滴个神啊)。关于怎么着运用JavaFX工具集中的新的超棒性情来塑造响应式的高效应用程序,这里有点建议!

译自《Components》

图片 1

1. 属性值

一旦您对JavaFX组件做过完整的打听,移动境遇过属性(Property)这几个事物。FX库中大约每个值都得以被观看,分区(divider)的增长幅度,图片的尺码,文本标记(label)中的文字,二个列表中的子项以致复选框(checkbox)的情况。属性分成另类:可写属性和可读属性。可写值能够被更改,使用设置器方法还是直接改造。
JavaFX
会处管事人件处置进度并有限支持各类信任于此属性的组件都会被通知到。可读属性具有能令你在其值被改过时接到到通报的艺术。

示例:

// 可读-且可写
StringProperty name = new SimpleStringProperty("Emil"); 
// 只读
ObservableBooleanValue nameIsEmpty = name.isEmpty();

组件

JavaFX使用戏剧类比来协会贰个暗含StageScene组件的Application
TornadoFX通过提供ViewControllerFragment构件也营造在那类比基本功之上。
即便TornadoFX也利用StageScene,但ViewControllerFragment引进了能够简化开荒的新定义。
那几个组件大多被活动爱戴为单例(singletons),并且能够经过轻巧的信任注入(dependency
injections)和别的艺术互相通讯。

你还能选择采用FXML,稍后构和谈。
但首先,让我们三回九转App来创设用于运营TornadoFX应用程序的入口点。

其次有的的主旨

  • 成立四个模型类。
  • ObservableList运用模型类。
  • 使用ControllersTableView上出示数据。

2. 绑定值

当您有着三个可写和可读值的时候,你能够早先就那几个值怎么关联定义法规。三个可写属性能够被绑定到三个可读属性,如此其值总是会协作到可读的这些。绑定并不会登时发出,可是它们会在值被考察以前开展(看看笔者在此做的就清楚了卡塔尔国。
绑定能够是单向也许双向的。当然,假使它们之间是双向的,就须求八个属性都是可写的。

示例:

TextField fieldA = new TextField();
TextField fieldB = new TextField();
fieldA.prefWidthProperty().bind(fieldB.widthProperty());

App和View的功底知识

要开创TornadoFX应用程序,您必需至罕见二个世襲了App的类。App是应用程序的入口点,并点名初步View
实际上它一连了JavaFX的Application
,可是你不必然须要钦定一个start()main()方法。

但首先,让大家后续App来成立协调的完成,并将主视图(primary
view)钦命为构造函数的率先个参数。

class MyApp: App(MyView::class)

视图(View)包罗展现逻辑以致节点(Nodes)的布局,形似于JavaFX的Stage
它被看作单例(singleton)来机关管理。
当您声美素佳儿(Friso卡塔尔(قطر‎个View,您必需钦赐一个root属性,该属性能够是其他Node类型,並且将保留视图(View)的从头到尾的经过。

在同叁个Kotlin文件或在另四个新文件中,从View一而再出来一个新类。
覆盖其抽象root品质并赋值VBox,或你选择的别样此外Node

class MyView: View() {
    override val root = VBox()
}

而是,大家兴许想填充这几个VBox,作为root控件。 使用起初化程序块
(initializer
block),让我们抬高中二年级个JavaFX的Button和一个Label
您可以使用 “plus assign”
+=运算符将子项增添到任何Pane品种,富含这里的VBox

class MyView: View() {
    override val root = VBox()

    init {
        root += Button("Press Me")
        root += Label("")
    }
}

纵然从查看上述代码来看,很理解发生了哪些,但TornadoFX还提供了三个营造器语法(builder
syntax),能够进一层简化您的UI代码,并可由此翻看代码来更容易地演绎出最终的UI。
大家将逐年转化营造器语法,最终在下一章中完美介绍创设器(builders)。

虽说大家会向你介绍新定义,但您恐怕不常候还只怕会见到未有应用最棒做法的代码。
大家这么做是为了向您介绍这么些概念,并让您更彻底地领会底层产生的景况。
渐渐地,我们将会以越来越好的章程介绍越来越强有力的结构来解决那一个难题。

接下去大家将看到什么样运转那个应用程序。

创建模型类。

大家需求三个模子类来保存联系人音信到大家的简报录中。在模型包中
(ch.makery.address.model卡塔尔国加多八个叫Person的类。Person类将会有一部分变量,名字,地址和生辰。将以下代码增加到类。在代码后,我将解释一些
JavaFX 的细节。

Person.java

package ch.makery.address.model;import java.time.LocalDate;import javafx.beans.property.IntegerProperty;import javafx.beans.property.ObjectProperty;import javafx.beans.property.SimpleIntegerProperty;import javafx.beans.property.SimpleObjectProperty;import javafx.beans.property.SimpleStringProperty;import javafx.beans.property.StringProperty;/** * Model class for a Person. * * @author Marco Jakob */public class Person {    private final StringProperty firstName;    private final StringProperty lastName;    private final StringProperty street;    private final IntegerProperty postalCode;    private final StringProperty city;    private final ObjectProperty<LocalDate> birthday;    /**     * Default constructor.     */    public Person() {        this(null, null);    }    /**     * Constructor with some initial data.     *      * @param firstName     * @param lastName     */    public Person(String firstName, String lastName) {        this.firstName = new SimpleStringProperty(firstName);        this.lastName = new SimpleStringProperty;        // Some initial dummy data, just for convenient testing.        this.street = new SimpleStringProperty("some street");        this.postalCode = new SimpleIntegerProperty;        this.city = new SimpleStringProperty("some city");        this.birthday = new SimpleObjectProperty<LocalDate>(LocalDate.of(1999, 2, 21));    }    public String getFirstName() {        return firstName.get();    }    public void setFirstName(String firstName) {        this.firstName.set(firstName);    }    public StringProperty firstNameProperty() {        return firstName;    }    public String getLastName() {        return lastName.get();    }    public void setLastName(String lastName) {        this.lastName.set;    }    public StringProperty lastNameProperty() {        return lastName;    }    public String getStreet() {        return street.get();    }    public void setStreet(String street) {        this.street.set;    }    public StringProperty streetProperty() {        return street;    }    public int getPostalCode() {        return postalCode.get();    }    public void setPostalCode(int postalCode) {        this.postalCode.set(postalCode);    }    public IntegerProperty postalCodeProperty() {        return postalCode;    }    public String getCity() {        return city.get();    }    public void setCity(String city) {        this.city.set;    }    public StringProperty cityProperty() {        return city;    }    public LocalDate getBirthday() {        return birthday.get();    }    public void setBirthday(LocalDate birthday) {        this.birthday.set;    }    public ObjectProperty<LocalDate> birthdayProperty() {        return birthday;    }}

3. 可观看的列表

个性并不是独一可以被考察的东西。借使列表是被包裹到了叁个 ObservableList 中,那么列表的分子平等也是足以被调查到的。ObservableList
的响应模型是一定先进的。你不单能在列表被校勘时接到通知,也得以看看列表具体是什么被涂改的。

示例:

List<String> otherList = Arrays.asList("foo", "bar", "bar");
ObservableList<String> list = FXCollections.observableList(otherList);

list.addListener((ListChangeListener.Change<? extends String> change) -> {
    System.out.println("Received event.");
    while (change.next()) {
        if (change.wasAdded()) {
            System.out.println(
                "Items " + change.getAddedSubList() + " was added.");
        }

        if (change.wasRemoved()) {
            System.out.println(
                "Items " + change.getRemoved() + " was removed.");
        }
    }
});

System.out.println("Old list: " + list);
list.set(1, "foo");
System.out.println("New list: " + list);

上边代码的运维输出如下:

Old list: [foo, bar, bar]
Received event.
Items [foo] was added.
Items [bar] was removed.
New list: [foo, foo, bar]

如您所见,设置操作只会接触一遍事件。

开发银行TornadoFX应用程序

较新本子的JVM知道哪些在未有main()主意的动静下运维JavaFX应用程序。
JavaFX应用程序(TornadoFX应用程序是其扩大),是后续javafx.application.Application的其他类。
由于tornadofx.App继承了javafx.application.Application
,TornadoFX应用程序未有啥区别。
因而,您将经过援引com.example.app.MyApp发轫该应用程序,并且您不肯定必要五个main()函数,除非你需求提供命令行参数。
在这种气象下,您将急需增加多个包级其他主函数到MyApp.kt文件:

fun main(args: Array<String>) {
  Application.launch(MyApp::class.java, *args)
}

其一主函数将被编写翻译进com.example.app.MyAppKt – 注意最终的Kt
当您创造包等第的主函数时,它将从来富有完全约束包的类名,加上文件名,附加Kt

对于运维和测量试验App ,大家将利用AMDlij IDEA。 导航到Run→Edit
Configurations
(图3.1)。

图片 2

图3.1

单击蟹灰“+”标志并创办三个新的应用程序配置(图3.2)。

图片 3

图3.2

点名 “主类(Main class)” 的称谓,那应该是你的App类。
您还索要内定它所在的模块(module)。给布署三个有意义的名目,如
“Launcher”。 之后点击 “OK”(图3.3)。

图片 4

图3.3

你能够通过甄选Run→Run ‘Launcher’或任何你命名的陈设来运维TornadoFX应用程序(图3.4)。

图片 5

图3.4

您现在应当看见你的应用程序运维了(图3.5)

图片 6

图3.5

恭贺! 您曾经编写制定了你的第叁个(就算简易)TornadoFX应用程序。
今后看起来只怕不是很好,不过当大家富含更加的多TornadoFX的强盛效用时,大家将创制大气令人记念深入的客户界面,大约从未稍稍代码,何况只要求相当少时间。
但首先让我们来更加好地询问AppView时期产生的情状。

解释

  • 在JavaFX中,对一个模型类的全数属性使用Properties是很宽泛的.
    二个Property同意大家, 打个借使,
    lastName或别的质量被退换时自动选取布告,
    那有利于大家维持视图与数量的一道,阅读Using JavaFX Properties and
    Binding学习越多关于Properties的内容。
  • birthday, 大家接纳了LocalDate项目, 这在Date and 提姆e API for JDK
    第88中学是七个新的部分.

询问视图(View)

让咱们深切摸底View的劳作规律以致哪些接受它。
看看大家正好营造的AppView类。

class MyApp: App(MyView::class)

class MyView: View() {
    override val root = VBox()

    init {
        with(root) {
            this += Button("Press Me")
            this += Label("Waiting")
        }
    }
}

View含有JavaFX节点的层次构造,并在它被调用的职责通过名称注入。
在下一节中,我们将学习如何使用刚劲的营造器(powerful
builders)来超级快创制那些Node档次构造。TornadoFX维护的MyView唯有三个实例,有效地使其变为单例。TornadoFX还支持范围(scopes),它们得以将View
FragmentController的聚集组合在二个独自的命名空间中,假如你愿意的话,那么View只好是该限定内的单例。
那对于多文书档案接口应用程序(Multiple-Document Interface
applications)和任何高级用例极度实用。
稍后加以。

职员列表

咱俩的选用关键管理的数额是一批人的新闻.让大家在MainApp类里面制造多少个Person指标的列表。稍后别的具有的决定器类将存取MainApp的为主列表。

动用inject(卡塔尔国和停放视图(Embedding Views)

您也得以将四个或多个视图注入另一个View
上边大家将TopViewBottomView嵌入到MasterView
请注意,大家选用inject()代办属性(delegate
property)来懒惰地流入TopViewBottomView实例。
然后大家调用种种child Viewroot来赋值给BorderPane(图3.6)。

class MasterView: View() {
    val topView: TopView by inject()
    val bottomView: BottomView by inject()

    override val root = borderpane {
        top = topView.root
        bottom = bottomView.root
    }
}

class TopView: View() {
    override val root = label("Top View")
}

class BottomView: View() {
    override val root = label("Bottom View")
}

图片 7

图3.6

倘让你要求在视图间互为交流,您能够在各种child
View中开创五个性质来保存parent View

class MasterView : View() {
    override val root = BorderPane()

    val topView: TopView by inject()
    val bottomView: BottomView by inject()

    init {
        with(root) {
            top = topView.root
            bottom = bottomView.root
        }

        topView.parent = this
        bottomView.parent = this
    }
}

class TopView: View() {
    override val root = Label("Top View")
    lateinit var parent: MasterView
}

class BottomView: View() {
    override val root = Label("Bottom View")
    lateinit var parent: MasterView
}

更多如牛毛地,您将选用ControllerViewModel在视图之间举办通讯,稍后我们将做客此大旨。

ObservableList

作者们管理JavaFX的view classes需求在人口列表产生其余退换时都被文告.
那是非常重大的,否则视图就能和多少不一致步.为了达成这一个指标,JavaFX引进了有些新的联谊类.

在此些聚聚焦, 我们必要的是ObservableList.
将以下代码增到MainApp类的上马去创建二个新的ObservableList.
大家也会追加二个布局器去成立一些样书数量和叁个共用的getter方法:

MainApp.java

    // ... AFTER THE OTHER VARIABLES ...    /**     * The data as an observable list of Persons.     */    private ObservableList<Person> personData = FXCollections.observableArrayList();    /**     * Constructor     */    public MainApp() {        // Add some sample data        personData.add(new Person("Hans", "Muster"));        personData.add(new Person("Ruth", "Mueller"));        personData.add(new Person("Heinz", "Kurz"));        personData.add(new Person("Cornelia", "Meier"));        personData.add(new Person("Werner", "Meyer"));        personData.add(new Person("Lydia", "Kunz"));        personData.add(new Person("Anna", "Best"));        personData.add(new Person("Stefan", "Meier"));        personData.add(new Person("Martin", "Mueller"));    }      /**     * Returns the data as an observable list of Persons.      * @return     */    public ObservableList<Person> getPersonData() {        return personData;    }      // ... THE REST OF THE CLASS ...

使用find()来注入

inject()代办(delegate)将懒惰地将一个加以的机件赋值给壹天个性。
第4回调用该器件时,它将被搜寻。
也许,不利用inject()代办,您能够行使find()函数来搜寻View或此外零零器件的单例实例。

class MasterView : View() {
    override val root = BorderPane()

    val topView = find(TopView::class)
    val bottomView = find(BottomView::class)

    init {
        with(root) {
            top = topView.root
            bottom = bottomView.root
        }
    }
}

class TopView: View() {
    override val root = Label("Top View")
}

class BottomView: View() {
    override val root = Label("Bottom View")
}

你能够使用find()inject()
,可是利用inject()代理是实施注重注入的首推办法。

即便大家将要下一章更加尖锐地介绍构建器(builders),但近日是时候来宣布上述示范能够用更为简洁明了的语法来编排了:

class MasterView : View() {
    override val root = borderpane {
        top(TopView::class)
        bottom(BottomView::class)
    }
}

我们不是先注入TopViewBottomView,然后将它们各自的root节点赋值给BorderPanetopbottom特性,而是接收创设器语法(builder
syntax,全体大写)来钦定BorderPane,然后注明性地告诉TornadoFX拉入多少个子视图,并使她们活动赋值到topbottom品质。
大家意在你会认可,那是很具表现力的,具备少得多的样子(boiler plate)。
那是TornadoFX试图以此为生的最首要的标准之一:减弱样本(boiler
plate),升高可读性。 最后的结果往往是越来越少的代码和更加少的失实。

The PersonOverviewController

不久前我们终于要将数据参预到表格中了,大家要求三个调整器为了PersonOverview.fxml,.

  1. view包下创立五个名称叫PersonOverviewController.java的平常java类(大家须要将那一个类位居和PersonOverview.fxml一律的包下,
    不然SceneBuilder会找不到它 – 起码在日前的版本卡塔尔(قطر‎.
  2. 咱俩要求追加部分实例变量来访谈表格和在视图中的标签.那个属性和有些格局有三个别具肺肠的@FXML注解.
    那对于fxml文件访问私有属性和村办方法来讲是不可或缺的.
    当将一切都在fxml文件中装置好之后,
    应用程序会在fxml文件被载入时自动地填写那几个变量.
    让我们抬高以下的代码:

Note:切记要接收javafx imports, 而不是awt和swing!

PersonOverviewController.java

package ch.makery.address.view;import javafx.fxml.FXML;import javafx.scene.control.Label;import javafx.scene.control.TableColumn;import javafx.scene.control.TableView;import ch.makery.address.MainApp;import ch.makery.address.model.Person;public class PersonOverviewController {    @FXML    private TableView<Person> personTable;    @FXML    private TableColumn<Person, String> firstNameColumn;    @FXML    private TableColumn<Person, String> lastNameColumn;    @FXML    private Label firstNameLabel;    @FXML    private Label lastNameLabel;    @FXML    private Label streetLabel;    @FXML    private Label postalCodeLabel;    @FXML    private Label cityLabel;    @FXML    private Label birthdayLabel;    // Reference to the main application.    private MainApp mainApp;    /**     * The constructor.     * The constructor is called before the initialize() method.     */    public PersonOverviewController() {    }    /**     * Initializes the controller class. This method is automatically called     * after the fxml file has been loaded.     */    @FXML    private void initialize() {        // Initialize the person table with the two columns.        firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty;        lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty;    }    /**     * Is called by the main application to give a reference back to itself.     *      * @param mainApp     */    public void setMainApp(MainApp mainApp) {        this.mainApp = mainApp;        // Add observable list data to the table        personTable.setItems(mainApp.getPersonData;    }}

想必定要解释一下这段代码:

  • 全数fxml文件需求探访的个性和措施必得抬高@FXML注解.实际上,唯有在民用的情景下才须要,
    可是让它们保持个人并且用证明标识的秘籍更加好!
  • initialize()方法在fxml文件完结载入时被电动调用. 那时候,
    全体的FXML属性都应已被发轫化.
  • 咱俩在表格列上行使setCellValueFactory来规定为一定列使用Person对象的某部属性.
    箭头->表示我们在选取Java 8的Lambdas特征.
    (另二个精选是运用PropertyValueFactory, 但它不是类别安全的卡塔尔.

控制器(Controllers)

在数不胜数场馆下,将UI分为四个分化的有个别被以为是一种很好的做法:

    1. 模型(Model) – 具有大旨逻辑和数量的作业代码层。
    1. 视图(View)- 具备种种输入和输出控件的视觉显示。
    1. 控制器(Controller) – “中间人(middleman)”
      参预(mediating)模型和视图之间的平地风波。

还会有任何的MVC流派,比方MVVM和MVP,全数这几个都足以在TornadoFX中应用。

固然你可以将模型和调节器的兼具逻辑放在视图之中,可是最佳将那多少个部分精通地分开,以便最大程度地促成可重用性。
贰个常用的形式是MVC情势。
在TornadoFX中,能够注入叁个Controller来支持View

此地给出二个简洁明了的事例。 使用叁个TextField创造一个粗略的View
,当一个Button被点击时,其值被写入到一个“数据库”。
大家能够注入贰个拍卖与写入数据库的模型交互的Controller
由于这些事例是简化的,所以不会有实在的数据库,但打字与印刷的音信将用作占位符(图3.7)。

class MyView : View() {
    val controller: MyController by inject()
    var inputField: TextField by singleAssign()

    override val root = vbox {
        label("Input")
        inputField = textfield()
        button("Commit") {
            action {
                controller.writeToDb(inputField.text)
                inputField.clear()
            }
        }
    }
}

class MyController: Controller() {
    fun writeToDb(inputValue: String) {
        println("Writing $inputValue to database!")
    }
}

图片 8

图3.7

当大家营造UI时,大家保障增进对inputField的引用,以便现在能够在“Commit”按键的onClick事件管理程序中援用。
当单击“Commit”按键时,您将看见调节器向调整台打字与印刷一行。

Writing Alpha to database!

根本的是要专心,尽管上述代码是可职业的,以至恐怕看起来也不易,然而很好的做法是要避免直接征引其余UI成分。
如若您将UI成分绑定到属性并操作属性,那么您的代码将更易于重构。
稍后大家将介绍ViewModel,它提供了更简短的章程来管理那系列型的相互。

连接 MainApp 和 PersonOverviewController

setMainApp必须被MainApp类调用.
那让大家能够访谈MainApp对象并获得Persons的列表和任王志平西.
用以下代码替换showPersonOverview()方法. 它满含了增加产能的两行:

MainApp.java – new showPersonOverview() method

/** * Shows the person overview inside the root layout. */public void showPersonOverview() {    try {        // Load person overview.        FXMLLoader loader = new FXMLLoader();        loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));        AnchorPane personOverview = (AnchorPane) loader.load();        // Set person overview into the center of root layout.        rootLayout.setCenter(personOverview);        // Give the controller access to the main app.        PersonOverviewController controller = loader.getController();        controller.setMainApp;    } catch (IOException e) {        e.printStackTrace();    }}

长日子运作的职分

每当你在调节器中调用函数时,须求分明该函数是或不是登时赶回,或然执行秘密的长日子运作的天职。
倘令你在JavaFX应用程序线程中调用函数,则UI将要响应达成早前无响应。
无响应的UI是客户感知(user
perception)的杀人犯,由此请确定保障您在后台运维高昂的操作。
TornadoFX提供了runAsync功用来协助你。

停放在一个runAsync块内的代码将要后台运营。
借使后台调用的结果须求改过您的UI,则必得确定保证您在JavaFX的应用程序线程中运用校勘。ui区块就是这么。

val textfield = textfield()
button("Update text") {
    action {
        runAsync {
            myController.loadText()
        } ui { loadedText ->
            textfield.text = loadedText
        }
    }
}

当单击开关时,将运维action构建器(将ActionEvent代理给setAction主意)中的操作。
它调用myController.loadText(),并当它回到shi将结果使用于textfieldtext天性。
当调整器效用运维时,UI保持响应。

在外界以下,
runAsync会创设三个JavaFX的Task目的,并将创设二个独门的线程以在Task里运行你的调用。
您能够将此Task赋值给变量,并将其绑定到UI,以在运转时呈现速度。

其实,那是很广阔的,为此还可能有三个名称为TaskStatus的默认ViewModel,它包含running
messagetitleprogress等可观望值。
您能够选用TaskStatus对象的特定实例来提供runAsync调用,或利用私下认可值。

TornadoFX源代码在AsyncProgressApp.kt文本中隐含三个示范用法。

还会有三个名字为runAsyncWithProgressrunAsync版本,
runAsync在长日子运作的操作运营时,以速度提醒器来隐讳当前节点。

将View与Controller挂钩

我们就要完结了! 不过有件麻烦事被脱漏了:
到现在未有告诉PersonOverview.fxml利用的是哪个调节器以致成分与调控器中的属性的应和关系.

  1. 使用SceneBuilder打开PersonOverview.fxml.

  2. 展开左侧的Controller组选择PersonOverviewController作为controller
    class
    .
    图片 9

  3. Hierarchy组选择TableView并选择Code组将personTable作为fx:id.
    图片 10

  4. 对列做同样的事同一时间将firstNameColumnandlastNameColumn个别作为fx:id.

  5. 对在第二列的each label, 接收相应的fx:id.
    图片 11

  6. 最首要事项: 回到eclipse况兼refresh the entire AddressApp project.
    那是不可贫乏的因为临时候eclipse并不知道在Scene Builder中作出的更正.

singleAssign(卡塔尔属性代理

在下面的事例中,大家用singleAssign代理起头化了inputField质量。
纵然要保险只赋值一回值,可以动用singleAssign()代办代替Kotlin的lateinit器重字。
那将诱致第三个赋值引发错误,何况在赋值在此以前过早访谈时也会出错。

你可以在附录A1中详尽查看有关singleAssign(卡塔尔(قطر‎的越来越多音讯,不过今后知道它保险只可以赋值二次给var。
它也是线程安全的,有帮忙缓解可变性(mutability)难题。

您仍是可以使用调整器向View提供数据(图3.8)。

class MyView : View() {
    val controller: MyController by inject()

    override val root = vbox {
        label("My items")
        listview(controller.values)
    }
}

class MyController: Controller() {
    val values = FXCollections.observableArrayList("Alpha","Beta","Gamma","Delta")
}

图片 12

图3.8

VBox带有多个Label和一个ListViewControllervalues本性被赋值给ListViewitems属性。

无论是他们是读数据依然写多少,调整器都恐怕会实行长时间运作的天职,从而不能够在JavaFX线程上推行任务。
本章后边您将学习如何利用runAsync组织来轻易地将职业卸载到办事线程。

起步应用程序

当你未来起步了您的使用,你应当见到了近乎那篇博客开始的截图的程序分界面.

恭喜!

———————本文来自 jobbible 的CSDN 博客
,全文地址请点击:

分段(Fragment)

你创立的别的View都以单例,那意味着你平时只可以在二个地点一遍选取它。
原因是在JavaFX应用程序中View的根节点(root node)只可以具有单个父级。
假设你赋值另三个父级,它将从它的先前的父级消失。

不过,假让你想创建一个短间隔赛跑(short-lived)的UI,或然能够在三个地方选拔,请考虑接受Fragment
片段(Fragment)是能够有八个实例的优秀类型的View
它们对于弹出窗口或更加大的UI以至是单个ListCell都特别有用。
稍后大家将拜候到三个名字为ListCellFragment的特别的一对。

ViewFragment支持openModal()
openWindow()openInternalWindow()
,它将要独立的窗口(Window)中展开根节点。

class MyView : View() {
    override val root = vbox {
        button("Press Me") {
            action {
                find(MyFragment::class).openModal(stageStyle = StageStyle.UTILITY)
            }
        }
    }
}

class MyFragment: Fragment() {
    override val root = label("This is a popup")
}

你也能够将可选参数字传送递给openModal()以改正其某个作为。

openModal()的可选参数

参数 类型 描述
stageStyle StageStyle 定义·Stage·可能的枚举样式之一。 默认值: ·StageStyle.DECORATED·
modality Modality 定义Stage一个可能的枚举模式类型。 默认值: Modality.APPLICATION_MODAL
escapeClosesWindow Boolean 设置ESC键调用closeModal() 。 默认值: true
owner Window 指定此阶段的所有者窗口
block Boolean 阻止UI执行,直到窗口关闭。 默认值: false

InternalWindow

尽管openModal在二个新的Stage打开,
openInternalWindow却在如今的根节点(current root
node)或任何你钦命的其余节点上开荒:

 button("Open editor") {
        action {
            openInternalWindow(Editor::class)
        }
    }

图片 13

图3.9

里面窗口(internal window)的叁个很好的用例是单舞台(single
stage)意况(如JPro),恐怕只要要自定义窗口,修剪该窗口使其看起来更切合您的应用程序的统筹。
内部窗口(Internal Window)能够利用CSS体制。
有关体制可纠正(styleable)属性的更加多新闻,请查看InternalWindow.Styles类。

里面窗口(internal
window)API在几个非常重要方面与模态/窗口(modal/window)差别。
由于窗口(window)在现成节点上开发,您平日会在您想要其在上开发的View中调用openInternalWindow()
您提供要出示的视图(View),您也得以接受通过owner参数提供要在其上开辟的节点(node)。

openInternalWindow()的可选参数

参数 类型 描述
view UIComponent 组件将是新窗口的内容
view KClass 或者,您可以提供视图的类而不是实例
icon Node 可选的窗口图标
scope Scope 如果指定视图类,则还可以指定用于获取视图的作用域
modal Boolean 定义在内部窗口处于活动状态时是否应该禁用被覆盖节点。 默认值: true
escapeClosesWindow Boolean 设置ESC键调用close() 。 默认值: true
owner Node 指定此窗口的所有者节点。 默认情况下,该窗口将覆盖此视图的根节点

关门情势窗口

使用openModal()
openWindow()openInternalWindow()展开的任何Component都足以经过调用closeModal()关闭。
纵然急需利用findParentOfType(InternalWindow::class)也得以一向访谈InternalWindow实例。

改动视图和接通事件(Replacing Views and Docking 伊芙nts)

应用TornadoFX,能够利用replaceWith()方便地与当前View进行置换,并可选拔增加叁个退换(transition)。
在底下的身教重于言教中,每一种View上的Button将切换来另二个视图,能够是MyView1MyView2
(图3.10)。

class MyView1: View() {
    override val root = vbox {
        button("Go to MyView2") {
            action {
                replaceWith(MyView2::class)
            }
        }
    }
}

class MyView2: View() {
    override val root = vbox {
        button("Go to MyView1") {
            action {
                replaceWith(MyView1::class)
            }
        }
    }
}

图片 14

图3.10

您还足以筛选为八个视图之间的转变钦赐一个Mini的动漫。

replaceWith(MyView1::class, ViewTransition.Slide(0.3.seconds, Direction.LEFT)

那可以透过用另一个Viewroot交替给定View上的root
View享有八个函数能够重载(override),用于在其root Node一而再一连到父级(
onDock() )以至断开连接( onUndock() )时。
每当View进去或退出时,您可以行使这多个事件开展“连接”和“清理”。
运营上边的代码时你会注意到,每当View被换来时,它将吊销(undock
)上三个View并停靠(dock )新的。
您能够动用那四个事件来治本开头化(initialization)和拍卖(disposal)任务。

class MyView1: View() {
    override val root = vbox {
        button("Go to MyView2") {
            action {
                replaceWith(MyView2::class)
            }
        }
    }

    override fun onDock() {
        println("Docking MyView1!")
    }

    override fun onUndock() {
        println("Undocking MyView1!")
    }
}

class MyView2: View() {
    override val root = vbox {
        button("Go to MyView1") {
            action {
                replaceWith(MyView1::class)
            }
        }
    }

    override fun onDock() {
        println("Docking MyView2!")
    }
    override fun onUndock() {
        println("Undocking MyView2!")
    }
}

将参数字传送递给视图

在视图之间传递音信的精品办法日常是流入ViewModel
即便如此,能够将参数字传送递给其余零器件仍为很有益于的。
find()inject()函数协助Pair<String, Any>这样的varargs,就能够用于此指标。
思索在三个客商列表中,为选定的顾客项张开顾客消息编辑器的情景。
编辑客户音讯的操作可能如下所示:

fun editCustomer(customer: Customer) {
    find<CustomerEditor>(mapOf(CustomerEditor::customer to customer).openWindow())
}

这么些参数作为粲焕传递,个中键(key)是视图中的属性(property),值(value)是你希望的习性的别样值。
那为你提供了一种配备指标视图参数的云浮方式。

此地大家使用Kotlin的to语法来创建参数。
假若你愿意,这也足以写成Pair(CustomerEditor::customer, customer)
编辑器今后能够那样访问参数:

class CustomerEditor : Fragment() {
    val customer: Customer by param()

}

设若要反省参数,实际不是靠不住注重它们是可用的,您可以将其声称为可空(nullable),或参照其params映射:

class CustomerEditor : Fragment() {
    init {
        val customer = params["customer"] as? Customer
        if (customer != null) {
            ...
        }
    }
}

一旦你不关心类型安全性,仍然为能够将参数作为mapOf("customer" to customer)传送,但是只要在对象视图中重命名属性,则会失去自动重构(automatic
refactoring)。

拜候主舞台(primary stage)

View负有多少个名叫primaryStage的性质,允许你操作支持它的Stage的特性,比方窗口大小。
通过openModal()打开的别样ViewFragment也将有八个modalStage属性可用。

拜会场景(scene)

不时必要从ViewFragment取妥帖前程象。
那足以经过root.scene来促成,也许只要您身处叁个连串安全的营造器(type
safe builder)内部,还或许有叁个越来越短的格局,只需接收scene

做客能源(resources)

不少JavaFX API将财富作为URLURLtoExternalForm
要寻觅财富url,日常会如下所写:

val myAudioClip = AudioClip(MyView::class.java.getResource("mysound.wav").toExternalForm())

每个Component都有一个resources指标,能够查找resources的外界方式url(external
form url),如下所示:

val myAudiClip = AudioClip(resources["mysound.wav"])

纵然你必要三个其实的URL,可以如此检索:

 val myResourceURL = resources.url( "mysound.wav" ) 

resources助理还应该有一对其余有效的效用,可扶植您将相对于Component的文件调换为所需项目标对象:

val myJsonObject = resources.json("myobject.json")
val myJsonArray = resources.jsonArray("myarray.json")
val myStream = resources.stream("somefile")

值得说的是, jsonjsonArray函数也得以在InputStream目标上使用。

资源与Component相呼应,但你也得以经过总体路线,从/开班物色财富。

动作的快速键和组合键

您能够在键入某个组合键时接触动作(fire actions)。
这是用shortcut函数完毕的:

shortcut(KeyCombination.valueOf("Ctrl+Y")) {
    doSomething()
}

还会有叁个字符串版本的shortcut函数与此相近,可是不太冗长:

shortcut("Ctrl+Y")) {
    doSomething()
}

你仍可以够直接向按钮操作增添迅速格局:

button("Save") {
    action { doSave() }
    shortcut("Ctrl+S")
}

触摸扶植

JavaFX对动手的协助开箱即用,现在独一须求改过的地点正是以更利于的办法管理shortpresslongpress
它由八个像样于action的函数组成,能够在别的Node上开展配置:

shortpress { println("Activated on short press") }
longpress { println("Activated on long press") }

那多个函数都承担consume参数,暗中认可情状下为false
将其安装为true将防备按压事件(press event)发惹事变冒泡(event
bubbling)。longpress函数还帮助三个threshold参数,用于鲜明longpress积存的日子。
默以为700.millis

总结

TornadoFX充满了简约,直观而又有力的流入工具来保管视图和调节器(Views and
Controllers)。 它还动用Fragment简化对话框和其他小型UI。
即使于今截至,大家构建的应用程序特别轻易,但希望你能赏识到TornadoFX给JavaFX引进的简化概念。
在下一章中,大家将介绍能够说是TornadoFX最精锐的功力:Type-Safe Builders

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图