5. UI 中处理数据

开发进行到这个阶段,我们已经有了入职步骤和部门的管理功能,以及默认的用户管理功能,并且为用户添加了 Onboarding status 属性。现在我们需要将用户与入职步骤和部门关联起来。

本节中,我们将完成:

  • User 实体添加 departmentjoiningDate 属性,并在 UI 展示。

  • 创建 UserStep 实体,关联一个用户和一个入职步骤。

  • User 实体添加 UserStep 实体集合,并在 User.edit 界面展示。

  • User.edit 界面实现生成和保存 UserStep 实例的功能。

下图展示本节要用到的实体和属性:

Diagram

添加关联属性

我们已经在之前的章节完成了类似的任务:为 Department 实体添加关联至 User 实体的 HR 经理属性。现在我们需要创建一个反向连接:一个用户需要属于某个部门。

Diagram

如果你的应用程序正在运行,先通过主工具栏的 Stopsuspend)按钮停止运行。

Jmix 工具窗口双击 User 实体并选择其最后一个属性(我们要在最后添加新属性):

Attributes 工具栏中,点击 Addadd)。

弹出的 New Attribute 对话框中,Name 字段填写 department。然后选择:

  • Attribute typeASSOCIATION

  • TypeDepartment

  • CardinalityMany to One

add attr 1

点击 OK

然后选中 department 属性,在 Attributes 工具栏中点击 Add to Screensadd attribute to screens)按钮:

add attr 2

出现的对话框中会显示 User.editUser.browse 界面。我们都选上,然后点击 OK

Studio 会在 User.browse 界面的表格组件和 User.edit 界面的表单组件中添加 department 属性。

你可能会注意,Studio 在 User.browse 的 XML 中添加了下面的代码:

<data readOnly="true">
    <collection id="usersDc"
                class="com.company.onboarding.entity.User">
        <fetchPlan extends="_base">
            <property name="department" fetchPlan="_base"/> <!-- added -->
        </fetchPlan>

以及 User.edit

<data>
    <instance id="userDc"
              class="com.company.onboarding.entity.User">
        <fetchPlan extends="_base">
            <property name="department" fetchPlan="_base"/> <!-- added -->
        </fetchPlan>

有了这些代码,关联的部门实体会与用户实体在同一个数据库查询中加载。

即使 fetch plan 中没有包含 department,基于关联实体的 懒加载 机制,界面也能正常工作。但是在懒加载时,关联实体是通过单独的数据库请求进行加载。因此,懒加载在浏览界面会造成性能问题,该界面首先执行一个加载所有用户的请求,然后针对每个用户,执行单独的请求加载部门(N+1 查询问题)。

运行应用程序可以查看新的属性。

点击主工具栏中的 Debugstart debugger)按钮启动应用程序。

Studio 会生成 Liquibase 更改日志,添加了 USER_ 表的 DEPARTMENT_ID 列,并创建外键和索引。确认这个改动。

Studio 会执行更改日志,然后构建并运行应用程序。

在浏览器打开 http://localhost:8080 并使用 admin / admin 凭证登录。

点击主菜单的 ApplicationUsers,打开 User.browse 界面,可以看到新加的 Department 列。创建用户时,可以看到 User.edit 中的 Department 选择控件:

add attr 3

使用下拉框选择

默认情况下,Studio 会使用 entityPicker 组件选择关联实体。可以在 User.edit 界面中看到这样的组件。打开 user-edit.xml 并通过顶部的按钮切换至 XML 编辑器:

dropdown 1

form 中找到 entityPicker 组件:

<layout ...>
    <form id="form" dataContainer="userDc">
        <column width="350px">
            ...
            <entityPicker id="departmentField" property="department"/>
        </column>
    </form>

该组件支持通过一个查找界面选择实体,支持过滤、排序或者分页。但是当备选的记录相对比较少时(比如少于 1000),通过简单的下拉框列表选择会更加方便。

我们将修改 User.edit 界面,使用 entityComboBox 组件选择用户的部门。

将组件的 XML 元素修改为 entityComboBox

<entityComboBox id="departmentField" property="department"/>

切换至运行的应用程序,重新打开用户编辑界面。

可以看到,Department 字段现在变成了下拉框,但是列表是空的,即便已经创建了一些部门。

dropdown 2

创建选项数据容器

