使用JavaFX2.0的控件

关于本教程

本教程覆盖了JavaFX API中内置的JavaFX UI控件。

本文包含以下章节:

  • JavaFX的UI控件
  •  标签
  • 按钮
  • 单选按钮
  • 切换按钮
  • 复选框
  • 选择框
  • 文本框
  • 密码框
  • 滚动条
  • 滚动窗格
  • 列表视图
  • 表格
  • 分隔条
  •  滑块
  •  进度条和进度指示器
  •  超链接
  • 工具提示
  • HTML编辑器
  • 标题面板和可折叠面板

每章提供了代码示例和应用程序来说明实际的UI控件的功能。你可以找到上表列出的应用程​的源文件和对应的NetBeans项目文件。

JavaFX UI控件

         通过JavaFX API提供的JavaFX UI控件通过场景中的结点来构建,因此,控件可以使用JavaFX平台的丰富的视觉功能。由于JavaFX API是完全实现自Java的,因此你可以方便地将JavaFX UI控件集成到已有的Java应用程序中。

1 JavaFX2.0中支持的UI控件

         构建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;

[java]  view plain copy
  1. import javafx.animation.KeyValue;  
  2. import javafx.animation.Timeline;  
  3. import javafx.util.Duration;  
  4. import javafx.scene.control.Button;  
  5. import javafx.scene.text.Font;  
  6. import javafx.scene.effect.Reflection;  
  7.    
  8. Button button = new Button();  
  9.     button.setText("OK");  
  10.     button.setFont(newFont("Tahoma"24));  
  11.     button.setEffect(newReflection());  
  12.    
  13. final Timeline timeline = new Timeline();  
  14.      //将循环次数设为永久  
  15.    timeline.setCycleCount(Timeline.INDEFINITE);  
  16.     timeline.setAutoReverse(true);  
  17.     final KeyValue kv = newKeyValue(button.opacityProperty(), 0);  
  18.     final KeyFrame kf = newKeyFrame(Duration.valueOf(600), kv);  
  19.     timeline.getKeyFrames().add(kf);  
  20.     timeline.play();  

你还可以在上例中使用javafx.scene.effect包中的其他可视效果,如阴影、光照或者动态模糊。

用CSS设定UI控件的风格

         你可以通过层叠样式表(CSS)定制内置的UI控件的外观,在JavaFX中使用CSS与在HTML中基本相同,因为都使用相同的CSS规范。控件的可视状态通过例1-2的.css文件来定义。

         例1-2:在CSS文件中定义UI控件的样式

[css]  view plain copy
  1. /*controlStyle.css */  
  2.    
  3. .scene{  
  4.     -fx-font14pt "CambriaBold";  
  5.     -fx-color#e79423;  
  6.     -fx-background#67644e;  
  7. }  
  8.    
  9. .button{  
  10.     -fx-text-fill: #006464;  
  11.     -fx-background-color#e79423;  
  12.     -fx-border-radius: 20;  
  13.     -fx-background-radius: 20;  
  14.     -fx-padding5;  
  15. }  

         你可以通过例1-3所示的方法,用Scene类中的getStylesheets方法在应用程序中开启特定的样式。

         例1-3:应用CSS

[java]  view plain copy
  1. Scene scene = new Scene();  
  2. scene.getStylesheets().add("/uicontrolssample/controlStyle.css");  

         因为HTML中的每个元素有自已的ID和类属性,因此JavaFX程序中的每个控件都可通过setId()和setStyleClass()方法获得ID和样式类。此外,你还可以直接在程序中定义控件的样式。

         例1-4中为切换按钮定义的-fx-base属性覆盖了在.css文件中为场景中所有控件统一定义的相应属性。

         例1-4:在JavaFX程序中定义切换按钮的样式

[java]  view plain copy
  1. ToggleButton tb3 = new ToggleButton ("I don't know");  
  2. tb3.setStyle("-fx-base: #ed1c24");  

         图1-4展示了切换按钮加入到程序中时的样式。


图1-4 将CSS样式应用到切换按钮

图表

         作为对典型UI界面元素的补充,JavaFXSDK在javafx.scene.chart包中提供了现成的图表控件。该控件支持的图表种类为:面积图、柱状图、气泡图、折线图、饼图和散点图。图表可包含多个系列。

         图1-5展示了进口水果的饼图


图1-5 饼图

         不同于其他的Java客户端工具包。使用JavaFXSDK,你可以在你的程序中仅用很少的代码就可以创建这样的图表。例1-5展示了这种特性。

例1-5:创建图表

[java]  view plain copy
  1. ObservableList pieChartData =  
  2.    FXCollections.observableArrayList(  
  3.         newPieChart.Data("Grapefruit"13),  
  4.         newPieChart.Data("Oranges"25),  
  5.         newPieChart.Data("Plums"10),  
  6.         newPieChart.Data("Pears"22),  
  7.         newPieChart.Data("Apples"30)  
  8. );  
  9. PieChart chart = new PieChart(pieChartData);  
  10. chart.setTitle("Imported Fruits");  

以上的代码段包含以容:
  • 通过PieChart.Data类生成数据
  • 图表通过PieChart类的实例来生成
  • 创建的图表包含了标题

将JavaFX2.0控件集成到Swing中

你可以将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”教程来了解更多信息。

2 标签控件

标签控件位于JavaFX API的javafx.scene.control包中,它扩展了标签类。使用标签可以显示文本元素。你可以将一段文本包装在一个特定的空间内,还可以为文本添加一个图像。

图2-1展示了三种普通的标签用例,左边的标签是一个带图像的文本,中间的展示了旋转后的文本,右边的标签绘制了一段文本。


图2-1 标签例子

创建标签

JavaFX API提供了三种标签类的构造器来创建标签,如例2-1所示。

例2-1 创建标签

[java]  view plain copy
  1. //空标签  
  2. Label label1 = new Label();  
  3. //纯文字标签  
  4. Label label2 = new Label("Search");  
  5. //带图标和文字的标签  
  6. Image image = new Image(getClass().getResourceAsStream("labels.jpg"));  
  7. Label label3 = new Label("Search"new ImageView(image));  

一旦在代码中创建了标签,你可以通过标签类的以下方法向标签对象增加文字或图像内容。

  • setText(String text)方法—为标签对象指定文字内容
  • setGraphic(Node graphic)—指定图标

setTextFill方法指定颜色来绘制标签的文本内容,研究一下例2-2,它首先创建了一个标签,然后加了一个图标、并指定了文本的绘制颜色。

例2-2 向标签增加图标和文本颜色

[java]  view plain copy
  1. Label label1 = new Label("Search");  
  2. Image image = newImage(getClass().getResourceAsStream("labels.jpg"));  
  3. label1.setGraphic(new ImageView(image));  
  4. label1.setTextFill(Color.web("#0076a3"));  

将这段代码加到程序中后,将产生图2-2所示的标签效果。


图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 应用字体设置

[java]  view plain copy
  1. //使用字体裁类的构造方法  
  2. label1.setFont(new Font("Arial"30));  
  3. //使用字体类的方法  
  4. label2.setFont(Font.font("Cambria"32));  

或者,你还可以通过层叠样式表(CSS)为标签控件指定字体设定。研究一下例2-4就可明白如何为label1和label2指定相同的字体设定。

例2-4 设定标签样式

[java]  view plain copy
  1. label1.setStyle("-fx-font: 30 arial");  
  2. label2.setStyle("-fx-font: 32 cambria");  

多行文本

当你创建一个标签时,有时你必须将标签放置在一个比绘制区小的空间里,将文本打断(换行),以便以布局大小相适应,为setWrapText方法指定true值就可实现这种需求。如例2-5所示:

例2-5 开启文本换行

[java]  view plain copy
  1. Label label3 = new Label("A label that needs to be wrapped");  
  2. label3.setWrapText(true);  

当label3被加入到程序的内容中,就会像图2-3所示的效果一样被绘制。


图2-3 换行标签

由于标签的布局大小不仅被它的宽度所限制,也被高度所限制。当不可能绘制整个字串时,你可以指定它的表现形式。使用标签类的setTextOverrun方法,并选择有效的OverrunStyle类型来处理不能被正确地绘制的那些文本。请参阅API文档来了解OverrunStyle类型的更多信息。

应用特效

虽然标签属静态文本,无法被编辑。但你可以使用可视化效果或者变换。例2-6的代码段将label2旋转了270度,并将变换了纵向位置。

例2-6 旋转标签

[java]  view plain copy
  1. Label label2 = new Label ("Values");  
  2. label2.setFont(new Font("Cambria"32));  
  3. label2.setRotate(270);  
  4. label2.setTranslateY(50);  

旋转和位移都是JavaFX API中典型的变换方法。此外,你还可以设置一种效果,当鼠标停留在标签上时会将标签放大。

例2-7中的代码段对label3使用了放大效果。当标签的MOUSE_ENTERED事件被触发时,比例因子1.5应用到了setScaleX和setScaleY方法,当用户将鼠标从标签上移开时,MOUSE_EXITED事件发生,比例因子重设为1.0,标签使用原来的大小来绘制。

例2-7 应用放大效果

[java]  view plain copy
  1. label3.setOnMouseEntered(new EventHandler() {  
  2.     @Override public voidhandle(MouseEvent e) {  
  3.         label3.setScaleX(1.5);  
  4.         label3.setScaleY(1.5);  
  5.     }  
  6. });  
  7.    
  8. label3.setOnMouseExited(new EventHandler() {  
  9.     @Override public voidhandle(MouseEvent e) {  
  10.         label3.setScaleX(1);  
  11.         label3.setScaleY(1);  
  12.     }  
  13. });  

图2-4展示了label3的两种状态


图2-4 放大标签

相关API

  • Label
  • Labeled

3 按钮控件

通过JavaFX API提供的Button类的方法,开发一员可以在用户按下一个按钮时来处理相应的动作。Button类继承自Label类,它可以显示文本、图像者两者相结合。图3-1展示了不同的效果。本章将学习如何创建这些不同类型的按钮。


图3-1 按钮的种类

创建一个按钮

         你可以在JavaFX程序中通过例3-1展示的三种构造方法来创建按钮控件。

         例3-1 创建按钮

[java]  view plain copy
  1. //空按钮  
  2. Button button1 = new Button();  
  3. //使用特定本文为标题的按钮  
  4. Button button2 = new Button("Accept");  
  5. //使用标题和图标的按钮  
  6. Image imageOk = newImage(getClass().getResourceAsStream("ok.png"));  
  7. Button button3 = new Button("Accept"new ImageView(imageOk));  

         由于Button类继承自Label类,你可以通过以下的方法来为未指定图标或标题的按钮指定按钮的内容。
  • setText(String text)—为按钮指定标题
  • setGraphic(Node graphic)—指定图标

例3-2展示了如何创建一个只有图标没有标题的按钮

[java]  view plain copy
  1. Image imageDecline = newImage(getClass().getResourceAsStream("not.png"));  
  2. Button button5 = new Button();  
  3. button5.setGraphic(new ImageView(imageDecline));  

