我将从一个大胆的声明开始:我一直很喜欢Java Swing或applet。 在那里,我说了。 如果我进行一些自我分析,那么这种钦佩可能是在我入门Java时开始的。 Swing(实际上)是我使用Java所做的第一件事,它给出了一些统计结果,并使我能够使用该语言做一些事情。 在我年轻的时候,我们建立了一些自制的胖客户来管理我们的3.5英寸软盘/ CD集合(用VB编写,然后用Basic编写),这可能也起到了作用。
无论如何,足以说明我的个人稀缺性。 事实是,Swing帮助许多人构建了出色的应用程序,但众所周知,Swing有它的缺点。 对于初学者来说,很长一段时间以来一直没有发展。 如果还需要很多样板代码
您想要创建高质量的代码。 它带有一些古怪的设计“缺陷”,缺少MVC等现成的模式。 样式有一定的局限性,因为您必须依靠有限的L&F架构,默认情况下不会内置I18N。 可以说,如今开发Swing很好,基本上可以追溯到过去。
幸运的是,甲骨文几年前通过启动JavaFX试图改变这一状况。 我记得在Devoxx(或Javapolis,当时的名称)上被引入JavaFX的介绍。 漂亮的演示程序看起来非常有前途,所以我很高兴看到Swing的继任者终于来了。 从我看到其内部结构的那一刻起,情况就发生了变化。 它的主要缺点之一是它基于一种黑暗的新语法(称为JavaFX脚本)。 如果您从未见过JavaFX脚本; 它看起来像Java,JSON和JavaScript之间的怪异品种。 尽管已将其编译为Java字节码,并且可以使用其中的Java API,但与Java的集成从未真正好。
语言本身(尽管功能很强大)需要您花费大量时间来了解细节,以便最终获得源代码,但是这次比普通的Java代码更难管理和支持。 事实证明,我并不是唯一的一个。 许多人都感到相同(当然也有其他原因),JavaFX从未取得过巨大成功。 但是,不久前Oracle通过引入JavaFX 2改变了潮流。
首先,他们摆脱了JavaFX脚本(不再受支持)并将其转变为真正的本机Java SE API(JavaFX 2.2.3是Java 7 SE更新6的一部分)。 JavaFX API现在看起来更像是熟悉的Swing API,这是一件好事。 它为您提供布局管理器的外观,事件侦听器以及您以前习惯的所有其他组件,但效果甚至更好。 因此,如果您希望像Swing一样编写JavaFX,尽管语法和改进的体系结构稍有不同。 现在也可以
将现有的Java Swing应用程序与JavaFX混合 。
但是还有更多。 他们引入了一种基于XML的标记语言,使您可以描述视图。 这具有一些优点,首先是XML编码比Java编码更快。 与Java相比,可以更容易地生成XML,并且用于描述视图的语法也更加紧凑。 使用某种标记来表示视图也更加直观,尤其是如果您曾经做过一些Web开发的话。 因此,可以拥有以FXML描述的视图(即其调用方式),应用程序控制器与该视图分离(在Java中是这样)和在CSS中的样式(是的,因此不再有L&F,CSS支持是标准的)。 您仍然可以直接在FXML中嵌入Java(或其他语言)。 但这可能不是您想要的(脚本反模式)。 另一个好处是对绑定的支持。 通过将fx:id属性放在视图组件上,并将@FXML批注放在应用程序控制器中的实例变量上,可以将视图中的每个组件绑定到应用程序控制器。 然后将自动注入相应的元素,因此您可以在应用程序控制器内部更改其数据或行为。 事实证明,通过一些代码行,您可以轻松地集成所选的DI框架,这不是很好吗? 那工具呢?
好吧,首先,有一个用于Eclipse的插件(fxclipse),它将即时为您提供FXML。 您可以通过Eclipse市场安装它:
该插件将立即呈现您进行的任何调整:
请注意,至少需要JDK7u6才能使该插件正常工作。 如果您的JDK太旧,则会在Eclipse中看到一个空白窗格。 另外,如果您创建JavaFX项目,则需要将jfxrt.jar手动放入构建类路径中。 您可以在%JAVA_HOME%/ jre / lib中找到此文件。
直到知道该插件在视觉上(通过拖放)对您没有帮助,但是那里有一个单独的IDE:
现场建设者 。 该构建器也集成在Netbeans中,对于AFAIK,尚不支持eclipse,因此如果要使用它,则必须单独运行它。 该构建器允许您使用拖放方式以可视方式开发FXML。 细节不错; 场景构建器实际上是用JavaFX编写的。 然后,您还有一个名为Scenic View的单独的应用程序,它对正在运行的JavaFX应用程序进行自省,并显示其构建方式。 您将获得具有不同节点及其层次结构的图。 对于每个节点,您可以看到其属性等等:
好的,让我们从一些代码示例开始。 我要做的第一件事是在场景生成器中设计演示应用程序:
我通过将容器/控制器d&d绘制到视图上以图形方式进行了此操作。 我还提供了要绑定到我的视图和fx:id的控件,您也可以通过场景生成器来做到这一点:
特别是对于按钮,我还添加了onAction(单击按钮后应在控制器上执行的方法):
接下来,我在Eclipse的源代码视图中手动添加了控制器。 每个FXML只能有一个控制器,应该在顶层元素中声明它。 我制作了两种FXML,一种代表主屏幕,另一种充当菜单栏。 您可能希望将逻辑划分为多个控制器,而不是在一个控制器中塞满很多东西–在这里,单一职责是一个很好的设计指南。 第一个FXML是“ search.fxml”,代表搜索条件和结果视图:
<?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的导入,如下所示:
<?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视图,则将获得与场景构建器相同的渲染视图。 如果您想在代码中进行一些小的更改以使其直接反映出来,那么它很方便:启动应用程序的代码非常标准:
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();@Overridepublic 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);}
}
唯一需要注意的是我们从Application扩展。 这是一个样板代码,例如,它将确保UI的创建发生在JavaFX应用程序线程上。 您可能还记得Swing中的此类故事,其中每个UI交互都需要在事件分派器线程(EDT)上发生,JavaFX也是一样。 默认情况下,当应用程序调用您时,您将处于“右线程”(例如,动作侦听器等方法)。 但是,如果您启动应用程序或在单独的线程中执行长时间运行的任务,则需要确保在正确的线程上启动UI交互。 挥杆你会用
JavaFX的SwingUtilities.invokeLater() : Platform.runLater() 。
更特别的是我们的SpringFxmlLoader:
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>() {@Overridepublic 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)。 通过指定自定义工厂,我们可以定义控制器的实例化方式。 在这种情况下,我们从应用程序上下文中查找bean。 最后,我们有两个控制器SearchController:
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 {@Autowiredprivate OrderService orderService;@FXMLprivate Button search;@FXMLprivate TableView<Order> table;@FXMLprivate TextField productName;@FXMLprivate TextField minPrice;@FXMLprivate TextField maxPrice;@Overridepublic 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除了关闭我们的应用程序外没有其他特殊功能:
package be.error.javafx.controller;import javafx.application.Platform;
import javafx.event.ActionEvent;public class FileMenuController {public void exit(ActionEvent actionEvent) {Platform.exit();}
}
最后(不太令人兴奋)结果:
搜索后:
使视图更宽,也拉长了列:
文件菜单允许我们退出:
在玩完JavaFX2之后,我印象深刻。 还有越来越多的控件(我相信已经有了浏览器控件等)。 所以我认为我们在这里是正确的。
参考:来自Koen Serneels –技术博客博客的JCG合作伙伴 Koen Serneels提供的带有Spring的JavaFX 2 。
翻译自: https://www.javacodegeeks.com/2013/03/javafx-2-with-spring.html