3.5.1.3. 打开界面

可以通过主菜单URL 导航或从另外一个界面以编程方式打开。在本节,将介绍如何以编程的方式打开界面。


使用 Screens 接口

使用 ScreenBuilders bean

传递参数给界面


使用 Screens 接口




Screens 接口允许创建和显示任何类型的界面。



假设有一个用于显示具有一些特殊格式的消息的界面:



界面控制器



  1. @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- 一个界面参数




界面描述



  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>






那么可以从另一个界面创建并打开它,如下所示:





  1. @Inject
    private Screens screens;

    private void showFancyMessage(String message) {
    FancyMessageScreen screen = screens.create(FancyMessageScreen.class);
    screen.setFancyMessage(message);
    screens.show(screen);
    }






请注意这里是如何创建界面实例、为其提供参数,然后显示界面。



如果界面不需要来自调用方的任何参数,可以仅用一行代码创建并打开它:





  1. @Inject
    private Screens screens;

    private void showDefaultFancyMessage() {
    screens.create(FancyMessageScreen.class).show();
    }








screens 不是 Spring bean,所以只能将它注入到界面控制器或使用 ComponentsHelper.getScreenContext(component).getScreens() 静态方法获取。





使用 ScreenBuilders bean




ScreenBuilders bean 允许打开带有各种参数的任何界面。



有个很常见的需求就是在打开的界面关掉之后需要运行一些代码。下面是在打开任意界面的时候使用 withAfterCloseListener() 构建方法的示例:





  1. @Inject
    private ScreenBuilders screenBuilders;
    @Inject
    private Notifications notifications;

    private void openOtherScreen() {
    screenBuilders.screen(this)
    .withScreenClass(OtherScreen.class)
    .withAfterCloseListener(e -> {
    OtherScreen screen = e.getScreen();
    CloseAction closeAction = e.getCloseAction();
    notifications.create()
    .withCaption("Closed " + screen + " with action " + closeAction)
    .show();
    })
    .build()
    .show();
    }






下面我们看看如何操作编辑界面和查找界面。



Customer 实体实例打开默认编辑界面的示例:





  1. @Inject
    private ScreenBuilders screenBuilders;

    private void editSelectedEntity(Customer entity) {
    screenBuilders.editor(Customer.class, this)
    .editEntity(entity)
    .build()
    .show();
    }






在这种情况下,编辑界面将更新实体,但调用界面将不会接收到更新后的实例。



最常见的情况是需要编辑某些用 TableDataGrid 组件显示的实体。那么应该使用以下调用方式,它更简洁且能自动更新表格组件:





  1. @Inject
    private GroupTable<Customer> customersTable;
    @Inject
    private ScreenBuilders screenBuilders;

    private void editSelectedEntity() {
    screenBuilders.editor(customersTable).build().show();
    }






要创建一个新的实体实例并打开它的编辑界面,只需在构建器上调用 newEntity() 方法:





  1. @Inject
    private GroupTable<Customer> customersTable;
    @Inject
    private ScreenBuilders screenBuilders;

    private void createNewEntity() {
    screenBuilders.editor(customersTable)
    .newEntity()
    .build()
    .show();
    }








默认编辑界面由以下过程确定:



-
如果存在使用@PrimaryEditorScreen注解的编辑界面,则使用它。

-
否则,使用 id 是 {entity_name}.edit 的编辑界面(例如,sales_Customer.edit)。





构建器提供了许多方法来设置被打开界面的可选参数。例如,以下代码以对话框的方式打开的特定编辑界面,同时新建并初始化实体:





  1. @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 实体的默认查找界面的示例:





  1. @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();
    }






如果需要将找到的实体设置到字段,可使用更简洁的方式:





  1. @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();
    }








默认查找界面由以下过程确定:



-
如果存在使用@PrimaryLookupScreen注解的查找界面,则使用它。

-
否则,如果存在 id 为 {entity_name}.lookup 的界面,则使用它(例如,sales_Customer.lookup)。

-
否则,使用 id 为 {entity_name}.browse 的界面(例如,sales_Customer.browse)。





与使用编辑界面一样,使用构建器方法设置打开界面的可选参数。例如,以下代码以对话框的方式打开特定的查找界面,在这个界面中查找 User 实体:





  1. @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 如下所示:





  1. @Inject
    private ScreenBuilders screenBuilders;

    private void showFancyMessage(String message) {
    FancyMessageScreen screen = screenBuilders.screen(this)
    .withScreenClass(FancyMessageScreen.class)
    .build();
    screen.setFancyMessage(message);
    screen.show();
    }






另一个方式是为参数定义一个特殊的类,然后在界面构造器中将该类的实例传递给标准的 withOptions() 方法。参数类必需实现 ScreenOptions 标记接口。示例:





  1. 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 界面,可以通过InitEventAfterInitEvent处理器获取参数:





  1. @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 如下所示:





  1. @Inject
    private ScreenBuilders screenBuilders;

    private void showFancyMessage(String message) {
    screenBuilders.screen(this)
    .withScreenClass(FancyMessageScreen.class)
    .withOptions(new FancyMessageOptions(message))
    .build()
    .show();
    }






可以看到,这个方式需要在控制界接收参数的时候进行类型转换,所以需要考虑清楚再使用。推荐还是用上面介绍的类型安全的使用 setter 的方式。



如果界面是基于legacy API从另一个界面打开的,那么使用 ScreenOptions 对象是唯一能获取到参数的方法。此时,参数对象是 MapScreenOptions 类型的,可以在打开的界面中按照如下处理:





  1. @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);
    }
    }