我们为 entityComboBox 组件提供一组选项,用于选择关联的部门实体。选项列表包含所有的部门,按名称排序。

在右侧的 Component Palette 中选择 Data components,然后将 Collection 拖至 Component Hierarchy 中的 data 元素上。在 Data Container Properties Editor 弹窗的 Entity 字段选择 Department,点击 OK

options container 1

然后在 data 元素下会创建 id 为 departmentsDccollection 元素。通过 Component Hierarchy 和 XML 代码都能看到:

<data>
    ...
    <collection id="departmentsDc" class="com.company.onboarding.entity.Department">
        <fetchPlan extends="_base"/>
        <loader id="departmentsDl">
            <query>
                <![CDATA[select e from Department e]]>
            </query>
        </loader>
    </collection>
</data>

该元素定义一个 集合数据容器(collection data container),以及容器关联的一个 数据加载器(loader)。数据容器包含由加载器加载的部门实体列表,加载使用的查询语句在加载器中指定。

可以在 XML 中直接编辑查询语句,或者通过 Component Inspectorquery 属性打开 JPQL 设计器进行编辑:

options container 2

JPQL Query Designer 窗口中,切换至 ORDER 标签页并添加 name 属性:

options container 3

点击 OK

在 XML 中生成的查询语句如下:

<data>
    ...
    <collection id="departmentsDc" class="com.company.onboarding.entity.Department">
        <fetchPlan extends="_base"/>
        <loader id="departmentsDl">
            <query>
                <![CDATA[select e from Department e
                order by e.name asc]]>
            </query>
        </loader>
    </collection>
</data>

现在需要将 entityComboBox 组件与 departmentsDc 数据容器进行关联。

Component Hierarchy 选中 departmentField,然后在 Component Inspector 中为 optionsContainer 属性选择 departmentsDc

options container 4

切换至运行的应用程序并重新打开用户编辑界面。

可以看到 Department 下拉框现在已经有了选项:

dropdown 3
entityComboBox 组件支持直接在组件内输入的方式对选项进行过滤。过滤的过程是在服务端的内存中进行,所有的选项已经一次从数据加载出来了。

创建 UserStep 实体

本小节中,我们将创建 UserStep 实体,用来表示特定用户的入职步骤:

Diagram

如果你的应用程序正在运行,先通过主工具栏的 Stopsuspend)按钮停止运行。

Jmix 工具窗口中,点击 Newadd)→ JPA Entity 并与 之前 的步骤一样创建带有 Versioned 特性的 UserStep 实体。

为实体添加下列属性:

Name Attribute type Type Cardinality Mandatory

user

ASSOCIATION

User

Many to One

true

step

ASSOCIATION

Step

Many to One

true

dueDate

DATATYPE

LocalDate

-

true

completedDate

DATATYPE

LocalDate

-

false

sortValue

DATATYPE

Integer

-

true

实体设计器的最终状态如下:

create user step 1

添加组合属性

我们来看看 UserUserStep 实体的关系。UserStep 实例仅当特定的 User 实例存在时才有意义(即,属于该用户)。一个 UserStep 实例不能修改其所有者;此外,其他数据模型也没有关联 UserStep,也就是说 UserStep 实例都是包含在 User 实例内的。

在 Jmix 中,这种实体间的关系被称为 组合(composition)User 由一组 UserSteps 和其他的属性共同组成。

Jmix 中的组合实现了 DDD(Domain-Driven Design)中的聚合(Aggregate)模式。

在父实体中创建包含一组组合子实体的属性也很方便。

我们在 User 实体中创建 steps 属性:

Diagram

如果你的应用程序正在运行,先通过主工具栏的 Stopsuspend)按钮停止运行。

User 实体设计器的 Attributes 工具栏中,点击 Addadd)。弹出的 New Attribute 对话框中,Name 字段填写 steps,然后选择:

  • Attribute typeCOMPOSITION

  • TypeUserStep

  • CardinalityOne to Many

composition 1

注意,Mapped by 字段会自动选择 user。这是 UserStep 实体中的一个属性,映射至一个数据库列,用于维护 UserStepsUsers 的关系(外键)。

点击 OK

该属性的源代码会带有 @Composition 注解:

@Composition
@OneToMany(mappedBy = "user")
private List<UserStep> steps;

UserSteps 需要在用户编辑界面展示,因此,选中 steps 属性并点击 Attributes 工具栏中的 Add to Screensadd attribute to screens)按钮,选择 User.edit 界面,点击 OK

