Статьи

JavaFX 2 с пружиной

Я собираюсь начать это со смелого утверждения: мне всегда нравились Java Swing или апплеты в этом отношении. Там я это сказал. Если я проведу некоторый самоанализ, это восхищение, вероятно, началось, когда я познакомился с Java. Swing был (практически) первым, что я когда-либо делал с Java, который дал какой-то статистический результат и позволил мне сделать что-то с языком в то время. Когда я был моложе, мы создавали толстых клиентов для домашнего приготовления, чтобы управлять нашей коллекцией дискет / компакт-дисков 3.5 ″ (написанной на VB и до этого в основном), это, вероятно, также сыграло свою роль.

Во всяком случае, достаточно о моей личной редкости. Фактом является то, что Swing помог многим создавать отличные приложения, но, как мы все знаем, у Swing есть свои недостатки. Для начала это не развивалось с тех пор, долгое время. Это также требует много кода котельной плиты, если

Вы хотите создать высококачественный код. Он поставляется с некоторыми причудливыми конструктивными «недостатками», в которых отсутствуют стандартные шаблоны, такие как MVC. Стилизация является небольшим ограничением, так как вам приходится прибегать к ограниченной архитектуре L & F, I18N не встроен по умолчанию и так далее. Можно сказать, что разработка Swing в наши дни — это хорошо, в основном, уходя в прошлое.

К счастью, Oracle попыталась изменить это несколько лет назад, запустив JavaFX. Я помню, как познакомился с JavaFX на Devoxx (или Javapolis, как тогда называли). Изящная демонстрация выглядела очень многообещающе, поэтому я был рад видеть, что преемник Swing наконец-то продвигается. Это изменилось с того момента, как я увидел его внутренности. Одним из главных его недостатков было то, что он был основан на совершенно новом синтаксисе (называемом скриптом JavaFX). В случае, если вы никогда не видели скрипт JavaFX; это выглядит как странная порода между Java, JSON и JavaScript. Несмотря на то, что он скомпилирован в байт-код Java, и вы можете использовать API Java из него, интеграция с Java никогда не была действительно хорошей.

Сам язык (хотя и довольно мощный) требовал от вас много времени, чтобы понять детали, чтобы в итоге получить исходный код, ну, на этот раз менее управляемый и поддерживаемый, чем простой Java-код. Как оказалось, я был не единственным. Многие люди чувствовали то же самое (наверняка были и другие причины), и JavaFX никогда не имел большого успеха. Однако некоторое время назад Oracle изменила ситуацию, представив JavaFX 2.

Прежде всего они избавились от скрипта JavaFX (который больше не поддерживается) и превратили его в настоящий нативный API Java SE (JavaFX 2.2.3 является частью обновления Java 7 SE 6). JavaFX API теперь больше похож на знакомый Swing API, что хорошо. Это дает вам двойники менеджеров, слушатели событий и все те другие компоненты, к которым вы так привыкли, но даже лучше. Поэтому, если вы хотите, вы можете кодировать JavaFX так же, как вы это делали на Swing, хотя и с немного другим синтаксисом и улучшенной архитектурой. Теперь также возможно
смешивать существующие приложения Java Swing с JavaFX .

Но это еще не все. Они представили язык разметки на основе XML, который позволяет описывать представление. Это имеет некоторые преимущества, прежде всего кодирование в XML работает быстрее, чем в Java. XML может быть сгенерирован легче, чем Java, и синтаксис для описания представления просто более компактен. Также более интуитивно понятно выразить представление с помощью некоторой разметки, особенно если вы когда-либо занимались веб-разработкой ранее. Таким образом, можно иметь представление, описанное в FXML (так оно называется), контроллеры приложений отделены от представления как в Java, так и в стиле в CSS (да, так что больше нет L & F, поддержка CSS не является стандартной). Вы все еще можете встраивать Java (или другие языки) непосредственно в FXML; но это, вероятно, не то, что вы хотите (анти-паттерн скриптлет). Еще одна приятная вещь — поддержка привязки. Вы можете привязать каждый компонент в вашем представлении к контроллеру приложения, поместив атрибут fx: id в компонент представления и аннотацию @FXML в переменной экземпляра в контроллере приложения. Затем соответствующий элемент будет автоматически введен, так что вы можете изменить его данные или поведение изнутри вашего контроллера приложения. Также оказывается, что с некоторыми строками кода вы можете безболезненно интегрировать DI-фреймворк по вашему выбору, не правда ли? А как насчет инструментов?

Ну, во-первых, есть плагин для Eclipse (fxclipse), который будет отображать вам FXML на лету. Вы можете установить его через торговую площадку Eclipse:

