一 安装JavaJX
-
中文文档: https://www.yiibai.com/javafx
-
JavaFX 官方文档的位置: https://docs.oracle.com/javase/8/javase-clienttechnologies.htm
-
自己的开发环境: mac, jdk1.8
-
自己本来是用的 2020-06版本,但是失败.后续改成 2020-09版本 才可以.
- Help -> Install New Software…
- Add…
- Name: e(fx)clipse Location: http://download.eclipse.org/efxclipse/updates-released/2.3.0/site/
- 选中出现的2行,然后下一步到最后即可. 然后重启.
二 新建第一个javaJX程序
- File -> New -> Project -> JavaFX / JavaFX Project
package application;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
- 出现上述代码, 表示成功.
三 JavaFX 项目结构
默认生成一下文件:
- Main.java 主界面
- application.css 样式
- build.fxbuild 用于程序的打包发布
1 Main.java里的结构
- Application: 应用程序
- Stage: 舞台(窗口)
- Scene: 场景
- Node: 节点
Stage -> Scene -> Pane(Node Tree)
四 常见操作
1 添加标签 Label
Label 标签 用于显示文本
- 演示: 添加一个 Label控件, 显示一段文本
// 添加文本
Label label = new Label("hello world!");
root.setCenter(label);
完整代码如下
package application;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application
{
@Override
public void start(Stage primaryStage)
{
try
{
BorderPane root = new BorderPane();
// 添加文本
Label label = new Label("hello world!");
root.setCenter(label);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
对象的层级结构
2 添加按钮
- 添加一个按钮
Button btn = new Button("点我");
root.setCenter(btn);
- 添加事件处理器类 MyEventHandler (内部类)
private class MyEventHandler implements EventHandler<ActionEvent>
{
@Override
public void handle(ActionEvent e)
{
System.out.println("哎哟!");
}
}
注意:
- EventHandler 是一个接口(泛型接口, 带类型参数)
- 导包的时候要 import javafx.* 开头的包
- 设置处理器 将按钮和事件绑定
MyEventHandler handler = new MyEventHandler();
btn.setOnAction(handler);
或者直接写成一行
btn.setOnAction(new MyEventHandler());
- 上述是 先写自定义类实现接口,然后再绑定. 当我们熟练之后,可以使用 匿名类.
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent arg0)
{
System.out.println("哎哟!");
}
});
- 不建议使用 lambda 表达式
- lambda表达式降低了可读性!新人请勿使用 lambda表达式!
- 重点:掌握内部类的写法! 当熟练之后再写成匿名类!
完整代码
package application;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application
{
@Override
public void start(Stage primaryStage)
{
try
{
BorderPane root = new BorderPane();
// 添加一个按钮
Button btn = new Button("hello world");
root.setCenter(btn);
// 事件绑定处理 方法一
MyEventhandler handler = new MyEventhandler();
// 给按钮绑定事件
btn.setOnAction(handler);
/*
* 事件绑定处理 方法二
* 上述可以简写为 btn.setOnAction(new MyEventhandler());
*
*/
/**
* 事件绑定处理 方法三
* 匿名类
btn.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
System.out.println("触发点击事件!");
}
});
*/
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
private class MyEventhandler implements EventHandler<ActionEvent>
{
@Override
public void handle(ActionEvent event)
{
System.out.println("触发点击事件!");
}
}
public static void main(String[] args)
{
launch(args);
}
}
3 添加图片
- Image: 用于加载图片文件
- ImageView: 用于显示图片
- 位置: javafx.scene.image.*
图片的来源
Image 可以用来加载多重渠道的图片,支持bmp, jpg, jpeg, gif, png图片格式.
Image image = new Image(URL)
其中, URL: Uniform Resource Locator , URL可以指向网络图片,资源图片或本地图片.
- 显示网络图片
Image image = new Image(“https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png”);
完整代码
package application;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application
{
@Override
public void start(Stage primaryStage)
{
try
{
BorderPane root = new BorderPane();
// 添加网络图片
Image image = new Image("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");
ImageView imageView = new ImageView();
imageView.setImage(image);
// 图片加入到窗口中
root.setCenter(imageView);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
2 显示本地文件
Image image = new Image( “file:/c:/examples/1.jpg” )
可以这么写:
File f = new File("c:/examples/1.jpg");
String url = f.toURI().toString();
Image image= new Image(url);
简写为:
File f = new File("c:/examples/1.jpg");
Image image= new Image(f.toURI().toString());
- 注: 和网络图片比较: 一个是 http: 开头的, 一个是 file: 开头
完整代码
package application;
import java.io.File;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application
{
@Override
public void start(Stage primaryStage)
{
try
{
BorderPane root = new BorderPane();
/*
* 另外一种写法
* File f = new File("1.jpg");
* String url = f.toURI().toString();
* Image image = new Image(url);
*
*/
Image image = new Image("file:1.jpg");
ImageView imageView = new ImageView();
imageView.setImage(image);
root.setCenter(imageView);
// 根据图片的宽度高度来设置窗口的宽度高度
double width = image.getWidth();
double height = image.getHeight();
Scene scene = new Scene(root, width, height);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
3 显示资源文件
放在 CLASSPATH 下的文件称为资源文件(与 class 文件一起), 资源文件用包路径定位, 例如
Image image = new Image(“application/2.jpg”);
其中, application 是 package 路径.
注: Eclipse 会自动把 src 下的文件复制到 bin 目录, 程序运行时访问的是 bin 下的文件. (bin在CLASSPATH里)
完整代码
package application;
import java.io.File;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application
{
@Override
public void start(Stage primaryStage)
{
try
{
BorderPane root = new BorderPane();
// 资源写法
Image image = new Image("application/2.jpg");
ImageView imageView = new ImageView();
imageView.setImage(image);
root.setCenter(imageView);
// 根据图片的宽度高度来设置窗口的宽度高度
double width = image.getWidth();
double height = image.getHeight();
Scene scene = new Scene(root, width, height);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
4 相册案例
- 布局
上面显示按钮 Button,中间显示图片 ImageView
root.setTop(btnOpen); // 上面显示按钮
root.setCenter(imageView); // 中间显示图片
- 图片数组
// 资源图片
String[] imageUrls = {
"application/res/hangmu.jpg",
"application/res/tiangong.jpg",
"application/res/kongjing500.jpg",
};
Image[] images = new Image[3];
ImageView imageView = new ImageView();
方法中,加载图片
for(int i=0; i<images.length; i++)
{
images[i] = new Image(imageUrls[i]);
}
- 响应按钮点击
// 按钮点击事件的处理
btnOpen.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event)
{
showNext();
}
});
- 循环显示图片
// 显示下个图片
public void showNext()
{
currentIndex ++;
if(currentIndex >=3) currentIndex = 0;
imageView.setImage( images[ currentIndex ]);
}
完整代码
package application;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application
{
// 资源图片
String[] imageUrls =
{ "application/res/1.jpg", "application/res/2.jpg", "application/res/3.jpg" };
Image[] images = new Image[3];
ImageView imageView = new ImageView();
int currentIndex = 0; // 当前显示的是哪个图片
public void showNext()
{
currentIndex++;
if (currentIndex >= 3)
currentIndex = 0;
imageView.setImage(images[currentIndex]);
}
@Override
public void start(Stage primaryStage)
{
try
{
BorderPane root = new BorderPane();
Button btnOpen = new Button("下一张");
// 按钮点击事件的处理
btnOpen.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
showNext();
}
});
// 加载图片
for (int i = 0; i < images.length; i++)
{
images[i] = new Image(imageUrls[i]);
}
// 保持比例,适应窗口显示
imageView.setPreserveRatio(true);
imageView.setFitWidth(400);
imageView.setFitHeight(400);
root.setTop(btnOpen); // 上面显示按钮
root.setCenter(imageView); // 中间显示图片
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
showNext();
} catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
五 布局
1 边缘布局 BorderPane
语法分析
无论是容器还是控件, 都是继承于 Node.
JavaFX 里有几种 Node:
- 容器, 如 BorderPane, HBox
- 控件, 如 Button, Label
- 形状, 如 Circle, Rectangle ( Text 也是形状 )
- 图表, 如 PieChart, XYChart
- 其他, 如 Canvas, ImageView, MediaView, WebView
示例
package application;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application
{
@Override
public void start(Stage primaryStage)
{
try
{
BorderPane root = new BorderPane();
//顶部
Label label = new Label("大桥");
root.setTop(label);
//中央
Image image = new Image("application/bridge.jpg");
ImageView imageView = new ImageView();
imageView.setImage(image);
imageView.setPreserveRatio(true);
imageView.setFitWidth(400);
imageView.setFitHeight(400);
root.setCenter(imageView);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
2 水平布局 HBox
语法分析
hbox.getChildren().addAll(…)
不定长度的参数: 可以任意个数的参数
hbox.getChildren().addAll(node1, node2, node3)
相当于
hbox.getChildren().add( node1 );
hbox.getChildren().add( node2 );
hbox.getChildren().add( node3 );
案例的效果和实现代码如下
package application;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class Main extends Application
{
@Override
public void start(Stage primaryStage)
{
try
{
HBox hbox = new HBox();
// 设置 HBox 竖直居中
hbox.setAlignment(Pos.CENTER);
// 设置距离(padding)
hbox.setPadding(new Insets(10));
TextField textField = new TextField();
Button selectBtn = new Button("选择文件");
Button uploadBtn = new Button("开始上传");
// 控件加到 HBox中
hbox.getChildren().addAll(textField, selectBtn, uploadBtn);
// 让 textField 随着水平方向的增长而增长
HBox.setHgrow(textField, Priority.ALWAYS);
// 修改 HBox 的高度
Scene scene = new Scene(hbox, 400, 40);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
3 控件的尺寸
在一个 HBox 中添加3个按钮, 观察它们的显示尺寸
默认的, 每个按钮的尺寸不同 (与文本长度相关)
思考: 如何修改每个按钮的 显示尺寸 ?
控件有3个属性:
- 最小尺寸: Min
- 最佳尺寸: Pref
- 最大尺寸: Max
同时存在的话, 已最佳尺寸为主. 例如
b1.setMinWidth(10); ×
b1.setPrefWidth(200); √ 3条同时存在的话, 已最优尺寸为最终样式.
b1.setMaxWidth(400); ×
可以分别设置宽度和高度, 例如:
Button b1 = new Button("test");
b1.setMaxWidth(100);
b1.setMaxHeight(50);
b1.setMaxSize(100, 500);
几个常量
- Control.USE_COMPUTED_SIZE (-1.0) 默认值
- Control.USE_PREF_SIZE
- Double.MAX_VALUE
比如: USE_COMPUTED_SIZE 控件能完成显示内容所需要的尺寸,例如, 显示 “中国” 和 “中国人民共和国” 尺寸是不一样的.
完整代码
package application;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Control;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application
{
@Override
public void start(Stage primaryStage)
{
try
{
// 声明 HBox 对象(这里是 根节点)
HBox hbox = new HBox();
// 声明3个按钮
Button btn1 = new Button("中国");
Button btn2 = new Button("China");
Button btn3 = new Button("中华人民共和国");
// 设置 btn1
btn1.setMinWidth(10); // 最小宽度
btn1.setPrefWidth(100); // 最优宽度
btn1.setMaxWidth(400); // 最大宽度
// 设置 btn2
btn2.setMinHeight(50); // 最小宽度
// 设置 btn3 用常量的方式
btn3.setMinWidth(Control.USE_COMPUTED_SIZE);
btn3.setMinHeight(Control.USE_PREF_SIZE);
// 将3个 button 加入到 HBox
hbox.getChildren().addAll(btn1, btn2, btn3);
Scene scene = new Scene(hbox, 400, 50);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
- 结论: 显示尺寸最终由布局容器决定, min, pref, max 只是参考值.
4 控件的布局
- 填充 Padding: 子控件(与父级)的边距
- 间距 Spacing: 各个子控件之间的间距
- 对齐 Alignment: 靠左,靠右,居中…
- 填充方法 setPadding() 方法
hbox.setPadding(new Insets(10)); // 上右下左都是 10.
hbox.setPadding(new Insets(10, 10, 30, 40)); // 单独设置 上右下左的距离.
- 设置间距 setSpacing() 方法
hbox.setSpacing(10); // 每个子控件之间的距离是 10
- 设置对齐,对齐分为 水平和竖直方向.
hbox.setAlignment(Pos.CENTER); // 这样写是 水平方向和竖直方向都居中对齐.
实现效果
完整代码
package application;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application
{
TextField a = new TextField();
Button b = new Button("选择文件");
Button c = new Button("上传");
@Override
public void start(Stage primaryStage)
{
try
{
HBox hbox = new HBox();
hbox.getChildren().addAll(a, b, c);
// 填充 padding, 上下左右都是10px
hbox.setPadding(new Insets(10));
// 间距 每个子控件之间的距离
hbox.setSpacing(10);
// 对齐
hbox.setAlignment(Pos.CENTER); // 水平方向和竖直方向都是对齐的
Scene scene = new Scene(hbox, 400, 40);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
增长优先
HBox.setHgrow( a, Priority.ALWAYS);
实现效果
精确布局
例如, 在 HBox 下有 a,b,c 三个控件
- a:固定300px
- b: 剩余空间的 50%
- c: 剩余空间的 50%
这样的布局该如何实现呢?
- 自定义类继承 HBox类
- 然后定义各个子控件的 大小和坐标. resizeRelocate() 方法
package application;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class Main extends Application
{
TextField a = new TextField();
Button b = new Button("选择文件");
Button c = new Button("上传");
@Override
public void start(Stage primaryStage)
{
try
{
HBox hbox = new MyHBox();
hbox.getChildren().addAll(a, b, c);
Scene scene = new Scene(hbox, 500, 40);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
// 派生 HBox
class MyHBox extends HBox
{
protected void layoutChildren()
{
// 如果完全自己计算布局, 就不需要调用 super
super.layoutChildren();
// 自定义布局
double width = getWidth();
double height = getHeight();
// 计算每个控件的宽度
double aa = 300;
double bb = (width - aa) * 0.5;
double cc = width - aa - bb;
// 依次定位: 左上角坐标 x,y/宽,高
double x = 0;
a.resizeRelocate(x, 0, aa, height);
x += aa;
b.resizeRelocate(x, 0, bb, height);
x += bb;
c.resizeRelocate(x, 0, cc, height);
}
}
public static void main(String[] args)
{
launch(args);
}
}
实现效果
5 自定义容器
容器 Pane: 可以容纳多个子节点的窗口面板
现有子类:
AnchorPane, BorderPane, DialogPane, FlowPane, GirdPane, HBox, StackPane, TextFlow, TilePane, VBox…
自定义容器: 派生Pane类, 重写 layoutChildren()
注: Pane.getChildren() 可以获取它的子节点的列表.
6 布局的嵌套
思考: 这样的界面如何实现?
// 顶部
hbox.getChildren().addAll(textField, button);
HBox.setHgrow(textField, Priority.ALWAYS);
// BorderPane 里嵌套HBox
BorderPane root = new BorderPane();
root.setTop(hbox);
root.setCenter(textArea);
最终效果
完整代码
package application;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class Main extends Application
{
HBox hbox = new HBox();
TextField textField = new TextField();
Button button = new Button("添加");
TextArea textArea = new TextArea();
@Override
public void start(Stage primaryStage)
{
try
{
// 顶部
hbox.getChildren().addAll(textField, button);
HBox.setHgrow(textField, Priority.ALWAYS);
// BorderPane 里嵌套HBox
BorderPane root = new BorderPane();
root.setTop(hbox);
root.setCenter(textArea);
// 实现点击之后,将 textField的内容加入到 TextArea中
// 按钮点击事件
button.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
onAdd();
}
});
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
public void onAdd()
{
// 在 TextField 中,取出用户输入的文本
String str = textField.getText();
// 把取到的文本加入到 下面的文本框中
textArea.appendText(str + "\n");
}
public static void main(String[] args)
{
launch(args);
}
}
六 列表控件
1 列表控件 ListView
JavaFX 有 4种控件来表示多项数据:
- ListView: 列表
- TreeView: 树
- TableView: 表格
- TreeTableView: 多列列表
创建ListView步骤
- 定义数据项
添加一个类,表示列表中每一项对应的数据
static class Student
{
public int id;
public String name;
public boolean sex;
public Student()
{
}
public Student(int id, String name, boolean sex)
{
this.id = id;
this.name = name;
this.sex= sex;
}
}
- 数据源
用一个ObservableList来存储原始数据:
ObservableList listData = FXCollections.observableArrayList();
注:ObservableList 是一个比较难懂的设计,初学时只要把它当成普通的ArrayList来用即可。
- 添加数据
listData.add(new Student(1, "shao", true));
listData.add(new Student(2, "wang", true));
listData.add(new Student(3, "jiang", false));
- 创建ListView
创建 ListView时,要指定数组项的类型 。在JavaFX里,泛型被广泛使用,大家要学习习惯它。
ListView listView = new ListView();
- 设置数据源
listView.setItems( listData );
- 设置单元格的显示
添加 ListCell 类, ListCell负责列表项里每一个Cell的显示
static class MyListCell extends ListCell<Student>
{
@Override
public void updateItem(Student item, boolean empty)
{
// FX框架要求必须先调用 super.updateItem()
super.updateItem(item, empty);
// 自己的代码
if (item == null)
{
this.setText(""); // 清空显示
}
else
{
this.setText( item.name ); // 显示该项的值
}
}
}
可以看到,这里也使用了泛型。每一处泛型的参数都是
- 设置单元格生成器
JavaFX里广泛使用了 Factory 的概念,大致意思是说, 由一个工厂来负责提供每个Cell
listView.setCellFactory(new Callback<ListView<Student>,ListCell<Student>>() {
@Override
public ListCell<Student> call(ListView<Student> param)
{
return new MyListCell();
}
});
可以看到,call() 方法返回的是一个 ListCell对象。
- 添加到布局
BorderPane root = new BorderPane();
root.setCenter(listView);
显示效果如图:
完整代码
package application;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application
{
// 创建 ListView, 指定数据项类型
ListView<Student> listView = new ListView<Student>();
// 数据源
ObservableList<Student> listData = FXCollections.observableArrayList();
@Override
public void start(Stage primaryStage)
{
try
{
// 准备数据
listData.add(new Student(1, "shao", true));
listData.add(new Student(2, "wang", true));
listData.add(new Student(3, "jiang", false));
// 设置数据源
listView.setItems(listData);
// 设置单元格生成器 (工厂)
listView.setCellFactory(new Callback<ListView<Student>, ListCell<Student>>()
{
@Override
public ListCell<Student> call(ListView<Student> param)
{
return new MyListCell();
}
});
BorderPane root = new BorderPane();
root.setCenter(listView);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
// 负责单元格Cell的显示
static class MyListCell extends ListCell<Student>
{
@Override
public void updateItem(Student item, boolean empty)
{
// FX框架要求必须先调用 super.updateItem()
super.updateItem(item, empty);
// 自己的代码
if (item == null)
{
this.setText(""); // 清空显示
} else
{
this.setText(item.name); // 显示该项的值
}
}
}
// 数据项
static class Student
{
public int id;
public String name;
public boolean sex;
public Student()
{
}
public Student(int id, String name, boolean sex)
{
this.id = id;
this.name = name;
this.sex = sex;
}
}
public static void main(String[] args)
{
launch(args);
}
}
2 列表项的操作
直接对ObservableList 进行增加、删除、和修改操作,ListView会被通知到并自动更新内容。
- 增加一项
public void onInsert()
{
String name = textField.getText();
listData.add(new Student(0, name, true));
}
- 删除当前选中项
public void onRemove()
{
int index = listView.getSelectionModel().getSelectedIndex(); //当前选中项
if(index >=0 )
{
listData.remove( index );
}
}
- 修改当前选中项
public void onChange()
{
String name = textField.getText();
int index = listView.getSelectionModel().getSelectedIndex();
if(index >=0 )
{
Student s = listData.get(index);
s.name = name;
listData.set(index, s);
}
}
实现效果
完整代码
package application;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application
{
// 创建ListView,指定数据项类型
ListView<Student> listView = new ListView<Student>();
// 数据源
ObservableList<Student> listData = FXCollections.observableArrayList();
HBox hbox = new HBox();
TextField textField = new TextField();
Button btnInsert = new Button("增加");
Button btnRemove = new Button("删除");
Button btnChange = new Button("修改");
@Override
public void start(Stage primaryStage)
{
try
{
// 准备数据
listData.add(new Student(1, "shao", true));
listData.add(new Student(2, "wang", true));
listData.add(new Student(3, "jiang", false));
// 设置数据源
listView.setItems(listData);
// 设置单元格生成器(工厂)
listView.setCellFactory(new Callback<ListView<Student>, ListCell<Student>>()
{
@Override
public ListCell<Student> call(ListView<Student> param)
{
return new MyListCell();
}
});
BorderPane root = new BorderPane();
root.setCenter(listView);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
hbox.getChildren().addAll(textField, btnInsert, btnRemove, btnChange);
HBox.setHgrow(textField, Priority.ALWAYS);
root.setTop(hbox);
btnInsert.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
onInsert();
}
});
btnRemove.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
onRemove();
}
});
btnChange.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
onChange();
}
});
} catch (Exception e)
{
e.printStackTrace();
}
}
public void onInsert()
{
String name = textField.getText();
listData.add(new Student(0, name, true)); // 里面会通知 ListView
}
public void onRemove()
{
int index = listView.getSelectionModel().getSelectedIndex();
if (index >= 0)
{
listData.remove(index); // 里面会通知 ListView
}
}
public void onChange()
{
String name = textField.getText();
int index = listView.getSelectionModel().getSelectedIndex();
if (index >= 0)
{
Student s = listData.get(index);
s.name = name;
listData.set(index, s); // 里面会通知 ListView
}
}
// 负责单元格 Cell 的显示
static class MyListCell extends ListCell<Student>
{
@Override
protected void updateItem(Student item, boolean empty)
{
// FX框架要求必须先调用 super.updateItem()
super.updateItem(item, empty);
// 自己的代码
if (item == null)
{
this.setText(""); // 清空
} else
{
this.setText(item.name);// 显示该项的值
}
}
}
// 数据项
static class Student
{
public int id;
public String name;
public boolean sex;
public Student()
{
}
public Student(int id, String name, boolean sex)
{
this.id = id;
this.name = name;
this.sex = sex;
}
}
public static void main(String[] args)
{
launch(args);
}
}
3 多项选择
- 源代码看不懂
4 鼠标事件处理
- 鼠标事件 MouseEvent
通常在 GUI 开发时, 我们关注的鼠标事件有:
- 鼠标左键点击(选中)
- 鼠标左键双击(打开)
- 鼠标右键点击(上下文菜单)
- 鼠标拖拽…
在 MouseEvent 对象里, 能得到以下信息:
- event.getButton() 按钮(左,中,右)
- event.getClickCount() 移动(0), 单击(1), 双击(2)
- event.getX() 点击位置(窗口坐标)
- event.getScenex() 点击位置(屏幕坐标)
鼠标点击事件的处理
- listView.setOnMouseClicked(EventHandler)
5 实战练习-阅读器
七 树控件
1 树控件
树控件, TreeView, 以树状结构来显示数据用法与 ListView 类似
注意:
- TreeItem 类用于表示树上的一个节点
- 一个 TreeView 必须有一个根节点
- TreeItem 可以添加图标 TreeItem.setGraphic()
步骤
- 数据项, 先定义一个类,表示每一个树节点所对应的数据
private static class ItemData
{
public String name; // 文件名或目录名
boolean isDir = false; // 是否目录
public ItemData()
{
}
public ItemData(boolean isDir, String name)
{
this.isDir = isDir;
this.name = name;
}
}
- 单元格的显示, TreeCell 负责每个树节点的显示
private static class MyTreeCell extends TreeCell<ItemData>
{
@Override
protected void updateItem(ItemData item, boolean empty)
{
super.updateItem(item, empty);
if(item == null)
{
this.setText("");
}
else
{
this.setText(item.name);
}
}
}
- 添加节点
向 TreeView 里添加节点
(1)添加根节点
TreeItem item_root = new TreeItem( … )
treeView.setRoot ( item_root )
(2)添加子节点
TreeItem item_c = new TreeItem (…)
TreeItem item_java = new TreeItem (…)
item_root.getChildren().addAll ( item_c, item_java )
(3)继续添加子节点 …
在每个TreeItem 下又可以添加下一层子节点,最终形成一个树状结构
完整代码
package application;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application
{
TreeView<ItemData> treeView = new TreeView<ItemData>();
@Override
public void start(Stage primaryStage)
{
try
{
BorderPane root = new BorderPane();
initTreeView();
root.setCenter(treeView);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
// 初始化树
private void initTreeView()
{
ItemData data_root = new ItemData(true, "啊发你好");
TreeItem<ItemData> item_root = new TreeItem<ItemData>(data_root);
ItemData data_c = new ItemData(true, "C/C++学习指南");
TreeItem<ItemData> item_c = new TreeItem<ItemData>(data_c);
ItemData data_java = new ItemData(true, "Java学习指南");
TreeItem<ItemData> item_java = new TreeItem<ItemData>(data_java);
ItemData data_common = new ItemData(true, "公共基础课程");
TreeItem<ItemData> item_common = new TreeItem<ItemData>(data_common);
item_root.getChildren().addAll(item_c, item_java, item_common);
// 在"Java学习指南系列" 下添加子节点
if (true)
{
ItemData data = new ItemData(false, "快速入门");
item_java.getChildren().add(new TreeItem<ItemData>(data));
}
if (true)
{
ItemData data = new ItemData(false, "高级语法");
item_java.getChildren().add(new TreeItem<ItemData>(data));
}
// 在"公共基础课程" 下添加子节点
if (true)
{
ItemData data = new ItemData(false, "CentOS使用教程");
item_common.getChildren().add(new TreeItem<ItemData>(data));
}
if (true)
{
ItemData data = new ItemData(false, "MySQL使用教程");
item_common.getChildren().add(new TreeItem<ItemData>(data));
}
// 设置根节点
treeView.setRoot(item_root);
// 设置 CellFactory
treeView.setCellFactory(new Callback<TreeView<ItemData>, TreeCell<ItemData>>()
{
@Override
public TreeCell<ItemData> call(TreeView<ItemData> param)
{
return new MyTreeCell();
}
});
}
// 单元格的使用
private static class MyTreeCell extends TreeCell<ItemData>
{
@Override
protected void updateItem(ItemData item, boolean empty)
{
super.updateItem(item, empty);
if (item == null)
{
this.setText("");
} else
{
this.setText(item.name);
}
}
}
private static class ItemData
{
public String name; // 文件名或目录
public boolean isDir = false; // 是否目录
public ItemData()
{
}
public ItemData(boolean isDir, String name)
{
this.isDir = isDir;
this.name = name;
}
}
public static void main(String[] args)
{
launch(args);
}
}
2 多列树控件
TreeTableView:
特点:
- 整体是树状结构
- 支持多列显示
- 基于行进行操作, 每行为一个 TreeItem, 每列显示一个字段.
3 实战练习 - 文件浏览器
八 对话框
1 简单提示框 Alert
什么叫模式对话框(Model)?
- showAndWait() 时, 当前输入的焦点在对话框里,后面的界面无法输入.
- 程序卡在 showAndWait() 不往下走, 直到对话框关闭.
// 弹窗显示
Alert warning = new Alert(AlertType.WARNING);
warning.setHeaderText("出错");
warning.setContentText("用户名错误");
warning.showAndWait();
完整代码
package application;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application
{
// 用户输入框
TextField username = new TextField();
// 密码框
PasswordField password = new PasswordField();
// 登录按钮
Button login = new Button("登录");
@Override
public void start(Stage primaryStage)
{
try
{
BorderPane root = new BorderPane();
VBox vbox = new VBox();
vbox.getChildren().addAll(username, password, login);
// 设置子控件的间距
vbox.setSpacing(10);
root.setCenter(vbox);
// application.css 样式定义
root.setId("root");
username.setPromptText("用户名"); // 提示文字
password.setPromptText("密码"); // 密码
login.setMaxWidth(Double.MAX_VALUE); // 按钮放宽显示
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
// 登录按钮事件
login.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
doLogin();
}
});
} catch (Exception e)
{
e.printStackTrace();
}
}
private void doLogin()
{
String _user = username.getText();
String _pass = password.getText();
// 检查用户名和密码
if (!_user.equals("shaofa") || !_pass.equals("123456"))
{
System.out.println("用户名/密码错误!");
// 弹窗显示
Alert warning = new Alert(AlertType.WARNING);
warning.setHeaderText("出错");
warning.setContentText("用户名错误");
warning.showAndWait();
System.out.println("下一步 ...");
} else
{
Alert warning = new Alert(AlertType.INFORMATION);
warning.setHeaderText("成功");
warning.setContentText("欢迎进入系统!");
warning.showAndWait();
}
}
public static void main(String[] args)
{
launch(args);
}
}
显示效果
2 自定义对话框
除了 Alert 之外, 自带的对话框类还有 TextInputDialog, ChoiceDialog.
示例
package application;
import java.util.Optional;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.DialogPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application
{
TextArea textArea = new TextArea();
@Override
public void start(Stage primaryStage)
{
try
{
BorderPane root = new BorderPane();
root.setCenter(textArea);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
// 添加按钮
Button add = new Button("添加");
root.setTop(add);
add.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
onAdd();
}
});
} catch (Exception e)
{
e.printStackTrace();
}
}
private void onAdd()
{
// 对话框内容
VBox content = new VBox();
TextField t_id = new TextField();
TextField t_name = new TextField();
TextField t_phone = new TextField();
t_id.setPromptText("学号");
t_name.setPromptText("姓名");
t_phone.setPromptText("手机号");
content.setSpacing(10);
content.getChildren().addAll(t_id, t_name, t_phone);
// Dialog -> DialogPane -> Root Node
DialogPane dialogPane = new DialogPane();
dialogPane.setContent(content);
// 添加按钮
ButtonType ok = new ButtonType("确定", ButtonData.OK_DONE);
dialogPane.getButtonTypes().add(ok);
// 创建对话框
Dialog<ButtonType> dlg = new Dialog<ButtonType>();
dlg.setDialogPane(dialogPane);
dlg.setTitle("添加学生信息");
// 显示对话框, 并接受返回结果
Optional<ButtonType> result = dlg.showAndWait();
if (result.isPresent() && result.get().getButtonData() == ButtonData.OK_DONE)
{
int id = Integer.valueOf(t_id.getText());
String name = t_name.getText();
String phone = t_phone.getText();
textArea.appendText("学号: " + id + "\t姓名: " + name + "\t手机号: " + phone + "\n");
}
}
public static void main(String[] args)
{
launch(args);
}
}
显示效果
3 对话框的封装
- 封装成一个工具类, 可以重复使用
使用场景
- 该对话框需在多处都有调用
- 该对话框内容比较负责
4 文件对话框
使用 FileChooser 可以打开一个文件选择对话框
- 以读方式选择一个文件
- 以写方式选择一个文件
区别:
- 读方式时, 要选择一个已经存在的文件
- 写方式时, 可以选择存在的文件, 也可以新输入一个文件
九 菜单栏
1 菜单栏
- 菜单栏 MenuBar
2 右键菜单
- 上下文菜单 Context Menu
package application;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application
{
// 创建ListView, 指定数据项类型
ListView<Student> listView = new ListView<Student>();
// 数据源
ObservableList<Student> listData = FXCollections.observableArrayList();
// ListView 的上下文菜单
ContextMenu listContextMenu = new ContextMenu();
@Override
public void start(Stage primaryStage)
{
try
{
// 准备数据
listData.add(new Student(1, "shao", true));
listData.add(new Student(2, "wang", true));
listData.add(new Student(3, "jiang", false));
// 设置数据源
listView.setItems(listData);
// 设置单元格生成器(工厂)
listView.setCellFactory(new Callback<ListView<Student>, ListCell<Student>>()
{
@Override
public ListCell<Student> call(ListView<Student> param)
{
return new MyListCell();
}
});
initContextMenu();
BorderPane root = new BorderPane();
root.setCenter(listView);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e)
{
e.printStackTrace();
}
}
// 初始化上下文菜单
private void initContextMenu()
{
MenuItem menuItemRemove = new MenuItem("删除");
menuItemRemove.setOnAction((ActionEvent e) -> {
removeItem();
});
// 添加菜单项
listContextMenu.getItems().add(menuItemRemove);
// 给ListView设置上下文菜单
listView.setContextMenu(listContextMenu);
}
// 删除当前选中的项
private void removeItem()
{
int index = listView.getSelectionModel().getSelectedIndex();
if (index >= 0)
{
listData.remove(index);
}
}
// 负责单元格Cell的显示
static class MyListCell extends ListCell<Student>
{
@Override
public void updateItem(Student item, boolean empty)
{
// FX框架要求必须先调用 super.updateItem()
super.updateItem(item, empty);
// 自己的代码
if (item == null)
{
this.setText(""); // 清空显示
} else
{
this.setText(item.name); // 显示该项的值
}
}
}
// 数据项
static class Student
{
public int id;
public String name;
public boolean sex;
public Student()
{
}
public Student(int id, String name, boolean sex)
{
this.id = id;
this.name = name;
this.sex = sex;
}
}
public static void main(String[] args)
{
launch(args);
}
}
3 实战练习
十 样式单
1 使用样式单
- CSS, Cascading Style Sheets 用于定义界面的显示样式(背景, 边框, 字体等)
-fx-padding: 填充
-fx-font-size: 字体大小
-fx-text-fill: 文字颜色