ZetCode

JavaFX 基本控件

最后修改于 2023 年 10 月 18 日

控件是应用程序的基本构建块。Control 是场景图中可以由用户操作的节点。它支持常见的用户交互,其方式对用户来说一致且可预测。JavaFX 拥有广泛的内置控件。在本章中,我们将介绍五个控件:LabelCheckBoxChoiceBoxSliderProgressBarImageViewTextField 控件也会简要提及。

JavaFX 标签

Label 是一个不可编辑的文本控件。标签可以使用省略号或截断来调整字符串的大小以适应。

com/zetcode/LabelEx.java
package com.zetcode;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class LabelEx extends Application {

    String lyrics = "It's way too late to think of\n"
            + "Someone I would call now\n"
            + "And neon signs got tired\n"
            + "Red eye flights help the stars out\n"
            + "I'm safe in a corner\n"
            + "Just hours before me\n"
            + "\n"
            + "I'm waking with the roaches\n"
            + "The world has surrendered\n"
            + "I'm dating ancient ghosts\n"
            + "The ones I made friends with\n"
            + "The comfort of fireflies\n"
            + "Long gone before daylight\n"
            + "\n"
            + "And if I had one wishful field tonight\n"
            + "I'd ask for the sun to never rise\n"
            + "If God leant his voice for me to speak\n"
            + "I'd say go to bed, world\n"
            + "\n"
            + "I've always been too late\n"
            + "To see what's before me\n"
            + "And I know nothing sweeter than\n"
            + "Champaign from last New Years\n"
            + "Sweet music in my ears\n"
            + "And a night full of no fears\n"
            + "\n"
            + "But if I had one wishful field tonight\n"
            + "I'd ask for the sun to never rise\n"
            + "If God passed a mic to me to speak\n"
            + "I'd say stay in bed, world\n"
            + "Sleep in peace";

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        var root = new HBox();
        root.setPadding(new Insets(10));

        var lbl = new Label(lyrics);
        root.getChildren().add(lbl);

        var scene = new Scene(root);

        stage.setTitle("No sleep");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例显示了 Cardigans 乐队的一首歌曲的歌词。

String lyrics = "It's way too late to think of\n"
        + "Someone I would call now\n"
        + "And neon signs got tired\n"
        + "Red eye flights help the stars out\n"
...        

该字符串由多行文本组成。

var root = new HBox();
root.setPadding(new Insets(10));

标签控件放置在一个 HBox 中。我们在框周围添加了一些填充。

var lbl = new Label(lyrics);

创建了一个 Label 控件。它将字符串作为其唯一参数。

root.getChildren().add(lbl);

标签已添加到容器中。

labelFor 属性

labelFor 属性指定一个节点,如果按下助记符,焦点将发送到该节点。

com/zetcode/LabelForEx.java
package com.zetcode;

import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

public class LabelForEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        var root = new GridPane();
        root.setVgap(10);
        root.setHgap(5);
        root.setPadding(new Insets(10));

        var lbl1 = new Label("_Name:");
        var lbl2 = new Label("_Address:");
        var lbl3 = new Label("_Occupation:");

        var field1 = new TextField();
        var field2 = new TextField();
        var field3 = new TextField();

        lbl1.setLabelFor(field1);
        lbl1.setMnemonicParsing(true);
        lbl2.setLabelFor(field2);
        lbl2.setMnemonicParsing(true);
        lbl3.setLabelFor(field3);
        lbl3.setMnemonicParsing(true);

        root.add(lbl1, 0, 0);
        root.add(field1, 2, 0);
        root.add(lbl2, 0, 1);
        root.add(field2, 2, 1);
        root.add(lbl3, 0, 2);
        root.add(field3, 2, 2);

        GridPane.setHalignment(lbl1, HPos.RIGHT);
        GridPane.setHalignment(lbl2, HPos.RIGHT);
        GridPane.setHalignment(lbl3, HPos.RIGHT);

        var scene = new Scene(root);

        stage.setTitle("TextField");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {

        launch(args);
    }
}

该示例使用 labelFor 属性和助记符将焦点转移到指定的文本字段。

var root = new GridPane();
root.setVgap(10);
root.setHgap(5);
root.setPadding(new Insets(10));

我们的应用程序是一个典型的基于表单的程序。 GridPane 非常适合此。我们在控件周围和控件之间设置了一些空间。