Плагин выполнит любую настройку, которую вы сделаете немедленно:

Обратите внимание, что для работы этого плагина вам нужен как минимум JDK7u6. Если ваш JDK слишком стар, вы получите пустую панель в затмении. Кроме того, если вы создаете проект JavaFX, мне нужно было вручную поместить файл jfxrt.jar в мой путь к классу сборки. Вы найдете этот файл в% JAVA_HOME% / jre / lib.

Вплоть до того, что вы узнаете, что плагин не помогает вам визуально (путем перетаскивания), но есть отдельная IDE:
Сценарист Этот компоновщик также интегрирован в Netbeans, для AFAIK пока нет поддержки eclipse, поэтому вам придется запускать его отдельно, если вы хотите его использовать. Конструктор позволяет вам разрабатывать FXML визуальным способом, используя drag & drop. Приятная деталь; Конструктор сцены фактически написан на JavaFX. Затем у вас также есть отдельное приложение, называемое живописным видом, которое самоанализом выполняет работающее приложение JavaFX и показывает, как оно создается. Вы получаете график с различными узлами и их иерархической структурой. Для каждого узла вы можете увидеть его свойства и так далее:

Итак, давайте начнем с нескольких примеров кода. Первым делом я разработал демонстрационное приложение в построителе сцен:

Я сделал это графически, добавив контейнеры / контроллеры к представлению. Я также дал элементы управления, которые я хочу привязать к своему представлению и fx: id, вы можете сделать это также через конструктор сцены:

В частности, для кнопок я также добавил onAction (это метод, который должен выполняться на контроллере после нажатия кнопки):

Затем я добавил контроллер вручную в исходном представлении в Eclipse. На FXML может быть только один контроллер, и он должен быть объявлен в элементе верхнего уровня. Я сделал два FXML, один из которых представляет основной экран, а другой действует как строка меню. Вы, вероятно, хотите разделить свою логику на несколько контроллеров, а не добавлять много к одному контроллеру — единственная ответственность — хорошее руководство по дизайну. Первый FXML — «search.fxml» и представляет критерии поиска и представление результатов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<?xml version="1.0" encoding="UTF-8"?>
  
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.cell.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
  
