Java/JavaFX企业级应用开发平台(fxEAP)
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

JavaFX简介

简述

2008年12月05日,SUN发布了JavaFX第一个正式版本,以期望Java在UI端能够更好地应用于开发富客户端的互联网应用(Rich Internet Cliet)。

2011年发布的JavaFX2.0取消之前基于Script的模式,改为使用原生Java结合CSS等进行了重构。

2014年发布的JavaFX8.0,与JDK1.8进行整合,并运用JDK8的新特性。

我们在上一篇文章中,已经简要地介绍了JavaUI框架的发展历史及特性。从这篇文章开始介绍JavaFX的特性,使用方法,优缺点以及注意事项等。

总体来说,使用JavaFX开发界面,要比Swing、SWT等要快捷一些。但作为一个已经发展了10多年的“主流”Java UI框架,在功能丰富程度以及一些处理细节上,还有比较多的欠缺甚至不方便、官方无解的问题。举几个例子:

1、JavaFX的Alert对话话比Swing提供的好用、漂亮,但是在Swing中使用JavaFX Alert时,由于其各自的派遣线程的问题,无法使用Modal模式。这种问题简直让人抓狂。

2、TableView在样式、某些处理机制方面,同样不可理喻。默认情况下,不管TableView中有多少数据,都会为整个表格绘制表格线;在编辑单元格时,必须敲回车键才能提交修改,如果点击其它单元格、其它行,或者敲Tab键,TableView将自动Cancel修改的内容;提供的TableCell太少,功能太弱。像必须敲回车才能提交修改的问题,开发人员N久以前就作为Bug提交给SUN/Oracle了,有人说JDK9会修复这个问题,但实际上我看了JDK9.0.1中相关的源代码,并没有。开发人员写了几行较详细的注释,说明这个问题不是他的责任,你们自行想办法。

这类问题都留给开发人员自行解决,个人感觉是十分说不过去的。在开发lrJAP平台的过程中,我数次被这样的问题折腾,甚至在日志中写下了这样一段话:

2017-08-25,越来越感觉JavaFX与主流JS(如ExtJS)有差距,今天已经特别想放弃JavaFX,而且特别到一定要把这句话记下来。唉。

拔特,问题总是能够被解决的,所以我们继续。先介绍介绍一下JavaFX与JS类框架相比的一些特性。

1、JavaFX已经与JDK融合,理论上可以使用JDK所有功能,以及所有的第三方Java类库,只要资源能够加载到JVM中,JavaFX就可以使用这些资源。这为我们在各方面都提供了便利。并且降低了上手的难度。

2、提供了较丰富的基础UI组件以及布局。特别是BorderPane,有利于界面模式的开发。可以通过CSS进行代码风格控制。

3、提供了基础的数据绑定功能,可以实现View-Model之间的双向数据(属性)绑定,并能够实现较复杂的事件处理机制。简化了与数据展现、控制相关的代码。

4、提供了使用比较方便的事件机制,并且可以与JDK8提供的Lamda结合,简化事件处理的代码。

5、提供了一些基本的图表。如饼图、折线图等。

个人感觉,JavaFX比较适用于以下业务场景:

1、基于企业内部局域网的应用,或基于相对比较封闭的网络环境下的集团级应用。也就是应用于传统的企业内部管理、业务系统的开发。

2、适用于业务数据结构复杂、业务处理逻辑复杂、有一定的性能需求,但对界面炫酷程度没有极端要求的应用。

3、功能界面可以被抽象、归类,希望在此基础上,实现快速开发。

基于JavaFX的应用如果运行在Internet环境中,则安全、带宽等方面要有相应的保证。个人感觉,对于一般用户来说,使用基于JavaFX的应用,主要困难在于下载、安装、配置JRE,以及基于信任、安全方面的考虑。

第一个JavaFX程序:HelloWorld

所有示例程序,除非特别说明,否则一律基于以下主要环境:Win10,IntelliJ IDEA,JDK 11.0.8,JavaFX SDK 17.0.2。

界面结构

J1002

Stage->Scene->Node

舞台->场景->演员

代码