var lbl1 = new Label("_Name:");
var lbl2 = new Label("_Address:");
var lbl3 = new Label("_Occupation:");

创建了三个 Labels。下划线字符位于助记键之前。

var field1 = new TextField();
var field2 = new TextField();
var field3 = new TextField();

TextField 是一个用于编辑单行未格式化文本的控件。每个文本字段都放置在一个标签控件旁边。

lbl1.setLabelFor(field1);

setLabelFor 设置一个目标节点,当按下助记符时,焦点将转移到该节点。

lbl1.setMnemonicParsing(true);

默认情况下,标签不设置助记符。我们必须使用 setMnemonicParsing 方法启用它们。

The labelFor property
图:labelFor 属性

在某些平台上,有必要按 mouseless 修饰符(通常是 Alt)才能显示下划线。在图中,焦点通过按 Alt+A 转移到中间文本字段。

JavaFX 复选框

CheckBox 是一个三态选择控件框,在选中时显示复选标记或勾号。该控件默认具有两种状态:已选中和未选中。setAllowIndeterminate 启用第三种状态:不确定。

com/zetcode/CheckBoxEx.java
package com.zetcode;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class CheckBoxEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        var root = new HBox();
        root.setPadding(new Insets(10, 0, 0, 10));

        var cbox = new CheckBox("Show title");
        cbox.setSelected(true);

        cbox.setOnAction((ActionEvent event) -> {
            if (cbox.isSelected()) {
                stage.setTitle("CheckBox");
            } else {
                stage.setTitle("");
            }
        });

        root.getChildren().add(cbox);

        var scene = new Scene(root, 300, 200);

        stage.setTitle("CheckBox");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例根据是否选中复选框显示或隐藏窗口标题。

var cbox = new CheckBox("Show title");

创建了一个 CheckBox 控件。指定的文本是它的标签。

cbox.setSelected(true);

由于窗口的标题默认可见,我们使用 setSelected 方法选中该控件。

cbox.setOnAction((ActionEvent event) -> {
    if (cbox.isSelected()) {
        stage.setTitle("CheckBox");
    } else {
        stage.setTitle("");
    }
});

使用 setOnAction 方法,我们设置复选框的动作,当复选框被触发时会调用该动作。我们使用 isSelected 方法确定其状态。根据当前状态,我们使用 setTitle 方法显示或隐藏窗口标题。

CheckBox
图:复选框

注意复选框文本周围的蓝色矩形。它表示此控件具有键盘焦点。可以使用 Space 键选择和取消选择复选框。

JavaFX 滑块

Slider 是一个控件,它允许用户通过在有界间隔内滑动旋钮来以图形方式选择一个值。滑块可以选择性地显示刻度线和标签,指示不同的滑块位置值。

