首页 > 基础资料 博客日记

Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)

2024-06-27 07:00:08基础资料围观85

Java资料网推荐Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)这篇文章给大家,欢迎收藏Java资料网享受知识的乐趣

文章目录

JavaFx是Java图形化界面技术AWT、Swing技术的新替代品,如果想系统学习JavaFx,推荐一个B站UP主:https://space.bilibili.com/5096022/channel/collectiondetail?sid=210809
如果是想快速入门JavaFx,推荐视频:
https://www.bilibili.com/video/BV1H14y1g7ji/
https://www.bilibili.com/video/BV1Qf4y1F7Zv/

如果你因毕设或项目中用到图形化界面技术JavaFx,赶时间都不想看,请看下面的文字版,通过多个案例,直接带你快速入门JavaFx,轻松上手看懂并学会使用!


一、JavaFx介绍

1、JavaFx简介

JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。
JavaFX允许开发人员快速构建丰富的跨平台应用程序。JavaFX通过硬件加速图形支持现代GPU。
JavaFX允许开发人员在单个编程接口中组合图形,动画和UI控件。
图表编程语言可用于开发互联网应用程序(RIA)。JavaFX技术主要应用于创建Rich internet applications(RIAs)。当前的JavaFX包括JavaFX脚本和JavaFX Mobile (一种运营于行动装置的操作系统),今后JavaFX将包括更多的产品。JavaFX Script编程语言 (以下称为JavaFX) 是一种声明性的、静态类型脚本语言。
JavaFX技术有着良好的前景,包括可以直接调用Java API的能力。因为JavaFX Script是静态类型,它同样具有结构化代码、重用性和封装性,如包、类、继承和单独编译和发布单元,这些特性使得使用JavaFX技术创建和管理大型程序变为可能。

JavaFx官网:https://openjfx.io/

下面是基于JavaFx实现的一些第三方开发案例和应用场景:

2、可用性

JavaFX APIJava SE运行时环境(JRE)和Java开发工具包(JDK)绑在一起。
JDK可用于所有主要的桌面平台,WindowsMac OS XLinux
JavaFX应用程序可以在所有主要的桌面平台上编译和运行。
JDK for ARM平台包括JavaFx的基础,图形和控件组件。

3、主要特征

JavaFX中包含以下功能-

  • JavaFX是用Java编写的,JavaFX应用程序代码可以从任何Java库引用API。
  • JavaFX应用程序的外观和感觉可以定制。因此可以使用级联样式表(CSS)来对JavaFX应用程序进行风格化。平面设计师可以通过CSS自定义外观和样式。
  • 还可以在FXML脚本语言中描述UI的表示方面,并使用Java对应用程序逻辑进行编码。
  • 通过使用JavaFX Scene Builder,可以通过拖放来设计Ul。Scene Builder将创建可以移植到集成开发环境(IDE)的FXML标记,以便开发人员可以添加业务逻辑。
  • JavaFX有一个称为WebView的控件,可以呈现复杂的网页。WebView支持JavaScript,我们可以从Java API在Web页面中调用Javascript。 WebView不支持额外的HTML5功能,包括Web套接字,Web Workers和Web字体,还可以从WebView打印网页。
  • Swing互操作性。现有的Swing应用程序可以使用JavaFX类,例如图表和WebView。还可以使用SwingNode类将Swing内容嵌入到我们应用程序中。
  • 3D图形功能。JavaFX支持Shape,如Box,Cylinder,MeshView和Sphere子类,SubScene,Material,PickResult,AmbientLight和PointLight。
  • Canvas API。 使用Canvas API,可以在JavaFX场景上绘制。
  • 打印APl。 javafx.print包提供了JavaFX Printing API的类。
  • 富文本支持。JavaFX支持增强的文本,包括双向文本和复杂的文本脚本,例如泰语和印度教的控件,以及多行,多种风格的文本。
  • 多点触控支持,JavaFX提供对多点触摸操作的支持。
  • JavaFX支持Hi-DPI显示。

4、UI控件

以下列出了JavaFX API中提供的一些内置JavaFX UI控件:

  • Label
  • Button
  • Radio Button
  • Toggle Button
  • Checkbox
  • ChoiceBox
  • Text Field
  • Password Field
  • Scroll Bar
  • Scroll Pane
  • List View
  • Table View
  • Tree View
  • Tree Table View
  • Combo Box
  • Separator
  • Slider
  • Progress Bar
  • Progress Indicator
  • Hyperlink
  • Tooltip
  • HTML Editor
  • Titled Pane
  • AccordionMenu
  • Color Picker
  • Date Picker
  • Pagination Control
  • File Chooser

JavaFX允许UI控制节点和形状节点在场景图上共存。
我们可以像任何其他JavaFX节点一样处理任何UI控件,例如可以缩放,旋转,样式和添加效果。


二、JavaFx概述

1、JavaFx结构图

一般来说,JavaFX应用程序包含一个或多个对应于窗口的阶段。每个阶段都有一个场景。每个场景都可以有一个控件、布局等附加到它的对象图,称为场景图。这些概念都将在后面更详细地解释。下面是JavaFX应用程序的般结构的图示:

2、JavaFx组件

(1)舞台

舞台是JavaFX应用程序的外部框架。舞台通常对应于一个窗口。在 JavaFX 可以在浏览器中运行的早期阶段,舞台还可以指网页内 JavaFX 可用于绘制自身的区域。
由于 Java 浏览器插件的弃用,JavaFX 主要用于桌面应用程序。在这里,JavaFX 取代了 Swing 作为推荐的桌面GUI 框架。而且JavaFX 看起来比 Swing 更加一致且功能丰富。
在桌面环境中使用时,JavaFX 应用程序可以打开多个窗口。每个窗口都有自己的舞台。
每个阶段都由StageJavaFX 应用程序中的一个对象表示。StageJavaFX 应用程序有一个由JavaFX 运行时为您创建的主对象。如果 JavaFX 应用程序Stage需要打开其他窗口,它可以创建其他对象。例如,对于对话框、向导等。

(2)场景

要在JavaFX 应用程序的舞台上显示何内容,您需要一个场景。一个舞台一次只能显示一个场景,但可以在运行时交换场景。就像剧院中的舞台可以重新安排以在戏剧期间显示多个场景一样,JavaFX 中的舞台对象可以在JavaFX 应用程序的生命周期内显示多个场景 (一次一个) 。
您可能想知道为什么 JavaFX 应用程序的每个阶段会有多个场景。想象一个电脑游戏。一个游戏可能有多个“屏幕”向用户显示。例如,初始菜单屏幕、主游戏屏幕(玩游戏的地方)、游戏结束屏幕和高分屏幕。这些屏幕中的每一个都可以由不同的场景来表示。当游戏需要从一屏切换到下一屏时,它只需将相应的场景附加到StageJavaFX 应用程序的对象上即可。
场景由SceneJavaFX 应用程序中的对象表示。JavaFX 应用程序必须创建Scene它需要的所有对象。

① 场景图

所有视觉组件(控件、布局等)都必须附加到要显示的场景,并且该场景必须附加到舞台才能使整个场景可见。附加到场景的所有控件、布局等的总对象图称为场景图。

② 节点

附加到场景图的所有组件都称为节点。所有节点都是JavaFX 类的子类,称为javafx.scene.Node。
有两种类型的节点:分支节点和叶节点。分支节点是可以包合其他节点(子节点)的节点。分支节点也称为父节点,因为它们可以包含子节点。叶节点是不能包含其他节点的节点。

(3)控件

JavaFX 控件是 JavaFX 组件,它们在JavaFX 应用程序中提供某种控制功能。例如,按钮、单选按钮、表格、树
为了使控件可见,它必须附加到某个Scene对象的场景图中。
控件通常嵌套在一些 JavaFX 布局组件中,这些组件管理控件相对于彼此的布局。
JavaFX 包含以下控件:

  • 手风琴
  • 按钮
  • 复选框
  • 选择框
  • 选色器
  • 组合框
  • 日期选择器
  • 标签
  • 列表显示
  • 菜单
  • 菜单栏
  • 密码字段
  • 进度条
  • 单选按钮
  • 滑块
  • 微调器
  • 拆分菜单按钮
  • 拆分窗格
  • 表视图
  • 选项卡窗格
  • 文本区域
  • 文本域
  • 标题窗格
  • 切换按钮
  • 工具栏
  • 树表视图
  • 树视图

这些控件中的每一个都将在单独的文本中进行解释。

(4)布局

JavaFX 布局是其中包含其他组件的组件。布局组件管理嵌套在其中的组件的布局。JavaFX 布局组件有时也称为父组件,因为它们包含子组件,而且布局组件是JavaFX 类的子类javafx.scene.Parent。
布局组件必须附加到某个Scene对象的场景图才能可见。
JavaFX 包含以下布局组件:

  • 团体
  • 地区
  • 窗格
  • HBox
  • 盒子
  • 流窗格
  • 边框窗格
  • 边框窗格
  • 堆栈窗格
  • 瓷砖窗格
  • 网格窗格
  • 锚点窗格
  • 文本流

这些布局组件中的每一个都将在单独的文本中进行介绍。

(5)图表

JavaFX 带有一组内置的即用型图表组件,因此您不必每次需要基本图表时都从头开始编写图表代码。
JavaFX 包含以下图表组件:

  • 面积图
  • 条形图
  • 气泡图
  • 折线图
  • 饼形图
  • 散点图
  • 堆积面积图
  • 堆积条形图

(6)2D图形

JavaFX 包含可以轻松在屏幕上绘制2D 图形的功能。

(7)3D图形

JavaFX 包含可以轻松在屏幕上绘制 3D 图形的功能。

(8)声音

JavaFX 包含使在 JavaFX 应用程序中播放音频变得容易的功能。这通常在游戏或教育应用中很有用。

(9)视频

JavaFX 包合使在 JavaFX 应用程序中播放视频变得容易的功能。这通常在流媒体应用程序、游戏或教育应用程序中很有用。