<StackPane id="StackPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml" fx:controller="be.error.javafx.controller.SearchController">
  <children>
    <SplitPane dividerPositions="0.39195979899497485" focusTraversable="true" orientation="VERTICAL" prefHeight="200.0" prefWidth="160.0">
      <items>
        <GridPane fx:id="grid" prefHeight="91.0" prefWidth="598.0">
          <children>
     <fx:include source="/menu.fxml"/>
            <GridPane prefHeight="47.0" prefWidth="486.0" GridPane.columnIndex="1" GridPane.rowIndex="5">
              <children>
                <Button fx:id="clear" cancelButton="true" mnemonicParsing="false" onAction="#clear" text="Clear" GridPane.columnIndex="1" GridPane.rowIndex="1" />
                <Button fx:id="search" defaultButton="true" mnemonicParsing="false" onAction="#search" text="Search" GridPane.columnIndex="2" GridPane.rowIndex="1" />
              </children>
              <columnConstraints>
                <ColumnConstraints hgrow="SOMETIMES" maxWidth="338.0" minWidth="10.0" prefWidth="338.0" />
                <ColumnConstraints hgrow="SOMETIMES" maxWidth="175.0" minWidth="0.0" prefWidth="67.0" />
                <ColumnConstraints hgrow="SOMETIMES" maxWidth="175.0" minWidth="10.0" prefWidth="81.0" />
              </columnConstraints>
              <rowConstraints>
                <RowConstraints maxHeight="110.0" minHeight="10.0" prefHeight="10.0" vgrow="SOMETIMES" />
                <RowConstraints maxHeight="72.0" minHeight="10.0" prefHeight="40.0" vgrow="SOMETIMES" />
              </rowConstraints>
            </GridPane>
            <Label alignment="CENTER_RIGHT" prefHeight="21.0" prefWidth="101.0" text="Product name:" GridPane.columnIndex="0" GridPane.rowIndex="1" />
            <TextField fx:id="productName" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
            <Label alignment="CENTER_RIGHT" prefWidth="101.0" text="Min price:" GridPane.columnIndex="0" GridPane.rowIndex="2" />
            <Label alignment="CENTER_RIGHT" prefWidth="101.0" text="Max price:" GridPane.columnIndex="0" GridPane.rowIndex="3" />
            <TextField fx:id="minPrice" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
            <TextField fx:id="maxPrice" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="3" />
          </children>
          <columnConstraints>
            <ColumnConstraints hgrow="SOMETIMES" maxWidth="246.0" minWidth="10.0" prefWidth="116.0" />
            <ColumnConstraints fillWidth="false" hgrow="SOMETIMES" maxWidth="537.0" minWidth="10.0" prefWidth="482.0" />
          </columnConstraints>
          <rowConstraints>
            <RowConstraints maxHeight="64.0" minHeight="10.0" prefHeight="44.0" vgrow="SOMETIMES" />
            <RowConstraints maxHeight="68.0" minHeight="0.0" prefHeight="22.0" vgrow="SOMETIMES" />
            <RowConstraints maxHeight="68.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" />
            <RowConstraints maxHeight="68.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" />
            <RowConstraints maxHeight="167.0" minHeight="10.0" prefHeight="14.0" vgrow="SOMETIMES" />
            <RowConstraints maxHeight="167.0" minHeight="10.0" prefHeight="38.0" vgrow="SOMETIMES" />
          </rowConstraints>
        </GridPane>
        <StackPane prefHeight="196.0" prefWidth="598.0">
          <children>
            <TableView fx:id="table" prefHeight="200.0" prefWidth="200.0">
              <columns>
                <TableColumn prefWidth="120.0" resizable="true" text="OrderId">
                  <cellValueFactory>
                    <PropertyValueFactory property="orderId" />
                  </cellValueFactory>
                </TableColumn>
                <TableColumn prefWidth="120.0" text="CustomerId">
                  <cellValueFactory>
                    <PropertyValueFactory property="customerId" />
                  </cellValueFactory>
                </TableColumn>
                <TableColumn prefWidth="120.0" text="#products">
                  <cellValueFactory>
                    <PropertyValueFactory property="productsCount" />
                  </cellValueFactory>
                </TableColumn>
                <TableColumn prefWidth="120.0" text="Delivered">
                  <cellValueFactory>
                    <PropertyValueFactory property="delivered" />
                  </cellValueFactory>
                </TableColumn>
                <TableColumn prefWidth="120.0" text="Delivery days">
                  <cellValueFactory>
                    <PropertyValueFactory property="deliveryDays" />
                  </cellValueFactory>
                </TableColumn>
                <TableColumn prefWidth="150.0" text="Total order price">
                  <cellValueFactory>
                    <PropertyValueFactory property="totalOrderPrice" />
                  </cellValueFactory>
                </TableColumn>
              </columns>
            </TableView>
          </children>
        </StackPane>
      </items>
    </SplitPane>
  </children>
</StackPane>

В строке 11 вы можете видеть, что я настроил класс контроллера приложения, который должен использоваться с представлением. В строке 17 вы можете увидеть импорт отдельного menu.fxml, который показан здесь:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<?xml version='1.0' encoding='UTF-8'?>
 
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.MenuItem?>
 
<Pane prefHeight='465.0' prefWidth='660.0' xmlns:fx='http://javafx.com/fxml' fx:controller='be.error.javafx.controller.FileMenuController'>
 <children>
  <MenuBar layoutX='0.0' layoutY='0.0'>
   <menus>
    <Menu mnemonicParsing='false' text='File'>
     <items>
      <MenuItem text='Exit' onAction='#exit' />
     </items>
    </Menu>
   </menus>
  </MenuBar>
 </children>
</Pane>

В строке 7 вы можете увидеть, что он использует другой контроллер. В Eclipse, если вы откроете представление fxclipse из плагина, вы получите то же представление, что и в построителе сцен. Однако это удобно, если вы хотите внести небольшие изменения в код, чтобы увидеть их непосредственное отражение: код для запуска приложения довольно стандартный:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package be.error.javafx;
 
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
 
public class TestApplication extends Application {
 
 private static final SpringFxmlLoader loader = new SpringFxmlLoader();
 
 @Override
 public void start(Stage primaryStage) {
  Parent root = (Parent) loader.load('/search.fxml');
  Scene scene = new Scene(root, 768, 480);
  primaryStage.setScene(scene);
  primaryStage.setTitle('JavaFX demo');
  primaryStage.show();
 }
 
 public static void main(String[] args) {
  launch(args);
 }
}

Единственное, что следует отметить, это то, что мы расширяем приложение. Это небольшой пример кода, который, например, гарантирует, что создание пользовательского интерфейса происходит в потоке приложения JavaFX. Вы можете вспомнить такие истории из Swing, где каждое взаимодействие с пользовательским интерфейсом должно происходить в потоке диспетчера событий (EDT), то же самое с JavaFX. По умолчанию вы находитесь в «правильном потоке», когда приложение вызывает вас обратно (например, в методах прослушивателей действий). Но если вы запускаете приложение или выполняете длительные задачи в отдельных потоках, вам необходимо убедиться, что вы запускаете взаимодействие с пользовательским интерфейсом в нужном потоке. Для качелей вы бы использовали
SwingUtilities.invokeLater () для JavaFX: Platform.runLater () .

