3.5.1.5. 界面 Mixins
通过 Mixin 可以创建能在多个UI界面中重复使用的功能,而且不需要从公共基类继承界面。Mixin 通过 Java 接口实现,使用了接口的默认方法。
Mixin 有如下特性:
-
一个界面可以有多个 Mixin。
-
Mixin 接口可以订阅 界面事件。
-
Mixin 可以在界面中保存一些状态,如果需要的话。
-
Mixin 也可以获取界面组件和基础架构 bean,比如 Dialogs,Notifications 等。
-
如果需要参数化 mixin 的行为,mixin 可以依赖界面的注解或者引入抽象方法交由界面实现。
使用 mixin 与在界面控制器中实现特定的接口一样简单。下面的示例中,CustomerEditor
界面使用了由 HasComments
,HasHistory
,HasAttachments
接口实现的 mixin 功能:
public class CustomerEditor extends StandardEditor<Customer>
implements HasComments, HasHistory, HasAttachments {
// ...
}
Mixin 可以使用以下类来处理界面和界面基础架构:
-
com.haulmont.cuba.gui.screen.Extensions
提供静态方法,用来保存和获取 mixin 使用的界面状态,还能访问BeanLocator
,这可以用来获取任何 Spring 管理的 bean。 -
UiControllerUtils
提供对界面UI和数据组件的访问。
下面是展示如何创建和使用 mixin 的示例。
- DeclarativeLoaderParameters mixin
-
下面这个 mixin 可以帮助在数据容器之间创建主从关系。通常的做法,是需要订阅主容器的
ItemChangeEvent
事件,将改动的主容器内容设置为从容器的数据加载器参数,如数据组件之间的依赖所述。但是如果参数是指向主容器的特殊名称,mixin 能自动完成此功能。Mixin 会使用状态对象在事件处理器之间传递信息。这里为了演示,我们将逻辑分散开,但实际上所有的逻辑都可以在一个
BeforeShowEvent
处理器中完成。首先,为共享状态创建一个类。包含单一字段,用来保存将在
BeforeShowEvent
处理器中触发的一组数据加载器:package com.company.demo.web.mixins; import com.haulmont.cuba.gui.model.DataLoader; import java.util.Set; public class DeclarativeLoaderParametersState { private Set<DataLoader> loadersToLoadBeforeShow; public DeclarativeLoaderParametersState(Set<DataLoader> loadersToLoadBeforeShow) { this.loadersToLoadBeforeShow = loadersToLoadBeforeShow; } public Set<DataLoader> getLoadersToLoadBeforeShow() { return loadersToLoadBeforeShow; } }
接下来,创建 mixin 接口:
package com.company.demo.web.mixins; import com.haulmont.cuba.gui.model.*; import com.haulmont.cuba.gui.screen.*; import java.util.HashSet; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; public interface DeclarativeLoaderParameters { Pattern CONTAINER_REF_PATTERN = Pattern.compile(":(container\\$(\\w+))"); @Subscribe default void onDeclarativeLoaderParametersInit(Screen.InitEvent event) { (1) Screen screen = event.getSource(); ScreenData screenData = UiControllerUtils.getScreenData(screen); (2) Set<DataLoader> loadersToLoadBeforeShow = new HashSet<>(); for (String loaderId : screenData.getLoaderIds()) { DataLoader loader = screenData.getLoader(loaderId); String query = loader.getQuery(); Matcher matcher = CONTAINER_REF_PATTERN.matcher(query); while (matcher.find()) { (3) String paramName = matcher.group(1); String containerId = matcher.group(2); InstanceContainer<?> container = screenData.getContainer(containerId); container.addItemChangeListener(itemChangeEvent -> { (4) loader.setParameter(paramName, itemChangeEvent.getItem()); (5) loader.load(); }); if (container instanceof HasLoader) { (6) loadersToLoadBeforeShow.add(((HasLoader) container).getLoader()); } } } DeclarativeLoaderParametersState state = new DeclarativeLoaderParametersState(loadersToLoadBeforeShow); (7) Extensions.register(screen, DeclarativeLoaderParametersState.class, state); } @Subscribe default void onDeclarativeLoaderParametersBeforeShow(Screen.BeforeShowEvent event) { (8) Screen screen = event.getSource(); DeclarativeLoaderParametersState state = Extensions.get(screen, DeclarativeLoaderParametersState.class); for (DataLoader loader : state.getLoadersToLoadBeforeShow()) { loader.load(); (9) } } }
1 - 订阅 InitEvent。 2 - 获取 ScreenData
对象,其中注册了 XML 中定义的所有数据容器和加载器。3 - 检查加载器的参数是否符合 :container$masterContainerId
模式的定义。4 - 从参数名中抽取主容器id,然后为该容器注册一个 ItemChangeEvent
监听器。5 - 使用新的主实体重新加载从实体数据加载器。 6 - 将主加载器添加到集合中,以便之后在 BeforeShowEvent
处理器中能触发。7 - 创建共享状态对象,使用 Extensions
工具类将该对象保存在界面中。8 - 订阅 BeforeShowEvent 事件。 9 - 触发在 InitEvent
处理器中找到的所有主加载器。在界面 XML 描述中定义主从容器以及数据加载器。从加载器需要带有一个参数,其名称类似
:container$masterContainerId
:<collection id="countriesDc" class="com.company.demo.entity.Country" view="_local"> <loader id="countriesDl"> <query><![CDATA[select e from demo_Country e]]></query> </loader> </collection> <collection id="citiesDc" class="com.company.demo.entity.City" view="city-view"> <loader id="citiesDl"> <query><![CDATA[ select e from demo_City e where e.country = :container$countriesDc ]]></query> </loader> </collection>
在界面控制器中,只需要添加 mixin 接口,然后就能自动触发加载器了:
package com.company.demo.web.country; import com.company.demo.entity.Country; import com.company.demo.web.mixins.DeclarativeLoaderParameters; import com.haulmont.cuba.gui.screen.*; @UiController("demo_Country.browse") @UiDescriptor("country-browse.xml") @LookupComponent("countriesTable") public class CountryBrowse extends StandardLookup<Country> implements DeclarativeLoaderParameters { }