当按钮被加入到程 序中,这段代码产生的按钮如图3-2.


图3-2 带图标的按钮

在例3-2和图3-2中,图标是一个ImageView对象。然而,你可以使用其他的图形对象,例如,可以使用javafx.scene.shape包中的形状。当同时为按钮定义文本和图形后,你可以使用setGraphicTextGap方法来设定两者的间距。

Button类的默认皮肤区分了以下的可视化状态。图3-3展示了一个带图标的按钮的默认状态。


图3-3 按钮状态

赋予动作

按钮的主要功能是在单击时产生一个动作,使用Button类的setOnAction方法可以定义当用户单击按钮时将要完成的事情,例3-3展示了为button2定义动作的代码片段。

         例3-3 为按钮定义动作

[java]  view plain copy
  1. button2.setOnAction(new EventHandler() {  
  2.     @Override public voidhandle(ActionEvent e) {  
  3.        label.setText("Accepted");  
  4.     }  
  5. });  

ActionEvent是一个被EventHandler处理的事件类型,EventHandler对象提供了handle方法来处理按钮触发的动作,例3-3展示了如何覆写handle方法,以更当用户按下button2时标签文本可以设为“Accepted”。

你可以使用Button类来根据需要设定很多事件—处理(event--handling)方法,以便引发特定的行为或应用可视化特效。

应用特效

因为Button类继承自Node类,你可以使作javafx.scene.effect包中的任何特效来增强按钮的可视化效果。在例3-4中,当button3触发了onMouseEntered事件时,该按钮使用了DropShadow(译注:一种阴影效果)效果。

例3-4 应用DropShadow特效

[java]  view plain copy
  1. DropShadow shadow = new DropShadow();  
  2. //当鼠标位于按钮上时,增加阴影特效  
  3. button3.addEventHandler(MouseEvent.MOUSE_ENTERED,  
  4.     newEventHandler() {  
  5.         @Override public voidhandle(MouseEvent e) {  
  6.            button3.setEffect(shadow);  
  7.         }  
  8. });  
  9. //鼠标离开时,去除特效  
  10. button3.addEventHandler(MouseEvent.MOUSE_EXITED,  
  11.     newEventHandler() {  
  12.         @Override public voidhandle(MouseEvent e) {  
  13.             button3.setEffect(null);  
  14.         }  
  15. });  

图3-4展示了当鼠标进入和离开按钮时的状态。


图3-4 有阴影特效的按钮

使用样式

改善按钮可视化效果的下一步是应用通过Skin类定义的CSS样式,在JavaFX2.0中使用CSS与在HTML中类似,因为两者都基于同样的规范。

你可以将样式定义在独立的CSS文件中,然后在应用程序中通过setStyleClass方法来开启,此方法继承自Node类,所有的UI控件都起可用。另外,你还可以直接通过setStyle方法直接为一个控件定义样式。例3-5和图3-4演示了这种方法。

例3-5 样式化一个按钮

[java]  view plain copy
  1. button1.setStyle("-fx-font: 22 arial; -fx-base: #b6e7c9");  

-fx-font-size属性为button1设定字大属性,-fx-base属性覆盖了按钮的默认颜色。结果是,button1的颜色是淡绿色的,并且字体稍大,效果如图3-5所示:


 图3-5 用CSS样式化的按钮

相关API

  • Button
  • Labeled

4 单选按钮

单选按钮(RadioButton)类实现自切换按钮(ToggleButton)类,一个单选按钮要么选中,要么不选。一般来说,单选按钮都捆绑成一组,一次只能选择一个。这种行为可以和切换按钮区分开来,因为一组内的所有切换按钮均可以有未选取的状态。

图4-1展示了三个单选按钮例子的屏幕截图,例子中三个单选按钮属同一组。

 

图4-1 单选按钮例子

通过研究后面的单节来学习如何在你的应用程序中实现单选按钮。

创建单选按钮

在JavaFX API的Javafx.scene.control包中,单选按钮类提供了两种创建单选按钮的构造方法,例4-1展示了两个按钮,无参数的构造方法用来创建rb1,rb1的文本标题通过使用setText方法来实现,rb2的文本标题是通过相应的构造方法来定义的。

例4-1 创建单选按钮

[java]  view plain copy
  1. //一个标题为空的按钮  
  2. RadioButton rb1 = new RadioButton();  
  3. //设定标题  
  4. rb1.setText("Home");  
  5. //一个有特定标题的按钮  
  6. RadioButton rb2 = new RadioButton("Calendar");  

你可以明确地通过setSelected方法将一个单选按钮设定为选中状态,并将它的值设定为true。如果你需要检查一个单选按钮是否处于选中状态,可心使用isSelected方法。

由于单选按钮类是Labeled类的扩展类,你不但可以为它设定标题,还可以设定图像。使用setGraphic方法可以设定图像。例4-2演示了如何在应用程序中实现图像单选按钮。

例4-2 创建图像化单选按钮

[java]  view plain copy
  1. Image image = newImage(getClass().getResourceAsStream("ok.jpg"));  
  2. RadioButton rb = new RadioButton("Agree");  
  3. rb.setGraphic(new ImageView(image));  

向组中添加单选按钮

典型的单选按钮使用于组中,代表几个相互独立的选项。切换按钮类为所有单选按钮提供的参考将它们联系在一起,以便在某一时刻只能选择其中之一。例4-3创建了一个捆绑按钮组,然后创建了三个单选按钮,再把它们添加到组中,最后确定当程序启动后哪一个按钮被选中。

例4-3 创建一个单选按钮组

[java]  view plain copy
  1. final ToggleGroup group = new ToggleGroup();  
  2. RadioButton rb1 = new RadioButton("Home");  
  3. rb1.setToggleGroup(group);  
  4. rb1.setSelected(true);  
  5. RadioButton rb2 = new RadioButton("Calendar");  
  6. rb2.setToggleGroup(group);  
  7. RadioButton rb3 = new RadioButton("Contacts");  
  8. rb3.setToggleGroup(group);  

当这些按钮通过布局容器放置并添加到程序中后,输出如图4-2所示。


图4-2 绑定在一组中的三个单选按钮

处理单选按钮的事件

典型地,当组中的单选按钮被选中时,程序会执行一个动作。查看代码段4-4可以学习如何根据单选按钮的选取状态来改变图标。

例4-4 处理单选按钮的动作

[java]  view plain copy
  1. ImageView image = new ImageView();  
  2. rb1.setUserData("Home")  
  3. rb2.setUserData("Calendar");  
  4. rb3.setUserData("Contacts");  
  5.    
  6. final ToggleGroup group = new ToggleGroup();  
  7. group.selectedToggleProperty().addListener(new ChangeListener(){  
  8.     public voidchanged(ObservableValueextends Toggle> ov,  
  9.         Toggle old_toggle, Togglenew_toggle) {  
  10.             if(group.getSelectedToggle() != null) {  
  11.                 final Image image =new Image(  
  12.                     getClass().getResourceAsStream(  
  13.                        group.getSelectedToggle().getUserData().toString() +  
  14.                            ".jpg"  
  15.                     )  
  16.                 );  
  17.                 icon.setImage(image);  
  18.             }                 
  19.         }  
  20. });  
用户数据被赋予了每个单选按钮,ChangeListener对象检查组中的被选按钮,它用getSelectedToggle方法来标识当前哪个单选按钮被选中,然后通过getUserData方法提取用户数据,再以用户数据来构造一个供调用的图像文件名。

例如,当rb3被选中时,getSelectedToogle方法返回“rb3”,getUserData方法返回“Contacts”。这样,getResourceAsStream方法接收值“Contacts.jpg”。程序的输出如图4-1年示。

为单选按钮申请焦点

在一组单选按钮中,默认第一个按钮拥有焦点,此时如果你通过setSelected方法将组中的第二个按钮设为选中状态,你将获得如图4-3所示的效果。


图4-3 默认焦点设置

第二个按钮被选中,但第一个焦点仍保留焦点。使用requestFocus函数可以改变焦点,如例4-5所示。

[java]  view plain copy
  1. rb2.setSelected(true);  
  2. rb2.requestFocus();  

当此函数被调用后,输出结果如图4-4所示。


图4-4 为所选按钮设定焦点

相关API

  • RadioButton
  • Labeled

5 切换按钮

切换按钮类在JavaFX API中代表另个一种类型的按钮,两个或多个这种类型的按钮可以被捆绑在一组中,但同一时刻只能选择其中之一,或者都不选。图5-1是一个在组中捆绑了三个切换按钮的应用程序的截屏图。程序根据选中的切换按钮来确定颜色绘制一个矩形。


图5-1 三个切换按钮

创建一个切换按钮

你可以在应用程序中使用切换按钮类的三个构造函数中的任何一个来创建切换按钮,如例5-1所示。

例5-1 创建切换按钮

[java]  view plain copy
  1. //无标题和图标的切换按钮  
  2. ToggleButton tb1 = new ToggleButton();  
  3. //有标题的切换按钮  
  4. ToggleButton tb2 = new ToggleButton("Press me");  
  5. //有标题和图标的切换按钮  
  6. Image image = newImage(getClass().getResourceAsStream("icon.png"));  
  7. ToggleButton tb3 = new ToggleButton ("Press me", newImageView(image));  
切换按钮类是Labeled类的扩展,因此你可以为它指定标题、图标或两者。可以使用Lebeled类的setText和setGraphic为切换按钮指定文本和图像内容。

一旦你在代码中定义了切换按钮,你可以将它们绑定为一组,并设定一个特定的行为。

向组中添加切换按钮

实现切换按钮类与单选按钮类非常相似。然而,不同于单选按钮,组中的切换按钮不能强制选择组中的单一按钮。也就是说,当单击选中的切换按钮时会导致它变为非选中状态,但单击一个选中的单选按钮时则不会改变什么。

花点时间来研究一个例5-2的代码段。

例5-2 绑定组中的切换按钮

[java]  view plain copy
  1. final ToggleGroup group = new ToggleGroup();  
  2.    
  3. ToggleButton tb1 = new ToggleButton("Minor");  
  4. tb1.setToggleGroup(group);  
  5. tb1.setSelected(true);  
  6.    
  7. ToggleButton tb2 = new ToggleButton("Major");  
  8. tb2.setToggleGroup(group);  
  9. tb2.setUserData(Color.LIGHTBLUE);  
  10.    
  11. ToggleButton tb3 = new ToggleButton("Critical");  
  12. tb3.setToggleGroup(group);  
例5-2创建了三个切换按钮并将它们设为一组,tb1调用了setSelected方法以便当程序启动时选中它。然而,你可以不选Minor这个切换按钮,这样程序启动后组中不会有任何按钮选选中。如图5-2所示。


图5-2 组中的三个切换按钮

一般来说,你用一组切换按钮来为每个按钮指定特定的行为。下一部份解释如何利用切换按钮来区分矩形的颜色。

设定行为

切换按钮类继承自Node类的setUserData方法帮助你指定任何选项值,在例5-3中,用户数据指示哪种颜色用来绘制矩形。

例5-3 为切换按钮设定用户数据