JavaFX 包含一个WebView能够显示网页 (HTML5、CSS 等)的组件。JavaFXWebView组件基于 WebKit-Chrome 和 Safari 中也使用的网页染引擎。
该WebView组件使得将桌面应用程序与 Web 应用程序混合成为可能。有时这很有用。例如,如果您已经有一个不错的 Web 应用程序,但需要一些只有桌面应用程序才能提供的功能一一例如磁盘访问、与 HTTP 以外的其他网络协议 (例如 UDP、IAP 等) 的通信。


三、JavaFx快速入门

JavaFx中文官方网站:https://openjfx.cn/

1、IDEA配置JavaFx环境

JDK8集成了JavaFx,而JDK11后就不自带JavaFx了,需要自己下载jar包进行导入。
如果使用的是Java8,请直接跳到下面第二步。

2、编码与运行


创建以下包目录结构、主入口Main类:

编写一个JavaFx基本结构代码:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

// 继承Application抽象类,重新start方法
public class Main extends Application {
    public static void main(String[] args) {
        // 入口函数里调用Application的静态方法launch,之后会自动调用start方法
        Application.launch(args);
    }

    /**
     * @param primaryStage 主窗口
     */
    @Override
    public void start(Stage primaryStage) throws Exception {
        // 设置一个场景,场景里添加一个树形组件图,先创建一个标签
        Label label = new Label("Hello JavaFx!");
        // 创建布局,将标签放入布局里,BorderPane布局把场景划分为上下左右中,默认加入的控件在中间位置
        BorderPane pane = new BorderPane(label);
        // 创建场景,将布局放入场景里,设置宽度和高度
        Scene scene = new Scene(pane, 300, 300);
        // 将场景设置到窗口里
        primaryStage.setScene(scene);
        // 设置标题
        primaryStage.setTitle("我是窗口");
        primaryStage.show();
    }
}

运行main方法,测试结果:


四、组件功能介绍

1、Application

  1. Application是JavaFx程序的入口,任何JavaFx应用程序程序都要继承该类并重写start()方法。
public class Main extends javafx.application.Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
    }
}
  1. 通过main()执行Application的launch(String… args)方法,当然该方法不传入任何值也是可以执行的。launch()方法会默认执行本类下的init()、start()、stop()方法。执行下面的main()方法后显示顺序为:
import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
        System.out.println("这是main方法");
    }

    @Override
    public void init() {
        System.out.println("这是初始化方法");
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.show();
        System.out.println("这是start()方法");
    }

    @Override
    public void stop() throws Exception {
        System.out.println("这个是stop()方法");
    }
}

可以看出只有当stop()方法被调用时(即Application应用程序被正常关闭,也可以是图形界面右上角的❌被点击时),launch()方法才执行完毕。(通过Platform.exit()退出界面也是属于正常退出)
通过IDE控制台终止应用程序属于非正常终止,它是通过关闭JVM从而关闭应用程序的。

  1. 也可以在其他类中的main()方法中调用launch()方法,但要传入相应的参数,第一个参数指定一个Application的子类,从而调用该类下的init()、start()、stop()方法,第二个参数制定一个String类型的值。

该方法launch(Class<? extends Application> appClass, String… args)也可以不指定String类型的值。

  1. Application类下的getHostServices()方法,该方法返回一个HostServices实例,该实例的showDocument()方法可以指定一个网站地址或者URI,从而以系统默认的浏览器打开该URI。getDocumentBase()返回一个String类型的当前文档所在的路径。
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.stage.Stage;

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        HostServices hostServices = getHostServices();
        hostServices.showDocument("www.baidu.com");
        System.out.println(hostServices.getDocumentBase());
    }
}

也可以写一个按钮,点击触发事件后弹出默认浏览器进入网站:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Button button = new Button("点击进入百度");
        BorderPane pane = new BorderPane(button);
        button.setOnAction(e -> {
            getHostServices().showDocument("www.baidu.com");
        });
        Scene scene = new Scene(pane, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.setTitle("JavaFx App");
        primaryStage.show();
    }
}

另外线程方面,执行main()方法时是主线程,当调用init()方法时是JavaFX-Launcher线程,当调用start()方法时是JavaFX Application Thread线程,当调用stop()方法时依然是JavaFX Application Thread线程。
因此,我们可以在init()方法中做一些连接数据库等初始化工作,可与start()方法的不同线程同步进行,提高运行效率。


2、Stage舞台(窗口)

JavaFx Stage:javafx.stage.Stage表示JavaFx桌面应用程序中的窗口。在JavaFx中,窗口Stage可以插入一个场景Scene,它表示在窗口内显示的内容。
当JavaFx应用程序启动时,它会创建一个根Stage对象,该对象将传递给 start(Stage primaryStage) JavaFx应用程序的根类的方法。此Stage对象代表JavaFx应用程序的主窗口。您可以在应用程序生命周期的后期创建新Stage对象,以防应用程序需要打开更多窗口。

(1)创建舞台

像创建任何其他Java对象一样创建Stage对象:

Stage stage = new Stage();

(2)展示舞台

简单地创建Stage对象不会显示它,为了使Stage可见,必须调用它的show()或showAndWait()方法:

