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-дескриптор экрана<?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(); }Screensне является Spring-бином, поэтому его можно только инжектировать в контроллер экрана или получить его с помощью статического методаComponentsHelper.getScreenContext(component).getScreens().
- Бин ScreenBuilders
-
Бин
ScreenBuildersпозволяет открывать все типы экранов с различными параметрами. Ниже приведён пример вызова экрана и выполнения некоторого кода после того, как экран закрывается (более подробно см. здесь):@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(); }Далее мы рассмотрим работу с экранами редактирования и выбора сущностей.
Пример открытия редактора по умолчанию для сущности
Customer:@Inject private ScreenBuilders screenBuilders; private void editSelectedEntity(Customer entity) { screenBuilders.editor(Customer.class, this) .editEntity(entity) .build() .show(); }В данном примере редактор изменит экземпляр сущности
Customer, но вызывающий экран не получит назад обновлённую сущность.Часто требуется отредактировать сущность, отображаемую, к примеру, компонентом
TableилиDataGrid. В этом случае следует использовать другую форму вызова редактора, она короче и позволяет автоматически обновить исходный экземпляр в таблице:@Inject private GroupTable<Customer> customersTable; @Inject private ScreenBuilders screenBuilders; private void editSelectedEntity() { screenBuilders.editor(customersTable).build().show(); }Чтобы создать новый экземпляр сущности и открыть экран его редактирования, достаточно вызвать метод
newEntity()builder’а:@Inject private GroupTable<Customer> customersTable; @Inject private ScreenBuilders screenBuilders; private void createNewEntity() { screenBuilders.editor(customersTable) .newEntity() .build() .show(); }Редактор сущности по умолчанию определяется по следующей схеме:
-
Если существует экран редактирования с аннотацией @PrimaryEditorScreen, будет использован он.
-
Если такого экрана нет, будет использован экран с идентификатором вида
{entity_name}.edit(например,sales_Customer.edit).
Builder предоставляет множество методов для передачи дополнительных параметров в открываемый экран. К примеру, следующий код создаёт сущность, сначала инициализируя новый экземпляр, в конкретном экране редактирования, открываемом в режиме диалогового окна:
@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(); }Экран выбора сущности по умолчанию определяется по следующей схеме:
-
Если существует экран выбора с аннотацией @PrimaryLookupScreen, будет использован он.
-
Если такого экрана нет, будет использован экран с идентификатором вида
{entity_name}.lookup(например,sales_Customer.lookup). -
Если и такого экрана нет, будет использован экран с идентификатором вида
{entity_name}.browse(например,sales_Customer.browse).
Как и в случае с экранами редактирования, вы можете использовать методы builder’а для передачи дополнительных параметров в открываемые экраны. Например, следующий код поможет выбрать сущность
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. Пример вызова того же самого экрана
FancyMessageScreenс передачей параметра и использованиемScreenBuilders:@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. Например: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); } }Пример вызова экрана
FancyMessageScreenчерезScreenBuildersс передачейScreenOptions:@Inject private ScreenBuilders screenBuilders; private void showFancyMessage(String message) { screenBuilders.screen(this) .withScreenClass(FancyMessageScreen.class) .withOptions(new FancyMessageOptions(message)) .build() .show(); }Как видите, данный подход требует приведения типов в контроллере, получающем параметры, поэтому используйте его только когда это необходимо и предпочитайте type-safe подход с setter-методами, описанный выше.
Использование объекта
ScreenOptionsявляется единственным способом получения параметров, если экран открывается из другого экрана, основанного на устаревшем API. В этом случае, объект параметров имеет тип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(); }Объект события предоставляет информацию о том, как экран был закрыт: его метод
getCloseAction()возвращает объект с интерфейсомCloseAction. ИнтерфейсFrameOwner, реализуемый контроллерами экранов, содержит несколько констант, определяющих реализации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") private void onOkBtnClick(Button.ClickEvent event) { result = "Done"; close(WINDOW_COMMIT_AND_CLOSE_ACTION); (1) } @Subscribe("cancelBtn") private void onCancelBtnClick(Button.ClickEvent event) { closeWithDefaultAction(); (2) } }1 - при нажатии кнопки "OK", установить некоторое результирующее значение и закрыть экран со стандартным действием закрытия WINDOW_COMMIT_AND_CLOSE_ACTION.2 - при нажатии кнопки "Cancel", закрыть экран с действием закрытия по умолчанию. Теперь в слушателе
AfterCloseEventможно проанализировать, как экран был закрыт, и, если необходимо, прочитать возвращаемое экраном значение:@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.getCloseAction().equals(WINDOW_COMMIT_AND_CLOSE_ACTION)) { 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; } }Теперь можно использовать данное действие при закрытии экрана:
@Inject private Screens screens; @Inject private Notifications notifications; private void openOtherScreen() { Screen otherScreen = screens.create("demo_OtherScreen", 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, вызывающий код не обязан знать класс открываемого экрана, так как ему не нужено вызывать его методы. По тому экран можно создавать по его строковому идентификатору.Разумеется, данный подход к возврату значений через действия закрытия может использоваться и при открытии экранов с помощью
ScreenBuilders.