[java]  view plain copy
  1. tb1.setUserData(Color.LIGHTGREEN);  
  2. tb2.setUserData(Color.LIGHTBLUE);  
  3. tb3.setUserData(Color.SALMON);  
  4.    
  5. final Rectangle rect = new Rectangle(16750);  
  6.    
  7. final ToggleGroup group = new ToggleGroup();  
  8. group.selectedToggleProperty().addListener(newChangeListener(){  
  9.     public voidchanged(ObservableValueextends Toggle> ov,  
  10.         Toggle toggle, Togglenew_toggle) {  
  11.             if (new_toggle == null)  
  12.                rect.setFill(Color.WHITE);  
  13.             else  
  14.                 rect.setFill(  
  15.                     (Color)group.getSelectedToggle().getUserData()  
  16.                 );  
  17.          }  
  18. });  

ChangeListener对象检测组中的选择项,如果组中无选择项,则绘制一个白色的方框。如果选择的组中的项目,则后续会调用getSelectedToggle和getUserData方法,根据返回的颜色来绘制方框。

例如,如果用户选择了tb2按钮,getSelectedToggle.getUserData方法返回Color.LIGHTBLUE,结果如图5-3所示。


图5-3 使用选项按钮来绘制方框

查看ToggleButtonSample.java可以检查程序的完整代码。

定制选项按钮

你可以将CSS样式应用到切换按钮来改善这个应用程序,在JavaFX中,CSS的用法与在HTML中一样,因为两者都基于同样的规范。例5-4使用setStyle方法以改变切换按钮的-fx-base样式属性。

例5-4 将样式应用到切换按钮

[java]  view plain copy
  1. tb1.setStyle("-fx-base: lightgreen");  
  2. tb2.setStyle("-fx-base: lightblue");  
  3. tb3.setStyle("-fx-base:salmon");  

把这几行代码加到程序中后,产生的可视效果如图5-4所示。


图5-4 绘制切换按钮

你可能还想试试其他的CSS样式、或应用JAVAFXAPI中提供的动画、变换各可视化特效。

相关API

  • ToggleButton
  • ToggleGroup

6 选择框

CheckBox类提供了在应用程序中创建选择框的能力。虽然选择框看上去与单选按钮类似,然而不能将它们绑定到一个切换组以具备同时选择多个选项的能力(注:原文如此)。更多信息,请参考单选按钮和切换按钮章节。

图6-1展示了一个程序的截图。在这个程序里,使用了三个选择框来打开和关闭程序中工具条的图标。


图6-1 选择框例子

创建选择框

例6-1 创建2个简单选择框

[java]  view plain copy
  1. //第一个没有标签  
  2. CheckBox cb1 = new CheckBox();  
  3. //另一个有文字标签  
  4. CheckBox cb2 = new CheckBox("Second");  
  5. cb1.setText("First");  
  6. 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 设定选择框的行为

[java]  view plain copy
  1. final String[] names = new String[]{"Security","Project""Chart"};  
  2. final Image[] images = new Image[names.length];  
  3. final ImageView[] icons = new ImageView[names.length];  
  4. final CheckBox[] cbs = new CheckBox[names.length];  
  5.    
  6. for (int i = 0; i < names.length; i++) {  
  7.     final Image image = images[i] =  
  8.         newImage(getClass().getResourceAsStream(names[i] + ".png"));  
  9.     final ImageView icon = icons[i] =new ImageView();  
  10.     final CheckBox cb = cbs[i] = newCheckBox(names[i]);  
  11.    cb.selectedProperty().addListener(new ChangeListener() {  
  12.         public voidchanged(ObservableValueextends Boolean> ov,  
  13.             Boolean old_val, Booleannew_val) {  
  14.                 icon.setImage(new_val? image : null);  
  15.         }  
  16.     });  
  17. }  

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 定制选择框

[java]  view plain copy
  1. cb1.setStyle(  
  2.     "-fx-border-color:lightblue; "  
  3.     + "-fx-font-size: 20;"  
  4.     + "-fx-border-insets: -5;"  
  5.     + "-fx-border-radius:5;"  
  6.     + "-fx-border-style:dotted;"  
  7.     + "-fx-border-width:2;"  
  8. );  

新的样式包含了点状的浅蓝色边框、增大的字体。图6-3展示了应用这种样式的选择框cb1。


图6-3 样式化的选择框

要为所有选择框设定特定的样式,请使用下列的过程:

  • 创建一个.css文件
  • 在.css文件中创建选择框的CSS类
  • 在选择框的CSS类中定义需要的样式
  • 在你的JavaFX程序中通过使用setStyleClass方法来应用样式单

相关API

  • CheckBox
  • JavaFX CSS Specification

7 组合框

组合框提供了在几个选项中快速选择的能力,考虑一下图7-1所示的组合框的实现。


例7-1 创建一个有三个选项的组合框

创建组合框

例7-1 创建一个有三个选项的组合框

[java]  view plain copy
  1. ChoiceBox cb = new ChoiceBox(FXCollections.observableArrayList(  
  2.     "First","Second""Third")  
  3. );  

例7-1展示了一个在ChoiceBox类内部创建项目列表的例子。列表项通过使用一个观察者数组来提供。当然,你可以使用类的空构造器来创建组合档,然后通过setItems方法指定项目列表。如例7-2所示。

例7-2有文本元素和分隔条的组合框

[java]  view plain copy
  1. ChoiceBox cb = new ChoiceBox();  
  2. cb.setItems(FXCollections.observableArrayList(  
  3.     "New Document","Open ",  
  4.     new Separator(),"Save""Save as")  
  5. );  

注意:组合框不仅可包含文本,也可以包含其他对象。在例7-2中,使用一个分隔条来分开选项。当运行这个代码段时,产生的效果如图7-2所示。


图7-2 通过组合框创建的菜单

在真实的应用中,组合框用来构建多个选项的列表。

设定组合框的行为

图7-3所示的应用程序提供了一个有5个选项的组合框,当一个选定特定的语言时,相应的欢迎语被绘制出来。


图7-3 多选项列表

例7-4提供了一个代码段来展现如何通过从组合框中选定项目来决定哪个欢迎语被绘制。

例7-4 选择一个组合框项目


ChangeListener对象通过连续调用getSelectionModel和getSelectedIndex方法来检测当前选取的项目的序号。getSelectionModel方法返回被选项目,getSelectedIndex方法返回组合框cb的SELECTED_INDEX属性。结果是,作为序号的数值定义了欢迎数组的一个元素,并且为标签指定了一个相应的文本。例如,如果用户选择第二个选项,它对应的是西班牙语,所选序号是1,从greetings数组中选出第一个值“Hola”作为欢迎语。这样,标签绘制出“Hola”。

你可以通过使用提示来使组合框拥有更多的信息量。提示是javafx.scene.control包中提供的一个UI控件,它可以应用到任何JavaFX UI控件。

使用工具提示

Tooltip类提供了一个预定的提示语,它可以通过调用setTooltip方法来方便的为组合框(或其他任何控件)提供提示语。如例7-5所示。

例7-5 为组合框增加提示语

[java]  view plain copy
  1. cb.setTooltip(new Tooltip("Select the language"));  
用户一般通过Tooltip类的构造器来定义提示文本。然而,如果你的应用程序的逻辑需要为UI动态地设定提示文本,你可以先通过一个空构造器来创建工具提示对象,然后再通过它的setText方法来指定文字信息。

组合框cb应用了更改后的提示,当用户将鼠标放在组合框上时,会看到图7-5所示的内容。


图7-5 应用工具提示的组合框

为了进一步改善你的应用程序,你可以通过CSS属性或应用可视化特效来定制组合框。

相关API

  • ChoiceBox
  • Tooltip

8 文本框

TextBox类实现了一种接受并显示输入的UI控件,它提供了接受用户输入文本的功能。与之相应的还有另一种输入控件:PasswordBox,它继承自TextInputControl类。所有文本控件的超类都通过JavaFX API来提供。

图8-1展示了一个附标签的典型文本框


图8-1 标签和文本框

创建文本框

在例8-1中,一个文本框与一个标签组合起来,以便指出应该在文本框中填写什么内容。

例8-1 创建文本框

[java]  view plain copy
  1. Label label1 = new Label("Name:");  
  2. TextBox textBox = new TextBox ();  
  3. HBox hb = new HBox();  
  4. hb.getChildren().addAll(label1, textBox);  
  5. hb.setSpacing(10);  

你可以像例8-1一样的创建一个空的文本框,也可以创建一个有实际文字数据的文本框。要创建一个有特定内容的文本框,请使用TextBox类的这个构造器:TextBox(“Hello World”)。你可以在任何时侯通过调用getText方法来获取文本框的值。

你可以使用TextInputControl类的setColumns方法来设定文本框的尺寸,定义它能接受的最大字符数。

用文本框来构建UI

一般地,TextBox对象在表单中来使用,以创建几个文本框。图8-2中的程序显示了3个文本框,并且处理用户的输入。


图8-2 文本框的例子

例8-2中的代码段创建了三个文本框和两个按钮,然后通过使用GridPane容器将它们添加到程序的场景中。当你需要为你的UI实现灵活的布局时,使用容器可提供极大的便利。

例8-2 在程序中添加文本框

[java]  view plain copy
  1. //Creating a GridPane container  
  2. GridPane grid = new GridPane();  
  3. grid.setPadding(new Insets(10101010));  
  4. grid.setVgap(5);  
  5. grid.setHgap(5);  
  6. //Defining the Name text field  
  7. final TextBox name = new TextBox();  
  8. name.setPromptText("Enter your first name.");  
  9. name.setColumns(18);  
  10. GridPane.setConstraints(name, 00);  
  11. grid.getChildren().add(name);  
  12. //Defining the Last Name text field  
  13. final TextBox lastName = new TextBox();  
  14. lastName.setPromptText("Enter your last name.");  
  15. GridPane.setConstraints(lastName, 01);  
  16. grid.getChildren().add(lastName);  
  17. //Defining the Comment text field  
  18. final TextBox comment = new TextBox();  
  19. comment.setColumns(24);  
  20. comment.setPromptText("Enter your comment.");  
  21. GridPane.setConstraints(comment, 02);  
  22. grid.getChildren().add(comment);  
  23. //Defining the Submit button  
  24. Button submit = new Button("Submit");  
  25. GridPane.setConstraints(submit, 10);  
  26. grid.getChildren().add(submit);  
  27. //Defining the Clear button  
  28. Button clear = new Button("Clear");  
  29. GridPane.setConstraints(clear, 11);  
  30. grid.getChildren().add(clear);  

让我们花点时间来学习这个代码段。name、lastName、comment文本框通过TextBox类的空构造器来创建。与例8-1不同,标签在代码段中没有附着于文本框,取而代之的是使用提示文字来通知用户应该在文本框中输入何种数据。setPromptText方法定义了程序开始时出现在文本框中的文字。当例8-2被添加到程序中时,会产生图8-3所示的输出。


图8-3 具有提示的三个文本框

提示文本和用户键入的内容有所不同,提示文本不能通过getText方法获得。