Stage stage = new Stage();
stage.show();

(3)在舞台上设置场景

JavaFX在Stage中显示任何场景内容,必须在Stage显示前将Scene创建,并设置在Stage中:

VBox VBox = new VBox(new Label("A JavaFx Label"));
Scene scene = new Scene(vBox);

Stage stage = new Stage();
stage.setScene(scene);
// ...
// stage.show();

(4)舞台标题

通过setTitle()方法设置Stage标题,将显示在Stage窗口的标题栏中:

stage.setTitle("JavaFx stage window Title");

(5)舞台位置

通过其setX()和setY()方法设置窗口左上角的位置(X, Y),即启动后窗口在屏幕中的坐标:

stage.setX(100);
stage.setY(100);

注意,如果设置X和Y位置,可能还需要设置宽度和高度,否则舞台窗口可能会变得非常小。

(6)舞台宽度和高度

通过其setWidth()和setHeight()方法,设置JavaFx的宽度和高度:

stage.setWidth(600);
stage.setHeight(300);

一般我们不需要单独设置舞台宽高,只需通过场景的宽高设置来影响舞台的宽高。

(7)舞台风格

通过其initStyle()方法,参数为枚举类StageStyle设置JavaFx的样式,可以选择一组不同的样式:

  • DECORATED:默认装饰Stage是带有操作系统装饰(标题栏和最小化/最大化/关闭按钮)和白色背景的标准窗口。
  • UNDECORATED:未装饰Stage是没有操作系统装饰的标准窗口,但仍具有白色背景。
  • TRANSPARENT:透明Stage是具有透明背景的未装饰窗口。
  • UNIFIED:统一Stage就像一个装饰的舞台,只是装饰区域和主要内容区域之间没有边界。
  • UTILITY:实用程序Stage是装饰过的窗口,但装饰最少。
stage.initStyle(StageStyle.DECORATED);	// 默认窗口样式
//stage.initStyle(StageStyle.UNDECORATED);	// 无装饰窗口样式,自定义窗口时常用
//stage.initStyle(StageStyle.TRANSPARENT);	// 透明窗口样式,与无装饰类似
//stage.initStyle(StageStyle.UNIFIED);	// 和默认窗口效果差不多
//stage.initStyle(StageStyle.UTILITY);	// 简单窗口样式,只有Title和关闭

(8)舞台全屏模式

通过其setFullScreen()方法将窗口设置为全屏模式:

stage.setFullScreen(true);

(9)阶段生命周期事件

Stage可以发出一些可以监听的事件,这些舞台事件是:

Close Request
Hiding
Hidden
Showing
Shown

关闭阶段事件监听器
可以在舞台上侦听关闭事件,当用户单击舞台窗口右上角的关闭按钮时,触发回调事件。
如需要在主Stage窗口关闭后清理一些资源、停止一些线程等。监听Stage关闭事件会很有用:

stage.setOnCloseRequest(event -> System.out.println("closing stage"));
  • 小案例:设置关闭窗口监听事件,当用户单击舞台窗口右上角的关闭按钮时,触发回调事件弹出确认框,点击确认后关闭窗口,点击取消不做动作。
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.stage.Stage;

import java.util.Optional;

public class Main extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        // 设置关闭窗口的监听事件,首先操作系统默认的关闭事件取消掉
        Platform.setImplicitExit(false);    // 取消操作系统默认退出事件,点击关闭后系统不结束运行
        primaryStage.setOnCloseRequest(event -> {	// 点击右上角关闭触发事件
            event.consume();    // 取消关闭窗口动作,点击关闭后不会关闭窗口
            Alert alert = new Alert(Alert.AlertType.CONFIRMATION);  // 弹出确认框
            alert.setTitle("退出程序");
            alert.setHeaderText(null);
            alert.setContentText("确认是否要退出程序?");
            Optional<ButtonType> result = alert.showAndWait();
            if (result.get() == ButtonType.OK) {    // 点击确定按钮
                //Platform.exit();    // 退出程序
                primaryStage.close(); // 仅关闭窗口,不退出程序
            }
        });
        primaryStage.setTitle("设置关闭窗口的监听事件Demo");
        primaryStage.show();
    }
}

(10)键盘或鼠标事件

可以在Stage上设置侦听键盘事件,可以捕获在舞台具有焦点时发生的所有键盘事件。

stage.addEventHandler(KeyEvent.KEY_PRESSED, e -> System.out.println("键盘按下的键:" + e.getCode().getName()));
stage.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> System.out.println("鼠标按下的键:" + e.getButton()));
  • 小案例:WASD控制图片移动
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;

import java.io.FileInputStream;