Studio 会修改 user-edit.xml

<data>
    <instance id="userDc"
              class="com.company.onboarding.entity.User">
        <fetchPlan extends="_base">
            <property name="department" fetchPlan="_base"/>
            <property name="steps" fetchPlan="_base"/> (1)
        </fetchPlan>
        <loader/>
        <collection id="stepsDc" property="steps"/> (2)
    </instance>
    ...
<layout ...>
    <form id="form" dataContainer="userDc">
        ...
    </form>
    <groupBox id="stepsBox" ...>
        <table id="stepsTable" dataContainer="stepsDc" ...> (3)
            <actions>
                <action id="create" type="create"/>
                <action id="edit" type="edit"/>
                <action id="remove" type="remove"/>
            </actions>
            <columns>
                <column id="version"/>
                <column id="dueDate"/>
                <column id="completedDate"/>
                <column id="sortValue"/>
            </columns>
            <buttonsPanel>
                <button action="stepsTable.create"/>
                <button action="stepsTable.edit"/>
                <button action="stepsTable.remove"/>
            </buttonsPanel>
        </table>
    </groupBox>
1 Fetch plan 添加了 steps 属性,确保 UserSteps 与 User 一起进行预加载。
2 内部的 stepsDc 集合数据容器用于将可视化组件与 steps 集合属性做绑定。
3 groupBox 内部的 table 组件用于展示 stepsDc 数据容器中的数据。

运行应用程序查看这些改动。

点击主工具栏中的 Debugstart debugger)按钮启动应用程序。

Studio 会生成 Liquibase 更改日志,包含创建 USER_STEP 表、关联至 USER_STEP 的外键约束和索引。确认这些改动。

Studio 会执行更改日志,然后运行应用程序。

应用程序准备好后,在浏览器打开 http://localhost:8080 并使用 admin / admin 凭证登录。

打开一个用户进行编辑。可以看到 Steps 表格展示 UserStep 实体:

composition 2

如果点击 Steps 表格中的 Create 按钮,系统会抛出异常:Screen 'UserStep.edit' is not defined。是的,我们还没有为 UserStep 实体创建编辑界面。但是实际上我们不需要创建这个界面,因为 UserStep 实例可以通过给用户分配预定义的 Step 实体生成。

为用户生成 UserSteps

本节中,我们将为编辑的 User 实体生成并展示 UserStep 实例。

添加 JoiningDate 属性

首先,为 User 实体添加 joiningDate 属性:

Diagram

该属性将用于计算 UserStep 实体中的 dueDate 属性:UserStep.dueDate = User.joiningDate + Step.duration

如果你的应用程序正在运行,先通过主工具栏的 Stopsuspend)按钮停止运行。

User 实体设计器的 Attributes 工具栏中,点击 Addadd)。弹出的 New Attribute 对话框中,Name 字段填写 joiningDate,然后在 Type 下拉框中选择 LocalDate

joining date 1

点击 OK

然后选中新创建的 joiningDate 属性,在 Attributes 工具栏中点击 Add to Screensadd attribute to screens)按钮。在弹窗中选择 User.editUser.browse 界面并点击 OK

点击主工具栏中的 Debugstart debugger)按钮启动应用程序。

Studio 会生成 Liquibase 更改日志,为 USER_ 表添加 JOINING_DATE 列。确认此改动。

Studio 会执行更改日志,然后运行应用程序。在浏览器打开 http://localhost:8080,登录后在用户浏览界面和编辑界面确认新属性已经添加上了。

添加自定义按钮

现在我们需要删除管理 UserSteps 的默认操作按钮,然后添加一个按钮用于启动自定义逻辑创建实体。

打开 user-edit.xml 并删除 table 内的 actions 元素和全部的 button 元素:

<table id="stepsTable" dataContainer="stepsDc" width="100%" height="200px">
    <columns>
        <column id="version"/>
        <column id="dueDate"/>
        <column id="completedDate"/>
        <column id="sortValue"/>
    </columns>
    <buttonsPanel>
    </buttonsPanel>
</table>

然后从 Component Palette 中将一个 Button 组件拖放至 Component Hierarchy 中的 buttonsPanel 元素。选择新创建的 button 元素并在 Component Inspector 中设置其 idgenerateButtoncaptionGenerate。切换至 Handlers 标签页,创建一个 ClickEvent 的处理器方法:

button 1

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。重新打开用户编辑界面,查看我们新添加的按钮正常展示而不是展示默认的 CRUD 按钮:

button 2

创建并保存 UserStep 实例

现在实现生成 UserStep 实例的逻辑。

UserEdit 控制器中添加下列字段:

public class UserEdit extends StandardEditor<User> {

    @Autowired
    private DataManager dataManager;

    @Autowired
    private DataContext dataContext;

    @Autowired
    private CollectionPropertyContainer<UserStep> stepsDc;

    //...
}

可以通过操作面板中的 Inject 按钮注入界面中的组件和 Spring bean:

inject 1

generateButton 点击处理方法中添加创建和保存 UserStep 的逻辑:

@Subscribe("generateButton")
public void onGenerateButtonClick(Button.ClickEvent event) {
    User user = getEditedEntity(); (1)

    if (user.getJoiningDate() == null) { (2)
        notifications.create()
                .withCaption("Cannot generate steps for user without 'Joining date'")
                .show();
        return;
    }

    List<Step> steps = dataManager.load(Step.class)
            .query("select s from Step s order by s.sortValue asc")
            .list(); (3)

    for (Step step : steps) {
        if (stepsDc.getItems().stream().noneMatch(userStep ->
                    userStep.getStep().equals(step))) { (4)
            UserStep userStep = dataContext.create(UserStep.class); (5)
            userStep.setUser(user);
            userStep.setStep(step);
            userStep.setDueDate(user.getJoiningDate().plusDays(step.getDuration()));
            userStep.setSortValue(step.getSortValue());
            stepsDc.getMutableItems().add(userStep); (6)
        }
    }
}
1 使用 StandardEditor 父类中的 getEditedEntity() 方法获取正在编辑的 User 实体。
2 如果 joiningDate 属性未设置,展示消息并退出。
3 加载已经添加的入职步骤。
4 忽略 stepsDc 集合容器中已经存在的步骤。
5 DataContext.create() 方法创建新的 UserStep 实例。
6 将新的 UserStep 实例添加至 stepsDc 集合容器,以便在 UI 展示。
当通过 DataContext 对象创建实例时,实例自动由 DataContext 进行管理,并会在界面提交时(点击界面的 OK 按钮时)自动保存至数据库。

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。重新打开用户编辑界面,此时我们点击 Generate 按钮,会创建对应入职步骤的几条记录。

如果通过点击 OK 提交界面,所有创建的 UserSteps 实例都会自动保存。如果点击 Cancel,则不会保存。因为在上面的代码中,我们没有显式地保存创建的实体。而是通过 DataContext.create() 将实体合并至界面的 DataContext 内,只有在整个界面的 DataContext 提交时才会保存新创建的实例。

优化 UserSteps 表格

下面的小节中,我们将完善处理生成的 UserSteps 的 UI 界面。

内部集合的排序

你可能已经注意到,当打开一个带有生成 UserSteps 的用户界面时,这些步骤并没有按照 sortValue 属性排序:

ordering 1

表格展示 User 实体的 steps 集合属性,因此我们可以在数据模型级别引入排序。

打开 User 实体,选择 steps 属性并在 Order by 字段输入 sortValue

ordering 2

如果切换至 Text 标签页,可以看到 steps 属性上添加了 @OrderBy 注解:

@OrderBy("sortValue")
@Composition
@OneToMany(mappedBy = "user")
private List<UserStep> steps;

现在当加载 User 实体时,实体内部的 steps 集合会按照 UserStep.sortValue 属性进行排序。

如果应用程序正在运行,请重新启动。

打开用户编辑界面。可以看到步骤的顺序现在对了:

ordering 3

表格的列重排

此时,UserSteps 表格提供的信息并不是很有用。我们需要移除 VersionSort value 列并添加展示步骤名称的列。

删除列很简单,在 Component Hierarchy 中选择这些列并按下 Delete,或直接从 XML 代码里删除相应的元素即可。

添加列时,在 Component Hierarchy 选择 columns 元素并在 Component Inspector 中点击 AddColumn,会出现 Add Column 弹窗:

columns 2

可以看到,这里并不允许添加步骤名称。这是因为 step 属性是一个关联属性,而我们没有定义一个合适的 fetch plan 去加载这个实体。