在真实的应用程序中,输入到文本框中的数据根据特定商业需求的程序逻辑来处理。下节将解释如何使用文本框来评估输入的文本,并向用户产生一个回应。

处理文本框数据

就像先前提到的一样,用户输入到文本框中的数据可以通过TextInputControl类的getText方法来获得。

研究8-3来学习如何处理文本框对象的数据。

例8-3 为提交和清除按钮定义动作

[java]  view plain copy
  1. //Adding a Label  
  2. final Label label = new Label();  
  3. GridPane.setConstraints(label, 03);  
  4. GridPane.setColumnSpan(label, 2);  
  5. grid.getChildren().add(label);  
  6.    
  7. //Setting an action for the Submit button  
  8. submit.setOnAction(new EventHandler() {  
  9.    
  10. @Override  
  11.     public void handle(ActionEvent e){  
  12.         if ((comment.getText() !=null && !comment.getText().isEmpty())) {  
  13.            label.setText(name.getText() + " " + lastName.getText() +", "  
  14.                 + "thank you foryour comment!");  
  15.         } else {  
  16.             label.setText("Youhave not left a comment.");  
  17.         }  
  18.      }  
  19.  });  
  20.    
  21. //Setting an action for the Clear button  
  22. clear.setOnAction(new EventHandler() {  
  23.    
  24. @Override  
  25.     public void handle(ActionEvent e){  
  26.         name.clear();  
  27.         lastName.clear();  
  28.         comment.clear();  
  29.         label.setText(null);  
  30.     }  
  31. });  

添加到GridPane中的标签控件向用户绘制程序的回应。当用户按下提交按钮时,setOnAction方法检查文本框的内容。如果是非容字符,一个感谢信息被绘制。否则,程序通知用户内容还设有确定,如图8-4所示。


图8-4 文本框为空

当用户按下清除按钮时,清除方法被调用,删除所有三个文本框中的内容。

学习一下你可能在使用文本框时会用到的其他有用的方法。

  • copy()–将当前选取的文本传递到剪贴板上,保留所选内容.
  • cut()–将当前选取的文本传递到剪贴板上,清除所选内容.
  • paste()–将剪贴板中的内容传递到文本框中,替换所选部份.

相关API

  • TextBox
  • TextInput

9 密码框

密码框实现了一个特别的文本输入控件,用户键入的字符被隐藏,取而代之的是显示一个回应字串。图9-1展示了一个有提示语的密码框。

  

图9-1 有提示的密码框

创建密码框

先用例9-1的代码来创建一个初级的密码框。

例9-1 创建密码框

[java]  view plain copy
  1. PasswordBox passwordBox = new PasswordBox();  
  2. passwordBox.setColumns(12);  
  3. passwordBox.setPromptText("Your password");  

在你的用户界面上,可以为密码框指定一个伴随的提示语或增加一个通知标签。和TextBox类一样,PasswordBox提供了setText方法来在控件中绘制文本。然而,用setText方法设定的字串在密码框中会被回应字符掩盖。默认的回应字符是“*”号。图9-2展示了有预定义字符的密码框。


图9-2 设定了文字的密码框

在密码框中键入的值可以通过getText方法获得,你可以在你的程序中处理这个值来设定适当的验证逻辑。

设定密码

花点时间看下例9-2的代码,它演示了在你的用户界面中实现的密码框。

例9-2 实现验证逻辑

[java]  view plain copy
  1. final Label message = new Label("");  
  2.    
  3. VBox vb = new VBox();  
  4. vb.setPadding(new Insets(100010));  
  5. vb.setSpacing(10);  
  6. HBox hb = new HBox();  
  7. hb.setSpacing(10);  
  8. hb.setAlignment(Pos.CENTER_LEFT);  
  9.    
  10. Label label = new Label("Password");  
  11. final PasswordBox pb = new PasswordBox();  
  12. pb.setColumns(12);  
  13. pb.setAction(new Runnable() {  
  14.    
  15.     @Override  
  16.     public void run() {  
  17.         if(!pb.getText().equals("T2f$Ay!")) {  
  18.            message.setText("Your password is incorrect!");  
  19.            message.setTextFill(Color.rgb(2103930));  
  20.         } else {  
  21.            message.setText("Your password has been confirmed.");  
  22.            message.setTextFill(Color.rgb(2111784));  
  23.         }  
  24.         pb.clear();  
  25.     }  
  26. });  
  27.    
  28. hb.getChildren().addAll(label, pb);  
  29. vb.getChildren().addAll(hb, message);  

密码框的验证逻辑在setAction实例变量中被定义,变理代表一个函数,当键入的值提交后,它就会被调用。一般而言,当按下回车键时就会被调用。如果键入的值与预设的不一致,相应的红色提示信息会出现。如图9-3.


图9-3 密码不正确

如果键入的值满足预定标准,确认信息出现。如图9-4.


因为安全原因,当值被键入后,一般需要清除密码框中的数据。在例9-2中,认证完成后调用了clear方法来清除。

设定回应字符

你可以通过调用PasswordBox类的setEchoChar来更改默认回应符。另一种方法是在定义控件的时侯在PasswordBox类的构造器中指定默认的回应字符。例9-3表现了这两种意图。

例9-3 使用不同的回应符

