(李佳明译自 www.JavaFX.com,原文:Alla Redko/甲骨文高级技术专家)
本教程覆盖了JavaFX API中内置的JavaFX UI控件。
本文包含以下章节:
每章提供了代码示例和应用程序来说明实际的UI控件的功能。你可以找到上表列出的应用程的源文件和对应的NetBeans项目文件。
通过JavaFX API提供的JavaFX UI控件通过场景中的结点来构建,因此,控件可以使用JavaFX平台的丰富的视觉功能。由于JavaFX API是完全实现自Java的,因此你可以方便地将JavaFX UI控件集成到已有的Java应用程序中。
构建UI的类存在于API的javafx.scene.control包中。图1-1展示了使用包中一些控件构建的例子的截屏图。
图1-1 JavaFXUI的例子
该例子使用用以下的类:
这个UI列表包含了以往你在开发Java客户端程序时可能用到的典型控件,无论如何,JavaFX2.2 SDK还包含了一些新的控件,比如选项面板和表格控件。
图1-2展示了三个包含社交网络列表的选项面板程序的截屏图。列表可以上下收缩。
图1-2 选项面板
要查看完整的UI控件的列表,请参阅API文档。
UI控件类相比典型的用户交互类提供了额外的变量和方法来直观地实现及户交互,你可以通过层叠样式表(CSS)为控件指定特殊的风格。对一些不常用的任务,你可能需要通过继承控件类创建自定义的UI组件或通过皮肤接口为控件定义新的外观。
尝试JavaFX产品中提供的设定程序来测试控件的功能、表现和风格。
因为javafx.scene.control包中的控件都继承自Node类,它们都可以使用场景渲染、动画、变换和动画过渡来宗合展示。
考虑一个创建按钮的任务,它使用了反射,将它的透明度连续动态地从最大变到最小。
图1-3通过动画展示了按钮的三种状态,左边的图是一个透明度为1.0的按钮,中的的按钮透明度为0.8,右边的透明度为0.5.
图1-3 动画按钮
通过使用JavaFX API,你可以使用很少的代码来实现这些功能。
例一创建并开始了一个无限期时间线,关键帧的时间间隔600毫秒,按钮的透明度从默认值(1.0)逐渐变为0.0。setAutoReverse方法可以反向变化。
例1:创建动画按钮
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.util.Duration;
import javafx.scene.control.Button;
import javafx.scene.text.Font;
import javafx.scene.effect.Reflection;
Button button = new Button();
button.setText("OK");
button.setFont(newFont("Tahoma", 24));
button.setEffect(newReflection());
final Timeline timeline = new Timeline();
//将循环次数设为永久
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
final KeyValue kv = newKeyValue(button.opacityProperty(), 0);
final KeyFrame kf = newKeyFrame(Duration.valueOf(600), kv);
timeline.getKeyFrames().add(kf);
timeline.play();
你还可以在上例中使用javafx.scene.effect包中的其他可视效果,如阴影、光照或者动态模糊。
你可以通过层叠样式表(CSS)定制内置的UI控件的外观,在JavaFX中使用CSS与在HTML中基本相同,因为都使用相同的CSS规范。控件的可视状态通过例1-2的.css文件来定义。
例1-2:在CSS文件中定义UI控件的样式
/*controlStyle.css */
.scene{
-fx-font: 14pt "CambriaBold";
-fx-color: #e79423;
-fx-background: #67644e;
}
.button{
-fx-text-fill: #006464;
-fx-background-color: #e79423;
-fx-border-radius: 20;
-fx-background-radius: 20;
-fx-padding: 5;
}
例1-3:应用CSS
Scene scene = new Scene();
scene.getStylesheets().add("/uicontrolssample/controlStyle.css");
例1-4中为切换按钮定义的-fx-base属性覆盖了在.css文件中为场景中所有控件统一定义的相应属性。
例1-4:在JavaFX程序中定义切换按钮的样式
ToggleButton tb3 = new ToggleButton ("I don't know");
tb3.setStyle("-fx-base: #ed1c24");
图1-4 将CSS样式应用到切换按钮
作为对典型UI界面元素的补充,JavaFXSDK在javafx.scene.chart包中提供了现成的图表控件。该控件支持的图表种类为:面积图、柱状图、气泡图、折线图、饼图和散点图。图表可包含多个系列。
图1-5展示了进口水果的饼图
图1-5 饼图
不同于其他的Java客户端工具包。使用JavaFXSDK,你可以在你的程序中仅用很少的代码就可以创建这样的图表。例1-5展示了这种特性。
例1-5:创建图表
ObservableList pieChartData =
FXCollections.observableArrayList(
newPieChart.Data("Grapefruit", 13),
newPieChart.Data("Oranges", 25),
newPieChart.Data("Plums", 10),
newPieChart.Data("Pears", 22),
newPieChart.Data("Apples", 30)
);
PieChart chart = new PieChart(pieChartData);
chart.setTitle("Imported Fruits");
你可以将JavaFX2.0控件集成到现有的使用Swing工具包创建的Java客户端应用程序中。
要将JavaFX内容集成到Swing程序中,其步骤为:
1、 将使用到的所有JavaFXUI控件逐一添加javafx.scene.Scene对象中,使用容器或组来管理它们。
2、 将Scene对象添加到Swing程序的内容窗格中。
如果你需要将JavaFX的单一控件定位到已有的Swing程序中,你也必须完成上述的两个步骤。
即使JavaFX控件被集成到了Swing程序中,JavaFX UI控件仍然使用Prism图形库来绘制,并且拥有该引擎所拥有的全站优势。
关于JavaFX和Swing的集成,请参考“在Swing中使用JavaFX”教程来了解更多信息。
标签控件位于JavaFX API的javafx.scene.control包中,它扩展了标签类。使用标签可以显示文本元素。你可以将一段文本包装在一个特定的空间内,还可以为文本添加一个图像。
图2-1展示了三种普通的标签用例,左边的标签是一个带图像的文本,中间的展示了旋转后的文本,右边的标签绘制了一段文本。
图2-1 标签例子
JavaFX API提供了三种标签类的构造器来创建标签,如例2-1所示。
例2-1 创建标签
//空标签
Label label1 = new Label();
//纯文字标签
Label label2 = new Label("Search");
//带图标和文字的标签
Image image = new Image(getClass().getResourceAsStream("labels.jpg"));
Label label3 = new Label("Search", new ImageView(image));
一旦在代码中创建了标签,你可以通过标签类的以下方法向标签对象增加文字或图像内容。
setTextFill方法指定颜色来绘制标签的文本内容,研究一下例2-2,它首先创建了一个标签,然后加了一个图标、并指定了文本的绘制颜色。
例2-2 向标签增加图标和文本颜色
Label label1 = new Label("Search");
Image image = newImage(getClass().getResourceAsStream("labels.jpg"));
label1.setGraphic(new ImageView(image));
label1.setTextFill(Color.web("#0076a3"));
图2-2带图标的标签
如果你同时为标签定义了文本和图像内容,你可以通过setGraphicTextGap方法来指定文本与图像间的间距。
另外,你还可以通过setVPos和setHPos方法来改变标签在其布局区内的位置,或者通过setTextAlignment方法来设定文本元素的对齐方式。你还可以通过setContentDisplay方法,再指定常量:LEFT,RIGHT,CENTER,TOP,BOTTPM来定义图像相对于文本的位置。
对比图2-1与2-2中的搜索标签,可以发现图2-1中的标签字体较大。这是因为例2-2代码段没有为标签指定任何字体,它使用了默认的字体大小来绘制。
要为标签提供一个字体文字大小以替换默认大小,请使用Labeled类中的setFont。在代码片段2-3中将label1的文本大小设为30点,字体名称为Arial。对于label2,大小为32点,字体名称Cambria。
例2-3 应用字体设置
//使用字体裁类的构造方法
label1.setFont(new Font("Arial", 30));
//使用字体类的方法
label2.setFont(Font.font("Cambria", 32));
或者,你还可以通过层叠样式表(CSS)为标签控件指定字体设定。研究一下例2-4就可明白如何为label1和label2指定相同的字体设定。
例2-4 设定标签样式
label1.setStyle("-fx-font: 30 arial");
label2.setStyle("-fx-font: 32 cambria");
当你创建一个标签时,有时你必须将标签放置在一个比绘制区小的空间里,将文本打断(换行),以便以布局大小相适应,为setWrapText方法指定true值就可实现这种需求。如例2-5所示:
例2-5 开启文本换行
Label label3 = new Label("A label that needs to be wrapped");
label3.setWrapText(true);
图2-3 换行标签
由于标签的布局大小不仅被它的宽度所限制,也被高度所限制。当不可能绘制整个字串时,你可以指定它的表现形式。使用标签类的setTextOverrun方法,并选择有效的OverrunStyle类型来处理不能被正确地绘制的那些文本。请参阅API文档来了解OverrunStyle类型的更多信息。
虽然标签属静态文本,无法被编辑。但你可以使用可视化效果或者变换。例2-6的代码段将label2旋转了270度,并将变换了纵向位置。
例2-6 旋转标签
Label label2 = new Label ("Values");
label2.setFont(new Font("Cambria", 32));
label2.setRotate(270);
label2.setTranslateY(50);
例2-7中的代码段对label3使用了放大效果。当标签的MOUSE_ENTERED事件被触发时,比例因子1.5应用到了setScaleX和setScaleY方法,当用户将鼠标从标签上移开时,MOUSE_EXITED事件发生,比例因子重设为1.0,标签使用原来的大小来绘制。
例2-7 应用放大效果
label3.setOnMouseEntered(new EventHandler() {
@Override public voidhandle(MouseEvent e) {
label3.setScaleX(1.5);
label3.setScaleY(1.5);
}
});
label3.setOnMouseExited(new EventHandler() {
@Override public voidhandle(MouseEvent e) {
label3.setScaleX(1);
label3.setScaleY(1);
}
});
图2-4 放大标签
通过JavaFX API提供的Button类的方法,开发一员可以在用户按下一个按钮时来处理相应的动作。Button类继承自Label类,它可以显示文本、图像者两者相结合。图3-1展示了不同的效果。本章将学习如何创建这些不同类型的按钮。
图3-1 按钮的种类
你可以在JavaFX程序中通过例3-1展示的三种构造方法来创建按钮控件。
例3-1 创建按钮
//空按钮
Button button1 = new Button();
//使用特定本文为标题的按钮
Button button2 = new Button("Accept");
//使用标题和图标的按钮
Image imageOk = newImage(getClass().getResourceAsStream("ok.png"));
Button button3 = new Button("Accept", new ImageView(imageOk));
例3-2展示了如何创建一个只有图标没有标题的按钮
Image imageDecline = newImage(getClass().getResourceAsStream("not.png"));
Button button5 = new Button();
button5.setGraphic(new ImageView(imageDecline));
图3-2 带图标的按钮
在例3-2和图3-2中,图标是一个ImageView对象。然而,你可以使用其他的图形对象,例如,可以使用javafx.scene.shape包中的形状。当同时为按钮定义文本和图形后,你可以使用setGraphicTextGap方法来设定两者的间距。
Button类的默认皮肤区分了以下的可视化状态。图3-3展示了一个带图标的按钮的默认状态。
图3-3 按钮状态
按钮的主要功能是在单击时产生一个动作,使用Button类的setOnAction方法可以定义当用户单击按钮时将要完成的事情,例3-3展示了为button2定义动作的代码片段。
例3-3 为按钮定义动作
button2.setOnAction(new EventHandler() {
@Override public voidhandle(ActionEvent e) {
label.setText("Accepted");
}
});
你可以使用Button类来根据需要设定很多事件—处理(event--handling)方法,以便引发特定的行为或应用可视化特效。
因为Button类继承自Node类,你可以使作javafx.scene.effect包中的任何特效来增强按钮的可视化效果。在例3-4中,当button3触发了onMouseEntered事件时,该按钮使用了DropShadow(译注:一种阴影效果)效果。
例3-4 应用DropShadow特效
DropShadow shadow = new DropShadow();
//当鼠标位于按钮上时,增加阴影特效
button3.addEventHandler(MouseEvent.MOUSE_ENTERED,
newEventHandler() {
@Override public voidhandle(MouseEvent e) {
button3.setEffect(shadow);
}
});
//鼠标离开时,去除特效
button3.addEventHandler(MouseEvent.MOUSE_EXITED,
newEventHandler() {
@Override public voidhandle(MouseEvent e) {
button3.setEffect(null);
}
});
图3-4 有阴影特效的按钮
改善按钮可视化效果的下一步是应用通过Skin类定义的CSS样式,在JavaFX2.0中使用CSS与在HTML中类似,因为两者都基于同样的规范。
你可以将样式定义在独立的CSS文件中,然后在应用程序中通过setStyleClass方法来开启,此方法继承自Node类,所有的UI控件都起可用。另外,你还可以直接通过setStyle方法直接为一个控件定义样式。例3-5和图3-4演示了这种方法。
例3-5 样式化一个按钮
button1.setStyle("-fx-font: 22 arial; -fx-base: #b6e7c9");
图3-5 用CSS样式化的按钮
单选按钮(RadioButton)类实现自切换按钮(ToggleButton)类,一个单选按钮要么选中,要么不选。一般来说,单选按钮都捆绑成一组,一次只能选择一个。这种行为可以和切换按钮区分开来,因为一组内的所有切换按钮均可以有未选取的状态。
图4-1展示了三个单选按钮例子的屏幕截图,例子中三个单选按钮属同一组。
图4-1 单选按钮例子
通过研究后面的单节来学习如何在你的应用程序中实现单选按钮。
在JavaFX API的Javafx.scene.control包中,单选按钮类提供了两种创建单选按钮的构造方法,例4-1展示了两个按钮,无参数的构造方法用来创建rb1,rb1的文本标题通过使用setText方法来实现,rb2的文本标题是通过相应的构造方法来定义的。
例4-1 创建单选按钮
//一个标题为空的按钮
RadioButton rb1 = new RadioButton();
//设定标题
rb1.setText("Home");
//一个有特定标题的按钮
RadioButton rb2 = new RadioButton("Calendar");
由于单选按钮类是Labeled类的扩展类,你不但可以为它设定标题,还可以设定图像。使用setGraphic方法可以设定图像。例4-2演示了如何在应用程序中实现图像单选按钮。
例4-2 创建图像化单选按钮
Image image = newImage(getClass().getResourceAsStream("ok.jpg"));
RadioButton rb = new RadioButton("Agree");
rb.setGraphic(new ImageView(image));
典型的单选按钮使用于组中,代表几个相互独立的选项。切换按钮类为所有单选按钮提供的参考将它们联系在一起,以便在某一时刻只能选择其中之一。例4-3创建了一个捆绑按钮组,然后创建了三个单选按钮,再把它们添加到组中,最后确定当程序启动后哪一个按钮被选中。
例4-3 创建一个单选按钮组
final ToggleGroup group = new ToggleGroup();
RadioButton rb1 = new RadioButton("Home");
rb1.setToggleGroup(group);
rb1.setSelected(true);
RadioButton rb2 = new RadioButton("Calendar");
rb2.setToggleGroup(group);
RadioButton rb3 = new RadioButton("Contacts");
rb3.setToggleGroup(group);
图4-2 绑定在一组中的三个单选按钮
典型地,当组中的单选按钮被选中时,程序会执行一个动作。查看代码段4-4可以学习如何根据单选按钮的选取状态来改变图标。
例4-4 处理单选按钮的动作
ImageView image = new ImageView();
rb1.setUserData("Home")
rb2.setUserData("Calendar");
rb3.setUserData("Contacts");
final ToggleGroup group = new ToggleGroup();
group.selectedToggleProperty().addListener(new ChangeListener(){
public voidchanged(ObservableValue extends Toggle> ov,
Toggle old_toggle, Togglenew_toggle) {
if(group.getSelectedToggle() != null) {
final Image image =new Image(
getClass().getResourceAsStream(
group.getSelectedToggle().getUserData().toString() +
".jpg"
)
);
icon.setImage(image);
}
}
});
用户数据被赋予了每个单选按钮,ChangeListener例如,当rb3被选中时,getSelectedToogle方法返回“rb3”,getUserData方法返回“Contacts”。这样,getResourceAsStream方法接收值“Contacts.jpg”。程序的输出如图4-1年示。
在一组单选按钮中,默认第一个按钮拥有焦点,此时如果你通过setSelected方法将组中的第二个按钮设为选中状态,你将获得如图4-3所示的效果。
图4-3 默认焦点设置
第二个按钮被选中,但第一个焦点仍保留焦点。使用requestFocus函数可以改变焦点,如例4-5所示。
rb2.setSelected(true);
rb2.requestFocus();
当此函数被调用后,输出结果如图4-4所示。
图4-4 为所选按钮设定焦点
切换按钮类在JavaFX API中代表另个一种类型的按钮,两个或多个这种类型的按钮可以被捆绑在一组中,但同一时刻只能选择其中之一,或者都不选。图5-1是一个在组中捆绑了三个切换按钮的应用程序的截屏图。程序根据选中的切换按钮来确定颜色绘制一个矩形。
图5-1 三个切换按钮
你可以在应用程序中使用切换按钮类的三个构造函数中的任何一个来创建切换按钮,如例5-1所示。
例5-1 创建切换按钮
//无标题和图标的切换按钮
ToggleButton tb1 = new ToggleButton();
//有标题的切换按钮
ToggleButton tb2 = new ToggleButton("Press me");
//有标题和图标的切换按钮
Image image = newImage(getClass().getResourceAsStream("icon.png"));
ToggleButton tb3 = new ToggleButton ("Press me", newImageView(image));
切换按钮类是Labeled类的扩展,因此你可以为它指定标题、图标或两者。可以使用Lebeled类的setText和setGraphic为切换按钮指定文本和图像内容。
一旦你在代码中定义了切换按钮,你可以将它们绑定为一组,并设定一个特定的行为。
实现切换按钮类与单选按钮类非常相似。然而,不同于单选按钮,组中的切换按钮不能强制选择组中的单一按钮。也就是说,当单击选中的切换按钮时会导致它变为非选中状态,但单击一个选中的单选按钮时则不会改变什么。
花点时间来研究一个例5-2的代码段。
例5-2 绑定组中的切换按钮
final ToggleGroup group = new ToggleGroup();
ToggleButton tb1 = new ToggleButton("Minor");
tb1.setToggleGroup(group);
tb1.setSelected(true);
ToggleButton tb2 = new ToggleButton("Major");
tb2.setToggleGroup(group);
tb2.setUserData(Color.LIGHTBLUE);
ToggleButton tb3 = new ToggleButton("Critical");
tb3.setToggleGroup(group);
例5-2创建了三个切换按钮并将它们设为一组,tb1调用了setSelected方法以便当程序启动时选中它。然而,你可以不选Minor这个切换按钮,这样程序启动后组中不会有任何按钮选选中。如图5-2所示。
图5-2 组中的三个切换按钮
一般来说,你用一组切换按钮来为每个按钮指定特定的行为。下一部份解释如何利用切换按钮来区分矩形的颜色。
切换按钮类继承自Node类的setUserData方法帮助你指定任何选项值,在例5-3中,用户数据指示哪种颜色用来绘制矩形。
例5-3 为切换按钮设定用户数据
tb1.setUserData(Color.LIGHTGREEN);
tb2.setUserData(Color.LIGHTBLUE);
tb3.setUserData(Color.SALMON);
final Rectangle rect = new Rectangle(167, 50);
final ToggleGroup group = new ToggleGroup();
group.selectedToggleProperty().addListener(newChangeListener(){
public voidchanged(ObservableValue extends Toggle> ov,
Toggle toggle, Togglenew_toggle) {
if (new_toggle == null)
rect.setFill(Color.WHITE);
else
rect.setFill(
(Color)group.getSelectedToggle().getUserData()
);
}
});
ChangeListener
例如,如果用户选择了tb2按钮,getSelectedToggle.getUserData方法返回Color.LIGHTBLUE,结果如图5-3所示。
图5-3 使用选项按钮来绘制方框
查看ToggleButtonSample.java可以检查程序的完整代码。
你可以将CSS样式应用到切换按钮来改善这个应用程序,在JavaFX中,CSS的用法与在HTML中一样,因为两者都基于同样的规范。例5-4使用setStyle方法以改变切换按钮的-fx-base样式属性。
例5-4 将样式应用到切换按钮
tb1.setStyle("-fx-base: lightgreen");
tb2.setStyle("-fx-base: lightblue");
tb3.setStyle("-fx-base:salmon");
把这几行代码加到程序中后,产生的可视效果如图5-4所示。
图5-4 绘制切换按钮
你可能还想试试其他的CSS样式、或应用JAVAFXAPI中提供的动画、变换各可视化特效。
CheckBox类提供了在应用程序中创建选择框的能力。虽然选择框看上去与单选按钮类似,然而不能将它们绑定到一个切换组以具备同时选择多个选项的能力(注:原文如此)。更多信息,请参考单选按钮和切换按钮章节。
图6-1展示了一个程序的截图。在这个程序里,使用了三个选择框来打开和关闭程序中工具条的图标。
图6-1 选择框例子
创建选择框
例6-1 创建2个简单选择框
//第一个没有标签
CheckBox cb1 = new CheckBox();
//另一个有文字标签
CheckBox cb2 = new CheckBox("Second");
cb1.setText("First");
cb1.setSelected(true);
一旦创建了选择框,你可以通过JAVAFXAPI中提供的方法来修改它。在例6-1中,setText方法定义了c1的标签,setSelected方法用来将cb1的状态设为选中,以便在程序开始时使cb1这个选择框选中。
选择框可以已定义或未定义,当处于已定义状态时,使用以选取或不选它。然而,当选择框未定义时,它不能被选择或不选择。可使用CheckBox类的setSelected和setDefined两个方法组给来设定选择框的状态。表6-1展示了根据它的DEFINED和SELECTED属性来决定的选择框状态。
表6-1 选择框的状态
Property Values | Checkbox Appearance |
---|---|
DEFINED = true SELECTED = false |
|
DEFINED = true SELECTED = true |
|
DEFINED = false SELECTED = true/false |
设定行为
当选择框用来代表UI要素混合的三种状态(例如“YES”、“NO”、“NOT”)时,你可能需要使应用程序的选择框具备三种状态。选择框的“ALLOW_TRI_STATE”属性决定了选择框是否能在三种状态(“选中”、“未选中”、“未定义”)中循环。如果该属性为true,则选择框可在三种状态中循环,否则控件只能在两种状态中循环。后续部份描述的程序构造了三个选择框,并且只打开了两种状态。
例6-2的代码片段创建了三个选择框,因此如果一个选择框被选中了,则相应的图标会出现在工具条上。
例6-2 设定选择框的行为
final String[] names = new String[]{"Security","Project", "Chart"};
final Image[] images = new Image[names.length];
final ImageView[] icons = new ImageView[names.length];
final CheckBox[] cbs = new CheckBox[names.length];
for (int i = 0; i < names.length; i++) {
final Image image = images[i] =
newImage(getClass().getResourceAsStream(names[i] + ".png"));
final ImageView icon = icons[i] =new ImageView();
final CheckBox cb = cbs[i] = newCheckBox(names[i]);
cb.selectedProperty().addListener(new ChangeListener() {
public voidchanged(ObservableValue extends Boolean> ov,
Boolean old_val, Booleannew_val) {
icon.setImage(new_val? image : null);
}
});
}
Names数组使用了一个for循环来创建选择框数组及相应的图标数组。例如,第一个选择框cbs[0]被赋予了“Security”标签。同时,当第一个图标对应的图像创建后,image[0]接收“Security.png”作为一个文件名提供给getResourceStream方法。如果特定的选择框被选中时,对应的图像被赋予相应的图标。如果选择框未选中,图标接收一个null值,相应的图标不会被绘制。
图6-2展示了“Security”和“Chart”选项框被选中、“Project”选项框未选中的程序的情况。
图6-2 赋予动作的选择框的程序
图6-2中的选择框设为CheckBox类的默认外观,你可以通过例6-3所示的方法来通过setStyle方法改变选择框的外观。
例6-3 定制选择框
cb1.setStyle(
"-fx-border-color:lightblue; "
+ "-fx-font-size: 20;"
+ "-fx-border-insets: -5;"
+ "-fx-border-radius:5;"
+ "-fx-border-style:dotted;"
+ "-fx-border-width:2;"
);
图6-3 样式化的选择框
要为所有选择框设定特定的样式,请使用下列的过程:
组合框提供了在几个选项中快速选择的能力,考虑一下图7-1所示的组合框的实现。
例7-1 创建一个有三个选项的组合框
例7-1 创建一个有三个选项的组合框
ChoiceBox cb = new ChoiceBox(FXCollections.observableArrayList(
"First","Second", "Third")
);
例7-2有文本元素和分隔条的组合框
ChoiceBox cb = new ChoiceBox();
cb.setItems(FXCollections.observableArrayList(
"New Document","Open ",
new Separator(),"Save", "Save as")
);
注意:组合框不仅可包含文本,也可以包含其他对象。在例7-2中,使用一个分隔条来分开选项。当运行这个代码段时,产生的效果如图7-2所示。
图7-2 通过组合框创建的菜单
在真实的应用中,组合框用来构建多个选项的列表。
图7-3所示的应用程序提供了一个有5个选项的组合框,当一个选定特定的语言时,相应的欢迎语被绘制出来。
图7-3 多选项列表
例7-4提供了一个代码段来展现如何通过从组合框中选定项目来决定哪个欢迎语被绘制。
例7-4 选择一个组合框项目
ChangeListener
你可以通过使用提示来使组合框拥有更多的信息量。提示是javafx.scene.control包中提供的一个UI控件,它可以应用到任何JavaFX UI控件。
Tooltip类提供了一个预定的提示语,它可以通过调用setTooltip方法来方便的为组合框(或其他任何控件)提供提示语。如例7-5所示。
例7-5 为组合框增加提示语
cb.setTooltip(new Tooltip("Select the language"));
用户一般通过Tooltip类的构造器来定义提示文本。然而,如果你的应用程序的逻辑需要为UI动态地设定提示文本,你可以先通过一个空构造器来创建工具提示对象,然后再通过它的setText方法来指定文字信息。
组合框cb应用了更改后的提示,当用户将鼠标放在组合框上时,会看到图7-5所示的内容。
图7-5 应用工具提示的组合框
为了进一步改善你的应用程序,你可以通过CSS属性或应用可视化特效来定制组合框。
TextBox类实现了一种接受并显示输入的UI控件,它提供了接受用户输入文本的功能。与之相应的还有另一种输入控件:PasswordBox,它继承自TextInputControl类。所有文本控件的超类都通过JavaFX API来提供。
图8-1展示了一个附标签的典型文本框
图8-1 标签和文本框
在例8-1中,一个文本框与一个标签组合起来,以便指出应该在文本框中填写什么内容。
例8-1 创建文本框
Label label1 = new Label("Name:");
TextBox textBox = new TextBox ();
HBox hb = new HBox();
hb.getChildren().addAll(label1, textBox);
hb.setSpacing(10);
你可以像例8-1一样的创建一个空的文本框,也可以创建一个有实际文字数据的文本框。要创建一个有特定内容的文本框,请使用TextBox类的这个构造器:TextBox(“Hello World”)。你可以在任何时侯通过调用getText方法来获取文本框的值。
你可以使用TextInputControl类的setColumns方法来设定文本框的尺寸,定义它能接受的最大字符数。
一般地,TextBox对象在表单中来使用,以创建几个文本框。图8-2中的程序显示了3个文本框,并且处理用户的输入。
图8-2 文本框的例子
例8-2中的代码段创建了三个文本框和两个按钮,然后通过使用GridPane容器将它们添加到程序的场景中。当你需要为你的UI实现灵活的布局时,使用容器可提供极大的便利。
例8-2 在程序中添加文本框
//Creating a GridPane container
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10, 10));
grid.setVgap(5);
grid.setHgap(5);
//Defining the Name text field
final TextBox name = new TextBox();
name.setPromptText("Enter your first name.");
name.setColumns(18);
GridPane.setConstraints(name, 0, 0);
grid.getChildren().add(name);
//Defining the Last Name text field
final TextBox lastName = new TextBox();
lastName.setPromptText("Enter your last name.");
GridPane.setConstraints(lastName, 0, 1);
grid.getChildren().add(lastName);
//Defining the Comment text field
final TextBox comment = new TextBox();
comment.setColumns(24);
comment.setPromptText("Enter your comment.");
GridPane.setConstraints(comment, 0, 2);
grid.getChildren().add(comment);
//Defining the Submit button
Button submit = new Button("Submit");
GridPane.setConstraints(submit, 1, 0);
grid.getChildren().add(submit);
//Defining the Clear button
Button clear = new Button("Clear");
GridPane.setConstraints(clear, 1, 1);
grid.getChildren().add(clear);
让我们花点时间来学习这个代码段。name、lastName、comment文本框通过TextBox类的空构造器来创建。与例8-1不同,标签在代码段中没有附着于文本框,取而代之的是使用提示文字来通知用户应该在文本框中输入何种数据。setPromptText方法定义了程序开始时出现在文本框中的文字。当例8-2被添加到程序中时,会产生图8-3所示的输出。
图8-3 具有提示的三个文本框
提示文本和用户键入的内容有所不同,提示文本不能通过getText方法获得。
在真实的应用程序中,输入到文本框中的数据根据特定商业需求的程序逻辑来处理。下节将解释如何使用文本框来评估输入的文本,并向用户产生一个回应。
就像先前提到的一样,用户输入到文本框中的数据可以通过TextInputControl类的getText方法来获得。
研究8-3来学习如何处理文本框对象的数据。
例8-3 为提交和清除按钮定义动作
//Adding a Label
final Label label = new Label();
GridPane.setConstraints(label, 0, 3);
GridPane.setColumnSpan(label, 2);
grid.getChildren().add(label);
//Setting an action for the Submit button
submit.setOnAction(new EventHandler() {
@Override
public void handle(ActionEvent e){
if ((comment.getText() !=null && !comment.getText().isEmpty())) {
label.setText(name.getText() + " " + lastName.getText() +", "
+ "thank you foryour comment!");
} else {
label.setText("Youhave not left a comment.");
}
}
});
//Setting an action for the Clear button
clear.setOnAction(new EventHandler() {
@Override
public void handle(ActionEvent e){
name.clear();
lastName.clear();
comment.clear();
label.setText(null);
}
});
添加到GridPane中的标签控件向用户绘制程序的回应。当用户按下提交按钮时,setOnAction方法检查文本框的内容。如果是非容字符,一个感谢信息被绘制。否则,程序通知用户内容还设有确定,如图8-4所示。
图8-4 文本框为空
当用户按下清除按钮时,清除方法被调用,删除所有三个文本框中的内容。
学习一下你可能在使用文本框时会用到的其他有用的方法。
密码框实现了一个特别的文本输入控件,用户键入的字符被隐藏,取而代之的是显示一个回应字串。图9-1展示了一个有提示语的密码框。
图9-1 有提示的密码框
先用例9-1的代码来创建一个初级的密码框。
例9-1 创建密码框
PasswordBox passwordBox = new PasswordBox();
passwordBox.setColumns(12);
passwordBox.setPromptText("Your password");
在你的用户界面上,可以为密码框指定一个伴随的提示语或增加一个通知标签。和TextBox类一样,PasswordBox提供了setText方法来在控件中绘制文本。然而,用setText方法设定的字串在密码框中会被回应字符掩盖。默认的回应字符是“*”号。图9-2展示了有预定义字符的密码框。
图9-2 设定了文字的密码框
在密码框中键入的值可以通过getText方法获得,你可以在你的程序中处理这个值来设定适当的验证逻辑。
花点时间看下例9-2的代码,它演示了在你的用户界面中实现的密码框。
例9-2 实现验证逻辑
final Label message = new Label("");
VBox vb = new VBox();
vb.setPadding(new Insets(10, 0, 0, 10));
vb.setSpacing(10);
HBox hb = new HBox();
hb.setSpacing(10);
hb.setAlignment(Pos.CENTER_LEFT);
Label label = new Label("Password");
final PasswordBox pb = new PasswordBox();
pb.setColumns(12);
pb.setAction(new Runnable() {
@Override
public void run() {
if(!pb.getText().equals("T2f$Ay!")) {
message.setText("Your password is incorrect!");
message.setTextFill(Color.rgb(210, 39, 30));
} else {
message.setText("Your password has been confirmed.");
message.setTextFill(Color.rgb(21, 117, 84));
}
pb.clear();
}
});
hb.getChildren().addAll(label, pb);
vb.getChildren().addAll(hb, message);
密码框的验证逻辑在setAction实例变量中被定义,变理代表一个函数,当键入的值提交后,它就会被调用。一般而言,当按下回车键时就会被调用。如果键入的值与预设的不一致,相应的红色提示信息会出现。如图9-3.
图9-3 密码不正确
如果键入的值满足预定标准,确认信息出现。如图9-4.
因为安全原因,当值被键入后,一般需要清除密码框中的数据。在例9-2中,认证完成后调用了clear方法来清除。
你可以通过调用PasswordBox类的setEchoChar来更改默认回应符。另一种方法是在定义控件的时侯在PasswordBox类的构造器中指定默认的回应字符。例9-3表现了这两种意图。
例9-3 使用不同的回应符
//Setting an echo character within a constructor
PasswordBox pb1 = new PasswordBox("#");
//Setting an echo character by using the setEchoChar method
PasswordBox pb2 = new PasswordBox();
pb2.setEchoChar("{1}quot;);
另外,例9-4中的setHideDelay方法提供了一个机会来设定一个毫秒级的时间延迟,它可以在键入字符时延迟一定的时间后再显示回应符(注:很Cool的改进,我第一次在我的Android智能手机中体验过)。这样,用户可以在继续前确认键入的内容。
例9-4 为回应符设定延迟
pb.setHideDelay(500);
例9-4为密码框设定一个500毫秒的延迟,当用户在里面键入时,他分辨率会体验到图图9-5所示的感觉。
图9-5 设定延迟后键入密码
ScrollBar类允许你在应用程序中使用滚动的面板和视图。图10-1展示了滚动条的三个区域:滑块、左右(上下)按钮和跟踪器。
图10-1 滚动条元素
让我们看一看例10-1的代码片段。
例10-1 简单的滚动条
ScrollBar sc = new ScrollBar();
sc.setMin(0);
sc.setMax(100);
sc.setValue(50);
setMin和setMax方法定义滚动条所代表的最小、最大值。当用户滑动滑块时,滚动条的值改变。在例10-1中,值是50。因此当程序开始时,滑块位于滚动条的中间。滚动条默认是水平放置的。然而,你可以通过调用setOrientation方法将它设定为垂直放置。
用户可以单击左右按钮(在垂直模式是上下按钮)来滚动一个单位。UNIT_INCREMENT属性定义当用户单击时滚动条滚动的量。另一个方法是单击跟踪区来滑动多个单位值,BLOCK_INCREMENT属性定义了单击跟踪条时滚动条的滚动量。
在你的程序中,你可以使用多种方法来滚动浏览超出可用区域的图形元素。
从动作来理解滚动条。例10-2的程序实现了一个滚动场景来浏览图像。这个程序的任务是让用户浏览垂面板的内容,它比场景的高度大。
例10-2 滚动浏览多个图像
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
final ScrollBar sc = newScrollBar();
final Image[] images = newImage[5];
final ImageView[] pics = newImageView[5];
final VBox vb = new VBox();
DropShadow shadow = newDropShadow();
@Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root, 180, 180);
scene.setFill(Color.BLACK);
stage.setScene(scene);
stage.setTitle("Scrollbar");
root.getChildren().addAll(vb,sc);
shadow.setColor(Color.GREY);
shadow.setOffsetX(2);
shadow.setOffsetY(2);
vb.setLayoutX(5);
vb.setSpacing(10);
sc.setLayoutX(scene.getWidth()-sc.getWidth());
sc.setMin(0);
sc.setOrientation(Orientation.VERTICAL);
sc.setPrefHeight(180);
sc.setMax(360);
for (int i = 0; i < 5;i++) {
final Image image =images[i] =
newImage(getClass().getResourceAsStream(
"fw"+(i+1)+ ".jpg")
);
final ImageView pic =pics[i] =
newImageView(images[i]);
pic.setEffect(shadow);
vb.getChildren().add(pics[i]);
}
sc.valueProperty().addListener(new ChangeListener() {
public voidchanged(ObservableValue extends Number> ov,
Number old_val,Number new_val) {
vb.setLayoutY(-new_val.doubleValue());
}
});
stage.setVisible(true);
}
public static void main(String[]args) {
Application.launch(args);
}
}
当滚动条的Value属性改变时,垂直面板的Y坐标一起改变。因此当滑块移动、单击按钮或跟踪块时,垂直面板移动。如例10-3。
例10-3 垂直滚动条的实现
sc.valueProperty().addListener(new ChangeListener() {
public voidchanged(ObservableValue extends Number> ov,
Number old_val, Numbernew_val) {
vb.setLayoutY(-new_val.doubleValue());
}
});
图10-2 滚动条例子
本程序演示了滚动条的典型应用,你也可以定制这个类在场景中创建滚动条。与其他任何UI控件和结点一样,滚动条可以通过样式来改变默认的实现风格。
考虑一下通过层叠样式表来样式化你的滚动条。例10-4演示了如何直接通过代码来样式化滚动条。
例10-4 将CSS样式应用到滚动条
sc.setStyle(
"-fx-base:lemonchiffon;"
+ "-fx-border-color:darkgreen; "
+"-fx-border-insets: -5; "
+"-fx-border-radius: 10;"
+ "-fx-border-style:dotted;"
+ "-fx-border-width:2;"
+"-fx-background-color: #b6e7c9;"
+"-fx-background-radius: 10;"
);
当把这些样式应用到例10-1中的代码中时,它的外观改变了。请参见图10-3来观察结果。
图10-3 样式化的滚动条
你可以通过在程序中增加css文件,然后在程序中声明相应的类来样式化。
滚动面板为UI要素提供滚动视图。此控件使得用户可以通过平移视图或使用滚动条来滚动组件内容。默认设置的并附加了图像的滚动面板如图11-1所示。
图11-1 滚动面板
例11-1演示了如何创建滚动面板。
例11-1 使用滚动面板来浏览图像
Image roses = newImage(getClass().getResourceAsStream("roses.jpg"));
ScrollPane sp = new ScrollPane();
sp.setNode(new ImageView(roses));
setNode方法定义了作为滚动面板内容使用有结点。你可以仅指定一个结点,要创建有多个组件的滚动视图,可以使用布局容器或Group类。你也可以为setPannable方法指定true值来通过单击并移动鼠标浏览图像,滚动条会作出相应的改变。
ScrollPane类提供了一种策略来决定何时出现滚动条:总是可见、隐藏或需要时出现。使用setHbarPolicy和setVbarPolicy方法来分别决定垂直或水平滚动条的滚动条策略。因此,在例11-2中,会出现垂直滚动条而没有水平滚动条。
例11-2 设定水平及垂直滚动条策略
sp.setHbarPolicy(ScrollBarPolicy.NEVER);
sp.setVbarPolicy(ScrollBarPolicy.ALWAYS);
结果是,你只能垂直滚动图像,如图11-2所示。
图11-2 关闭水平滚动条
设计UI界面的时候,你可能需要去调整组件的尺寸,以更能匹配滚动条的高度和宽度,将setFitToWidth或setFitToHeight方法的参数设为true以匹配实际的尺寸。
图11-3所示的滚动面板包含了单选按钮、文本框和密码框。组件的尺寸超出了滚动面板的预定尺寸,因此滚动条会自动出现。然而,由于将setFitToWidth的参数设为了true,组件在水平位置上自动收缩,所以水平滚动条不会出现。
图11-3 匹配滚动面板的宽度
默认地,FIT_TO_WIDTH和FIT_TO_HEIGHT的属性均为false,这样可调整大小的组件保持原来的尺寸。如果你在此程序中将setFitToWidth方法删除,你将看到图11-4的结果。
图11-4 匹配组件的默认属性
这个滚动面板能让你在水平、垂直方向检索面板内容当前、最小、最大值,你可以在你的程序中学习使用它。
例11-3使用滚动面板来显示内附图像的垂直框,ScrollPane类的VALUE属性帮助你识别当前显示的图像,并绘制图像的文件名。
例11-3 使用滚动面板显示图像
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
final ScrollPane sp = newScrollPane();
final Image[] images = newImage[5];
final ImageView[] pics = newImageView[5];
final VBox vb = new VBox();
final Label fileName = newLabel();
final String [] imageNames = newString [] {"fw1.jpg", "fw2.jpg",
"fw3.jpg","fw4.jpg", "fw5.jpg"};
@Override
public void start(Stage stage) {
VBox box = new VBox();
Scene scene = new Scene(box,180, 180);
stage.setScene(scene);
stage.setTitle("ScrollPane");
box.getChildren().addAll(sp,fileName);
box.setVgrow(sp,Priority.ALWAYS);
fileName.setLayoutX(30);
fileName.setLayoutY(160);
for (int i = 0; i < 5;i++) {
final Image image =images[i] =
new Image(getClass().getResourceAsStream(imageNames[i]));
final ImageView pic =pics[i] =
newImageView(images[i]);
pics[i].setFitWidth(100);
pics[i].setPreserveRatio(true);
vb.getChildren().add(pics[i]);
}
sp.setVmax(440);
sp.setPrefSize(115, 150);
sp.setNode(vb);
sp.vvalueProperty().addListener(new ChangeListener() {
public voidchanged(ObservableValue extends Number> ov,
Number old_val,Number new_val) {
fileName.setText(imageNames[(new_val.intValue() - 1) / 100]);
}
});
stage.setVisible(true);
}
public static void main(String[]args) {
Application.launch(args);
}
}
图11-5 滚动图像
滚动条的最大值与垂直框的高度相等,如下例11-4的代码段绘制已显示图像的文件名称。
例11-4 跟踪滚动面板的垂直移动
sp.vvalueProperty().addListener(new ChangeListener() {
public voidchanged(ObservableValue extends Number> ov,
Number old_val, Numbernew_val) {
fileName.setText(imageNames[(new_val.intValue() - 1) / 100]);
}
});
ImageView对象限制了图像的高度为100像素,这样,new_val_intValue-1除以100后,结果返回在imageNames数组中的当前图像。
在实际使用时,你还可以改变水平和垂直滚动条的最大最小值,这样可以动态更新你的用户界面。
列表视图类代表一个可滚动项目的列表,图12-1展示了所有可选的客房类型。
图12-1 简单列表视图
你可以通过使用setItems方法定义项目的方式来产生列表,你还可以应用setCellFactory方法来创建列表项的视图。
例12-1的代码段实现了用字符列表项实现了图12-1的功能。
例12-1 创建列表视图
ListView list = new ListView();
ObservableList items =FXCollections.observableArrayList (
"Single","Double", "Suite", "Family App");
list.setItems(items);
要改变列表控件的尺寸,可使用setPrefHeight和setPrefWidth方法。例12-2将列表大小改为100×70,结果如图12-2所示。
例12-2 设置列表视图的高和宽
list.setPrefWidth(100);
list.setPrefHeight(70);
图12-2 调整垂直列表大小
你可以将列表视图的定位属性设为Orientation.HORIZONTAL来将它的朝向更改为水平方向。可以这样做:list.setOrientation(Orientation.HORIZONTAL)。包含和图12-1所示的相同列表项的水平列表如图12-3所示。
图12-3 水平列表控制
你可以通过以下方法的组合来获取列表视图中每个列表项的状态。
需要注意的是,选取和聚焦状态是只读的,程序开始后你不能指定哪一个项目被选中和聚焦。
前述的列子代码展示了如何创建一个文字列表项,然而,列表视图可以包含任何结点对象。
研究下列代码来学习如何使用单元格工厂方法产生列表项。例12-3的所示的程序创建一个颜色列表。
例12-3 创建单元格工厂
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application {
ListView list = newListView();
ObservableList data= FXCollections.observableArrayList(
"chocolate","salmon", "gold", "coral",
"darkorchid","darkgoldenrod", "lightsalmon",
"black","rosybrown", "blue", "blueviolet",
"brown"
);
final Label label = new Label();
@Override
public void start(Stage stage) {
VBox box = new VBox();
Scene scene = new Scene(box,200, 200);
stage.setScene(scene);
stage.setTitle("ListViewSample");
box.getChildren().addAll(list, label);
box.setVgrow(list,Priority.ALWAYS);
label.setLayoutX(10);
label.setLayoutY(115);
label.setFont(newFont("Verdana", 20));
list.setItems(data);
list.setCellFactory(newCallback,
ListCell>() {
publicListCell call(ListView p) {
final Rectanglerect = new Rectangle(100, 20);
finalListCell cell = new ListCell() {
@Override publicvoid updateItem(String item,
booleanempty) {
super.updateItem(item, empty);
if (item!= null) {
rect.setFill(Color.web(item));
setNode(rect);
}
}
};
return cell;
}
});
list.setPrefHeight(100);
stage.setVisible(true);
}
public static void main(String[]args) {
Application.launch(args);
}
}
单元格工厂产生ListCell对象,每全单元格与一个单独的数据项联系在一起,绘制列表视图的“一行”,通过setNode(译者注:在第42个构件版本中,这个方法被抛弃了。取而代之的是setGraphics方法)方法设置的单元格的内容可以包含其他控件、文字、形状或图形。在此程序中,列表项表现了矩形的情况。
编译运行这个程序,产生的输出如下:
图12-4 颜色列表
你可以滚动列表,选择或取消列表中的任何项。你还可以扩充这个程序,使用颜色来填充文字标签。
增加例12-4所示的代码段,可以实际项目被选取时产生产生事件。
例12-4 处理列表项的事件
final Label label = new Label();
list.getSelectionModel().selectedItemProperty().addListener(
newChangeListener() {
public void changed(ObservableValue extends String> ov,
String old_val, Stringnew_val) {
label.setText(new_val);
label.setTextFill(Color.web(new_val));
}
});
addListener方法调用selectedItemProperty创建一个新的ChangeListener
图12-5 选择深紫色的情况
JavaFX SDK中设计了几个用于表现表格化数据的类,在JavaFX中用来创建表格的最重要的类是TableView、TableColumn和TableCell类。你可以通过实现数据模型或应用单元格生成器来创建表格。
表格类提供了内建的功能在需要时按列来排序或调整列的宽度。
图13-1展示了一个地址簿中的联系人的典型表格。
图13-1 表格例子
例13-1所示的代码段展示了创建三个列的表格,并将它增加到场景中的情况。
例13-1 增加一个表格
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extendsApplication {
private TableView table = newTableView();
public static void main(String[]args) {
launch(args);
}
@Override
public void start(Stage stage) {
Scene scene = new Scene(newGroup());
stage.setTitle("TableView Sample");
stage.setWidth(400);
stage.setHeight(450);
final Label label = newLabel("Address Book");
label.setFont(new Font("Arial",20));
table.setStyle("-fx-base: #b6e7c9;");
TableColumn firstNameCol =new TableColumn("First Name");
TableColumn lastNameCol = newTableColumn("Last Name");
TableColumn emailCol = newTableColumn("Email");
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.getChildren().addAll(label, table);
vbox.setPadding(newInsets(10, 0, 0, 10));
((Group)scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.setVisible(true);
}
}
表格控件通过实例化TableView类来创建。在例13-1中,表格由VBox容器来管理,然而,你可以将表格直接加入到应用程序的场景中。
例13-1定义了三个列来存储地址簿中联系人的姓、名和邮件地址信息,这些列通过TableColumn类来创建。
TableView类的getColumns()方法将前面创建的列增加到表格中。在你的程序中,你可以通过该方法来动态地增加或删除表格的列。
这个代码段同时使用了setStyle()方法来改变默认的表格外观。
编译并运行此代码,将产生如图13-2的输出。
图13-2 没有数据的表格
你可以通过调查setVisible方法来管理列是否可见。例如,如果你的应用程序的业务逻辑需要隐藏用户的邮件列,可以这样来实现:emailCol.setVisible(false)。
如果你的数据结构需要进行更复杂的表现方式,你可以创建一个嵌套列。
例如,假设地址簿中的联系人有两个email地址。那么就需要两列来展现主要地址和次要地址。你可以通过创建两个子列,再像例13-2所示的在emailCol列上调用getColumns方法来增加。
例13-2 创建嵌套列
TableColumn firstEmailCol = new TableColumn("Primary");
TableColumn secondEmailCol = new TableColumn("Secondary");
emailCol.getColumns().addAll(firstEmailCol, secondEmailCol);
一旦你将这些代码加到例13-1中后,然后编译并运行程序代码,就会产生图13-3的样式。
图13-3 有嵌套列的表格
虽然表格加入到了应用程序中,但由于没有数据,所以会出现一个标准表格标题“表格无数据”。如果要要改变这个标题,你可以调用setPlaceholder方法在空表中指定一个Node对象作为占位符。
在程序中创建表格时,最好的方法是实现一个定义了数据模型,并提供了方法和属性来操作表格的类。例13-3创建了Person类来定义地址簿中的数据。
例13-3 创建Person类
private final StringProperty firstName;
private final StringPropertylastName;
private final StringPropertyemail;
private Person(String fName,String lName, String email) {
this.firstName = newStringProperty(fName);
this.lastName = newStringProperty(lName);
this.email = newStringProperty(email);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(StringfName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(StringfName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(StringfName) {
email.set(fName);
}
}
firstName、lastName、和email被创建来引用实际的数据元素。另外,还为每个数据元素提供了get和set方法。因此可以用getFirstName方法来获得firstName属性的值,setFirstName方法来指定这个属性的值。
当数据模型在Person类中被描述后,你可以创建一个可观察列表(ObservableList)来定义你要在表中展示的任意多个数据行。例13-4的代码段实现了这个任务。
例13-4 在ObservableList中定表表格数据
final ObservableList data = FXCollections.observableArrayList(
new Person("Jacob","Smith", "[email protected]"),
new Person("Isabella","Johnson", "[email protected]"),
new Person("Ethan","Williams", "[email protected]"),
new Person("Emma","Jones", "[email protected]"),
new Person("Michael","Brown", "[email protected]")
);
下一步是将数据与表格的列关联起来。你可以通过为每个数据元素定义的属性来完成。如例13-5。
例13-5 设置列的数据属性
firstNameCol.setProperty("firstName");
lastNameCol.setProperty("lastName");
emailCol.setProperty("email");
定义了数据模型后,数据被填入,并与列形成了关联,你可以用TableView的setItems方法向表中增加数据。如:setItem(data)。
由于ObservableList对象可以跟踪列表内元素的变化,因此当数据改变时TableView会自动更新其内容。
仔细阅读一下例13-6的代码。
例13-6 创建表格并为表格添加数据
import javafx.application.Application;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
public static class Person {
private final StringPropertyfirstName;
private final StringPropertylastName;
private final StringPropertyemail;
private Person(String fName,String lName, String email) {
this.firstName = newStringProperty(fName);
this.lastName = new StringProperty(lName);
this.email = newStringProperty(email);
}
public String getFirstName(){
return firstName.get();
}
public voidsetFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public voidsetLastName(String fName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(StringfName) {
email.set(fName);
}
}
private TableViewtable = new TableView();
private finalObservableList data =
FXCollections.observableArrayList(
newPerson("Jacob", "Smith","[email protected]"),
newPerson("Isabella", "Johnson","[email protected]"),
new Person("Ethan","Williams", "[email protected]"),
newPerson("Emma", "Jones","[email protected]"),
newPerson("Michael", "Brown","[email protected]")
);
public static void main(String[]args) {
launch(args);
}
@Override
public void start(Stage stage) {
Scene scene = new Scene(newGroup());
stage.setTitle("TableView Sample");
stage.setWidth(400);
stage.setHeight(450);
final Label label = newLabel("Address Book");
label.setFont(newFont("Arial", 20));
table.setStyle("-fx-base: #b6e7c9;");
TableColumn firstNameCol =new TableColumn("First Name");
firstNameCol.setProperty("firstName");
TableColumn lastNameCol = newTableColumn("Last Name");
lastNameCol.setProperty("lastName");
TableColumn emailCol = newTableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setProperty("email");
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.getChildren().addAll(label, table);
vbox.setPadding(newInsets(10, 0, 0, 10));
((Group)scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.setVisible(true);
}
}
当你编译并运行这个程序后,产生的输出如图13-4所示:
图13-4 装填了数据的表格
图13-4的表格包含了5行数据,然而,目前为止你还不能修改数据。
你可以使用文本框来键入姓、名、邮件地址的新值。文本框控件可以让你的程序具有接受用户输入的能力。例13-7创建了三个文本框,并为每个文本框定义了掩码,然后创建了一个按钮。
例13-7 使用文本框在表中键入新值
final TextBox addFirstName = new TextBox();
addFirstName.setPromptText("First Name");
addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
final TextBox addLastName = new TextBox();
addLastName.setMaxWidth(lastNameCol.getPrefWidth());
addLastName.setPromptText("Last Name");
final TextBox addEmail = new TextBox();
addEmail.setMinWidth(emailCol.getPrefWidth());
addEmail.setPromptText("Email");
final Button addButton = new Button("Add");
addButton.setOnAction(new EventHandler() {
@Override public voidhandle(ActionEvent e) {
data.add(new Person(
addFirstName.getText(),
addLastName.getText(),
addEmail.getText())
);
addFirstName.clear();
addLastName.clear();
addEmail.clear();
}
});
当用户单击增加按钮时,键入文本框中的值被包含在Person类的构造器中增加到了可观察列表中,这样,新键入的联系人信息就会自动出现在表格中。
请仔细看一下例13-8的代码。
例13-8 包含可键入新联系人的文本框的表格
import javafx.application.Application;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
public static class Person {
private final StringPropertyfirstName;
private final StringPropertylastName;
private final StringPropertyemail;
private Person(String fName,String lName, String email) {
this.firstName = newStringProperty(fName);
this.lastName = newStringProperty(lName);
this.email = newStringProperty(email);
}
public String getFirstName(){
return firstName.get();
}
public voidsetFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public voidsetLastName(String fName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(StringfName) {
email.set(fName);
}
}
private TableViewtable = new TableView();
private finalObservableList data =
FXCollections.observableArrayList(
newPerson("Jacob", "Smith","[email protected]"),
newPerson("Isabella", "Johnson","[email protected]"),
newPerson("Ethan", "Williams","[email protected]"),
new Person("Emma","Jones", "[email protected]"),
newPerson("Michael", "Brown","[email protected]")
);
private HBox hb = new HBox();
public static void main(String[]args) {
launch(args);
}
@Override
public void start(Stage stage) {
Scene scene = new Scene(newGroup());
stage.setTitle("TableView Sample");
stage.setWidth(400);
stage.setHeight(450);
final Label label = newLabel("Address Book");
label.setFont(newFont("Arial", 20));
table.setStyle("-fx-base: #b6e7c9;");
TableColumn firstNameCol =new TableColumn("First");
firstNameCol.setProperty("firstName");
TableColumn lastNameCol = newTableColumn("Last");
lastNameCol.setProperty("lastName");
TableColumn emailCol = newTableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setProperty("email");
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final TextBox addFirstName =new TextBox();
addFirstName.setPromptText("First Name");
addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
final TextBox addLastName =new TextBox();
addLastName.setMaxWidth(lastNameCol.getPrefWidth());
addLastName.setPromptText("Last Name");
final TextBox addEmail = newTextBox();
addEmail.setMinWidth(emailCol.getPrefWidth());
addEmail.setPromptText("Email");
final Button addButton = newButton("Add");
addButton.setOnAction(newEventHandler() {
@Override public voidhandle(ActionEvent e) {
data.add(new Person(
addFirstName.getText(),
addLastName.getText(),
addEmail.getText())
);
addFirstName.clear();
addLastName.clear();
addEmail.clear();
}
});
hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);
hb.setSpacing(3);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.getChildren().addAll(label,table, hb);
vbox.setPadding(newInsets(10, 0, 0, 10));
((Group)scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.setVisible(true);
}
}
Clear方法用于删除文本框中的数据以便增加更多的数据。
这个程序没有提供任何过虑器来检查数据,例如检查邮件格式是否合法。开发你自已的程序时,可以提供这类功能。
现在这个版本也不检查是否有空的输入,如果没有提供值,单击按钮会插入一个空行。
图13-5演示了用户如何增加一行数据。
图13-5 向地址簿中增加联系人信息
图13-6展示了单击增加按钮后的情况,Emma White的信息已经出现在表中了。
图13-6 新增的条目
TableView类提供了内建的列排序功能,用户可以通过单击列标题来改变数据的顺序。第一次单击按升序排列,再一单击按降序排列,第三次单击取消排序。默认不进行排序。
用户可以对表进行多列排序,并可指定在排序时哪一列是排序的主要列。要对多列进行排序,用户可以在要排序的列标题是单击时按下SHIFT键。
在图13-7中,已对“姓”这一列按升序进行了排列,可以再对“名”这一列按降序进行排列。注意第一列比第二列具有更高的优先级,是主排序列。
图13-7 多列排序
如果是开发人员,你可以在程序中通过setSortAscending方法来设置各列的排序选项,为此方法传入“true”的参数为升序排列,如果参数为“false”则为降序。
你也可以通过从TableView.sortOrder观察者列表中增加或删除TableColoumn实例来指定对哪一列进行排序,列表中列的顺序代表排序的优先顺序(例如:第一个优先于第二个)。
如果要禁止排序,可对列调用setSortable(false)方法。
你还可以使用JavaFX SDK提供的FilteredList类为你的表格实现一个过滤器。这个类包装了ObservableList类,提供了方便的过滤功能。创建过滤器时,你要指定数据源和过滤准则(适配器)。它支持LIVE和BATCH过滤模式。FilterableList.FiterMode.LIVE模式为自动过滤。而FilterableList.FiterMode.BATCH模式通过FilterableList.fliter方法的要求来过滤。
SortedList类是ObservableList类的另一种包装器,当新数据加入到表中时,你可以使用它来排序。
TableView类不仅可以绘制表格化的数据,它还提供了编辑的能力。你可以使用TableView.edit(TablePosition)方法来启动编辑器。要取消编辑,可以为编辑器传一个null值。你还可以通过TableCell类来编辑表中的数据,如例13-9所示:
例13-9 实现单元格编辑
class EditingCell extends TableCell {
private final Label label;
private TextBox textBox;
public EditingCell() {
this.label = new Label();
}
@Override
public void startEdit() {
super.startEdit();
if (isEmpty()) {
return;
}
if (textBox == null) {
createTextBox();
} else {
textBox.setText(getItem());
}
setNode(textBox);
textBox.requestFocus();
textBox.selectAll();
}
@Override
public void cancelEdit() {
super.cancelEdit();
setNode(label);
}
@Override
public void commitEdit(Stringt) {
super.commitEdit(t);
setNode(label);
}
@Override
public void updateItem(Stringitem, boolean empty) {
super.updateItem(item,empty);
if (!isEmpty()) {
if (textBox != null){
textBox.setText(item);
}
label.setText(item);
setNode(label);
}
}
private void createTextBox(){
textBox = newTextBox(getItem());
textBox.setOnKeyReleased(new EventHandler() {
@Override public void handle(KeyEventt) {
if (t.getCode()== KeyCode.ENTER) {
commitEdit(textBox.getRawText());
} else if(t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
}
在例13-9中,createTextBox方法使用一个私有的textbox变量来分析键入的值序列,然后在输入ENTER或ESCAPE时分别调用commitEdit或cancelEdit方法。
TableColoumn类的setCellFactory方法用以为单元格安装一个自定义的单元格构造器,这个构造器的作用是在需要时返加新的TableCell实例。例13-10展示了如何为firstName、lastName和emailCol列实现一个单元格构造器。
例13-10 使用单元格构造器
Callback cellFactory =
new Callback() {
public TableCellcall(TableColumn p) {
return new EditingCell();
}
};
如例13-11一样,使用TableColumn类的setOnEditCommit方法来处理单元格内容的变化。这个方法识别修改内容,检索新的值,然后替换可观察列表中当前元素的数据。
例13-11 处理表中的数据编辑
//Modifying the firstName property
firstNameCol.setOnEditCommit(newEventHandler>() {
@Override public voidhandle(EditEvent t) {
((Person)t.getTableView().getItems().get(
t.getTablePosition().getRow())).setFirstName(t.getNewValue());
}
});
//Modifying the lastName property
lastNameCol.setOnEditCommit(newEventHandler>() {
@Override public voidhandle(EditEvent t) {
((Person)t.getTableView().getItems().get(
t.getTablePosition().getRow())).setLastName(t.getNewValue());
}
});
//Modifying the email property
emailCol.setOnEditCommit(new EventHandler>(){
@Override public voidhandle(EditEvent t) {
((Person)t.getTableView().getItems().get(
t.getTablePosition().getRow())).setEmail(t.getNewValue());
}
});
在图13-8中,用户正编辑MichaelBrown的名字。要编辑单元格,用户在单元格中键入新的值,然后按下Enter键。如果不按下Enter键,单元格的值不会被修改。这种行为主要是因为单元格编辑器实现了TextBox类。
图13-8 单元格编辑
JavaFX API提供的Separator类代表一个水平或垂直的线条。它用来在用户界面中分隔组件,它不会产生任何动作。不过,你可以对它进行样式化,增加特效或创建动画。默认地,分隔条是水平的,但可以用setOrientation方法来改变方向。
例14-1的代码段创建了一个水平分隔条和一个垂直分隔条。
例14-1 水平和垂直分隔条
//Horizontal separator
Separator separator1 = new Separator();
//Vertical separator
Separator separator2 = new Separator();
separator2.setOrientation(Orientation.VERTICAL);
Separator类是Node类的子类,因此,它继承了Node类的所有实例变更。
一般而言,分隔条用来分隔UI组件的组,研究一下例14-2的代码段,它将春季复选框和夏季复选框分开。
例14-2 在复选类型间使用分隔条
final String[] names = new String[]{"March", "April","May",
"June","July", "August"};
final CheckBox[] cbs = new CheckBox[names.length];
final Separator separator = new Separator();
final VBox vbox = new VBox();
for (int i = 0; i < names.length; i++) {
cbs[i] = new CheckBox(names[i]);
}
separator.setMaxWidth(40);
separator.setAlignment(Pos.CENTER_LEFT);
vbox.getChildren().addAll(cbs);
vbox.setSpacing(5);
vbox.getChildren().add(3, separator);
这段代码增加到程序中后,产生图14-1的控件样式。
图14-1 复选框和分隔条
分隔条占用了分配给它的水平或垂直位置。setMaxWidth方法用于定义实际的宽度,setVPos方法在已分配的布局空间内指定分隔条的垂直位置。相应的,你可以通过setHpos方法设置水平分隔线的位置。
在例14-2中,分隔条通过党规的add(index,node)方法添加到垂直框中。你可以在你的应用程序中使用这种方法在UI创建后或动态改变后来增加分隔条。
如前所述,分隔条于来分隔UI控件组,你也可以使用它来构造用户界面。考虑一下创建一个如图14-2所示的绘制一个天气预报信息的情况。
图14-2 用分隔条构建天气预报数据
对图14-2所示的程序来说,分隔条用于分隔Label和ImageView对象,请研究一下例14-3所示的程序的代码。
例14-3 在天气预报程序中使用分隔条
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
Button button3 = newButton("Decline");
DropShadow shadow = newDropShadow();
Label caption = newLabel("Weather Forecast");
Label friday = newLabel("Friday");
Label saturday = newLabel("Saturday");
Label sunday = newLabel("Sunday");
@Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root,500, 300);
stage.setScene(scene);
stage.setTitle("Separator Sample");
GridPane grid = newGridPane();
grid.setPadding(newInsets(10, 10, 10, 10));
grid.setVgap(2);
grid.setHgap(5);
scene.setRoot(grid);
Image cloudImage = newImage(getClass().getResourceAsStream("cloud.jpg"));
Image sunImage = newImage(getClass().getResourceAsStream("sun.jpg"));
caption.setFont(Font.font("Verdana", 20));
GridPane.setConstraints(caption, 0, 0);
grid.setColumnSpan(caption,8);
grid.getChildren().add(caption);
final Separator sepHor = newSeparator();
sepHor.setVpos(VPos.CENTER);
GridPane.setConstraints(sepHor, 0, 1);
grid.setColumnSpan(sepHor,7);
grid.getChildren().add(sepHor);
friday.setFont(Font.font("Verdana", 18));
GridPane.setConstraints(friday, 0, 2);
grid.setColumnSpan(friday,2);
grid.getChildren().add(friday);
final Separator sepVert1 =new Separator();
sepVert1.setOrientation(Orientation.VERTICAL);
sepVert1.setVpos(VPos.CENTER);
sepVert1.setPrefHeight(80);
GridPane.setConstraints(sepVert1, 2, 2);
grid.setRowSpan(sepVert1, 2);
grid.getChildren().add(sepVert1);
saturday.setFont(Font.font("Verdana",18));
GridPane.setConstraints(saturday, 3, 2);
grid.setColumnSpan(saturday,2);
grid.getChildren().add(saturday);
final Separator sepVert2 =new Separator();
sepVert2.setOrientation(Orientation.VERTICAL);
sepVert2.setVpos(VPos.CENTER);
sepVert2.setPrefHeight(80);
GridPane.setConstraints(sepVert2, 5, 2);
grid.setRowSpan(sepVert2, 2);
grid.getChildren().add(sepVert2);
sunday.setFont(Font.font("Verdana", 18));
GridPane.setConstraints(sunday, 6, 2);
grid.setColumnSpan(sunday,2);
grid.getChildren().add(sunday);
final ImageView cloud = newImageView(cloudImage);
GridPane.setConstraints(cloud,0, 3);
grid.getChildren().add(cloud);
final Label t1 = newLabel("16");
t1.setFont(Font.font("Verdana", 20));
GridPane.setConstraints(t1,1, 3);
grid.getChildren().add(t1);
final ImageView sun1 = newImageView(sunImage);
GridPane.setConstraints(sun1,3, 3);
grid.getChildren().add(sun1);
final Label t2 = newLabel("18");
t2.setFont(Font.font("Verdana", 20));
GridPane.setConstraints(t2,4, 3);
grid.getChildren().add(t2);
final ImageView sun2 = newImageView(sunImage);
GridPane.setConstraints(sun2,6, 3);
grid.getChildren().add(sun2);
final Label t3 = new Label("20");
t3.setFont(Font.font("Verdana", 20));
GridPane.setConstraints(t3,7, 3);
grid.getChildren().add(t3);
stage.setVisible(true);
}
public static void main(String[]args) {
Application.launch(args);
}
}
这个程序同时使用了水平和垂直分隔条,并且它们跨越了GridPane容器的行和列,在你的程序中,也可以为分隔条设定推荐的长度(对水平分隔条是长度,对垂直分隔条是高度),以便当用户界面的大小被调整时它能动态地改变。你还可以通过Separator对象提供的CSS类来为分隔条增加额外的可视化效果。
要为例14-3中的所有分隔条使用相同的样式,你可以创建一个CSS文件(例如:controlStyle.css),然后将它保存在与主类(main class)相同的包内。例14-4演示了可以增加到controlSytle文件中的CSS类。
例14-4 使用CSS类样式化分隔条
/*controlStyle.css */
.separator{
-fx-background-color: #e79423;
-fx-background-radius: 2;
-fx-background-insets: -2;
}
你可以在程序中通过getStylesheets方法来启用样式化的分隔条。如例14-5所示:
例14-5 在JavaFX程序中启用样式表
scene.getStylesheets().add("/separatorsample/controlStyle.css");
图14-3 展示了程序修改后分隔条在天气预报程序中的样子。
图14-3 样式化的分隔条
滑动条代表一种在表现和交互一定范围内的数值的控件,它包括一个跟踪条和一个滑动块。也可以包含表示数值范围的刻度和刻度标签。图15-1展示了典型的滑动条和它的主要构件。
图15-1 滑动条的构件
让我们花点时间来研究一下例15-1的代码段,它产生的输出如图15-1所示。
例15-1 创建滑动条
Slider slider = new Slider();
slider.setMin(0);
slider.setMax(100);
slider.setValue(40);
slider.setShowTickLabels(true);
slider.setShowTickMarks(true);
slider.setMajorTickUnit(50);
slider.setMinorTickCount(5);
slider.setBlockIncrement(10);
setMin和setMax方法分别定义了滑动条代表的最大和最小数值。setValue方法指定了滑动条的当前值,这个值永远比最大值小,比最小值大。当程序运行时,使用这个方法来指定滑动块的位置。
两个布尔方法,setShowTickMarks和setShowTickLabels定义了滑动块的外观。在例15-1中,刻度和标签已经启用。另外,主要刻度的间距设为了50,两个主要刻度间的次要刻度的间距设为了5。你可以将setSnapToTicks方法的参数设为true来保证滑动块的值正好位于刻度上。
setBlockIncrement方法定义了用户在跟踪条上单击时滑动块移动的距离。在例15-1中,这个值是10,滑动块会向单击方向移动10个单位。
现在查看一下图15-2,这个程序使用了三个滑动条来编辑图片的绘制属性。每个滑动条调整一个实际的可视化属性:透明度,棕褐色调值和比例因子。
图15-2 三个滑动条
例15-2展示了这个程序的源代码
例15-2 滑动条例子
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.effect.SepiaTone;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
final Slider opacityLevel = newSlider(0, 1, 1);
final Slider sepiaTone = newSlider(0, 1, 1);
final Slider scaling = new Slider(0.5, 1, 1);
final Image image = new Image(getClass().getResourceAsStream(
"cappuccino.jpg")
);
final Label opacityCaption = newLabel("Opacity Level:");
final Label sepiaCaption = newLabel("Sepia Tone:");
final Label scalingCaption = newLabel("Scaling Factor:");
final Label opacityValue = newLabel(
Double.toString(opacityLevel.getValue()));
final Label sepiaValue = newLabel(
Double.toString(sepiaTone.getValue()));
final Label scalingValue = newLabel(
Double.toString(scaling.getValue()));
final static Color textColor =Color.WHITE;
final static SepiaTonesepiaEffect = new SepiaTone();
@Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root,480, 400);
stage.setScene(scene);
stage.setTitle("SliderSample");
scene.setFill(Color.BLACK);
GridPane grid = newGridPane();
grid.setPadding(newInsets(10, 10, 10, 10));
grid.setVgap(10);
grid.setHgap(70);
final ImageView cappuccino =new ImageView (image);
cappuccino.setEffect(sepiaEffect);
GridPane.setConstraints(cappuccino, 0, 0);
grid.setColumnSpan(cappuccino, 3);
grid.getChildren().add(cappuccino);
scene.setRoot(grid);
opacityCaption.setTextFill(textColor);
GridPane.setConstraints(opacityCaption, 0, 1);
grid.getChildren().add(opacityCaption);
opacityLevel.valueProperty().addListener(newChangeListener() {
public voidchanged(ObservableValue extends Number> ov,
Number old_val,Number new_val) {
cappuccino.setOpacity(new_val.doubleValue());
opacityValue.setText(String.format("%.2f", new_val));
}
});
GridPane.setConstraints(opacityLevel, 1, 1);
grid.getChildren().add(opacityLevel);
opacityValue.setTextFill(textColor);
GridPane.setConstraints(opacityValue, 2, 1);
grid.getChildren().add(opacityValue);
sepiaCaption.setTextFill(textColor);
GridPane.setConstraints(sepiaCaption, 0, 2);
grid.getChildren().add(sepiaCaption);
sepiaTone.valueProperty().addListener(new ChangeListener(){
public voidchanged(ObservableValue extends Number> ov,
Number old_val,Number new_val) {
sepiaEffect.setLevel(new_val.doubleValue());
sepiaValue.setText(String.format("%.2f", new_val));
}
});
GridPane.setConstraints(sepiaTone, 1, 2);
grid.getChildren().add(sepiaTone);
sepiaValue.setTextFill(textColor);
GridPane.setConstraints(sepiaValue, 2, 2);
grid.getChildren().add(sepiaValue);
scalingCaption.setTextFill(textColor);
GridPane.setConstraints(scalingCaption, 0, 3);
grid.getChildren().add(scalingCaption);
scaling.valueProperty().addListener(new ChangeListener() {
public voidchanged(ObservableValue extends Number> ov,
Number old_val,Number new_val) {
cappuccino.setScaleX(new_val.doubleValue());
cappuccino.setScaleY(new_val.doubleValue());
scalingValue.setText(String.format("%.2f", new_val));
}
});
GridPane.setConstraints(scaling, 1, 3);
grid.getChildren().add(scaling);
scalingValue.setTextFill(textColor);
GridPane.setConstraints(scalingValue, 2, 3);
grid.getChildren().add(scalingValue);
stage.setVisible(true);
}
public static void main(String[]args) {
Application.launch(args);
}
}
ImageView对象的透明度属性根据名为透明级的第一个滑动条的值来改变,棕褐色调特效的级别根据棕褐色调滑动条的值来确定,第三个滑动条通过传递参数给setScaleX和setScaleY方法定义了图片的比例因子。
例13-5的代码段演示了由滑动条的getValue方法返回的Double值转为String的方法,还展示了应用格式化将滑动条的浮点值按两位小数来绘制的方法。
例15-3 格式化绘制滑动条的值
scalingValue.setText((Double.toString(value)).format("%.2f",value));
下一步可以通过应用可视化特效或CSS样式来改善滑动条的样式。
ProgressIndicator类和它的直接子类ProgressBar提供了指示特定任务正在运行并检测任务运行进度的能力,ProgressBar将进度绘制为一个进度条,ProgressIndicator将进度绘制为一个动态改奕的饼图,如图16-1所示:
图16-1 进度条和进度指示器
可在你的应用程序中使用例16-1中的代码段来插入一个进度控件。
例16-1 进度条和进度指示器的实现
ProgressBar pb = new ProgressBar(0.6);
ProgressIndicator pi = new ProgressIndicator(0.6);
你也可以通过无参数的空构造器来创建进度控件,在这种情况下,可以通过使用setProgress方法来赋值。另上种实例化进度控件的方法是使用ProgressBarBuilder类,它通过build和progress提供了相同的方法。关于本类的更多信息,请参阅API文档。
有时程序无法知道一个任务的准确的完成时间,在这种情况下,进度控件提供一个动态指示直到任务的长度能准确决定。图16-2展示了根据不同的进度值绘制的各种不同的进度状态。
图16-2 进度控件的不同状态
例16-2 展示了这个程序的源代码
例16-2 开启进度控件的不同状态
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extendsApplication {
final Float[] values = new Float[] {-1.0f, 0f, 0.6f, 1.0f};
final Label [] labels = new Label[values.length];
final ProgressBar[] pbs = new ProgressBar[values.length];
final ProgressIndicator[] pins = new ProgressIndicator[values.length];
final HBox hbs [] = new HBox [values.length];
@Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root,300, 150);
scene.getStylesheets().add("/progresssample/Style.css");
stage.setScene(scene);
stage.setTitle("ProgressControls");
for (int i = 0; i
设为0到1之间的正值用来表表进度控件的百分比,例如:0.4表示40%。负值表示进度条的完成时间不确定。使用isIndeterminate方法来检相进度控制是否属不确定状态。
图16-2展现了进度控制的一般状态,在现实的应用程序中,进度值可以通过其他其他UI组件获取。
研究一下例16-3,学习如何根据滑动条位置为进度条和进度指示器设定值。
例16-3 从滑动条接受进度值
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.Slider;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extendsApplication {
@Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = newScene(root);
stage.setScene(scene);
stage.setTitle("ProgressControls");
final Slider slider = newSlider();
slider.setMin(0);
slider.setMax(50);
final ProgressBar pb = newProgressBar(0);
final ProgressIndicator pi =new ProgressIndicator(0);
slider.valueProperty().addListener(new ChangeListener() {
public voidchanged(ObservableValue extends Number> ov,
Number old_val,Number new_val) {
pb.setProgress(new_val.doubleValue()/50);
pi.setProgress(new_val.doubleValue()/50);
}
});
final HBox hb = new HBox();
hb.setSpacing(5);
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(slider, pb, pi);
scene.setRoot(hb);
stage.show();
}
public static void main(String[]args) {
launch(args);
}
}
可以编译并运行这个程序,产生的输出如图16-3所示。
图16-3 通过滑动条设定进度指示
ChangeListenner对象决定滑动条的值是否改变,并计算进度条和进度指示器的值,以更进度控制的值范围在0.0到1.0之间。
超链接类代表另一种类型的Labeled控件,用于将格式文本作为超链接使用。图17-1演示了超链接实现的三种状态。
图17-1 超链接控件的三种状态
产生超链接的代码如例17-1所示。
例17-1 典型的超链接
Hyperlink link = new Hyperlink();
link.setText("http://example.com");
link.setOnAction(new EventHandler() {
@Override
public void handle(ActionEvent e){
System.out.println("Thislink is clicked");
}
});
setText实例方法定义了超链接的标签,由于超链接继承自Labeled类,你可以设置特定的字体和文本。setOnAction方法设定了动作,当超链接被单击时,动作方法会被调用,与Button类相应方法类似。在例17-1中,此方法被限制用于打印一个字串,然而,你可以设为实现一个更通用的任务。
图17-2的程序绘制了本地目录中的图像。
图17-2 浏览图像
该程序的代码如下。
例17-2 使用超链接浏览图像
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
final static String[] imageFiles = new String[]{
"product.png",
"education.png",
"partners.png",
"support.png"
};
final static String[] captions =new String[]{
"Products",
"Education",
"Partners",
"Support"
};
final ImageView selectedImage =new ImageView();
final ScrollPane list = newScrollPane();
final Hyperlink[] hpls = newHyperlink[captions.length];
final Image[] images = newImage[imageFiles.length];
public static void main(String[]args) {
Application.launch(args);
}
@Override
public void start(Stage stage) {
Scene scene = new Scene(newGroup());
stage.setTitle("Hyperlink Sample");
stage.setWidth(300);
stage.setHeight(200);
selectedImage.setLayoutX(100);
selectedImage.setLayoutY(10);
for (int i = 0; i () {
@Override
public voidhandle(ActionEvent e) {
selectedImage.setImage(image);
}
});
}
final Button button = newButton("Refresh links");
button.setOnAction(newEventHandler() {
@Override
public voidhandle(ActionEvent e) {
for (int i = 0; i< captions.length; i++) {
hpls[i].setVisited(false);
selectedImage.setImage(null);
}
}
});
VBox vbox = new VBox();
vbox.getChildren().addAll(hpls);
vbox.getChildren().add(button);
vbox.setSpacing(5);
((Group)scene.getRoot()).getChildren().addAll(vbox, selectedImage);
stage.setScene(scene);
stage.show();
}
}
该程序在for循环内创建了4个超链接对象,供每个超链接调用的setOnAction方法定义了用户单击超链接时的行为。在本例中,inages数组中的相应图像被赋给了selectedImage变量。
当用户单击超链接时,它被标记为已访问。你可以使用超链接类的setVisited方法来更新链接。例17-3的代码完成这项工作。
例17-3 更新链接
final Button button = new Button("Refresh links");
button.setOnAction(new EventHandler() {
@Override
public void handle(ActionEvent e){
for (int i = 0; i
单击更新链接按钮时,所有的超链接变为未访问状态,如图17-3所示。
图17-3 未访问链接
由于超链接类继承自Labeled类,你不公可以设定文本标签,还可以设置图像。下节提供的代码使用了文本和图像来创建超链接,并加载了远程HTML页面。
你可以在程序中场景嵌入WebView浏览器来绘制HTML内容。WebView组件提供了基本的Web页面浏览功能。它可以绘制Web页面内容,提供如超链接交互和执行JavaScript代码的能力。
研究一下例17-4的程序,它创建了4个有文本和图像的超链接,当超链接被单击时,相应的值作为URL链接至嵌入的浏览器中。
例17-4 调用远程Web页面。
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class Main extends Application {
final static String[] imageFiles= new String[]{
"product.png",
"education.png",
"partners.png",
"support.png"
};
final static String[] captions =new String[]{
"Products",
"Education",
"Partners",
"Support"
};
final static String[] urls = newString[]{
"http://www.oracle.com/us/products/index.html",
"http://education.oracle.com/",
"http://www.oracle.com/partners/index.html",
"http://www.oracle.com/us/support/index.html"
};
final ImageView selectedImage =new ImageView();
final Hyperlink[] hpls = newHyperlink[captions.length];
final Image[] images = newImage[imageFiles.length];
public static void main(String[]args){
launch(args);
}
@Override
public void start(Stage stage) {
VBox vbox = new VBox();
Scene scene = newScene(vbox);
stage.setTitle("Hyperlink Sample");
stage.setWidth(570);
stage.setHeight(550);
selectedImage.setLayoutX(100);
selectedImage.setLayoutY(10);
final WebView browser = newWebView();
final WebEngine webEngine =browser.getEngine();
for (int i = 0; i () {
@Override
public voidhandle(ActionEvent e) {
webEngine.load(url);
}
});
}
HBox hbox = new HBox();
hbox.getChildren().addAll(hpls);
vbox.getChildren().addAll(hbox, browser);
VBox.setVgrow(browser,Priority.ALWAYS);
stage.setScene(scene);
stage.show();
}
}
超链接在for循环中被创建,如例17-2一样。超链接的动作通过ruls数组中相应的URL访问页面,显示于内嵌浏览器的WebEngine对象中。
编译并运行程序,产生的窗口如图17-4所示。
图17-4 从Oracle专业网站调用页面
工具提示代表一种常规的UI组件,一般用来显不关于UI控件的其他信息。当鼠标在控件上悬停时,工具提示会显示出来。工具提示只能通过调用控件的setTooltip来设定。
工具提示有二种状态:活动和显示。鼠标在控件上时,它被激活,当工具提供处于显示状态时,它才实际出现。显示出来的工具提示同时也时活动的。从活动到显示通过会有一定的延迟时间。
具有工具提示的密码框如图18-1所示。
图18-1 加入工具提示的密码框
研究一下例18-1中的代码,它创建了前述的图中所展现的带有工具提示的密码框。
例18-1 为密码框增加工具提示
final PasswordField pf = new PasswordField();
final Tooltip tooltip = new Tooltip();
tooltip.setText(
"\nYour password mustbe\n" +
"at least 8 characters inlength\n" +
);
pf.setTooltip(tooltip);
javafx.scene.control包中的每个控件都有setTooltip方法用于设定工具提示。你可以通过Tooptip类的构造器或使用setText方法为工具提示指定提示文本。
由于工具所示类扩展自Labeled类,你还可以设定图标。例18-2的代码为密码框设定了带有图标的工具提示。
例18-2 为工具提示增加图标
Image image = new Image(
getClass().getResourceAsStream("warn.png")
);
tooltip.setGraphic(new ImageView(image));
将上述代码增加到程序中编译运行后,图18-2会出现。
图18-2 带图标的工具提示
工具提示不仅可以包含辅助信息,还可以表现数据。
图18-3的程序使用工具提示中的信息计算旅馆费用。
图18-3 计算旅馆费用
每个复选框都有一个相应的工具提示,每个工具提示显示了实际的登记费用。如果用户选择了一个复选框,相应的值增加到汇总费用中,反之则从汇总费用中扣除。
例18-3 使用工具提示来计算旅馆费用
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
final static String[] rooms = newString[]{
"Accommodation(BB)",
"Half Board",
"Late Check-out",
"Extra Bed"
};
final static Integer[] rates =new Integer[]{
100, 20, 10, 30
};
final CheckBox[] cbs = newCheckBox[rooms.length];
final Label total = newLabel("Total: $0");
Integer sum = 0;
public static void main(String[]args) {
launch(args);
}
@Override
public void start(Stage stage) {
Scene scene = new Scene(newGroup());
stage.setTitle("TooltipSample");
stage.setWidth(300);
stage.setHeight(150);
total.setFont(newFont("Arial", 20));
for (int i = 0; i () {
public voidchanged(ObservableValue extends Boolean> ov,
Boolean old_val,Boolean new_val) {
if(cb.isSelected()) {
sum = sum +rate;
} else {
sum = sum -rate;
}
total.setText("Total: {1}quot; + sum.toString());
}
});
}
VBox vbox = new VBox();
vbox.getChildren().addAll(cbs);
vbox.setSpacing(5);
HBox root = new HBox();
root.getChildren().add(vbox);
root.getChildren().add(total);
root.setSpacing(40);
root.setPadding(newInsets(20, 10, 10, 20));
((Group) scene.getRoot()).getChildren().add(root);
stage.setScene(scene);
stage.show();
}
}
例18-4中的代码行在例18-3中被用来创建工具提示并将文本赋予它,选项价格的Integer值转换为String值。
例18-4 为工具提示设定值
final Tooltip tooltip = new Tooltip("{1}quot; + rates[i].toString())
你可通过使用CSS为工具提示设定样式。
HTMLEditor控件是一个全功能的富文本编辑器,除基本编辑功能外,它不支持以下特点:
图19-1展示了增加到JavaFX程序中的富文本编辑器
图19-1 HTML编辑器
HTMLEditor类以HTML字串的形式表示编辑内容,例如:在图19-1的编辑器中键入的内容用下列的字串表示,"