打开视图

在列表或详情视图时,可以通过 标准操作 从主视图打开其他视图,也可以编程式打开。

下面介绍在应用程序代码中如何打开视图。

使用 ViewBuilders Bean

ViewBuilders bean 提供了打开视图的流式接口。流式接口的终止方法可以获取打开视图的实例。因此可以直接给视图实例传递输入参数,或者添加监听器在视图关闭后获取结果。

打开视图时,注入 ViewBuilders bean 并调用 view() 方法,参数为当前视图和需要打开的视图类或 id。然后调用 open() 终止方法:

@Autowired
private ViewBuilders viewBuilders;

private void openView() {
    viewBuilders.view(this, OtherView.class).open();
}

打开详情视图

在大多数情况下,可以用标准操作,比如 list_create 打开详情视图。下面我们看一个示例,使用 ViewBuilders API 从 actionbutton 的处理方法中直接打开详情视图。

如需在详情视图中创建一个新实体,请调用 newEntity() 方法,示例:

@Autowired
private ViewBuilders viewBuilders;

private void openDetailViewToCreate() {
    viewBuilders.detail(this, User.class)
            .newEntity()
            .open();
}

如需在详情视图编辑一个已有的实体,可以将实体用 editEntity() 方法传入:

@Autowired
private ViewBuilders viewBuilders;

private void openDetailViewToEdit(User user) {
    viewBuilders.detail(this, User.class)
            .editEntity(user)
            .open();
}

上面的示例中,详情视图会创建/更新实体,但是视图的调用方却无法拿到更新后的实例。

通常我们需要编辑显示在列表数据视图组件中的一个实体,例如,dataGrid。那么我们需要按照下面的方式调用,这样更方便并能自动更新 dataGrid

@ViewComponent
private DataGrid<User> usersDataGrid;

@Autowired
private ViewBuilders viewBuilders;

private void openDetailViewDataGridToEdit() {
    viewBuilders.detail(usersDataGrid)
            .open();
}

如果需要创建一个新实体并在详情视图打开,调用 newEntity() 方法即可:

@ViewComponent
private DataGrid<User> usersDataGrid;

@Autowired
private ViewBuilders viewBuilders;

private void openDetailViewDataGridToCreate() {
    viewBuilders.detail(usersDataGrid)
            .newEntity()
            .open();
}

同样的,如果需要创建或编辑设置到字段的实体,也可以用这个简单的方法:

@ViewComponent
private EntityPicker<User> userPicker;

@Autowired
private ViewBuilders viewBuilders;

private void openDetailViewFieldToEdit() {
    viewBuilders.detail(userPicker)
            .open();
}

ViewBuilders 提供了许多方法设置打开视图的可选参数。例如,下面的代码创建了一个新实体,并进行初始化,然后在一个以对话框方式打开的特定详情视图中编辑:

@Autowired
private ViewBuilders viewBuilders;

private void openDetailViewDialog() {
    viewBuilders.detail(this, User.class)
            .newEntity()
            .withInitializer(user -> {
                user.setTimeZoneId(getDefaultTimeZone());
            })
            .withOpenMode(ViewOpenMode.DIALOG)
            .open();
}

打开查找视图

打开查找视图也类似,与详情视图一样,多数情况是通过标准操作打开,例如,entity_lookup。下面的示例演示使用 ViewBuilders API,在不使用标准操作时也很方便。

如需从列表视图选择实体时,请用 lookup() 方法打开视图:

@Autowired
private ViewBuilders viewBuilders;

private void openLookupView() {
    viewBuilders.lookup(this, User.class)
            .withSelectHandler(users -> {
                User user = users.iterator().next();
                // ...
            })
            .open();
}

如果需要将查找的实体设置给一个字段,则可以用更简洁的方式:

@ViewComponent
private EntityPicker<User> userPicker;

@Autowired
private ViewBuilders viewBuilders;

private void openLookupViewToSelect() {
    viewBuilders.lookup(userPicker)
            .open();
}

同样的,如果需要将查找到的实体添加至列表数据组件,例如,dataGrid,示例:

@ViewComponent
private DataGrid<User> usersDataGrid;

@Autowired
private ViewBuilders viewBuilders;

private void openLookupViewDataGrid() {
    viewBuilders.lookup(usersDataGrid)
            .open();
}

与详情视图一样,可以用 builder 的方法设置打开视图的可选参数。例如,下面的代码用对话框模式查找 User 实体:

@Autowired
private ViewBuilders viewBuilders;

private void openLookupViewDialog() {
    viewBuilders.lookup(this, User.class, UserLookupView.class)
            .withOpenMode(ViewOpenMode.DIALOG)
            .withSelectHandler(users -> {
                User user = users.iterator().next();
                // ...
            })
            .open();
}

为视图传递参数

如果需要为打开的视图传递参数,可以调用 withViewConfigurer 方法为视图添加一个视图配置器。

如下面的视图:

@Route(value = "fancy-message-view", layout = DefaultMainViewParent.class)
@ViewController(id = "FancyMessageView")
@ViewDescriptor(path = "fancy-message-view.xml")
public class FancyMessageView extends StandardView {

    @ViewComponent
    private H1 fancyMessage;

    public void setMessage(String message) {
        fancyMessage.setText(message);
    }
}

ViewConfigurer 处理方法中,可以调用打开视图的 public setter:

@Autowired
private ViewBuilders viewBuilders;

private void openViewWithParameters(String message) {
    viewBuilders.view(this, FancyMessageView.class)
            .withViewConfigurer(fancyMessageView -> {
                fancyMessageView.setMessage(message);
            })
            .open();
}

视图关闭后执行代码以及返回值

每个视图会在关闭时发送 AfterCloseEvent 事件。使用 ViewBuilders 时,监听器可以在 withAfterCloseListener() 方法提供:

@Autowired
private ViewBuilders viewBuilders;

@Autowired
private Notifications notifications;

private void openViewWithCloseListener() {
    viewBuilders.view(this, OtherView.class)
            .withAfterCloseListener(afterCloseEvent -> {
                notifications.show("Closed: " + afterCloseEvent.getSource());
            })
            .open();
}

事件对象提供了视图关闭方式的信息。可以通过两种方法获取这个信息:

  • 检查视图是否以 StandardOutcome 枚举定义的某种输出关闭。

  • 通过获取 CloseAction 对象。第一种方式比较简单,但第二种方式更加灵活。

我们看看第一种方式:使用一个标准输出关闭视图,并在调用代码中检查输出。下面是我们将调用的视图:

@Route(value = "other-view", layout = DefaultMainViewParent.class)
@ViewController(id = "OtherView")
@ViewDescriptor(path = "other-view.xml")
public class OtherView extends StandardView {

    private String result;

    public String getResult() {
        return result;
    }

    @Subscribe(id = "saveBtn", subject = "clickListener")
    public void onSaveBtnClick(final ClickEvent<JmixButton> event) {
        result = "Save";
        close(StandardOutcome.SAVE);    (1)
    }

    @Subscribe(id = "closeBtn", subject = "clickListener")
    public void onCloseBtnClick(final ClickEvent<JmixButton> event) {
        result = "Close";
        close(StandardOutcome.CLOSE);   (2)
    }
}
1 点击 Save 按钮设置结果状态并以 StandardOutcome.SAVE 枚举值关闭视图。
2 点击 Close 按钮以 StandardOutcome.CLOSE 结果关闭视图.

AfterCloseEvent 监听器中,可以用事件的 closedWith() 方法检查视图是如何关闭的,并按需读取结果值:

@Autowired
private ViewBuilders viewBuilders;

@Autowired
private Notifications notifications;

private void openViewWithResult() {
    viewBuilders.view(this, OtherView.class)
            .withAfterCloseListener(afterCloseEvent -> {
                if (afterCloseEvent.closedWith(StandardOutcome.SAVE)) {
                    OtherView otherView = afterCloseEvent.getSource();
                    notifications.show("Result: " + otherView.getResult());
                }
            })
            .open();
}

使用自定义 CloseAction

另一个从视图返回值的方法是使用自定义的 CloseAction 实现。我们重写一下上面的示例,使用下面的操作类:

public class MyCloseAction extends StandardCloseAction {

    private final String result;

    public MyCloseAction(String result) {
        super("myCloseAction");

        this.result = result;
    }

    public String getResult() {
        return result;
    }
}

在关闭视图时,我们可以用这个操作:

@Route(value = "other-view", layout = DefaultMainViewParent.class)
@ViewController(id = "OtherView")
@ViewDescriptor(path = "other-view.xml")
public class OtherView extends StandardView {

    @Subscribe(id = "okBtn", subject = "clickListener")
    public void onOkBtnClick(final ClickEvent<JmixButton> event) {
        close(new MyCloseAction("Done"));   (1)
    }

    @Subscribe(id = "cancelBtn", subject = "clickListener")
    public void onCancelBtnClick(final ClickEvent<JmixButton> event) {
        closeWithDefaultAction();   (2)
    }
}
1 点击 Ok 按钮,创建自定义的关闭操作并设置结果值。
2 点击 Close 按钮,使用框架提供的默认操作。

AfterCloseEvent 监听器中,可以从事件中获取 CloseAction,并读取结果值:

@Autowired
private ViewBuilders viewBuilders;

@Autowired
private Notifications notifications;

private void openViewWithCloseAction() {
    viewBuilders.view(this, "OtherView")
            .withAfterCloseListener(afterCloseEvent -> {
                CloseAction closeAction = afterCloseEvent.getCloseAction();
                if (closeAction instanceof MyCloseAction myCloseAction) {
                    notifications.show("Result: " + myCloseAction.getResult());
                }
            })
            .open();
}

可以看到,当结果值通过自定义的 CloseAction 返回时,调用方无需知道打开的视图的任何信息,因为不需要调用该视图控制器的任何方法。因此视图可以通过 id 创建。

视图推断规则

一个查找视图或详情视图可以从实体类推断。

当使用 viewBuilders.lookup(this, SomeEntity.class) 打开列表视图时,框架按下列顺序选择需要打开的视图:

  1. @PrimaryLookupView(SomeEntity.class) 注解的视图。

  2. 视图 id 为 SomeEntity.lookup

  3. @PrimaryListView(SomeEntity.class) 注解的视图。

  4. 视图 id 为 SomeEntity.list

When opening a detail view using viewBuilders.detail(this, SomeEntity.class), the framework selects a view in the following order:

  1. @PrimaryDetailView(SomeEntity.class) 注解的视图。

  2. 视图 id 为 SomeEntity.detail