[java]  view plain copy
  1.  //Setting an echo character within a constructor  
  2. PasswordBox pb1 = new PasswordBox("#");  
  3. //Setting an echo character by using the setEchoChar method  
  4. PasswordBox pb2 = new PasswordBox();  
  5. pb2.setEchoChar("{1}quot;);  

另外,例9-4中的setHideDelay方法提供了一个机会来设定一个毫秒级的时间延迟,它可以在键入字符时延迟一定的时间后再显示回应符(注:很Cool的改进,我第一次在我的Android智能手机中体验过)。这样,用户可以在继续前确认键入的内容。

例9-4 为回应符设定延迟

[java]  view plain copy
  1. pb.setHideDelay(500);  

例9-4为密码框设定一个500毫秒的延迟,当用户在里面键入时,他分辨率会体验到图图9-5所示的感觉。


图9-5 设定延迟后键入密码

相关API

  • PasswordBox
  • TextInputControl

10 滚动条

ScrollBar类允许你在应用程序中使用滚动的面板和视图。图10-1展示了滚动条的三个区域:滑块、左右(上下)按钮和跟踪器。


图10-1 滚动条元素

创建滚动条

让我们看一看例10-1的代码片段。

例10-1 简单的滚动条

[java]  view plain copy
  1. ScrollBar sc = new ScrollBar();  
  2. sc.setMin(0);  
  3. sc.setMax(100);  
  4. sc.setValue(50);  

setMin和setMax方法定义滚动条所代表的最小、最大值。当用户滑动滑块时,滚动条的值改变。在例10-1中,值是50。因此当程序开始时,滑块位于滚动条的中间。滚动条默认是水平放置的。然而,你可以通过调用setOrientation方法将它设定为垂直放置。

用户可以单击左右按钮(在垂直模式是上下按钮)来滚动一个单位。UNIT_INCREMENT属性定义当用户单击时滚动条滚动的量。另一个方法是单击跟踪区来滑动多个单位值,BLOCK_INCREMENT属性定义了单击跟踪条时滚动条的滚动量。

在你的程序中,你可以使用多种方法来滚动浏览超出可用区域的图形元素。

在程序中使用滚动条

从动作来理解滚动条。例10-2的程序实现了一个滚动场景来浏览图像。这个程序的任务是让用户浏览垂面板的内容,它比场景的高度大。

例10-2 滚动浏览多个图像

[java]  view plain copy
  1. import javafx.application.Application;  
  2. import javafx.beans.value.ChangeListener;  
  3. import javafx.beans.value.ObservableValue;  
  4. import javafx.geometry.Orientation;  
  5. import javafx.scene.Group;  
  6. import javafx.scene.Scene;  
  7. import javafx.scene.control.ScrollBar;  
  8. import javafx.scene.effect.DropShadow;  
  9. import javafx.scene.image.Image;  
  10. import javafx.scene.image.ImageView;  
  11. import javafx.scene.layout.VBox;  
  12. import javafx.scene.paint.Color;  
  13. import javafx.stage.Stage;  
  14.    
  15. public class Main extends Application {  
  16.    
  17.     final ScrollBar sc = newScrollBar();  
  18.     final Image[] images = newImage[5];  
  19.     final ImageView[] pics = newImageView[5];  
  20.     final VBox vb = new VBox();  
  21.     DropShadow shadow = newDropShadow();  
  22.    
  23.     @Override  
  24.     public void start(Stage stage) {  
  25.         Group root = new Group();  
  26.         Scene scene = new Scene(root, 180180);  
  27.         scene.setFill(Color.BLACK);  
  28.         stage.setScene(scene);  
  29.        stage.setTitle("Scrollbar");  
  30.         root.getChildren().addAll(vb,sc);  
  31.    
  32.         shadow.setColor(Color.GREY);  
  33.         shadow.setOffsetX(2);  
  34.         shadow.setOffsetY(2);  
  35.    
  36.         vb.setLayoutX(5);  
  37.         vb.setSpacing(10);  
  38.    
  39.        sc.setLayoutX(scene.getWidth()-sc.getWidth());  
  40.         sc.setMin(0);  
  41.        sc.setOrientation(Orientation.VERTICAL);  
  42.         sc.setPrefHeight(180);  
  43.         sc.setMax(360);  
  44.    
  45.         for (int i = 0; i < 5;i++) {  
  46.             final Image image =images[i] =  
  47.                 newImage(getClass().getResourceAsStream(  
  48.                     "fw"+(i+1)+ ".jpg")  
  49.                 );  
  50.             final ImageView pic =pics[i] =  
  51.                 newImageView(images[i]);  
  52.             pic.setEffect(shadow);  
  53.            vb.getChildren().add(pics[i]);  
  54.         }  
  55.    
  56.        sc.valueProperty().addListener(new ChangeListener() {  
  57.             public voidchanged(ObservableValueextends Number> ov,  
  58.                 Number old_val,Number new_val) {  
  59.                    vb.setLayoutY(-new_val.doubleValue());  
  60.             }  
  61.         });  
  62.    
  63.         stage.setVisible(true);  
  64.     }  
  65.    
  66.     public static void main(String[]args) {  
  67.         Application.launch(args);  
  68.     }  
  69. }  

程序的前几行向场景中添加了一个内含图像和滚动条的垂直面板。

当滚动条的Value属性改变时,垂直面板的Y坐标一起改变。因此当滑块移动、单击按钮或跟踪块时,垂直面板移动。如例10-3。

例10-3 垂直滚动条的实现

[java]  view plain copy
  1. sc.valueProperty().addListener(new ChangeListener() {  
  2.     public voidchanged(ObservableValueextends Number> ov,  
  3.         Number old_val, Numbernew_val) {  
  4.            vb.setLayoutY(-new_val.doubleValue());  
  5.         }  
  6. });  

编译运行程序,输出如图10-2所示。


图10-2 滚动条例子

本程序演示了滚动条的典型应用,你也可以定制这个类在场景中创建滚动条。与其他任何UI控件和结点一样,滚动条可以通过样式来改变默认的实现风格。

样式化滚动条

考虑一下通过层叠样式表来样式化你的滚动条。例10-4演示了如何直接通过代码来样式化滚动条。

例10-4 将CSS样式应用到滚动条

 sc.setStyle(

[java]  view plain copy
  1.      "-fx-base:lemonchiffon;"  
  2.      + "-fx-border-color:darkgreen; "  
  3.      +"-fx-border-insets: -5; "  
  4.      +"-fx-border-radius: 10;"  
  5.      + "-fx-border-style:dotted;"  
  6.      + "-fx-border-width:2;"  
  7.      +"-fx-background-color: #b6e7c9;"  
  8.      +"-fx-background-radius: 10;"  
  9. );  

当把这些样式应用到例10-1中的代码中时,它的外观改变了。请参见图10-3来观察结果。


图10-3 样式化的滚动条

你可以通过在程序中增加css文件,然后在程序中声明相应的类来样式化。

相关API

  • ScrollBar
  • JavaFX CSS规范

11 滚动面板

滚动面板为UI要素提供滚动视图。此控件使得用户可以通过平移视图或使用滚动条来滚动组件内容。默认设置的并附加了图像的滚动面板如图11-1所示。


图11-1 滚动面板

创建滚动面板

例11-1演示了如何创建滚动面板。

例11-1 使用滚动面板来浏览图像

[java]  view plain copy
  1. Image roses = newImage(getClass().getResourceAsStream("roses.jpg"));  
  2. ScrollPane sp = new ScrollPane();  
  3. sp.setNode(new ImageView(roses));  

setNode方法定义了作为滚动面板内容使用有结点。你可以仅指定一个结点,要创建有多个组件的滚动视图,可以使用布局容器或Group类。你也可以为setPannable方法指定true值来通过单击并移动鼠标浏览图像,滚动条会作出相应的改变。

为滚动面板设定滚动条策略

ScrollPane类提供了一种策略来决定何时出现滚动条:总是可见、隐藏或需要时出现。使用setHbarPolicy和setVbarPolicy方法来分别决定垂直或水平滚动条的滚动条策略。因此,在例11-2中,会出现垂直滚动条而没有水平滚动条。

例11-2 设定水平及垂直滚动条策略

[java]  view plain copy
  1. sp.setHbarPolicy(ScrollBarPolicy.NEVER);  
  2. 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 使用滚动面板显示图像

[java]  view plain copy
  1. import javafx.application.Application;  
  2. import javafx.beans.value.ChangeListener;  
  3. import javafx.beans.value.ObservableValue;  
  4. import javafx.scene.Scene;  
  5. import javafx.scene.control.Label;  
  6. import javafx.scene.control.ScrollPane;  
  7. import javafx.scene.image.Image;  
  8. import javafx.scene.image.ImageView;  
  9. import javafx.scene.layout.Priority;  
  10. import javafx.scene.layout.VBox;  
  11. import javafx.stage.Stage;  
  12.    
  13. public class Main extends Application {  
  14.    
  15.     final ScrollPane sp = newScrollPane();  
  16.     final Image[] images = newImage[5];  
  17.     final ImageView[] pics = newImageView[5];  
  18.     final VBox vb = new VBox();  
  19.     final Label fileName = newLabel();  
  20.     final String [] imageNames = newString [] {"fw1.jpg""fw2.jpg",  
  21.         "fw3.jpg","fw4.jpg""fw5.jpg"};  
  22.    
  23.     @Override  
  24.     public void start(Stage stage) {  
  25.         VBox box = new VBox();  
  26.         Scene scene = new Scene(box,180180);  
  27.         stage.setScene(scene);  
  28.         stage.setTitle("ScrollPane");  
  29.         box.getChildren().addAll(sp,fileName);  
  30.         box.setVgrow(sp,Priority.ALWAYS);  
  31.    
  32.         fileName.setLayoutX(30);  
  33.         fileName.setLayoutY(160);  
  34.    
  35.         for (int i = 0; i < 5;i++) {  
  36.             final Image image =images[i] =  
  37.                 new Image(getClass().getResourceAsStream(imageNames[i]));  
  38.             final ImageView pic =pics[i] =  
  39.                 newImageView(images[i]);  
  40.                pics[i].setFitWidth(100);  
  41.                pics[i].setPreserveRatio(true);  
  42.             vb.getChildren().add(pics[i]);  
  43.         }  
  44.    
  45.         sp.setVmax(440);  
  46.         sp.setPrefSize(115150);  
  47.         sp.setNode(vb);  
  48.        sp.vvalueProperty().addListener(new ChangeListener() {  
  49.             public voidchanged(ObservableValueextends Number> ov,  
  50.                 Number old_val,Number new_val) {  
  51.                    fileName.setText(imageNames[(new_val.intValue() - 1) / 100]);  
  52.             }  
  53.         });  
  54.         stage.setVisible(true);  
  55.     }  
  56.    
  57.     public static void main(String[]args) {  
  58.         Application.launch(args);  
  59.     }  
  60. }  

编译和运行这段代码产生图11-5的输出。


图11-5 滚动图像

滚动条的最大值与垂直框的高度相等,如下例11-4的代码段绘制已显示图像的文件名称。

例11-4 跟踪滚动面板的垂直移动

[java]  view plain copy
  1. sp.vvalueProperty().addListener(new ChangeListener() {  
  2.     public voidchanged(ObservableValueextends Number> ov,  
  3.         Number old_val, Numbernew_val) {  
  4.            fileName.setText(imageNames[(new_val.intValue() - 1) / 100]);  
  5.         }  
  6. });  

ImageView对象限制了图像的高度为100像素,这样,new_val_intValue-1除以100后,结果返回在imageNames数组中的当前图像。

在实际使用时,你还可以改变水平和垂直滚动条的最大最小值,这样可以动态更新你的用户界面。

相关API

  • ScrollPane
  • ScrollBar

12 列表视图

列表视图类代表一个可滚动项目的列表,图12-1展示了所有可选的客房类型。


图12-1 简单列表视图

  你可以通过使用setItems方法定义项目的方式来产生列表,你还可以应用setCellFactory方法来创建列表项的视图。

创建列表视图

例12-1的代码段实现了用字符列表项实现了图12-1的功能。

例12-1 创建列表视图

[java]  view plain copy
  1. ListView list = new ListView();  
  2. ObservableList items =FXCollections.observableArrayList (  
  3.     "Single","Double""Suite""Family App");  
  4. list.setItems(items);  

要改变列表控件的尺寸,可使用setPrefHeight和setPrefWidth方法。例12-2将列表大小改为100×70,结果如图12-2所示。

例12-2 设置列表视图的高和宽

[java]  view plain copy
  1. list.setPrefWidth(100);  
  2. list.setPrefHeight(70);  

图12-2 调整垂直列表大小

你可以将列表视图的定位属性设为Orientation.HORIZONTAL来将它的朝向更改为水平方向。可以这样做:list.setOrientation(Orientation.HORIZONTAL)。包含和图12-1所示的相同列表项的水平列表如图12-3所示。


图12-3 水平列表控制

你可以通过以下方法的组合来获取列表视图中每个列表项的状态。

  • getSelectionModel().selectedIndexProperty()—返回当前选中项的序号
  • getSelectionModel().selectedItemProperty()—返回当前选中的列表项
  • getFocusModel().getFocusIndex()—返加当前有焦点的列表项的序号
  • getFocusModel().getFocusItem()—返加当前有焦点的列表项

需要注意的是,选取和聚焦状态是只读的,程序开始后你不能指定哪一个项目被选中和聚焦。

前述的列子代码展示了如何创建一个文字列表项,然而,列表视图可以包含任何结点对象。

产生带数据的列表视图

研究下列代码来学习如何使用单元格工厂方法产生列表项。例12-3的所示的程序创建一个颜色列表。

  例12-3 创建单元格工厂

[java]  view plain copy
  1. import javafx.application.Application;  
  2. import javafx.beans.value.ChangeListener;  
  3. import javafx.beans.value.ObservableValue;  
  4. import javafx.collections.FXCollections;  
  5. import javafx.collections.ObservableList;  
  6. import javafx.scene.Scene;  
  7. import javafx.scene.control.Label;  
  8. import javafx.scene.control.ListCell;  
  9. import javafx.scene.control.ListView;  
  10. import javafx.scene.layout.Priority;  
  11. import javafx.scene.layout.VBox;  
  12. import javafx.scene.paint.Color;  
  13. import javafx.scene.shape.Rectangle;  
  14. import javafx.scene.text.Font;  
  15. import javafx.stage.Stage;  
  16. import javafx.util.Callback;  
  17.    
  18. public class Main extends Application {  
  19.    
  20.     ListView list = newListView();  
  21.     ObservableList data= FXCollections.observableArrayList(  
  22.         "chocolate","salmon""gold""coral",  
  23.         "darkorchid","darkgoldenrod""lightsalmon",  
  24.         "black","rosybrown""blue""blueviolet",  
  25.         "brown"  
  26.     );  
  27.     final Label label = new Label();  
  28.    
  29.     @Override  
  30.     public void start(Stage stage) {  
  31.         VBox box = new VBox();  
  32.         Scene scene = new Scene(box,200200);  
  33.         stage.setScene(scene);  
  34.        stage.setTitle("ListViewSample");  
  35.        box.getChildren().addAll(list, label);  
  36.         box.setVgrow(list,Priority.ALWAYS);  
  37.    
  38.         label.setLayoutX(10);  
  39.         label.setLayoutY(115);  
  40.         label.setFont(newFont("Verdana"20));  
  41.    
  42.         list.setItems(data);  
  43.    
  44.         list.setCellFactory(newCallback,  
  45.            ListCell>() {  
  46.                 publicListCell call(ListView p) {  
  47.                     final Rectanglerect = new Rectangle(10020);  
  48.                     finalListCell cell = new ListCell() {  
  49.                     @Override publicvoid updateItem(String item,  
  50.                         booleanempty) {  
  51.                            super.updateItem(item, empty);  
  52.                             if (item!= null) {  
  53.                                 rect.setFill(Color.web(item));  
  54.                                setNode(rect);  
  55.                         }  
  56.                     }  
  57.                 };  
  58.                 return cell;  
  59.             }  
  60.         });  
  61.         list.setPrefHeight(100);   
  62.         stage.setVisible(true);  
  63.     }  
  64.    
  65.     public static void main(String[]args) {  
  66.         Application.launch(args);  
  67.     }  
  68. }  
单元格工厂产生ListCell对象,每全单元格与一个单独的数据项联系在一起,绘制列表视图的“一行”,通过setNode(译者注:在第42个构件版本中,这个方法被抛弃了。取而代之的是setGraphics方法)方法设置的单元格的内容可以包含其他控件、文字、形状或图形。在此程序中,列表项表现了矩形的情况。

编译运行这个程序,产生的输出如下:


图12-4 颜色列表

你可以滚动列表,选择或取消列表中的任何项。你还可以扩充这个程序,使用颜色来填充文字标签。

处理列表项的选取

增加例12-4所示的代码段,可以实际项目被选取时产生产生事件。

例12-4 处理列表项的事件

[java]  view plain copy
  1. final Label label = new Label();  
  2. list.getSelectionModel().selectedItemProperty().addListener(  
  3.     newChangeListener() {  
  4.         public void changed(ObservableValueextends String> ov,  
  5.             String old_val, Stringnew_val) {  
  6.                label.setText(new_val);  
  7.                 label.setTextFill(Color.web(new_val));  
  8.     }  
  9. });  
addListener方法调用selectedItemProperty创建一个新的ChangeListener对象来处理选择项的改变。例如,如果深蓝色被选取,标签接收“darkorchid”,并使用相应的颜色来绘制。修改后的程序输出如图12-5所示。


图12-5 选择深紫色的情况

相关API

  • ListView
  • ListCellFactory

13 表格视图

JavaFX  SDK中设计了几个用于表现表格化数据的类,在JavaFX中用来创建表格的最重要的类是TableView、TableColumn和TableCell类。你可以通过实现数据模型或应用单元格生成器来创建表格。

表格类提供了内建的功能在需要时按列来排序或调整列的宽度。

图13-1展示了一个地址簿中的联系人的典型表格。


图13-1 表格例子

创建表格

例13-1所示的代码段展示了创建三个列的表格,并将它增加到场景中的情况。

例13-1 增加一个表格

[java]  view plain copy
  1. import javafx.application.Application;  
  2. import javafx.geometry.Insets;  
  3. import javafx.scene.Group;  
  4. import javafx.scene.Scene;  
  5. import javafx.scene.control.Label;  
  6. import javafx.scene.control.TableColumn;  
  7. import javafx.scene.control.TableView;  
  8. import javafx.scene.layout.VBox;  
  9. import javafx.scene.text.Font;  
  10. import javafx.stage.Stage;  
  11.  public class Main extendsApplication {  
  12.      private TableView table = newTableView();  
  13.      public static void main(String[]args) {  
  14.         launch(args);  
  15.     }  
  16.    
  17.     @Override  
  18.     public void start(Stage stage) {  
  19.         Scene scene = new Scene(newGroup());  
  20.         stage.setTitle("TableView Sample");  
  21.         stage.setWidth(400);  
  22.         stage.setHeight(450);  
  23.    
  24.         final Label label = newLabel("Address Book");  
  25.         label.setFont(new Font("Arial",20));  
  26.    
  27.        table.setStyle("-fx-base: #b6e7c9;");  
  28.    
  29.         TableColumn firstNameCol =new TableColumn("First Name");  
  30.         TableColumn lastNameCol = newTableColumn("Last Name");  
  31.         TableColumn emailCol = newTableColumn("Email");  
  32.            
  33.        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);  
  34.         
  35.         final VBox vbox = new VBox();  
  36.         vbox.setSpacing(5);  
  37.        vbox.getChildren().addAll(label, table);  
  38.         vbox.setPadding(newInsets(100010));  
  39.    
  40.         ((Group)scene.getRoot()).getChildren().addAll(vbox);  
  41.    
  42.         stage.setScene(scene);  
  43.         stage.setVisible(true);  
  44.     }  
  45. }  