public class Main extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setTitle("WASD控制图片移动");
        stage.getIcons().add(new Image("images/测试图片.png"));
        FileInputStream input = new FileInputStream("src/resources/images/测试图片.png");
        Image image = new Image(input);
        ImageView imageview = new ImageView(image);
        imageview.setFitWidth(100);
        imageview.setFitHeight(100);

        Group group = new Group(imageview);
        Scene scene = new Scene(group, 600, 600);
        stage.setScene(scene);

        stage.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
            if ("W".equals(event.getCode().getName())) {
                imageview.setLayoutY(imageview.getLayoutY() - 10);
            }
            if ("S".equals(event.getCode().getName())) {
                imageview.setLayoutY(imageview.getLayoutY() + 10);
            }
            if ("A".equals(event.getCode().getName())) {
                imageview.setLayoutX(imageview.getLayoutX() - 10);
            }
            if ("D".equals(event.getCode().getName())) {
                imageview.setLayoutX(imageview.getLayoutX() + 10);
            }
        });

        stage.show();
    }
}

效果图:


3、Scene场景

场景对象是场景图的根。换句话说,场景包含其中的所有可视的GUI组件。场景由类表示javafx.scene.Scene。必须在Stage上设置Scene对象才能可见。
场景图是一种树状数据结构,用于排列(和分组)图形对象,以便于逻辑表示。它还允许图形引擎通过完全或部分跳过最终图像中看不到的对象,以最有效的方式渲染对象。下图显示了JavaFX场景图体系结构的一个示例:

(1)创建场景

Scene您可以通过其构造函数创建对象。需要传递根组件作为参数,下面是创建Scene对象的示例:

VBox vBox = new VBox();
Scene scene = new Scene(vBox);

(2)在舞台上设置场景

为了使Scene可见,必须在Stage上设置它:

VBox vBox = new VBox(new Label("A JavaFx Label"));
Scene scene = new Scene(vBox);

Stage stage = new Stage();
stage.setScene(scene);

注意:Stage一次只能显示一个Scene。

(3)场景鼠标光标

可以在Scene上设置鼠标光标。鼠标光标是显示在鼠标光标(指针)位置的小图标。Scene您可以通过setCursor()方法设置鼠标的光标。

  • 小案例:设置两个场景,通过按钮绑定事件,实现场景和鼠标光标的互相转换:
public class Main extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Button button0 = new Button("点击切换场景");
        button0.setLayoutX(200);
        button0.setLayoutY(200);
        AnchorPane root = new AnchorPane();
        root.getChildren().add(button0);
        Scene scene = new Scene(root, 500, 500);
        scene.setCursor(new ImageCursor(new Image("images/测试图片1.png")));

        Label label = new Label("你好,JavaFx");
        label.setLayoutX(200);
        label.setLayoutY(200);
        Button button1 = new Button("返回原场景");
        button1.setLayoutX(200);
        button1.setLayoutY(250);
        AnchorPane root1 = new AnchorPane();
        root1.getChildren().addAll(label, button1);
        Scene scene1 = new Scene(root1, 500, 500);
        scene1.setCursor(new ImageCursor(new Image("images/测试图片2.png")));

        button0.setOnAction(event -> {
            primaryStage.setScene(scene1);
        });

        button1.setOnAction(event -> {
            primaryStage.setScene(scene);
        });

        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

效果图:鼠标光标图片也会切换


4、Node节点 UI控件的通用属性

Node类是所有控件的父类,下面通过其控件子类示例演示Node节点的UI控件的通用属性。

(1)控件节点坐标系

每个 JavaFX 节点都有自己的笛卡尔坐标系。与常规笛卡尔坐标系的唯一区别是Y轴是相反的。也就是说,坐标系的原点在坐标系的左上角。随着Y值的增加,该点从坐标系的顶部向下移动。Y轴的这种反转在2D图形坐标系中是正常的。

JavaFX 节点可能具有负X和Y坐标。
每个节点都有自己的坐标系。此坐标系用于在父节点内定位子节点实例,或者在 JavaFX 画布上绘图时。这意味着,作为另一个节点的子节点的节点都有自己的坐标系,以及在其父节点坐标系中的位置 (X,Y)。
以下是父节点坐标系的示例,其中子节点位于父节点坐标系中的(25,25)处。子节点也有它自己的坐标系,它有它的 (0,0),其中子节点位于父坐标系中-意思是在父节点坐标系中的(25,25)。

(2)设置控件坐标

Label label = new Label("你好,JavaFx");
label.setLayoutX(200);  // 设置控件X轴坐标
label.setLayoutY(200);  // 设置控件Y轴坐标

(3)设置控件风格

与css语法类似,只是前面加上-fx,设置红色背景,蓝色边框和边框宽度为3个像素:

label.setStyle("-fx-background-color: red; -fx-border-color: blue; -fx-border-width: 3px"); // 设置控件风格,与css语法类似,只是前面加上-fx

(4)设置控件首要背景宽度和高度

label.setPrefWidth(200);    // 设置控件背景宽度
label.setPrefHeight(50);    // 设置控件背景高度

(5)设置文本居中

label.setAlignment(Pos.CENTER); // 设置文本居中

(6)设置是否显示/隐藏该控件

true:默认显示控件,false:隐藏控件

label.setVisible(false);    // 是否显示该控件

(7)设置控件半透明值

label.setOpacity(0.5);  // 设置控件半透明值

(8)设置控件旋转角度

label.setRotate(70);    // 设置控件旋转角度

