chapter 2 JavaFX 基础
javafx 是一个用来专门创建图形用户界面的工具包,因此他只包含了控件的集合。这章简短的介绍各种组件、API和JavaFX工具,在后面的章节中你需要使用到这些基础知识。这章适合那些希望创建JavaFx基本应用程序的开发者他不需要知道所有的细节自定义JavaFx控件,这章只是一个概述。为了获得更深入的认识,请阅读《快速入门指南的JavaFX》(麦格劳 - 希尔,2014年)Quick Start Guide to JavaFX (McGraw-Hill, 2014),
你的第一个javaFX应用程序
当你运行这个程序,屏幕上将显示一个对话框。
如图所示(这是在Mac OS X上运行的结果.):
这是一个能够创建的最简单的图形绘制应用程序。然而再简单,他也使用了javaFX的几个API,当你看到文件中引用申明的部分你将看到我们使用的类来自于以下三个javaFX包中:
javafx.application
javafx.stage
javafx.scene
在下面的小节中我们将讨论在该类中类和API的使用,你可以更好的理解javaFX是怎么工作的。
注意:java SE8 发布后 javaFX是与JDK 和 JRE捆绑的,因此编译程序是后不需要其他类库支持。
javaFX 应用程序的生命周期
就像在HelloWorld例子中一样,每个javaFX应用都需要继承javafx.application.Application 类,他已经定义了应用程序的生命周期,包含一下方法(这些方法都会被javafx框架自动调用):
Application.init() 在应用开始之前做一些准备。这个方法在 JavaFX launcher 这个特殊的线程中调用。
Application.start(Stage stage) 用来开启程序,可以用来定义完整的应用程序和通过添加一个场景定义应用程序的主窗口视图。他被JavaFX Application线程调用,我将在后面的章节里提供更多的关于线程模式的信息。
Application.stop() 在应用关闭的时候被调用一次,它可以由不同原因触发,比如用户点击了程序主窗口的退出按钮。这个方法被JavaFX应用程序线程调用。
这些方法在JavaFX Application中都可以被覆盖,大多数你只需要定义start() 方法,因为他是你定义全部的用户界面的地方。在例子中,界面是一个包含在StackPane控件中的按钮,main方法也是这个应用程序的一部分。你会发现大多数的JavaFX例子和教程里都是这样的。在一个大的应用中你需要提取main方法到不同的类中来管理你的应用程序,在main方法中调用了Application类的静态方法launch,这个方法的实质是通过创建所有需要的线程来开启JavaFX环境,比如生命周期等等。
使用Stage类定义主窗口
在例子中我们使用了Application.start(Stage stage) 来定义了应用程序的用户界面,参数stage提供JavaFX应用生命周期,并且他定义了程序的主窗口,它是由系统底层提供的一个window的包装类,JavaFX用他在屏幕上渲染界面。就像AWT,一个实体是通过Java访问本地窗口(同样的技术也被用到弹出窗口上pop-ups),下图使用UML展示了基础JavaFX的window类的层次试图和定义的:
你可以看到,一个stage是一个特殊的window。他提供一些额外的信息和方法以及皮肤定义JavaFX程序的主窗口下面的表格描述的所有的属性,他们只是是stage的一部分。
属性 | 类型 | 描述 |
fullScreen | 只读的boolean类型属性 | 如果是true,应用可以显示为一个没有被修饰过的全屏的窗口 |
fullScreenExitKey | ObjectProperty |
他指定了一组键允许推出全屏模式 |
fullScreenExitHint | ObjectProperty |
他定义了程序进入全屏模式时候显示的文本信息 |
iconified | 只读的boolean类型属性 | 定义stage是否已经被图表化 |
maxHeight | DoubleProperty | 定义了stage的高度的最大值 |
maxmized | 只读的boolean类型属性 | stage是否已经是最大化 |
maxWidth | DoubleProperty | 定义stage宽度的最大值 |
minHeight | DoubleProperty | 定义stage高度的最小值 |
minWidth | DoubleProperty | 定义stage宽度的最小值 |
resizable | boolean类型属性 | 如果设置成false则用户不能调整stage大小 |
title | String属性 | 如果stage被一个标题栏装饰那么这个字符串将显示在标题栏上 |
所有上面提到的属性都可以通过调用Javafx属性API实现,他们的使用在本书的例子中都会用到。如果到现在你还没有听说过属性API你也不用别担心。Stage累的说有属性包含了通常想你知道的其他的JavaBean的实现类似getter和setter;然而,在属性API中这些getter和setter和你之前了解到的POJO的对象不一样。这些API将在本章的后面深入介绍。另外除了Stage类的这些属性,他的父类,Window类也定义了一系列的属性,如下表:
属性 | 类型 | 描述 |
eventDispatcher | ObjectProperty |
指定该节点的事件调度器。默认的EventDispatcher将接受所有的输入事件和发送这些事件到事件处理器和过滤器。 |
focused | 只读BooleanProperty | 定义什么时候Window能够获得输入焦点。 |
height | 只读Double属性 | 定义Stage的高度 |
onCloseRequest | Object属性 |
指定一个处理器,当一个关闭窗口的外部请求被收到后,他将被调用。 |
onHidden | Object属性 |
指定一个处理器,当窗口已经隐藏时候被调用。 |
onHiding | Object属性 |
指定一个处理器,当窗口即将被隐藏时候调用。 |
onShowing | Object属性 |
指定一个处理器,当窗口即将要显示时候调用。 |
onShown | Object属性 |
指定一个处理器,当窗口已经显示时候被调用。 |
opacity | Double属性 | 设定stage的透明度,值为(0,1)之间 |
scene | 只读Object |
stage的scene |
showing | 只读Boolean属性 | 是否处于显示状态 |
width | 只读Double属性 | stage的宽度 |
x | 只读Double属性 | stage在屏幕左上角的x坐标 |
y | 只读Double属性 | stage在屏幕左上角的y坐标 |
除了以上的属性,Stage和Window类还包含了以下一些很有用的方法,用来与java代码中的实例进行交互。
方法 | 描述 |
void close() | 关闭Stage |
observableList |
反回一个用来修饰窗口的图标列表,最终效果要依赖于操作系统 |
modality getModality() | 返回stage的模式。 |
window getOwner() | 如果当前不是顶级窗口,返回当前的stage 的所有者窗口。 |
stageStyle getStyle() | 检索当前的stage的样式属性 |
void initModality (Modality modality) | 为当前的stage 指定模式,他必须在stage显示之前被调用,否则他会引发异常。 |
void initOwner(Window owner) | 指定stage的所属窗口对象,他必须在stage之前调用,否则会引发异常。 |
void initStyle(StageStyle style) | 为stage指定style,他必须在stage之前调用,否则会引发异常。 |
void showAndWait() | 显示stage 并且一直等到他被关闭,这个方法必须在JavaFX线程中调用。 |
void toBack() | 向后设置窗口 |
void toFront() | 在前面显示窗口 |
方法 | 描述 |
void centerOnScreen() | 设置x,y坐标属性使窗口在屏幕正中显示。 |
void fireEvent(Event event) | 触发指定的事件 |
void hide() | 尝试隐藏窗口 |
void requestFocus() | 请求窗口获得焦点 |
void sizeToScene() | 设置宽高属性以便适合定义好的场景大小 |
以上所述的方法类涉及到一些直到现在还没有说明过的类和方法,比如事件处理类,这些将在以后详细说明。
下面显示了可以定义的stage样式,使用他你可以简单的改变Stage实例的样式和形态,以下给出了平台支持的所有样式概要:
在Start(..)方法中设置上面的属性你可以指定主窗口的样式,下面是一个decorated样式的frame例子:
以下是在Mac OS 和 Windows 7.上运行的结果,应用程序按照指定样式创建窗口并且可以添加控件。
HelloWorld应用程序中使用到的另外一个API是场景图(Scene)。在JavaFX中,每个窗口包含一个所谓的场景图,在javafx中它是一个可以被Scene类访问的有向无环图。一个场景图管理者所有的即将被在屏幕上渲染的项目。每个项目可以有多个父对象,每个元素都是场景图中的一部分,被称为一个节点。比如:节点可以是一个圆形,一个按钮,或者一个可以容纳其他节点的面板。因为场景图用一个内部的模型管理所有的节点,他知道哪个节点该显示在屏幕上,那一个部件需要重绘。每个场景图都有一个根节点,默认的是一个group,它容纳所有层次的所有节点。StackPane 控件是HelloWorld程序中的根节点,他仅仅包含了一个节点:Hello World 按钮。
按钮的布局来自StackPane,不像Swing中一个JPanel类会有默认的容器和不同的布局管理器,就像BorderLayout和FlowLayout用来定位元素,JavaFX提供了不同的窗格(方框)用来负责分组和铺设子节点。
对于使用JavaFX场景图,开发者能够使用比Swing更多的牛逼的特性。场景图支持变换,就像缩放、旋转,同时可以添加和显示3d对象。由于在Javafx中场景图是所有控件的全局的超级实体,他的结构和API将在第三章详细说明 (应用程序的生命周期和描述Stage的API将不再进一步说明)。
现在,我已经讨论了三个在HelloWorld应用程序中使用的JavaFX的的API
(应用程序,舞台,场景图),我将介绍JavaFX和它的各种API的整个结构。
JavaFX的工具包的技术设计
就像你知道的其他很多的框架那样,JavaFX工具包也是有很多不同的API构成,JavaFX内部可以大致分为以下三层:
本地层Native layer
私有层Private layer
公共层Public layer
为更好地了解,下图显示其中不同的层及其部件:
JavaFX的开发人员只需要使用公共API来工作,就像JDK中的其他部件一样,不建议使用私有API进行编程,这些类可能随时会更新,任何一次的java更新后你的应用可能就是一个不兼容的程序了。由于这个原因,这里只是简单的说一下本地和私有层。
本地层
JavaFX的原生部分没有用Java编写的。相反,这些组件是操作系统层授予访问本地库。 JavaFX的的本机库的一个重要组成部分是Prism实现。Prism是一个独立于技术层,他用来渲染JavaFX视窗。对于一个UI工具箱来说由于高性能是一个很重要的问题,Prism 支持数个操作系统和硬件实现,在windows上,通常是有Direct3D版本工作,对于其他的系统有Open-gl库的实现来支持。除了这些实现,本地层还包含了媒体和web引擎的实现,通过他们你可以在你的程序中显示媒体内容比如电影,音乐,或者嵌入web页面。
通过使用原生接口,JavaFX的已实现在这些领域非常不错的表现。如前所述,本地层不是用java开发的。这也是为什么JavaFX不能简单地作为一JAR框架传送的原因:本机组件特定于操作系统和需要被传递作为编译库。虽然JavaFX的大部分都是开源的,除了一些字体库和VP6编解码器都没有,它都是由于创建许可证的问题导致的。
注意:
JavaFX的有一些可选的API和功能。多媒体的支持是众多可选特性中的一个例子。您可以通过使用Platform.isSupported(...)方法检查这些可选功能的可用性的代码(有的操作系统或者硬件平台不支持)。所有可选特性都是通过枚举定义javafx.application.ConditionalFeature。
私有API层
私有的API包含内部类是JRE的一部分。这些类定义在特殊包中(com.sun.*)。
,例如,Prism的API的定义就是私有API的一部分。
所有这些API都是在com.sun中封闭的,开发人员不应该使用它们,因为这些API可以在以后的新Java版本中有所改变。
公共API层
对每个开发人员来说的最重要的部分是架构堆栈的公共部分。这部分包含了所有能够并且应该开发一个应用程序时可以使用的JavaFX类。因为这一点,下一节将介绍JavaFX的所有主要公共API。
公共API层
JavaFX有很多不同的类包装在公共框架中。所有这些类被分配不同的API。通过以下表中JavaFX的包结构(见表2-6),你可以了解核心API。
包 | 描述 |
javafx.application | 这里包含了一般应用类和实用程序(如描述的HelloWorld应用程序) |
javafx.stage | Stage API包含了所用有来创建和管理窗口的类,如frame或弹出窗口(pop-ups) |
javafx.scene | 这是JavaFX最大的一部分,它包含了场景图和所有节点。布局和控件也都在这个包中。 |
javafx.event | 事件API指定事件在JavaFX中是怎么定义的。 一个事件的例子:鼠标点击触发一个JavaFX事件定义的侦听器 |
javafx.beans | 这个包里包含了属性api。这里通过定义bindable 属性为JavaVX beans增加了新的语法。 |
javafx.collections | javaVX 中的一组集合。为集合类提供监听器的支持,使得集合对象的内容发生变化可以被观察到。 |
javafx.concurrent | 为一些异步处理提供辅助类。 |
javafx.animation | 定义一些动画api,可以创建连续的动画。 |
javafx.fxml | 使用FXML,JavaFX提供一种XM基础语言来分离用户界面的定义和逻辑代码。 |
javafx.css | 节点可以设置css样式。如果你想使用css支持,这个包提供了一组相关辅助类。 |
javafx.print | 包含打印api |
javafx.embed | 包含了与Swing和SWT互操作的api |
javafx.gecmetry | 包括一些通用的几何类,如矩形或枚举向量 |
javafx.util | 提供一些助手类 |
接下来章节包含了不同的API的说明,当特定的控件显示的时候大多数将会被覆盖。这里我主要展示一些特定的功能,应为开发人员必须知道他们的目的和使用才能更好的理解书中的例子。
应用和生命周期
前面已经说过。
Stage API
前面也已经说过。
场景图和控件
场景图API是JavaFX的最大的部分之一。它包含的场景的定义
图形和各种节点。为了简化,将所有节点分为四类,如图所示:
简单节点, 比如直线和矩形。
组和布局面板, 比如 FlowPane
控件,碧土按钮和文本框
复杂节点,比如视频、图片、网页
这本书的主要的焦点就是在javafx.scene包和子包中的api。
事件处理:
JavaFX为不同类型的输入提供时间处理。因为这个原因,在JavaFX中有不同的事件类型,像MouseEvent,KeyEvent,TouchEvent。事件处理可以为不同的输入事件容易的注册。接下来的代码片段说明了怎么为触摸屏幕添加事件处理:
button.setOnTouchPressed (new EventHandler
public void handle (TouchEvent event){
touchx=event.getTouchPoint().getSceneX();
touchy=event.getTouchPoint().getSceneY();
}});
这个事件处理定义了用户和应用程序怎么相互影响,多数情况下事件处理被注册到单一的控件上,比如上面的button实例。当覆盖不同的控件,我将说明一些控件所具有的具体的事件类型。比如下拉列表的弹出和隐藏。
属性 API
JavaFX的包含了属性接口,使用强大的功能和简单但功能强大的API扩展了属性处理和绑定。大多数的JavaFXbean 使用属性API授权访问字段。通常情况下,使用getter和setter方法,现在有一种新的方法来访问资源属性。下面是一个DoubleProperty例如一个例子:
private DoubleProperty cellWidth;
public final Doubleproperty cellWidthProperty(){
if(cellWidth == null){
cellWidth= new SimpleDoubleProperty(64);
}
return cellWidth;
}
public void setCellWidth(double value){
cellWidthProperty().set(value);
}
public double getCellWidth(){
return cellWidth==null?64.0:cellWidth.get();
}
上面代码中没有 double cellWidth 字段,属性被包含到一个属性的实例中,getter和setter方法的工作直接使用了property的实例的set和get实现。JavaFX为主要的数据类型比如String double等等提供了一些基础的属性类。这些类都在javafx。beans。property.*; 中,几乎都是Simple***Property这样的类。这里也提供了只读的属性类,因为如果你要不让外部访问属性,只删除setter方法是不够的所以你需要使用只读的属性类。这些类的名称大概是ReadOnly**Property。
在JavaFX中使用这样的设计,会有很多方便,比如你可以为属性增加改变的监听器,javafx.beans.value.ChangeListener.;
SimpleStringProperty textProp=new SimpleStringProperty();
textProp.addListener(new ChangeListener
public void changed(ObervableValue extends String> observableValue,
String oldValue,String newValue){
System.out.println("value is changed!");
}
});
然后JavaFX支持属性绑定,提供的方法如下:
void bind(javafx.beans.value.ObservableValue other);
void unbind();
boolean isBound();
void bindBidirectional(javafx.beans.property.Property other);
void unbindBidirectional(javafx.beans.property.Property other);
使用这些方法你可以在两个JavaFX属性之间创建绑定,例如,你
可以使用的结合来表达变量之间的直接关系。对对象所做的更改将
自动反映到所有绑定的对象。在下面的例子中,滑块的值将是
绑定到另一个。现在无论滑块被改变时,其他人会采集到其值:
Slider slider1=...
Slider slider2=...
slider1.valueProperty().bindBidirectional(slider2.valueProperty());
JavaFX提供两种类型的绑定:双向和单向。滑块示例使用
双向绑定。使用绑定,可以改变两个滑块中的任何一个,另一个将
改变。通过使用bind(...)方法,你可以创建一个单向绑定。这里,只有一
属性绑定到另一个:改变第一滑块将影响第二个,但如果
改变第二滑块,这对第一个没有影响。
在所示的方法,你可以很容易地使用相同的值类型绑定两个或多个属性,但
有时你需要一个更复杂的绑定。假设你需要一个滑块值绑定到可见
的标签的属性。标签上应出现一次的滑块值达到最大。 JavaFX的
属性API提供了这些需求转化的一些方法。大多数属性类型提供特定的方法
来创造一个新的绑定。这里是一些方法使用的示例:
Slider s=new Slider();
Label l=LabelBuilder.create().text("alert!").visible(false).build();
l.visibleProperty().bind(s.valueProperty().multiply(2).greaterThan(100));
集合
JavaFX 提供一些新的集合类型,他们都继承了List,Map和Set接口,这些新集合类型
都放在javafx.collections包中,他们都遵循并且支持改变事件的监听。对于使用
ObservableList举例来说,你可以很容易的注册一个ListChangeListener接受每次列
表内容的变化事件,下面的程序片段可以为你展示如何使用这个API:
ObservableList list = FXCollections.observableArrayList();
list.addListener (new ListChangeListener(){
@override
public void onChanged(ListChangeListener.Change change){
System.out.println("OnChange event:");
while (change.next()){
if(change.wasAdded()){System.out.println("Elements added.Range:"
+change.getFrom()+"-"+change.getTo()};
if(change.wasRemoved()){System.out.println("Elements
removed.Range:"
+change.getFrom()+"-"+change.getTo()};
if(change.wasReplaced()){System.out.println("Elements
replaced.Range:"
+change.getFrom()+"-"+change.getTo()};
if(change.wasPermutated()){System.out.println("Elements
permutated.Range:"
+change.getFrom()+"-"+change.getTo()};
}
}
});
你可以在代码中看到并且分析列表的内容变化和检验变化范围以及是什么类型的变化
。另外还有一个叫FXCollections的类,如果你知道java.util.Collections类那么你
就会熟悉这个新的工具类。使用它你可以简单的创建一个JavaFX集合类型的实例,或
者执行特殊的操作比如随意移动列表的内容。在之前的列表代码片段中FXCollections
用来创建了一个ObservableList实例。
Concurrent API(并发api)
像前面规定的,JavaFX是一个单线程系统,所有的渲染和互动的发生都在JavaFX应用线程中执行,很多时候,你需要更多的线程在你的程序中,如果你想访问数据库,举例来说你不能把它放到应用程序的线程里。这个动作将会锁定线程很长时间。并且结果将会被冻结使你的渲染和用户界面都不能使用。为了帮助开发者碰到的这些问题,JavaFX包含了一些帮助类来创建异步活动,javafx.concurrent包包含了Worker接口,
他将用来创建一个后台线程并可以与UI通信Task和Service两个类实现了Worker接口。
在这些附加的类中,Platform。runLater()方法是最长用到的。使用这个方法可以运行一个runnable在JavaFX应用线程中,(Swing开发者可能知道类似于SwingUtilities.invokeLater())
动画(Animations)
作为一个现代的UI工具包,JavaFX的提供了一个很好的API来创建动画。 JavaFX的支持两个不同类型的方法来创建动画:变换和时间轴动画。变换是
最简单的创建动画的方法。通过使用特定的过渡类,你只需要定义
应该由动画和持续时间来达到的值。此外,你可以定义更多
属性影响变换的行为。这里是一个变换的简短的例子
淡入一个矩形出来时:
FadeTransition ft=new FadeTransition(Duration.millis(360),rectangle);
ft.setFromValue(1.0);
ft.setToValue(0.1);
ft.setCycleCount(Timeline.INDEFINAITE);
ft.setAutoReverse(true);
ft.play();
时间轴动画比过渡更加复杂。这些动画提供沿着连续的事件改变属性的能力
与变换动画不同,您可以往时间轴上添加关键帧,并且在特定的时间设定值。
FXML
用来分离视图定义和应用逻辑,你可以使用FXML定义视图。
FXML是基于XML的语言,用于定义用户界面的结构。通过使用FXML,
设计者可以定义应用程序的完整的用户界面,而无需掌握任何Java代码。
该FXML文件可以在运行时被加载,并且不需要进行编译。一个基本的视图结构
在FXML文件中定义,使用的代码如下所示:
<?xml version="1.0" encoding="UTF-8"?>
FXML是首选的方式来定义的JavaFX应用程序的意见。 FXML支持所有
默认JavaFX的控件,并且可以使用FXML定义可重用的组件,如登录表单。
FXML也支持自定义控件。正因为如此,这将是在这本书后面的章节中。
CSS支持
所有的JavaFX控件可以通过CSS来定义皮肤。每个控件都有一组属性,诸如前景的
颜色或字体大小,与相关的其外观屏幕,而这些性质可通过定义
CSS来实现。一个很好的例子显示,你可以在JavaFX中使用AquaFX库
提供皮肤应用到到所有默认的JavaFX控件,使它们看起来就像原生的Mac OS控件。你
可以在网站http://aquafx-project.com找到相关的开源库和文档。
通过使用CSS,你可以定义一个新的外观为一个类型控件或单一实例。以下
代码片段显示了一些CSS代码定义的一个按钮的皮肤:
。custom-button{
-fx-padding:10;
-fx-background-color:#ffaa99;
-fx-font:24px "Serif";
当使用控件或者自定义他们的工作中,你会经常使用CSS和JavaFX内部的API。 JavaFX对于CSS的支持,将在本书后面大量介绍。
打印
伴随版本8,对打印支持的api被添加到了JavaFX。使用他们你可以轻松的打印节点或者Javafx应用中的一个完整场景,这本书中不会介绍太多细节,大概来看一下下面的片段,用来打印一个JavaFX节点:
public void print(final Node node){
Printer printer = Printer.getDefaultPrinter();
PageLayout pageLayout = printer.createPageLayout(Paper.A4,
PageOrientation.PORTRAIT,Printer.MarginType.DEFAULT);
double scaleX=PageLayout.getPrintableWidth()/Node.getBoundsInParent().getWidth();
double scaleY=PageLayout.getPrintableHeight()/Node.getBoundsInParent().getHeight();
node.getTransforms().add(new Scale(scaleX,scaleY));
PrinterJob job=PrinterJob.createPrinterJob();
if(job!=null){
boolean success= job.printPage(node);
if(success){
job.endJob();
}
}
}
与Swing的互操作
JavaFX支持将JavaFX包含在Swing中,反过来也可以,从Swing仲谦意到javaf也是很容易的,详细的介绍将放在后面的部分。
工具
场景创建器
舞台视图
FX体验工具
开发、本地构建
其他语言的JavaFx进展
GroovyFX
ScalaFX
javaFX和Nashorn引擎
总结