表格控件通过实例化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 创建嵌套列

[java]  view plain copy
  1. TableColumn firstEmailCol = new TableColumn("Primary");  
  2. TableColumn secondEmailCol = new TableColumn("Secondary");  
  3.    
  4. emailCol.getColumns().addAll(firstEmailCol, secondEmailCol);  

  一旦你将这些代码加到例13-1中后,然后编译并运行程序代码,就会产生图13-3的样式。


图13-3 有嵌套列的表格

虽然表格加入到了应用程序中,但由于没有数据,所以会出现一个标准表格标题“表格无数据”。如果要要改变这个标题,你可以调用setPlaceholder方法在空表中指定一个Node对象作为占位符。

定义表格模型

在程序中创建表格时,最好的方法是实现一个定义了数据模型,并提供了方法和属性来操作表格的类。例13-3创建了Person类来定义地址簿中的数据。

例13-3 创建Person类

[java]  view plain copy
  1.     private final StringProperty firstName;  
  2.     private final StringPropertylastName;  
  3.     private final StringPropertyemail;  
  4.     private Person(String fName,String lName, String email) {  
  5.         this.firstName = newStringProperty(fName);  
  6.         this.lastName = newStringProperty(lName);  
  7.         this.email = newStringProperty(email);  
  8.     }  
  9.     public String getFirstName() {  
  10.         return firstName.get();  
  11.     }  
  12.     public void setFirstName(StringfName) {  
  13.         firstName.set(fName);  
  14.     }  
  15.     public String getLastName() {  
  16.         return lastName.get();  
  17.     }  
  18.     public void setLastName(StringfName) {  
  19.         lastName.set(fName);  
  20.     }  
  21.     public String getEmail() {  
  22.         return email.get();  
  23.     }  
  24.     public void setEmail(StringfName) {  
  25.         email.set(fName);  
  26.     }  
  27. }  
firstName、lastName、和email被创建来引用实际的数据元素。另外,还为每个数据元素提供了get和set方法。因此可以用getFirstName方法来获得firstName属性的值,setFirstName方法来指定这个属性的值。

当数据模型在Person类中被描述后,你可以创建一个可观察列表(ObservableList)来定义你要在表中展示的任意多个数据行。例13-4的代码段实现了这个任务。

例13-4 在ObservableList中定表表格数据

[java]  view plain copy
  1. final ObservableList data = FXCollections.observableArrayList(  
  2.     new Person("Jacob","Smith""[email protected]"),  
  3.     new Person("Isabella","Johnson""[email protected]"),  
  4.     new Person("Ethan","Williams""[email protected]"),  
  5.     new Person("Emma","Jones""[email protected]"),  
  6.     new Person("Michael","Brown""[email protected]")  
  7. );  

下一步是将数据与表格的列关联起来。你可以通过为每个数据元素定义的属性来完成。如例13-5。

例13-5 设置列的数据属性

[java]  view plain copy
  1. firstNameCol.setProperty("firstName");  
  2. lastNameCol.setProperty("lastName");  
  3. emailCol.setProperty("email");  

定义了数据模型后,数据被填入,并与列形成了关联,你可以用TableView的setItems方法向表中增加数据。如:setItem(data)。

由于ObservableList对象可以跟踪列表内元素的变化,因此当数据改变时TableView会自动更新其内容。

仔细阅读一下例13-6的代码。

例13-6 创建表格并为表格添加数据

[java]  view plain copy
  1. import javafx.application.Application;  
  2. import javafx.beans.property.StringProperty;  
  3. import javafx.collections.FXCollections;  
  4. import javafx.collections.ObservableList;  
  5. import javafx.geometry.Insets;  
  6. import javafx.scene.Group;  
  7. import javafx.scene.Scene;  
  8. import javafx.scene.control.Label;  
  9. import javafx.scene.control.TableColumn;  
  10. import javafx.scene.control.TableView;  
  11. import javafx.scene.layout.VBox;  
  12. import javafx.scene.text.Font;  
  13. import javafx.stage.Stage;  
  14.    
  15. public class Main extends Application {  
  16.      public static class Person {  
  17.          private final StringPropertyfirstName;  
  18.         private final StringPropertylastName;  
  19.         private final StringPropertyemail;  
  20.    
  21.         private Person(String fName,String lName, String email) {  
  22.             this.firstName = newStringProperty(fName);  
  23.             this.lastName = new StringProperty(lName);  
  24.             this.email = newStringProperty(email);  
  25.         }  
  26.    
  27.         public String getFirstName(){  
  28.             return firstName.get();  
  29.         }  
  30.    
  31.         public voidsetFirstName(String fName) {  
  32.             firstName.set(fName);  
  33.         }  
  34.    
  35.         public String getLastName() {  
  36.             return lastName.get();  
  37.         }  
  38.    
  39.         public voidsetLastName(String fName) {  
  40.             lastName.set(fName);  
  41.         }  
  42.    
  43.         public String getEmail() {  
  44.             return email.get();  
  45.         }  
  46.    
  47.         public void setEmail(StringfName) {  
  48.             email.set(fName);  
  49.         }  
  50.    
  51.     }  
  52.     private TableViewtable = new TableView();  
  53.     private finalObservableList data =  
  54.        FXCollections.observableArrayList(  
  55.             newPerson("Jacob""Smith","[email protected]"),  
  56.             newPerson("Isabella""Johnson","[email protected]"),  
  57.             new Person("Ethan","Williams""[email protected]"),  
  58.             newPerson("Emma""Jones","[email protected]"),  
  59.             newPerson("Michael""Brown","[email protected]")  
  60.         );  
  61.      
  62.     public static void main(String[]args) {  
  63.         launch(args);  
  64.     }  
  65.    
  66.     @Override  
  67.     public void start(Stage stage) {  
  68.         Scene scene = new Scene(newGroup());  
  69.         stage.setTitle("TableView Sample");  
  70.         stage.setWidth(400);  
  71.         stage.setHeight(450);  
  72.    
  73.         final Label label = newLabel("Address Book");  
  74.         label.setFont(newFont("Arial"20));  
  75.    
  76.        table.setStyle("-fx-base: #b6e7c9;");  
  77.    
  78.         TableColumn firstNameCol =new TableColumn("First Name");  
  79.        firstNameCol.setProperty("firstName");  
  80.    
  81.         TableColumn lastNameCol = newTableColumn("Last Name");  
  82.        lastNameCol.setProperty("lastName");  
  83.    
  84.         TableColumn emailCol = newTableColumn("Email");  
  85.         emailCol.setMinWidth(200);  
  86.        emailCol.setProperty("email");  
  87.    
  88.         table.setItems(data);  
  89.        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);  
  90.    
  91.         
  92.         final VBox vbox = new VBox();  
  93.         vbox.setSpacing(5);  
  94.        vbox.getChildren().addAll(label, table);  
  95.         vbox.setPadding(newInsets(100010));  
  96.    
  97.         ((Group)scene.getRoot()).getChildren().addAll(vbox);  
  98.    
  99.         stage.setScene(scene);  
  100.         stage.setVisible(true);  
  101.     }  
  102. }  

当你编译并运行这个程序后,产生的输出如图13-4所示:


图13-4 装填了数据的表格

增加新行

图13-4的表格包含了5行数据,然而,目前为止你还不能修改数据。

你可以使用文本框来键入姓、名、邮件地址的新值。文本框控件可以让你的程序具有接受用户输入的能力。例13-7创建了三个文本框,并为每个文本框定义了掩码,然后创建了一个按钮。

例13-7 使用文本框在表中键入新值

