3.5.1.3. 打开界面
可以通过主菜单、URL 导航、标准操作(当使用浏览和编辑实体界面时)或从另外一个界面以编程方式打开。在本节,将介绍如何以编程的方式打开界面。
使用 Screens 接口
Screens
接口允许创建和显示任何类型的界面。
假设有一个用于显示具有一些特殊格式的消息的界面:
界面控制器
@UiController("demo_FancyMessageScreen")
@UiDescriptor("fancy-message-screen.xml")
@DialogMode(forceDialog = true, width = "300px")
public class FancyMessageScreen extends Screen {
@Inject
private Label<String> messageLabel;
public void setFancyMessage(String message) { (1)
messageLabel.setValue(message);
}
@Subscribe("closeBtn")
protected void onCloseBtnClick(Button.ClickEvent event) {
closeWithDefaultAction();
}
}
1 | - 一个界面参数 |
界面描述
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd" caption="Fancy Message">
<layout>
<label id="messageLabel" value="A message" stylename="h1"/>
<button id="closeBtn" caption="Close"/>
</layout>
</window>
那么可以从另一个界面创建并打开它,如下所示:
@Inject
private Screens screens;
private void showFancyMessage(String message) {
FancyMessageScreen screen = screens.create(FancyMessageScreen.class);
screen.setFancyMessage(message);
screens.show(screen);
}
请注意这里是如何创建界面实例、为其提供参数,然后显示界面。
如果界面不需要来自调用方的任何参数,可以仅用一行代码创建并打开它:
@Inject
private Screens screens;
private void showDefaultFancyMessage() {
screens.create(FancyMessageScreen.class).show();
}
|
使用 ScreenBuilders bean
通过 ScreenBuilders
bean 可以使用各种参数打开所有类型的界面。下面是用它打开界面并且在界面关闭之后执行一些代码的例子(更多细节参考这里):
@Inject
private ScreenBuilders screenBuilders;
@Inject
private Notifications notifications;
private void openOtherScreen() {
screenBuilders.screen(this)
.withScreenClass(OtherScreen.class)
.withAfterCloseListener(e -> {
notifications.create().withCaption("Closed").show();
})
.build()
.show();
}
下面我们看看如何操作编辑界面和查找界面。注意,在大多数情况下你会使用 标准操作(比如 CreateAction 或 LookupAction)来打开这类界面,所以并不是非要直接用 ScreenBuilders
API。但是,如果不用标准的操作而是想通过 基础操作 或者 按钮 处理打开界面的话,可以参考下面的例子。
为 Customer
实体实例打开默认编辑界面的示例:
@Inject
private ScreenBuilders screenBuilders;
private void editSelectedEntity(Customer entity) {
screenBuilders.editor(Customer.class, this)
.editEntity(entity)
.build()
.show();
}
在这种情况下,编辑界面将更新实体,但调用界面将不会接收到更新后的实例。
我们经常需要编辑某些用 Table
或 DataGrid
组件显示的实体。那么应该使用以下调用方式,它更简洁且能自动更新表格组件:
@Inject
private GroupTable<Customer> customersTable;
@Inject
private ScreenBuilders screenBuilders;
private void editSelectedEntity() {
screenBuilders.editor(customersTable).build().show();
}
要创建一个新的实体实例并打开它的编辑界面,只需在构建器上调用 newEntity()
方法:
@Inject
private GroupTable<Customer> customersTable;
@Inject
private ScreenBuilders screenBuilders;
private void createNewEntity() {
screenBuilders.editor(customersTable)
.newEntity()
.build()
.show();
}
默认编辑界面的确定过程如下:
|
界面构建器提供了许多方法来设置被打开界面的可选参数。例如,以下代码以对话框的方式打开的特定编辑界面,同时新建并初始化实体:
@Inject
private GroupTable<Customer> customersTable;
@Inject
private ScreenBuilders screenBuilders;
private void editSelectedEntity() {
screenBuilders.editor(customersTable).build().show();
}
private void createNewEntity() {
screenBuilders.editor(customersTable)
.newEntity()
.withInitializer(customer -> { // lambda to initialize new instance
customer.setName("New customer");
})
.withScreenClass(CustomerEdit.class) // specific editor screen
.withLaunchMode(OpenMode.DIALOG) // open as modal dialog
.build()
.show();
}
实体查找界面也能使用不同参数打开。
下面是打开 User
实体的默认查找界面的示例:
@Inject
private TextField<String> userField;
@Inject
private ScreenBuilders screenBuilders;
private void lookupUser() {
screenBuilders.lookup(User.class, this)
.withSelectHandler(users -> {
User user = users.iterator().next();
userField.setValue(user.getName());
})
.build()
.show();
}
如果需要将找到的实体设置到字段,可使用更简洁的方式:
@Inject
private PickerField<User> userPickerField;
@Inject
private ScreenBuilders screenBuilders;
private void lookupUser() {
screenBuilders.lookup(User.class, this)
.withField(userPickerField) // set result to the field
.build()
.show();
}
默认查找界面的确定过程如下:
|
与使用编辑界面一样,使用构建器方法设置打开界面的可选参数。例如,以下代码以对话框的方式打开特定的查找界面,在这个界面中查找 User
实体:
@Inject
private TextField<String> userField;
@Inject
private ScreenBuilders screenBuilders;
private void lookupUser() {
screenBuilders.lookup(User.class, this)
.withScreenId("sec$User.browse") // specific lookup screen
.withLaunchMode(OpenMode.DIALOG) // open as modal dialog
.withSelectHandler(users -> {
User user = users.iterator().next();
userField.setValue(user.getName());
})
.build()
.show();
}
为界面传递参数
为打开界面传递参数的推荐方式是使用界面控制器的公共 setter 方法,如上面界面接口部分示范。
使用这个方式,可以为任意类型的界面传递参数,包括使用ScreenBuilders或者从主菜单打开的实体编辑和查找界面。带有传参使用 ScreenBuilders
来调用 FancyMessageScreen
如下所示:
@Inject
private ScreenBuilders screenBuilders;
private void showFancyMessage(String message) {
FancyMessageScreen screen = screenBuilders.screen(this)
.withScreenClass(FancyMessageScreen.class)
.build();
screen.setFancyMessage(message);
screen.show();
}
如果使用类似 CreateAction 的标准操作打开界面,可以用它的 screenConfigurer
处理器通过界面的公共 setters 传递参数。
另一个方式是为参数定义一个特殊的类,然后在界面构造器中将该类的实例传递给标准的 withOptions()
方法。参数类必需实现 ScreenOptions
标记接口。示例:
import com.haulmont.cuba.gui.screen.ScreenOptions;
public class FancyMessageOptions implements ScreenOptions {
private String message;
public FancyMessageOptions(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
在打开的 FancyMessageScreen
界面,可以通过InitEvent和AfterInitEvent处理器获取参数:
@Subscribe
private void onInit(InitEvent event) {
ScreenOptions options = event.getOptions();
if (options instanceof FancyMessageOptions) {
String message = ((FancyMessageOptions) options).getMessage();
messageLabel.setValue(message);
}
}
带有传递 ScreenOptions
参数使用 ScreenBuilders
来调用 FancyMessageScreen
如下所示:
@Inject
private ScreenBuilders screenBuilders;
private void showFancyMessage(String message) {
screenBuilders.screen(this)
.withScreenClass(FancyMessageScreen.class)
.withOptions(new FancyMessageOptions(message))
.build()
.show();
}
可以看到,这个方式需要在控制界接收参数的时候进行类型转换,所以需要考虑清楚再使用。推荐还是用上面介绍的类型安全的使用 setter 的方式。
如果使用类似 CreateAction 的标准操作打开界面,可以用它的 screenOptionsSupplier
处理器创建并初始化所需的 ScreenOptions
对象。
如果界面是基于legacy API从另一个界面打开的,那么使用 ScreenOptions
对象是唯一能获取到参数的方法。此时,参数对象是 MapScreenOptions
类型的,可以在打开的界面中按照如下处理:
@Subscribe
private void onInit(InitEvent event) {
ScreenOptions options = event.getOptions();
if (options instanceof MapScreenOptions) {
String message = (String) ((MapScreenOptions) options).getParams().get("message");
messageLabel.setValue(message);
}
}
关闭界面后执行代码以及关闭界面返回值
每个界面在关闭时都会发送 AfterCloseEvent
事件。可以为界面添加监听器,这样可以在界面关闭时收到通知,示例:
@Inject
private Screens screens;
@Inject
private Notifications notifications;
private void openOtherScreen() {
OtherScreen otherScreen = screens.create(OtherScreen.class);
otherScreen.addAfterCloseListener(afterCloseEvent -> {
notifications.create().withCaption("Closed " + afterCloseEvent.getScreen()).show();
});
otherScreen.show();
}
当使用 ScreenBuilders
时,可以在 withAfterCloseListener()
方法中提供监听器:
@Inject
private ScreenBuilders screenBuilders;
@Inject
private Notifications notifications;
private void openOtherScreen() {
screenBuilders.screen(this)
.withScreenClass(OtherScreen.class)
.withAfterCloseListener(afterCloseEvent -> {
notifications.create().withCaption("Closed " + afterCloseEvent.getScreen()).show();
})
.build()
.show();
}
事件对象能提供关于界面是如何关闭的信息。信息可以有两种方式获取:一种是检测界面是否通过 StandardOutcome
枚举类型定义的一种标准输出关闭,另一种是获取 CloseAction
对象。前一种方法比较简单,但是后一种方法比较灵活。
先看看第一种方式:使用标准输出关闭界面然后在调用代码内进行检测。
将调用下面这个界面:
package com.company.demo.web.screens;
import com.haulmont.cuba.gui.components.Button;
import com.haulmont.cuba.gui.screen.*;
@UiController("demo_OtherScreen")
@UiDescriptor("other-screen.xml")
public class OtherScreen extends Screen {
private String result;
public String getResult() {
return result;
}
@Subscribe("okBtn")
public void onOkBtnClick(Button.ClickEvent event) {
result = "Done";
close(StandardOutcome.COMMIT); (1)
}
@Subscribe("cancelBtn")
public void onCancelBtnClick(Button.ClickEvent event) {
close(StandardOutcome.CLOSE); (2)
}
}
1 | - 在点击 “OK” 按钮时,设置一些结果状态,并使用 StandardOutcome.COMMIT 枚举值关闭界面。 |
2 | - 在点击 “Cancel” 按钮时,使用 StandardOutcome.CLOSE 关闭界面。 |
在 AfterCloseEvent
监听器使用事件的 closedWith()
方法检查界面是如何关闭的,并且需要的话可以读取结果:
@Inject
private ScreenBuilders screenBuilders;
@Inject
private Notifications notifications;
private void openOtherScreen() {
screenBuilders.screen(this)
.withScreenClass(OtherScreen.class)
.withAfterCloseListener(afterCloseEvent -> {
OtherScreen otherScreen = afterCloseEvent.getScreen();
if (afterCloseEvent.closedWith(StandardOutcome.COMMIT)) {
String result = otherScreen.getResult();
notifications.create().withCaption("Result: " + result).show();
}
})
.build()
.show();
}
从界面返回值的另一个方法是使用自定义的 CloseAction
实现。重写一下上面的示例,使用如下操作类:
package com.company.demo.web.screens;
import com.haulmont.cuba.gui.screen.StandardCloseAction;
public class MyCloseAction extends StandardCloseAction {
private String result;
public MyCloseAction(String result) {
super("myCloseAction");
this.result = result;
}
public String getResult() {
return result;
}
}
然后可以使用该操作类关闭界面:
package com.company.demo.web.screens;
import com.haulmont.cuba.gui.components.Button;
import com.haulmont.cuba.gui.screen.*;
@UiController("demo_OtherScreen2")
@UiDescriptor("other-screen.xml")
public class OtherScreen2 extends Screen {
@Subscribe("okBtn")
public void onOkBtnClick(Button.ClickEvent event) {
close(new MyCloseAction("Done")); (1)
}
@Subscribe("cancelBtn")
public void onCancelBtnClick(Button.ClickEvent event) {
closeWithDefaultAction(); (2)
}
}
1 | - 在点击 “OK” 按钮时,创建自定义关闭操作,并设置结果值。 |
2 | - 在点击 “Cancel” 按钮时,使用框架提供的默认操作关闭界面。 |
在 AfterCloseEvent
监听器从事件获取 CloseAction
并读取结果:
@Inject
private Screens screens;
@Inject
private Notifications notifications;
private void openOtherScreen() {
Screen otherScreen = screens.create("demo_OtherScreen2", OpenMode.THIS_TAB);
otherScreen.addAfterCloseListener(afterCloseEvent -> {
CloseAction closeAction = afterCloseEvent.getCloseAction();
if (closeAction instanceof MyCloseAction) {
String result = ((MyCloseAction) closeAction).getResult();
notifications.create().withCaption("Result: " + result).show();
}
});
otherScreen.show();
}
可以看到,当使用自定义的 CloseAction
返回值时,调用方不需要知道打开的界面类是什么,因为不会调用具体的界面控制器内的方法。所以界面可以只通过其字符串 id 来创建。
当然,在使用 ScreenBuilders
打开界面时,也可以使用相同的方式通过关闭操作返回结果。