Component Hierarchy 中,选择 userDc 数据容器,然后点击 fetch plan 的 Editedit)按钮,可以在 Component InspectorfetchPlan 属性处点击或者直接在 XML 编辑器的装订线栏中点击:

columns 3

Edit Fetch Plan 窗口中,选中 stepsstep 属性,然后点击 OK

columns 4

内部的 step 属性会被添加至 fetch plan XML:

<instance id="userDc"
          class="com.company.onboarding.entity.User">
    <fetchPlan extends="_base">
        <property fetchPlan="_base" name="department"/>
        <property fetchPlan="_base" name="steps">
            <property name="step" fetchPlan="_base"/>
        </property>
    </fetchPlan>
    <loader/>
    <collection id="stepsDc" property="steps"/>
</instance>

现在 UserSteps 集合会与 User 实例一起从数据库进行预加载。

Component Hierarchy 选择 columns 元素并在 Component Inspector 中点击 AddColumn。现在 Add Column 弹窗中包含了关联的 Step 实体和属性了:

columns 5

选择 stepname,然后点击 OK。新列会添加在最后面:

<table id="stepsTable" dataContainer="stepsDc" ...>
    <columns>
        <column id="dueDate"/>
        <column id="completedDate"/>
        <column id="step.name"/>
    </columns>

除了 step.name 之外,也可以直接使用 step。此时,表格列中将展示实体的 实例名称。对于 Step 实体,实例名称是从 name 属性获取,所以结果没有不同。

也可以在 XML 中直接添加 step 列而不修改 fetch plan,由于关联实体的懒加载机制,UI 界面也能正常工作。但是此时 Step 会在单独的请求中加载,集合中的每个 UserStep 会触发一次(N+1 查询性能问题)。

step.name 移到最前面,可以直接在 XML 中修改或者在 Component Hierarchy 中拖动元素:

<table id="stepsTable" dataContainer="stepsDc" ...>
    <columns>
        <column id="step.name"/>
        <column id="dueDate"/>
        <column id="completedDate"/>
    </columns>

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。重新打开用户编辑界面,确保 Steps 表格现在展示步骤名称:

columns 6

添加自定义列

本小节中,我们将实现这样一个功能:可以直接在入职步骤表格的一行中,通过勾选一个复选框将当前步骤标记为已完成。

UI 表格组件支持 生成列(generated columns),这种列的内容可以不与特定的实体属性绑定。在生成列的单元格中,可以展示任何可视化组件或者带有多个内部组件的可视化容器。

我们添加一个生成列用于展示复选框。

Component Hierarchy 中选择 columns 元素并在 Component Inspector 中点击 AddColumn。显示 Add Column 对话框:

generated column 1

选择 New Custom Column 并点击 OK

Additional Settings for Custom Coulmn 弹窗中,Custom column id 字段输入 completed 并勾选 Create generator

generated column 2

点击 OK

Studio 会在表格的 XML 中添加 completed 列:

generated column 3

以及在 UserEdit 控制器中添加处理方法:

generated column 4

注意左侧的行标记:支持在 XML 中的列定义和控制器中的处理方法之间切换。

在控制器类中注入 UiComponents

@Autowired
private UiComponents uiComponents;
可以通过编辑器顶部操作面板中的 Inject 按钮注入界面中的组件和 Spring bean。

实现处理器方法:

@Install(to = "stepsTable.completed", subject = "columnGenerator") (1)
private Component stepsTableCompletedColumnGenerator(UserStep userStep) { (2)
    CheckBox checkBox = uiComponents.create(CheckBox.class); (3)
    checkBox.setValue(userStep.getCompletedDate() != null);
    checkBox.addValueChangeListener(e -> { (4)
        if (userStep.getCompletedDate() == null) {
            userStep.setCompletedDate(LocalDate.now());
        } else {
            userStep.setCompletedDate(null);
        }
    });
    return checkBox; (5)
}
1 @Install 注解表示该方法是一个 代理(delegate):一个 UI 组件(这个 case 中是表格)会在生命周期的某个阶段调用该方法。
2 这个特殊的代理(列生成器)接收一个实体实例作为参数,该实例在表格中的一行显示。
3 CheckBox 组件实例通过 UiComponents 工厂生成。
4 当点击复选框时,它的值会发生变化,复选框会调用其 ValueChangeEvent 监听器。在监听器中,设置 UserStep 实体的 completedDate 属性。
5 列生成器代理返回列单元格需要展示的可视化组件。