[java]  view plain copy
  1. final TextBox addFirstName = new TextBox();  
  2. addFirstName.setPromptText("First Name");  
  3. addFirstName.setMaxWidth(firstNameCol.getPrefWidth());  
  4. final TextBox addLastName = new TextBox();  
  5. addLastName.setMaxWidth(lastNameCol.getPrefWidth());  
  6. addLastName.setPromptText("Last Name");  
  7. final TextBox addEmail = new TextBox();  
  8. addEmail.setMinWidth(emailCol.getPrefWidth());  
  9. addEmail.setPromptText("Email");  
  10.    
  11. final Button addButton = new Button("Add");  
  12. addButton.setOnAction(new EventHandler() {  
  13.     @Override public voidhandle(ActionEvent e) {  
  14.         data.add(new Person(  
  15.             addFirstName.getText(),  
  16.             addLastName.getText(),  
  17.             addEmail.getText())  
  18.         );  
  19.         addFirstName.clear();  
  20.         addLastName.clear();  
  21.         addEmail.clear();  
  22.     }  
  23. });  

当用户单击增加按钮时,键入文本框中的值被包含在Person类的构造器中增加到了可观察列表中,这样,新键入的联系人信息就会自动出现在表格中。

请仔细看一下例13-8的代码。

例13-8 包含可键入新联系人的文本框的表格

[java]  view plain copy
  1. import javafx.application.Application;  
  2. import javafx.beans.property.StringProperty;  
  3. import javafx.collections.FXCollections;  
  4. import javafx.collections.ObservableList;  
  5. import javafx.event.ActionEvent;  
  6. import javafx.event.EventHandler;  
  7. import javafx.geometry.Insets;  
  8. import javafx.scene.Group;  
  9. import javafx.scene.Scene;  
  10. import javafx.scene.control.Button;  
  11. import javafx.scene.control.Label;  
  12. import javafx.scene.control.TableColumn;  
  13. import javafx.scene.control.TableView;  
  14. import javafx.scene.control.TextBox;  
  15. import javafx.scene.layout.HBox;  
  16. import javafx.scene.layout.VBox;  
  17. import javafx.scene.text.Font;  
  18. import javafx.stage.Stage;  
  19.    
  20. public class Main extends Application {  
  21.    
  22.     public static class Person {  
  23.    
  24.         private final StringPropertyfirstName;  
  25.         private final StringPropertylastName;  
  26.         private final StringPropertyemail;  
  27.    
  28.         private Person(String fName,String lName, String email) {  
  29.             this.firstName = newStringProperty(fName);  
  30.             this.lastName = newStringProperty(lName);  
  31.             this.email = newStringProperty(email);  
  32.         }  
  33.    
  34.         public String getFirstName(){  
  35.             return firstName.get();  
  36.         }  
  37.    
  38.         public voidsetFirstName(String fName) {  
  39.             firstName.set(fName);  
  40.         }  
  41.    
  42.         public String getLastName() {  
  43.             return lastName.get();  
  44.         }  
  45.    
  46.         public voidsetLastName(String fName) {  
  47.             lastName.set(fName);  
  48.         }  
  49.    
  50.         public String getEmail() {  
  51.             return email.get();  
  52.         }  
  53.    
  54.         public void setEmail(StringfName) {  
  55.             email.set(fName);  
  56.         }  
  57.    
  58.     }  
  59.     private TableViewtable = new TableView();  
  60.     private finalObservableList data =  
  61.        FXCollections.observableArrayList(  
  62.             newPerson("Jacob""Smith","[email protected]"),  
  63.             newPerson("Isabella""Johnson","[email protected]"),  
  64.             newPerson("Ethan""Williams","[email protected]"),  
  65.             new Person("Emma","Jones""[email protected]"),  
  66.             newPerson("Michael""Brown","[email protected]")  
  67.         );  
  68.    
  69.     private HBox hb = new HBox();  
  70.    
  71.     public static void main(String[]args) {  
  72.         launch(args);  
  73.     }  
  74.    
  75.     @Override  
  76.     public void start(Stage stage) {  
  77.         Scene scene = new Scene(newGroup());  
  78.         stage.setTitle("TableView Sample");  
  79.         stage.setWidth(400);  
  80.         stage.setHeight(450);  
  81.    
  82.         final Label label = newLabel("Address Book");  
  83.         label.setFont(newFont("Arial"20));  
  84.    
  85.        table.setStyle("-fx-base: #b6e7c9;");  
  86.    
  87.         TableColumn firstNameCol =new TableColumn("First");  
  88.        firstNameCol.setProperty("firstName");  
  89.    
  90.         TableColumn lastNameCol = newTableColumn("Last");  
  91.        lastNameCol.setProperty("lastName");  
  92.    
  93.         TableColumn emailCol = newTableColumn("Email");  
  94.         emailCol.setMinWidth(200);  
  95.        emailCol.setProperty("email");  
  96.    
  97.         table.setItems(data);  
  98.        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);  
  99.    
  100.         final TextBox addFirstName =new TextBox();  
  101.        addFirstName.setPromptText("First Name");  
  102.        addFirstName.setMaxWidth(firstNameCol.getPrefWidth());  
  103.         final TextBox addLastName =new TextBox();  
  104.        addLastName.setMaxWidth(lastNameCol.getPrefWidth());  
  105.        addLastName.setPromptText("Last Name");  
  106.         final TextBox addEmail = newTextBox();  
  107.        addEmail.setMinWidth(emailCol.getPrefWidth());  
  108.         addEmail.setPromptText("Email");  
  109.    
  110.         final Button addButton = newButton("Add");  
  111.         addButton.setOnAction(newEventHandler() {  
  112.             @Override public voidhandle(ActionEvent e) {  
  113.                 data.add(new Person(  
  114.                        addFirstName.getText(),  
  115.                        addLastName.getText(),  
  116.                        addEmail.getText())  
  117.                 );  
  118.                 addFirstName.clear();  
  119.                 addLastName.clear();  
  120.                 addEmail.clear();  
  121.             }  
  122.         });  
  123.    
  124.        hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);  
  125.         hb.setSpacing(3);  
  126.    
  127.         final VBox vbox = new VBox();  
  128.         vbox.setSpacing(5);  
  129.         vbox.getChildren().addAll(label,table, hb);  
  130.         vbox.setPadding(newInsets(100010));  
  131.    
  132.         ((Group)scene.getRoot()).getChildren().addAll(vbox);  
  133.    
  134.         stage.setScene(scene);  
  135.         stage.setVisible(true);  
  136.     }  
  137. }  
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 实现单元格编辑

[java]  view plain copy
  1. class EditingCell extends TableCell {  
  2.         private final Label label;  
  3.         private TextBox textBox;  
  4.    
  5.         public EditingCell() {  
  6.             this.label = new Label();  
  7.         }  
  8.    
  9.         @Override  
  10.         public void startEdit() {  
  11.             super.startEdit();  
  12.             if (isEmpty()) {  
  13.                 return;  
  14.             }  
  15.    
  16.             if (textBox == null) {  
  17.                 createTextBox();  
  18.             } else {  
  19.                textBox.setText(getItem());  
  20.             }  
  21.             setNode(textBox);  
  22.             textBox.requestFocus();  
  23.             textBox.selectAll();  
  24.         }  
  25.    
  26.         @Override  
  27.         public void cancelEdit() {  
  28.             super.cancelEdit();  
  29.             setNode(label);  
  30.         }  
  31.    
  32.         @Override  
  33.         public void commitEdit(Stringt) {  
  34.             super.commitEdit(t);  
  35.             setNode(label);  
  36.         }  
  37.    
  38.         @Override  
  39.         public void updateItem(Stringitem, boolean empty) {  
  40.             super.updateItem(item,empty);  
  41.             if (!isEmpty()) {  
  42.                 if (textBox != null){  
  43.                    textBox.setText(item);  
  44.                 }  
  45.                 label.setText(item);  
  46.                 setNode(label);  
  47.             }  
  48.         }  
  49.    
  50.         private void createTextBox(){  
  51.             textBox = newTextBox(getItem());  
  52.            textBox.setOnKeyReleased(new EventHandler() {  
  53.                 @Override public void handle(KeyEventt) {  
  54.                     if (t.getCode()== KeyCode.ENTER) {  
  55.                        commitEdit(textBox.getRawText());  
  56.                     } else if(t.getCode() == KeyCode.ESCAPE) {  
  57.                         cancelEdit();  
  58.                     }  
  59.                 }  
  60.             });  
  61.         }  
  62.     }  
在例13-9中,createTextBox方法使用一个私有的textbox变量来分析键入的值序列,然后在输入ENTER或ESCAPE时分别调用commitEdit或cancelEdit方法。

TableColoumn类的setCellFactory方法用以为单元格安装一个自定义的单元格构造器,这个构造器的作用是在需要时返加新的TableCell实例。例13-10展示了如何为firstName、lastName和emailCol列实现一个单元格构造器。

例13-10 使用单元格构造器

[java]  view plain copy
  1. Callback cellFactory =  
  2.     new Callback() {  
  3.         public TableCellcall(TableColumn p) {  
  4.             return new EditingCell();  
  5.     }  
  6. };  

如例13-11一样,使用TableColumn类的setOnEditCommit方法来处理单元格内容的变化。这个方法识别修改内容,检索新的值,然后替换可观察列表中当前元素的数据。

例13-11 处理表中的数据编辑

[java]  view plain copy
  1. //Modifying the firstName property  
  2. firstNameCol.setOnEditCommit(newEventHandler>() {  
  3.     @Override public voidhandle(EditEvent t) {  
  4.        ((Person)t.getTableView().getItems().get(  
  5.            t.getTablePosition().getRow())).setFirstName(t.getNewValue());  
  6.      }  
  7. });  
  8.    
  9.    
  10. //Modifying the lastName property  
  11. lastNameCol.setOnEditCommit(newEventHandler>() {  
  12.     @Override public voidhandle(EditEvent t) {  
  13.        ((Person)t.getTableView().getItems().get(  
  14.            t.getTablePosition().getRow())).setLastName(t.getNewValue());  
  15.     }  
  16. });  
  17.    
  18. //Modifying the email property  
  19. emailCol.setOnEditCommit(new EventHandler>(){  
  20.     @Override public voidhandle(EditEvent t) {  
  21.        ((Person)t.getTableView().getItems().get(  
  22.            t.getTablePosition().getRow())).setEmail(t.getNewValue());  
  23.     }  
  24. });  

在图13-8中,用户正编辑MichaelBrown的名字。要编辑单元格,用户在单元格中键入新的值,然后按下Enter键。如果不按下Enter键,单元格的值不会被修改。这种行为主要是因为单元格编辑器实现了TextBox类。


图13-8 单元格编辑

相关API

  • TableView
  • TableColumn
  • TableCell
  • TextBox
  • Button

14 分隔条

JavaFX API提供的Separator类代表一个水平或垂直的线条。它用来在用户界面中分隔组件,它不会产生任何动作。不过,你可以对它进行样式化,增加特效或创建动画。默认地,分隔条是水平的,但可以用setOrientation方法来改变方向。

创建分隔条

例14-1的代码段创建了一个水平分隔条和一个垂直分隔条。

例14-1 水平和垂直分隔条

[java]  view plain copy
  1. //Horizontal separator  
  2. Separator separator1 = new Separator();  
  3. //Vertical separator  
  4. Separator separator2 = new Separator();  
  5. separator2.setOrientation(Orientation.VERTICAL);  