(9)设置控件X轴和Y轴平移量

label.setTranslateX(60);    // 设置X轴平移量
label.setTranslateY(100);   // 设置Y轴平移量
  • 案例演示:
public class Main extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Label label = new Label("你好,JavaFx");
        label.setLayoutX(200);  // 设置控件X轴坐标
        label.setLayoutY(200);  // 设置控件Y轴坐标
        label.setStyle("-fx-background-color: red; -fx-border-color: blue; -fx-border-width: 3px"); // 设置控件风格,与css语法类似,只是前面加上-fx
        label.setPrefWidth(200);    // 设置控件背景宽度
        label.setPrefHeight(50);    // 设置控件背景高度
        label.setAlignment(Pos.CENTER); // 设置文本居中
        //label.setVisible(false);    // 是否显示该控件
        label.setOpacity(0.5);  // 设置控件半透明值
        label.setRotate(70);    // 设置控件旋转角度
        label.setTranslateX(60);    // 设置X轴平移量
        label.setTranslateY(100);   // 设置Y轴平移量
        AnchorPane root = new AnchorPane();
        root.getChildren().add(label);
        Scene scene = new Scene(root, 500, 500);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}


5、UI控件的属性绑定和属性监听

  • 小案例:在场景中画一个圆,利用属性单向绑定,使拖动边框大小时,圆始终保持在中心位置。并且设置X轴属性监听器,使得横向拖动边框时,控制台打印移动数据值,而Y轴不设置属性监听,纵向拖动时不打印数据值。
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class Main extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        AnchorPane root = new AnchorPane();
        Scene scene = new Scene(root, 500, 500);

        Circle circle = new Circle();
        circle.setCenterX(250);
        circle.setCenterY(250);
        circle.setRadius(100);
        circle.setStroke(Color.BLACK);
        circle.setFill(Color.WHITE);
        // 设置属性单向绑定,使圆始终保持在中心位置
        circle.centerXProperty().bind(scene.widthProperty().divide(2));
        circle.centerYProperty().bind(scene.heightProperty().divide(2));

        // 设置X轴属性监听器
        circle.centerXProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
                System.out.println("x轴中心点改变了,原来是:" + oldValue + ",现在是:" + newValue);
            }
        });

        root.getChildren().add(circle);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}


6、JavaFx应用里的事件驱动编程

  • 小案例:利用事件驱动,实现将一个任意文件拖拽到文本框中后,拖拽时显示移动,拖拽松开后文本框显示文件的绝对路径。
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class Main extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        AnchorPane root = new AnchorPane();
        Scene scene = new Scene(root, 500, 500);
        TextField textField = new TextField();  // 文本框
        textField.setLayoutX(150);
        textField.setLayoutY(200);

        textField.setOnDragOver(event -> {
            event.acceptTransferModes(TransferMode.ANY);
        });

        textField.setOnDragDropped(event -> {
            Dragboard dragboard = event.getDragboard();
            if (dragboard.hasFiles()) {
                String path = dragboard.getFiles().get(0).getAbsolutePath();
                textField.setText(path);
            }
        });
        root.getChildren().add(textField);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

效果图:


7、Color、Font、Image

本介绍直接采用案例演示法,具体参数方法请参阅JavaFx API文档。

(1)Color颜色

  • 案例演示:利用Color类设置圆的边框颜色和内部填充颜色
public class Main extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        AnchorPane root = new AnchorPane();
        Scene scene = new Scene(root, 500, 500);

        Circle circle = new Circle();
        circle.setCenterX(250); // 设置圆心X轴位置
        circle.setCenterY(250); // 设置圆心Y轴位置
        circle.setRadius(100);  // 设置半径
        //circle.setFill(Color.rgb(100, 50, 100));    // 设置内部填充颜色
        circle.setFill(Color.web("#f66a08"));   // 设置内部填充颜色,与CSS颜色语法一样
        circle.setStroke(Color.BLUE);  // 设置描边颜色
        circle.setStrokeWidth(10);    // 设置圆边框宽度

        root.getChildren().add(circle);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

效果图:

(2)Font字体

在C:\Windows\Fonts下可查阅系统字体。

  • 案例演示:为Label标签设置Font字体风格
public class Main12 extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        AnchorPane root = new AnchorPane();
        Scene scene = new Scene(root, 500, 500);

        Label label = new Label("文本Font演示");
        label.setLayoutX(150);
        label.setLayoutY(150);
        //label.setFont(new Font(30));  // 设置字体
        label.setFont(Font.font("华文行楷", FontWeight.BOLD, 30));   // 参数:字体名称,字重(斜体,黑体,粗体等),字体大小


        root.getChildren().add(label);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

效果图:

(3)Image图片

Image图片支持BMP/GIF/JPEG/PNG

  • 案例演示:通过Image和ImageView类显示图片
public class Main13 extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        AnchorPane root = new AnchorPane();
        Scene scene = new Scene(root, 500, 500);

        ImageView imageView = new ImageView();
        Image image = new Image("images/背景.png");
        imageView.setImage(image);

        root.getChildren().add(imageView);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

