Я собираюсь начать это со смелого утверждения: мне всегда нравились 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