completed 列移至最前面,并设置 caption 属性为空,width 设置为 50px

<table id="stepsTable" dataContainer="stepsDc" ...>
    <columns>
        <column id="completed" caption="" width="50px"/>
        <column id="step.name"/>
        <column id="dueDate"/>
        <column id="completedDate"/>
    </columns>

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。重新打开用户编辑界面并点击某些行的复选框。Completed date 列会做相应的更改:

generated column 5

UserStep 实例的改动会在点击界面的 OK 按钮后保存到数据库。由界面的 DataContext 负责:能跟踪所有实体的改动并保存修改的实例。

响应更改

当为用户生成入职步骤、标记一个 UserStep 完成或者删除一个步骤时,Onboarding status 字段应该也做相应的调整。

我们现在实现根据 UserSteps 集合变动做出响应的逻辑。

打开 UserEdit 控制器并在顶部的操作面板中点击 Generate Handler。收起所有的树结构,然后在 Data containers handlersstepsDc 下面选择 ItemPropertyChangeEventCollectionChangeEvent

container listener 1

点击 OK

Studio 会生成两个方法桩代码:onStepsDcItemPropertyChange()onStepsDcCollectionChange()。实现如下:

@Subscribe(id = "stepsDc", target = Target.DATA_CONTAINER)
public void onStepsDcItemPropertyChange(InstanceContainer.ItemPropertyChangeEvent<UserStep> event) {
    updateOnboardingStatus(); (1)
}

@Subscribe(id = "stepsDc", target = Target.DATA_CONTAINER)
public void onStepsDcCollectionChange(CollectionContainer.CollectionChangeEvent<UserStep> event) {
    updateOnboardingStatus(); (2)
}

private void updateOnboardingStatus() {
    User user = getEditedEntity(); (3)

    long completedCount = user.getSteps() == null ? 0 :
            user.getSteps().stream()
                    .filter(us -> us.getCompletedDate() != null)
                    .count();
    if (completedCount == 0) {
        user.setOnboardingStatus(OnboardingStatus.NOT_STARTED); (4)
    } else if (completedCount == user.getSteps().size()) {
        user.setOnboardingStatus(OnboardingStatus.COMPLETED);
    } else {
        user.setOnboardingStatus(OnboardingStatus.IN_PROGRESS);
    }
}
1 ItemPropertyChangeEvent 处理器在实体的属性发生更改时调用。
2 CollectionChangeEvent 处理器在容器中添加或者删除数据时调用。
3 获取当前编辑的 User 实例。
4 更新 onboardingStatus 属性。由于数据绑定机制,对该属性的更新会立即展示在 UI 属性中。

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。重新打开用户编辑界面,然后在 UserStep 表格中做一些改动,并查看 Onboarding status 字段的值。

小结

在本节中,我们实现了下面两个功能:

  1. 为用户指定部门。

  2. 为用户生成并管理入职步骤。

学习内容:

  • 关联属性应该包含在界面的 fetch plan 中,以避免 N+1 查询性能问题。

  • EntityComboBox 可以用来在下拉框中选择关联实体。该组件需要一个 集合实例容器,选项列表在 optionsContainer 属性设置。

  • UserUserStep 实体的关系是 组合 的一个很好的示例,关联实体(UserStep)仅作为其所有者(User)的一部分存在。这种关联通过 @Composition 注解标记。

  • 关联实体的集合可以用关联属性的 @OrderBy 注解进行排序。

  • Button 组件的 ClickEvent 处理器用来处理按钮的点击事件。可以通过 Component Inspector 工具窗口的 Handlers 标签页自动生成。

  • 编辑界面控制器getEditedEntity() 方法返回正在编辑的实体。

  • Notifications 接口用来展示弹出通知消息。

  • DataManager 接口可以用来从数据库加载数据。

  • 内部的嵌套关联实体集合会被加载到 CollectionPropertyContainer。容器的 getItems()getMutableItems() 方法可以用来遍历集合或者添加/删除集合项。

  • DataContext 跟踪实体的更改,并在用户点击界面的 OK 按钮时将改动的实例保存至数据库。

  • UI 表格支持 生成列,用来展示任意的可视化组件。

  • ItemPropertyChangeEventCollectionChangeEvent 可以用来对数据容器中的实体变更做出响应。