效果图:


五、FXML

FXML是一种可编写的、基于XML的用于构造JavaFX场景图的标记语言。在FXML中,一个FXML标签代表以下类型之一:

  • 某个类的实例
  • 某个类实例的属性
  • 某个静态属性
  • 一个定义代码块
  • 一个脚本代码块

一个FXML属性表示以下类型之一:

  • 某个类实例的属性
  • 某个静态属性
  • 事件处理程序


Introduction to FXML:https://openjfx.cn/javadoc/18/javafx.fxml/javafx/fxml/doc-files/introduction_to_fxml.html


1、FXML布局文件的使用

  • 案例演示:将下面的JavaFx文件代码中的容器、组件和监听事件,改写为FXML文件的格式,简化和方便管理JavaFx类的编写。
public class Main extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        AnchorPane root = new AnchorPane();
        Scene scene = new Scene(root, 500, 500);

        Label label = new Label("按键盘↓向下移动");
        label.setLayoutX(100);
        label.setLayoutY(150);
        label.setFont(new Font(30));
        Button button = new Button("点击按钮向上移动");
        button.setLayoutX(350);
        button.setLayoutY(200);

        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                label.setLayoutY(label.getLayoutY() - 5);
            }
        });

        scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {
                KeyCode keyCode = event.getCode();
                if (keyCode.equals(KeyCode.DOWN)) {
                    label.setLayoutY(label.getLayoutY() + 5);
                }
            }
        });

        root.getChildren().addAll(label, button);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

改写为FXML:

public class Demo extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        // 使用FXMLLoader类的load方法来加载FXML文件,并将其与Controller类进行关联。
        //Pane root = FXMLLoader.load(getClass().getClassLoader().getResource("com/aizen/javafx/fxml/demo.fxml"));
        Pane root = FXMLLoader.load(getClass().getResource("demo.fxml"));
        Scene scene = new Scene(root, 500, 500);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

public class DemoController {
    @FXML
    Label la;

    @FXML
    Button bu;


    public void handleButtonAction() {
        la.setLayoutY(la.getLayoutY() - 5);
    }

    public void handleKeyReleased(KeyEvent event) {
        KeyCode keyCode = event.getCode();
        if (keyCode.equals(KeyCode.DOWN)) {
            la.setLayoutY(la.getLayoutY() + 5);
        }
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<?import javafx.scene.text.Font?>
<AnchorPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="com.aizen.javafx.fxml.DemoController"
            onKeyReleased="#handleKeyReleased"
            prefHeight="400.0" prefWidth="600.0">
    <children>
        <Label fx:id="la" text="按键盘↓向下移动" layoutX="100" layoutY="150">
            <font>
                <Font size="30"/>
            </font>
        </Label>

        <Button fx:id="bu" text="点击按钮向上移动" layoutX="350" layoutY="200" onAction="#handleButtonAction"/>
    </children>
</AnchorPane>

2、Scene Builder里构建fxml布局文件

官方下载链接:https://openjfx.cn/scene-builder/

点击下一步,完成安装

选择空项目

布局和组件直接拖拽进场景即可,保存后可生成对应的fxml文件。


3、Controller里的initialize方法

有时我们是无法在fxml文件里填充数据的,并且有些内容需要初始化时就填充(如表格),而不是触发事件后填充,此时就可以使用initialize方法,做一些初始化的工作。

initialize()方法需要自定义,定义完之后会自动调用,该方法调用的时机是加载好fxml文件,并绑定好控件id之后,才会自动调用一次。

需要注意的是在initialize()方法中是无法访问Scene场景的。

  • 演示案例:使用initialize()方法初始化时填充完TableView的数据。
public class Main extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Pane root = FXMLLoader.load(getClass().getResource("hello.fxml"));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
}

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


public class Controller {

    @FXML
    private TableView<Person> tableView;

    @FXML
    private TableColumn<Person, String> name;

    @FXML
    private TableColumn<Person, Integer> age;
    
    public void initialize() {
        ObservableList<Person> cellDate = FXCollections.observableArrayList();
        name.setCellValueFactory(new PropertyValueFactory<Person, String>("name"));
        age.setCellValueFactory(new PropertyValueFactory<Person, Integer>("age"));
        cellDate.add(new Person("张三", 18));
        cellDate.add(new Person("李四", 19));
        cellDate.add(new Person("王五", 23));
        cellDate.add(new Person("赵六", 15));
        tableView.setItems(cellDate);
    }
}

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane fx:controller="com.aizen.javafx.fxml_02.Controller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <TableView fx:id="tableView" prefHeight="400.0" prefWidth="600.0">
        <columns>
          <TableColumn fx:id="name" prefWidth="75.0" text="name" />
          <TableColumn fx:id="age" prefWidth="75.0" text="age" />
        </columns>
      </TableView>
   </children>
</AnchorPane>

效果图:


4、在Application里操作Controller