Более особенным является наш SpringFxmlLoader:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package be.error.javafx;
 
import java.io.IOException;
import java.io.InputStream;
 
import javafx.fxml.FXMLLoader;
import javafx.util.Callback;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class SpringFxmlLoader {
 
 private static final ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringApplicationConfig.class);
 
 public Object load(String url) {
  try (InputStream fxmlStream = SpringFxmlLoader.class
    .getResourceAsStream(url)) {
   System.err.println(SpringFxmlLoader.class
    .getResourceAsStream(url));
   FXMLLoader loader = new FXMLLoader();
   loader.setControllerFactory(new Callback<Class<?>, Object>() {
    @Override
    public Object call(Class<?> clazz) {
     return applicationContext.getBean(clazz);
    }
   });
   return loader.load(fxmlStream);
  } catch (IOException ioException) {
   throw new RuntimeException(ioException);
  }
 }
}

Выделенные строки показывают пользовательский ControllerFactory. Без установки этого JavaFX просто создаст экземпляр класса, который вы указали в качестве контроллера в FXML, без чего-либо особенного. В этом случае класс не будет управляться Spring (если только вы не используете CTW / LTW AOP). Указав собственную фабрику, мы можем определить, как должен создаваться экземпляр контроллера. В этом случае мы ищем бин из контекста приложения. Наконец, у нас есть два контроллера, SearchController:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package be.error.javafx.controller;
 
import java.math.BigDecimal;
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
 
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
 
import be.error.javafx.model.Order;
import be.error.javafx.model.OrderSearchCriteria;
import be.error.javafx.model.OrderService;
 
public class SearchController implements Initializable {
 
 @Autowired
 private OrderService orderService;
 @FXML
 private Button search;
 @FXML
 private TableView<Order> table;
 @FXML
 private TextField productName;
 @FXML
 private TextField minPrice;
 @FXML
 private TextField maxPrice;
 
 @Override
 public void initialize(URL location, ResourceBundle resources) {
  table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
 }
 
 public void search() {
  OrderSearchCriteria orderSearchCriteria = new OrderSearchCriteria();
  orderSearchCriteria.setProductName(productName.getText());
  orderSearchCriteria
    .setMaxPrice(StringUtils.isEmpty(minPrice.getText()) ? null:new BigDecimal(minPrice.getText()));
  orderSearchCriteria
    .setMinPrice(StringUtils.isEmpty(minPrice.getText()) ? null: new BigDecimal(minPrice.getText()));
  ObservableList<Order> rows = FXCollections.observableArrayList();
  rows.addAll(orderService.findOrders(orderSearchCriteria));
  table.setItems(rows);
 }
 
 public void clear() {
  table.setItems(null);
  productName.setText('');
  minPrice.setText('');
  maxPrice.setText('');
 }
}

Выделенные строки в соответствующем порядке:

  • Автоматическое внедрение Spring, это наш управляемый сервис Spring, который мы будем использовать для поиска данных
  • Автоматическое внедрение с помощью JavaFX, наши элементы управления, которыми мы должны манипулировать или читать из нашего контроллера
  • Специальный метод init для инициализации нашей таблицы, чтобы столбцы автоматически изменяли размеры при увеличении представления
  • обратный вызов в стиле прослушивателя действий, который вызывается при нажатии кнопки поиска
  • обратный вызов стиля слушателя действия, который вызывается при нажатии кнопки очистки

Наконец, FileMenuController, который не делает ничего особенного, кроме закрытия нашего приложения:

01
02
03
04
05
06
07
08
09
10
11
package be.error.javafx.controller;
 
import javafx.application.Platform;
import javafx.event.ActionEvent;
 
public class FileMenuController {
 
 public void exit(ActionEvent actionEvent) {
  Platform.exit();
 }
}

И наконец (не такой захватывающий) результат:


После поиска:

Делая вид шире, также растягиваем колонны:

Файловое меню, позволяющее нам выйти:

После небольшой игры с JavaFX2 я был очень впечатлен. Также появляется все больше и больше элементов управления (я думаю, что уже есть элемент управления браузера и тому подобное). Так что я думаю, что мы на правильном пути.

Ссылка: JavaFX 2 с Spring от нашего партнера JCG