为了能够更清楚地说明代码结构以及处理细节,代码中的注释有点过度:

package com.lirong.javafx.demo;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class HelloWorldUI extends Application {

    private static final double DEFAULT_BUTTOH_HEIGHT = 30;

    public static void main(String[] args) {

        // 启动应用
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {

        Button buttonHelloWorld = new Button("HelloWorld");
        // 设置按钮大小
        buttonHelloWorld.setPrefSize(100, DEFAULT_BUTTOH_HEIGHT);
        // 事件处理
        buttonHelloWorld.setOnAction(action -> {
            // 弹出模态对话框
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setContentText("Hello World!");
            alert.initOwner(stage);
            alert.showAndWait();
        });

        // 用于验证ToolBar中组件之间间隔的按钮,仅显示,无事件处理
        Button buttonOther = new Button("Other");
        buttonOther.setPrefSize(80, DEFAULT_BUTTOH_HEIGHT);

        // 模拟工具栏效果
        HBox toolBar = new HBox();
        // 设置ToolBar中控件与边界上、下、左、右的距离
        toolBar.setPadding(new Insets(10));
        // 设置ToolBar中控件之间的间隔
        toolBar.setSpacing(10);

        // 为ToolBar增加元素
        toolBar.getChildren().addAll(buttonHelloWorld, buttonOther);

        // 设置最外层的容器
        BorderPane container = new BorderPane();
        container.setTop(toolBar);

        // 处理应用场景,默认窗口大小为800*600
        Scene scene = new Scene(container, 800, 600);
        stage.setTitle("'Hello World' JavaFX Application");
        stage.setScene(scene);
        stage.show();
    }
}

运行效果如下图:

J1002

与程序逻辑无关的代码量比较少,默认的界面风格还说得过去。

Application Launcher启动程序的过程

  1. main方法
  2. 无参构造器(constructor)
  3. 初始化(init)
  4. 启动(start)
  5. 停止(stop)

JavaFX框架和线程

J1002

JavaFX Application Thread

JavaFX的主线程。

Prism

用于渲染,支持操作系统特性(如OpenGL)、软件渲染和硬件加速(3D)。

Glass Windowing Toolkit

处于整体架构的中间位置,它处于JavaFX图形技术栈的最底层。其主要职责是提供本地操作服务,例如窗体、计时器、皮肤。它是连接JavaFX层与本地操作系统的平台无关层。 还负责管理事件序列。与AWT自行管理事件序列不同,它使用本地操作系统的事件队列来调度线程。同样与AWT不同的是Glass toolkit与JavaFX应用运行在一个线程之上,而在AWT中本地系统中的那部分代码运行在一个线程中,而Java部分的运行在另外一个线程上。这导致了很多问题,而这些问题在JavaFX由于采用了统一的线程而都得到了解决。

Quantum Toolkit

把Prism和Glass Windowing ToolKit绑在一起,使得它们可以被其上层的JavaFX层使用。它也负责管理与渲染有关的事件处理的线程规则。

Media Engine Thread

在后台运行,通过使用JavaFX应用程序线程来在场景图中同步最近的帧。

Web Engine

基于Webkit的JavaFX UI控件,其提供了一个Web Viewer,支持HTML5、CSS、JavaScript、DOM、SVG,并通过其API提供了完全的浏览功能。

Platform类

void exit()

退出JavaFX Application。

boolean isFxApplicationThread()

判断调用线程是否是JavaFX线程。

void setImplicitExit(boolean value)

是否隐式退出。默认为true,关闭最一个窗口后JavaFX线程将终止。设置为false时,关闭窗口并不会退出应用,需要显式地调用Application.exit()

boolean isSupported(ConditionalFeature feature)

判断当前平台(JRE)是否支持指定的特性/模块(JDK 9 Modules)。

void runLater(Runnable runnable)

非阻塞式执行。

startup()

阻塞式执行(在JavaFX Applicatoin主线程上执行)。

Stage相关特性

窗口标题

setTitle()

全屏

setFullScreen(boolean)

可缩放

setResizable(boolean)

透明度

setOpacity(double)

样式(StageStyle)

J1002

StageStyle.DECORATED

Stage默认的样式,是一个典型的frame应用的窗口。

StageStyle.UNDECORATED

无装饰样式,没有标题栏,但是有阴影。

StageStyle.TRANSPARENT

透明样式,可以定义背景颜色。

StageStyle.UNIFIED

统一的风格有小的装饰栏,但没有边框。用于创建对话与典型工具栏对话框。 使用PlatForm.isSupported(ConditionalFeature.UNIFIED_WINDOW)可以检查当前平台是否是否支持该特性:如果平台不支持该特性,将设置为StageStyle.DECORATED。

StageStyle.UTILITY

工具样式,有标题栏,但是不可以被最大化,是一个理想的工具对话框。

退出JavaFX程序

  • stop()
  • Platform.exit()
  • System.exit(0)

第一个JavaFX Embedded in Swing程序

通过JavaFX提供的JFXPanel,可以“较方便”地在Swing程序中使用JavaFX组件。如下代码演示如何在JFrame中显示一个使用JavaFX控件模拟的表单信息:

首先,构造一个4列10行的JavaFX GridPane:

package com.lirong.javafx.demo.j1002.swing;

import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;

public class TestGridPaneUI extends GridPane {

    public TestGridPaneUI() {

        super();
        initUI();
    }

    private void initUI() {

        // 显示网格线
        setGridLinesVisible(Boolean.TRUE);

        // 设置间隔
        setVgap(5);
        setHgap(5);
        setPadding(new Insets(5));

        // 设置为4列
        ColumnConstraints col1 = new ColumnConstraints();
        col1.setPercentWidth(20);

        ColumnConstraints col2 = new ColumnConstraints();
        col2.setPercentWidth(30);

        ColumnConstraints col3 = new ColumnConstraints();
        col3.setPercentWidth(20);

        ColumnConstraints col4 = new ColumnConstraints();
        col4.setPercentWidth(30);

        getColumnConstraints().addAll(col1, col2, col3, col4);

        // 填充控件
        int row = 0;
        int col = 0;

        while (row < 10) {

            Label label01 = new Label(String.format("Row-%d,Col-%d:", row + 1, col + 1));
            label01.setAlignment(Pos.CENTER_RIGHT);
            // 右对齐
            GridPane.setHalignment(label01, HPos.RIGHT);
            add(label01, col, row);

            col++;

            if (col > 3) {
                row++;
                col = 0;
            }

            TextField text01 = new TextField();
            // 填充整个单元格
            GridPane.setHgrow(text01, Priority.ALWAYS);
            add(text01, col, row);

            col++;

            if (col > 3) {
                row++;
                col = 0;
            }

            Label label02 = new Label(String.format("Row-%d,Col-%d:", row + 1, col + 1));
            add(label02, col, row);
            // 右对齐
            GridPane.setHalignment(label02, HPos.RIGHT);

            col++;

            if (col > 3) {
                row++;
                col = 0;
            }

            TextField text02 = new TextField();
            // 填充整个单元格
            GridPane.setHgrow(text02, Priority.ALWAYS);
            add(text02, col, row);

            col++;

            if (col > 3) {
                row++;
                col = 0;
            }
        }
    }
}

然后把这个GridPane显示在JFrame中:

package com.lirong.javafx.demo.j1002.swing;

import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javax.swing.JFrame;

public class TestSwingFX {

    public static void main(String[] args) {

        JFrame frame = new JFrame();
        frame.setTitle("Swing JavaFX Test");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setSize(800, 600);

        JFXPanel jfxPanel = new JFXPanel();
        TestGridPaneUI gridPaneUI = new TestGridPaneUI();
        jfxPanel.setScene(new Scene(gridPaneUI));
        frame.add(jfxPanel);
        frame.setVisible(Boolean.TRUE);
    }
}

运行效果如下:

J1002

取消网格线后的效果:

J1002

关键类为JFXPanel,它是处理Swing和JavaFX界面、事件等交互的中介。

在拖放JFrame时,GridPanel将根据我们设置的比例自动缩放。