  • 案例演示:和之前圆的案例一样,要求圆的中心点自适应边框大小,使用fxml实现。
public class JavaFxApplication extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader();   // 使用FXMLLoader获取布局里面的Controller的引用
        fxmlLoader.setLocation(getClass().getResource("hello.fxml"));
        Parent root = fxmlLoader.load();
        Scene scene = new Scene(root);
        // 在Application中操作Controller进行属性绑定
        Controller controller = fxmlLoader.getController();
        controller.circleLocationBind(scene);

        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

public class Controller {
    @FXML
    private Circle ci;

    public void circleLocationBind(Scene scene) {
        // 获得X和Y中心点的可绑定对象,设置中心点自适应边框大小
        ci.centerXProperty().bind(scene.widthProperty().divide(2));
        ci.centerYProperty().bind(scene.heightProperty().divide(2));
    }
}

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.shape.Circle?>


<AnchorPane fx:controller="com.aizen.javafx.fxml_03.Controller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="488.0" prefWidth="496.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Circle fx:id="ci" centerX="250.0" centerY="250.0" fill="DODGERBLUE" radius="100.0" stroke="BLACK" strokeType="INSIDE" />
   </children>
</AnchorPane>

效果图:


六、JavaFx多线程操作Platform.runLater

当我们执行一些耗时操作时,如加载资源。我们为了防止这些耗时操作占用JavaFx的主线程资源,下面的代码无法执行造成软件界面加载卡顿,我们通常使用JavaFx支持的多线程操作来解决这个问题。

  • 案例演示1:使用多线程技术实现点击Button按钮获取姓名数据(模拟数据库获取数据)显示在Label文字布局上。
public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Label label = new Label("姓名是?");
        label.setLayoutX(200);
        label.setLayoutY(350);
        Button button = new Button("点击获取姓名");
        button.setLayoutX(200);
        button.setLayoutY(400);
        button.setOnAction(event -> {
            new Thread(() -> {
                String name = "Aizen";	// 模拟数据库获取值的操作,这里直接定义
                label.setText(name);	// 更新UI控件的操作
            }).start();
        });
        AnchorPane pane = new AnchorPane();
        pane.getChildren().addAll(label, button);
        Scene scene = new Scene(pane, 500, 500);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

如果直接这样运行,点击后会报出非法状态异常,不在JavaFx的Application线程中,更新UI控件必须在Fx Application主线程中。

想要避免这个问题,需要使用到JavaFx提供的静态方法Platform.runLater(Runnable runnable),其中参数需要一个Runnber对象,runLater方法将Runnable放在任务队列里面,在Application线程空闲的时候,会执行队列里的任务。

public class Main0 extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Label label = new Label("姓名是?");
        label.setLayoutX(200);
        label.setLayoutY(350);
        Button button = new Button("点击获取姓名");
        button.setLayoutX(200);
        button.setLayoutY(400);
        button.setOnAction(event -> {
            // 报错案例:
            /*new Thread(() -> {
                String name = "Aizen";  // 模拟数据库获取值的操作,这里直接定义
                label.setText(name);    // 更新UI控件的操作
            }).start();*/
            
            // 正确案例:
            // runLater方法将Runnable放在任务队列里面,在Application线程空闲的时候,会执行队列里的任务
            new Thread(() -> {
                String name = "Aizen";  // 模拟数据库获取值的操作,这里直接定义
                Platform.runLater(() -> {
                    label.setText(name);    // 更新UI控件的操作
                });
            }).start();
        });
        AnchorPane pane = new AnchorPane();
        pane.getChildren().addAll(label, button);
        Scene scene = new Scene(pane, 500, 500);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

效果图:

其实在JavaFx很多控件的事件里面都内部调用了runLater()方法,比如下面这个案例:

  • 案例演示2:使用多线程技术实现点击Button按钮通过url加载图片,并显示图片和加载进度(模拟耗时操作)。
public class Main1 extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        ImageView imageView = new ImageView();
        imageView.setLayoutX(100);
        imageView.setLayoutY(20);

        Label label = new Label();
        label.setLayoutX(400);
        label.setLayoutY(350);

        Button button = new Button("点击获取图片");
        button.setLayoutX(400);
        button.setLayoutY(410);

        button.setOnAction(event -> {
            // 通过url加载图片,这种操作可能会比较耗时通常会另开一个线程去执行,而不占用主线程
            Thread thread = new Thread(() -> {
                Image image = new Image("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F0a0caee4-4afd-4fbf-bcf8-8269315c7915%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1715148006&t=f254957770daea43b9b1f4cdb6c2c987", true);
                // 该监听器的changed方法在底层已经封装了Platform.runLater()方法
                image.progressProperty().addListener(new ChangeListener<Number>() {
                    @Override
                    public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
                        int process = (int) (newValue.doubleValue() * 100);
                        // 显示图片联网加载进度
                        label.setText("图片加载了:" + process + "%");
                    }
                });
                imageView.setImage(image);
            });

            thread.setName("线程1");
            thread.start();
        });
        AnchorPane pane = new AnchorPane();
        pane.getChildren().addAll(imageView, label, button);
        Scene scene = new Scene(pane, 950, 500);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

效果图:



文章来源:https://blog.csdn.net/weixin_52152676/article/details/138005850
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云