Как оказалось, JavaFX не обладает удобным инструментом, чтобы реализовать навигацию между окнами. Но это решается не сложно. Преследуемая цель:
Вариантов реализовать подобный инструмент несколько. Почему выбран именно этот — об этом ниже.
Для тех, кто привык воспринимать через схемы:
Каждый экран должен реализовывать интерфейс Controller (Controller.java).
Изучение JavaFX, начинается с рассказа о том, что бывает файл разметки FXML и сопровождающий его класс контроллера на языке Java. Реализация нашего интерфейса, позволит задать (setView) и получить(getView) корневой элемент разметки файла FXML. Метод Show — отобразит содержимое fxml файла.
Далее, базовый для всех наших экранов класс BaseController (BaseController.java)
Каждый контроллер экрана, необходимо наследовать от BaseController:
А теперь о том как работает навигация — файл Navigation.java:
Чтобы возвращаться назад, нам понадобиться история переходов по окнам. Используем для этого список:
Можно использовать класс приложения, чтобы всякий раз иметь возможность получать доступ к объекту навигации. (Main.java)
Допустим необходимо чтобы контроллер View2 передал строку текста контроллеру View3. Для этого, объявляем в контроллере View3:
1 способ. Если при навигации, мы бы использовали принцип: один Stage — один контроллер, то конечно бы у нас получилось все хорошо. Но что если экранов много? Это можно сравнить с просмотром некоторого сайта — когда каждая ссылка открывается в новом окне. Но при таком подходе обнаружились проблемы при отображении окон в полноэкранном режиме. Временами новое окно отображалось просто белым и причину этого, к сожалению, так и не удалось найти.
2 способ. Что если менять сцены в одном окне? Этот способ, хоть и работает, но имеет одну неприятную особенность — при смене сцены происходит некрасивое переключение — быстрое, но не приятное. Насколько удалось заметить, в этот момент окно просто исчезает на мгновение и потом появляется уже с новой сценой.
Готовый файлы можно скачать отсюда (VAnavigation)
- минимум программного кода чтобы открыть новый экран
- возможность перемещаться по экранам назад
- передать данные между окнами
Вариантов реализовать подобный инструмент несколько. Почему выбран именно этот — об этом ниже.
Для тех, кто привык воспринимать через схемы:
Каждый экран должен реализовывать интерфейс Controller (Controller.java).
public interface Controller { Node getView(); void setView(Node view); void Show(); }
Изучение JavaFX, начинается с рассказа о том, что бывает файл разметки FXML и сопровождающий его класс контроллера на языке Java. Реализация нашего интерфейса, позволит задать (setView) и получить(getView) корневой элемент разметки файла FXML. Метод Show — отобразит содержимое fxml файла.
Далее, базовый для всех наших экранов класс BaseController (BaseController.java)
public class BaseController implements Controller { private Node view; @Override public Node getView() { return view; } @Override public void setView (Node view){ this.view = view; } @Override public void Show() { PreShowing(); Main.getNavigation().Show(this); PostShowing(); } public void PreShowing() { } public void PostShowing() { } }Этот класс и берет на себя реализацию интерфейса Controller. Методы PreShowing() и PostShowing() дадут нам возможность сделать что-либо на нашем экране перед открытием или после открытия.
Каждый контроллер экрана, необходимо наследовать от BaseController:
public class View1 extends BaseController implements Initializable { ... }
А теперь о том как работает навигация — файл Navigation.java:
public class Navigation { private final Stage stage; private final Scene scene; private List<Controller> controllers = new ArrayList<>(); public Navigation(Stage stage) { this.stage = stage; scene = new Scene(new Pane()); stage.setScene(scene); } public Controller load(String sUrl) { try { //loads the fxml file FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(sUrl)); Node root = fxmlLoader.load(); Controller controller = fxmlLoader.getController(); controller.setView(root); return controller; } catch(Exception e) { e.printStackTrace(); } return null; } public void Show(Controller controller) { try { scene.setRoot((Parent) controller.getView()); controllers.add(controller); System.out.println("Add to history: " + controller.toString() + ". Total scenes: " + controllers.size()); } catch(Exception e) { e.printStackTrace(); } } public void GoBack() { if (controllers.size() > 1) { controllers.remove(controllers.get(controllers.size() - 1)); scene.setRoot((Parent) controllers.get(controllers.size() - 1).getView()); } System.out.println("GoBack: " + controllers.get(controllers.size() - 1).toString() + ". Total scenes: " + controllers.size()); } public void ClearHistory() { while (controllers.size() > 1) controllers.remove(0); System.out.println("ClearHistory. Total scenes: " + controllers.size()); } }При создании объекта Navigation, указывается Stage — это то что принято называть окном. В JavaFX каждому Stage может быть задана Scene — сцена — область внутри Stage. В этой области будет рисоваться содержимое файла разметки fxml. В нашем случае, переключение между экранами происходит на одной сцене — будем подменять то, что должно отображаться на сцене (в окне).
Чтобы возвращаться назад, нам понадобиться история переходов по окнам. Используем для этого список:
private List<Controller> controllers = new ArrayList<>();Когда нам нужно будет сменить экран, нам останется сделать 2 шага:
- вызвать метод load, чтобы загрузить файл разметки FXML
- вызвать метод Show, который заменит содержимое сцены и добавит контроллер в список для истории.
Можно использовать класс приложения, чтобы всякий раз иметь возможность получать доступ к объекту навигации. (Main.java)
public class Main extends Application { private static Navigation navigation; public static Navigation getNavigation() { return navigation; } @Override public void start(Stage primaryStage) throws Exception{ navigation = new Navigation(primaryStage); primaryStage.setTitle("VA navigation"); primaryStage.show(); //navigate to first view Main.getNavigation().load(View1.URL_FXML).Show(); } public static void main(String[] args) { launch(args); } }И вот теперь в любом месте достаточно написать
Main.getNavigation().load(View1.URL_FXML).Show();и произойдет смена экрана.
Передача параметров между контроллерами.
Допустим необходимо чтобы контроллер View2 передал строку текста контроллеру View3. Для этого, объявляем в контроллере View3:
private String parameterFromView2; public String getParameterFromView2() { return parameterFromView2; } public void setParameterFromView2(String parameterFromView2) { this.parameterFromView2 = parameterFromView2; }И когда будем вызвать View3 из View2 немного изменим вызов:
View3 view3 = (View3)Main.getNavigation().load(View3.URL_FXML); view3.setParameterFromView2(txtParameter.getText()); view3.Show();Очень часто, получаемые в контроллерах параметры должны быть использованы для тех или иных действий перед отображением на экране. Именно для этого, в базовом классе, предусмотрена функция:
@Override public void PreShowing() { super.PreShowing(); lblParameter.setText(getParameterFromView2()); }
Почему именно так.
Почему именно смена содержимого сцены? Потому что опробованные 2 других способа имеют недостатки.1 способ. Если при навигации, мы бы использовали принцип: один Stage — один контроллер, то конечно бы у нас получилось все хорошо. Но что если экранов много? Это можно сравнить с просмотром некоторого сайта — когда каждая ссылка открывается в новом окне. Но при таком подходе обнаружились проблемы при отображении окон в полноэкранном режиме. Временами новое окно отображалось просто белым и причину этого, к сожалению, так и не удалось найти.
2 способ. Что если менять сцены в одном окне? Этот способ, хоть и работает, но имеет одну неприятную особенность — при смене сцены происходит некрасивое переключение — быстрое, но не приятное. Насколько удалось заметить, в этот момент окно просто исчезает на мгновение и потом появляется уже с новой сценой.
Готовый файлы можно скачать отсюда (VAnavigation)
Большое спасибо автору!
ОтветитьУдалитьРад, если получилось быть полезным!
Удалить