Separator类是Node类的子类,因此,它继承了Node类的所有实例变更。

一般而言,分隔条用来分隔UI组件的组,研究一下例14-2的代码段,它将春季复选框和夏季复选框分开。

例14-2 在复选类型间使用分隔条

[java]  view plain copy
  1. final String[] names = new String[]{"March""April","May",  
  2.     "June","July""August"};  
  3. final CheckBox[] cbs = new CheckBox[names.length];  
  4. final Separator separator = new Separator();  
  5. final VBox vbox = new VBox();  
  6.    
  7. for (int i = 0; i < names.length; i++) {  
  8.     cbs[i] = new CheckBox(names[i]);  
  9. }  
  10.                          
  11. separator.setMaxWidth(40);  
  12. separator.setAlignment(Pos.CENTER_LEFT);  
  13. vbox.getChildren().addAll(cbs);  
  14. vbox.setSpacing(5);  
  15. vbox.getChildren().add(3, separator);  

这段代码增加到程序中后,产生图14-1的控件样式。


图14-1 复选框和分隔条

分隔条占用了分配给它的水平或垂直位置。setMaxWidth方法用于定义实际的宽度,setVPos方法在已分配的布局空间内指定分隔条的垂直位置。相应的,你可以通过setHpos方法设置水平分隔线的位置。

在例14-2中,分隔条通过党规的add(index,node)方法添加到垂直框中。你可以在你的应用程序中使用这种方法在UI创建后或动态改变后来增加分隔条。

在程序中为UI添加分隔条

如前所述,分隔条于来分隔UI控件组,你也可以使用它来构造用户界面。考虑一下创建一个如图14-2所示的绘制一个天气预报信息的情况。


图14-2 用分隔条构建天气预报数据

对图14-2所示的程序来说,分隔条用于分隔Label和ImageView对象,请研究一下例14-3所示的程序的代码。

例14-3 在天气预报程序中使用分隔条

[java]  view plain copy
  1. import javafx.application.Application;  
  2. import javafx.geometry.Insets;  
  3. import javafx.geometry.VPos;  
  4. import javafx.scene.Group;  
  5. import javafx.scene.Scene;  
  6. import javafx.scene.control.*;  
  7. import javafx.scene.effect.DropShadow;  
  8. import javafx.scene.image.Image;  
  9. import javafx.scene.image.ImageView;  
  10. import javafx.scene.layout.GridPane;  
  11. import javafx.scene.text.Font;  
  12. import javafx.stage.Stage;  
  13.    
  14. public class Main extends Application {  
  15.    
  16.     Button button3 = newButton("Decline");  
  17.     DropShadow shadow = newDropShadow();  
  18.     Label caption = newLabel("Weather Forecast");  
  19.     Label friday = newLabel("Friday");  
  20.     Label saturday = newLabel("Saturday");  
  21.     Label sunday = newLabel("Sunday");  
  22.    
  23.     @Override  
  24.     public void start(Stage stage) {  
  25.         Group root = new Group();  
  26.         Scene scene = new Scene(root,500300);  
  27.         stage.setScene(scene);  
  28.        stage.setTitle("Separator Sample");  
  29.    
  30.         GridPane grid = newGridPane();  
  31.         grid.setPadding(newInsets(10101010));  
  32.         grid.setVgap(2);  
  33.         grid.setHgap(5);  
  34.    
  35.         scene.setRoot(grid);  
  36.    
  37.         Image cloudImage = newImage(getClass().getResourceAsStream("cloud.jpg"));  
  38.         Image sunImage = newImage(getClass().getResourceAsStream("sun.jpg"));  
  39.    
  40.        caption.setFont(Font.font("Verdana"20));  
  41.        GridPane.setConstraints(caption, 00);  
  42.         grid.setColumnSpan(caption,8);  
  43.         grid.getChildren().add(caption);  
  44.    
  45.         final Separator sepHor = newSeparator();  
  46.         sepHor.setVpos(VPos.CENTER);  
  47.        GridPane.setConstraints(sepHor, 01);  
  48.         grid.setColumnSpan(sepHor,7);  
  49.        grid.getChildren().add(sepHor);  
  50.    
  51.        friday.setFont(Font.font("Verdana"18));  
  52.        GridPane.setConstraints(friday, 02);  
  53.         grid.setColumnSpan(friday,2);  
  54.        grid.getChildren().add(friday);  
  55.    
  56.         final Separator sepVert1 =new Separator();  
  57.         sepVert1.setOrientation(Orientation.VERTICAL);  
  58.        sepVert1.setVpos(VPos.CENTER);  
  59.         sepVert1.setPrefHeight(80);  
  60.        GridPane.setConstraints(sepVert1, 22);  
  61.         grid.setRowSpan(sepVert1, 2);  
  62.        grid.getChildren().add(sepVert1);  
  63.    
  64.         saturday.setFont(Font.font("Verdana",18));  
  65.        GridPane.setConstraints(saturday, 32);  
  66.         grid.setColumnSpan(saturday,2);  
  67.        grid.getChildren().add(saturday);  
  68.    
  69.         final Separator sepVert2 =new Separator();  
  70.        sepVert2.setOrientation(Orientation.VERTICAL);  
  71.        sepVert2.setVpos(VPos.CENTER);  
  72.         sepVert2.setPrefHeight(80);  
  73.        GridPane.setConstraints(sepVert2, 52);  
  74.         grid.setRowSpan(sepVert2, 2);  
  75.        grid.getChildren().add(sepVert2);  
  76.    
  77.        sunday.setFont(Font.font("Verdana"18));  
  78.        GridPane.setConstraints(sunday, 62);  
  79.         grid.setColumnSpan(sunday,2);  
  80.        grid.getChildren().add(sunday);  
  81.    
  82.         final ImageView cloud = newImageView(cloudImage);  
  83.         GridPane.setConstraints(cloud,03);  
  84.        grid.getChildren().add(cloud);  
  85.    
  86.         final Label t1 = newLabel("16");  
  87.        t1.setFont(Font.font("Verdana"20));  
  88.         GridPane.setConstraints(t1,13);  
  89.         grid.getChildren().add(t1);  
  90.    
  91.         final ImageView sun1 = newImageView(sunImage);  
  92.         GridPane.setConstraints(sun1,33);  
  93.         grid.getChildren().add(sun1);  
  94.    
  95.         final Label t2 = newLabel("18");  
  96.        t2.setFont(Font.font("Verdana"20));  
  97.         GridPane.setConstraints(t2,43);  
  98.         grid.getChildren().add(t2);  
  99.    
  100.         final ImageView sun2 = newImageView(sunImage);  
  101.         GridPane.setConstraints(sun2,63);  
  102.         grid.getChildren().add(sun2);  
  103.    
  104.         final Label t3 = new Label("20");  
  105.        t3.setFont(Font.font("Verdana"20));  
  106.         GridPane.setConstraints(t3,73);  
  107.         grid.getChildren().add(t3);  
  108.    
  109.         stage.setVisible(true);  
  110.     }  
  111.     public static void main(String[]args) {  
  112.         Application.launch(args);  
  113.     }  
  114. }  

这个程序同时使用了水平和垂直分隔条,并且它们跨越了GridPane容器的行和列,在你的程序中,也可以为分隔条设定推荐的长度(对水平分隔条是长度,对垂直分隔条是高度),以便当用户界面的大小被调整时它能动态地改变。你还可以通过Separator对象提供的CSS类来为分隔条增加额外的可视化效果。

样式化分隔条

要为例14-3中的所有分隔条使用相同的样式,你可以创建一个CSS文件(例如:controlStyle.css),然后将它保存在与主类(main class)相同的包内。例14-4演示了可以增加到controlSytle文件中的CSS类。

例14-4 使用CSS类样式化分隔条

[css]  view plain copy
  1. /*controlStyle.css */  
  2. .separator{  
  3.     -fx-background-color#e79423;  
  4.     -fx-background-radius: 2;  
  5.     -fx-background-insets: -2;  
  6. }  

你可以在程序中通过getStylesheets方法来启用样式化的分隔条。如例14-5所示:

例14-5 在JavaFX程序中启用样式表

[java]  view plain copy
  1. scene.getStylesheets().add("/separatorsample/controlStyle.css");  
图14-3 展示了程序修改后分隔条在天气预报程序中的样子。


图14-3 样式化的分隔条

相关API

  • Separator
  • JavaFX CSS Specification

15 滑动条

滑动条代表一种在表现和交互一定范围内的数值的控件,它包括一个跟踪条和一个滑动块。也可以包含表示数值范围的刻度和刻度标签。图15-1展示了典型的滑动条和它的主要构件。


图15-1 滑动条的构件

创建滑动条

让我们花点时间来研究一下例15-1的代码段,它产生的输出如图15-1所示。

例15-1 创建滑动条

[java]  view plain copy
  1. Slider slider = new Slider();  
  2. slider.setMin(0);  
  3. slider.setMax(100);  
  4. slider.setValue(40);  
  5. slider.setShowTickLabels(true);  
  6. slider.setShowTickMarks(true);  
  7. slider.setMajorTickUnit(50);  
  8. slider.setMinorTickCount(5);  
  9. 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 滑动条例子

[java]  view plain copy
  1. import javafx.application.Application;  
  2. import javafx.beans.value.ChangeListener;  
  3. import javafx.beans.value.ObservableValue;  
  4. import javafx.geometry.Insets;  
  5. import javafx.scene.Group;  
  6. import javafx.scene.Scene;  
  7. import javafx.scene.control.Label;  
  8. import javafx.scene.control.Slider;  
  9. import javafx.scene.effect.SepiaTone;  
  10. import javafx.scene.image.Image;  
  11. import javafx.scene.image.ImageView;  
  12. import javafx.scene.layout.GridPane;  
  13. import javafx.scene.paint.Color;  
  14. import javafx.stage.Stage;  
  15.    
  16. public class Main extends Application {  
  17.    
  18.     final Slider opacityLevel = newSlider(011);  
  19.     final Slider sepiaTone = newSlider(011);  
  20.     final Slider scaling = new Slider(0.511);  
  21.     final Image image  = new Image(getClass().getResourceAsStream(  
  22.         "cappuccino.jpg")  
  23.     );  
  24.    
  25.     final Label opacityCaption = newLabel("Opacity Level:");  
  26.     final Label sepiaCaption = newLabel("Sepia Tone:");  
  27.     final Label scalingCaption = newLabel("Scaling Factor:");  
  28.    
  29.     final Label opacityValue = newLabel(  
  30.        Double.toString(opacityLevel.getValue()));  
  31.     final Label sepiaValue = newLabel(  
  32.        Double.toString(sepiaTone.getValue()));  
  33.     final Label scalingValue = newLabel(  
  34.        Double.toString(scaling.getValue()));  
  35.    
  36.     final static Color textColor =Color.WHITE;  
  37.     final static SepiaTonesepiaEffect = new SepiaTone();  
  38.    

你可能感兴趣的:(javafx2,javafx)