com/zetcode/SliderEx.java
package com.zetcode;

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class SliderEx extends Application {

    private ImageView iview;
    private Image muteImg;
    private Image minImg;
    private Image maxImg;
    private Image medImg;

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        var root = new HBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(15));

        loadImages();

        iview = new ImageView(muteImg);

        var slider = new Slider(0, 100, 0);
        slider.valueProperty().addListener(new MyChangeListener());

        var scene = new Scene(root);

        root.getChildren().addAll(slider, iview);

        stage.setTitle("Slider");
        stage.setScene(scene);
        stage.show();
    }

    private void loadImages() {

        muteImg = new Image("file:mute.png");
        minImg = new Image("file:min.png");
        maxImg = new Image("file:max.png");
        medImg = new Image("file:med.png");
    }

    private class MyChangeListener implements ChangeListener<Number> {

        @Override
        public void changed(ObservableValue<? extends Number> observable,
                Number oldValue, Number newValue) {

            double value = newValue.doubleValue();

            if (value == 0) {
                iview.setImage(muteImg);
            } else if (value > 0 && value <= 30) {
                iview.setImage(minImg);
            } else if (value > 30 && value < 80) {
                iview.setImage(medImg);
            } else {
                iview.setImage(maxImg);
            }
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在代码示例中,我们显示了一个 Slider 和一个 ImageView 控件。通过拖动滑块的旋钮,我们更改标签控件上的图像。

root.setAlignment(Pos.CENTER);

滑块和图像视图在行中居中。

iview = new ImageView(muteImg);

ImageView 显示使用 Image 类加载的图像。

var slider = new Slider(0, 100, 0);

使用指定的最大值、最小值和当前值创建一个 Slider 控件。

slider.valueProperty().addListener(new MyChangeListener());

将侦听器添加到滑块的值更改中。

double value = newValue.doubleValue();

if (value == 0) {
    iview.setImage(muteImg);
} else if (value > 0 && value <= 30) {
    iview.setImage(minImg);
} else if (value > 30 && value < 80) {
    iview.setImage(medImg);
} else {
    iview.setImage(maxImg);
}

基于滑块的当前值,我们将适当的图像设置为图像视图。

private void loadImages() {

    muteImg = new Image("file:mute.png");
    minImg = new Image("file:min.png");
    maxImg = new Image("file:max.png");
    medImg = new Image("file:med.png");
}

loadImages 方法从磁盘加载图像。

Slider
图:滑块

JavaFX ChoiceBox

ChoiceBox 用于向用户呈现一小组预定义的选项。当用户单击该框时,将显示一个选项列表。一次只能选择一个选项。当此列表未显示时,将显示当前选定的选项。ChoiceBox 项目选择由 SelectionModel 处理。

com/zetcode/ChoiceBoxEx.java
package com.zetcode;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ChoiceBoxEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        var root = new VBox(35);
        root.setPadding(new Insets(10));

        var lbl = new Label();

        var chbox = new ChoiceBox<>(FXCollections.observableArrayList(
                "Ubuntu", "Redhat", "Arch", "Debian", "Mint"));

        SingleSelectionModel<String> model = chbox.getSelectionModel();
        model.selectedItemProperty().addListener((observableValue, s, t1) -> lbl.setText(t1));

        root.getChildren().addAll(chbox, lbl);

        var scene = new Scene(root, 300, 250);

        stage.setTitle("ChoiceBox");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在我们的示例中,我们有一个选择框和一个标签。选择框包含一个字符串列表,表示 Linux 发行版的名称。从选择框中选择的项显示在标签中。

var lbl = new Label();

Label 显示当前从选择框中选择的项。

var chbox = new ChoiceBox<>(FXCollections.observableArrayList(
        "Ubuntu", "Redhat", "Arch", "Debian", "Mint"));

创建了一个 ChoiceBox。它将一个可观察的数组列表作为参数。

SingleSelectionModel<String> model = chbox.getSelectionModel();
model.selectedItemProperty().addListener((observableValue, s, t1) -> lbl.setText(t1));

要实现一个侦听器,我们需要使用 getSelectionModel 方法获取选择模型。该模型包含可观察的 selectedItem 属性。在处理程序方法中,我们获取选定的值并将其设置为标签。

ChoiceBox
图:ChoiceBox

JavaFX 进度条

ProgressBar 是一个控件,它通过完成条指示特定任务的处理。

com/zetcode/ProgressBarEx.java
package com.zetcode;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class ProgressBarEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        var root = new HBox(15);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        var pbar = new ProgressBar(0);
        pbar.setPrefWidth(150);

        var frame1 = new KeyFrame(Duration.ZERO,
                new KeyValue(pbar.progressProperty(), 0));

        var frame2 = new KeyFrame(Duration.seconds(3),
                new KeyValue(pbar.progressProperty(), 1));

        var task = new Timeline(frame1, frame2);

        var btn = new Button("Start");
        btn.setOnAction((ActionEvent actionEvent) -> task.playFromStart());

        root.getChildren().addAll(pbar, btn);

        var scene = new Scene(root);

        stage.setTitle("ProgressBar");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例由一个进度条和一个按钮组成。该按钮启动进度条,该进度条动画几秒钟。

var pbar = new ProgressBar(0);

构造函数使用给定的进度值创建一个新的 ProgressBar

var frame1 = new KeyFrame(Duration.ZERO, 
        new KeyValue(pbar.progressProperty(), 0));

var frame2 = new KeyFrame(Duration.seconds(3), 
        new KeyValue(pbar.progressProperty(), 1));        

var task = new Timeline(frame1, frame2);

此代码创建一个简单的动画任务。动画由两个帧组成。动画属性定义为 KeyValues

var btn = new Button("Start");
btn.setOnAction((ActionEvent actionEvent) -> task.playFromStart());

当触发时,按钮调用 playFromStart 方法,该方法从初始位置以正向播放动画。

ProgressBar
图:ProgressBar

在 JavaFX 教程的这一部分中,我们介绍了基本的 JavaFX 控件。