JavaFX 2.0内建控件教程(全部控件)


JavaFX 内置 UI 控件 本篇教程内容覆盖 JavaFX 内建的有效图形界面控件(UI 控件),包括如下章节内容:  JavaFX UI Controls  Label  Button  Radio Button  Toggle Button  Checkbox  Choice Box  Text Field  Password Field  Scroll Bar  Scroll Pane  List View  Table View  Tree View  Separator  Slider  Progress Bar and Progress Indicator  Hyperlink  Tooltip  HTML Editor  Titled Pane and Accordion  Menu 每部分内容提供相应示例和应用代码,以描述控件的功能作用。并可在内容列表中找到 应用的代码文件以及相应 netbeans 工程文件。 1 UI 控件概览 1.1 用户见面控件(UI 控件) JavaFX UI 的可用控件由使用中的节点经由 API 创建于场景图中。因此,这些控件可以使 用可视化地丰富 javaFX 平台特性。由于 JavaFX APIs 完全由 java 实现,你可以很容易把 JavaFX UI 控件集成到已存在的 Java 应用中。 图 1-1 展示了一些典型的 UI 控件的应用情况 图 1-1JavaFX UI 控件 1.2 JavaFX2.0 中支持的 UI 控件 构建 UI 控件的类所在的 API 包是 javafx.scene.control。UI 控件包含的典型界面组件可能 与你以前的 java 应用客户端开发所见大差不离。但是 JavaFX2.0 SDK 介绍了些新的 Java 界面 控件,如 TitlePane 和 TableView。 图 1-2 展示了 3 个带有设置清单的 TitlePane 元素。这些列表清单可以滑进和滑出(收 缩扩展)。 图 1-2 标题面板 详情可以查看 UI 控件的完整 API 描述文档。 图形界面控件(UI control,下文简称为 UI 控件)类基于 Control 类之上,以直观的方式 提供了附加的变量和方法来支持典型的用户交互。你能通过应用 CSS 为你的 UI 组件指定样 式。对于一些不常用的任务,可以自行扩展 Control 类创建定制的 UI 组件,或者使用 Skin 接口为存在的控件定义新的皮肤样式。 1.3 特性和效果 所有 UI 控件都在 javafx.scene.control 包中,并都扩展自 Node 类,因此控件可以被集成 到场景图中渲染,实现动画、转换以及动画转变等。比如创建一个按钮,应用一个倒影效果, 并通过改变透明度实现动画效果。图 1-3 展示了通过动画时间线改变按钮的 3 个状态。作图 展示了按钮透明度为 1.0 效果,中间图展示了透明度为 0.8 效果,右边图展示了透明度为 0.5 的效果。 图 1-3 动画按钮 通过使用 JavaFX APIs,用很少的代码行就能实现这样效果。 示例 1-1 创建并开始了不确定的时间线,在时间线内每帧 600 毫秒按钮透明度从缺省值 1.0 改变到 0.0,。setAutoReverse 方法使反转有序进行。 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(new Font("Tahoma", 24)); button.setEffect(new Reflection()); final Timeline timeline = new Timeline(); timeline.setCycleCount(Timeline.INDEFINITE); timeline.setAutoReverse(true); final KeyValue kv = new KeyValue(button.opacityProperty(), 0); final KeyFrame kf = new KeyFrame(Duration.millis(600), kv); timeline.getKeyFrames().add(kf); timeline.play(); ... 示例 1-1 创建动画按钮 你可以应用其它在 javafx.scene.effect 包中有效的可视效果,诸如阴影、亮化或动态模糊。 1.4 CSS 风格化 UI 控件 通过定制自己的层叠式样表(Cascading Style Sheets -CSS),可以自行定制内建控件的 外观。在 JavaFX 应用中使用 CSS 和在 HTML 中使用是非常一致的,因为它们都基于统一的 CSS 规范。控件的可视状态有.css 文件定义。如示例 1-2 所示: /*controlStyle.css */ .scene{ -fx-font: 14pt "Cambria Bold"; -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-2 CSS 文件中定义 UI 控件风格 在应用中,通过 Scene 类的 getStylesheets 方法使 CSS 起作用。如示例 1-3 所示 Scene scene = new Scene(); scene.getStylesheets().add("uicontrolssample/controlStyle.css"); 示例 1-3 应用 CSS 另外,通过使用控件的 setStyle 方法,可以在应用程序的代码中直接定义控件风格。在示例 1-4 中,-fx-base 属性为 toggle 按钮定义样式,这会覆盖.css 文件中为相应 scene 中所有控件 的属性风格定义。 ToggleButton tb3 = new ToggleButton ("I don't know"); tb3.setStyle("-fx-base: #ed1c24;"); 示例 1-4 代码定义式样 图-1-4 展示了代码应用样式的外观情形 图 1-4 代码应用 CSS 1.5 图表 除了典型的用户界面元素外,JavaFX SDK 在 javafx.scene.chart 包中提供了预置的图表支 持。支持的图表类型有区域图、条图、泡图、线图、饼图、散射图(area chart, bar chart, bubble chart, line chart, pie chart, and scatter chart.)。每一类图表可能包含好几个系列数据。图 1-5 展示了进口水果的饼图: 图 1-5 饼图 不像其它 java 客户端工具包,用 JavaFX SDK,通过增加几行代码就能构建这样的图表。也可 以定义不同的颜色模式和样式,应用可视效果、处理鼠标事件和创建动画等。 1.6 集成 FX2.0 控件到 Swing 可以把 JavaFX UI 控件集成到由 Swing 工具包构建的 java 客户端应用中。 为了把 JavaFX 内容集成到 Swing 应用中,采取如下步骤: 1. 在一个布局容器或一个组中,一个一个的把所有 UI 控件添加到 javafx.scene.Scene 对象中; 2. 把 Scene 对象添加到 Swing 应用程序的内容中。 如果需要放置一个单一的 javaFX 控件到 Swing 应用中,必须采用前述两步。 当 javaFX 控件被集成到 Swing 应用中时,这些 FX UI 控件依然是由 Prism 图形库渲染,并能 完全利用它的高级渲染能力。 下一节,将介绍 Label 控件。…… 2 Label(标签) 这节介绍 Label 类的使用。该类在 JavaFX API 包 javafx.scene.control 中,用于展示文本元 素。本节将学到如何包起文本元素以适应规定空间、如何增加图形图片以及如何应用效果等。 图 2-1 展示了三个普通标签的用法。左侧标签是一个带图片的文本元素,中间的标签是 个旋转的文本,右边的是一个渲染包起文本的标签情况。 图 2-1 标签应用示例 2.1 创建标签 JavaFX API 提供了三种 Label 类的构造方式,如示例 2-1 所示: 示例 2-1 创建标签 //An empty label Label label1 = new Label(); //A label with the text element Label label2 = new Label("Search"); //A label with the text element and graphical icon Image image = new Image(getClass().getResourceAsStream("labels.jpg")); Label label3 = new Label("Search", new ImageView(image)); 一旦在代码中创建了标签,就可添加文本化和图形化的内容,添加相应内容的方法为:  setText(String text)方法——为标签设定标题;  setGraphic(Node graphic)——为标签设定图标; SetTextFill 方法指定标签的绘制颜色。学习下示例 2-2,它创建了文本标签,增加一个小图标 并为文本指定了填充颜色。 示例 2-2 添加图标、文本 Label label1 = new Label("Search"); Image image = new Image(getClass().getResourceAsStream("labels.jpg")); label1.setGraphic(new ImageView(image)); label1.setTextFill(Color.web("#0076a3")); 这些代码片段增加到应用后,将产生如下图 2-2 的效果。 图 2-2 带图标标签 在为按钮定义文本和图形化内容时,也可以使用 setGraphicTextGap 方法来设置两者间距。 另外,也可以通过使用 setTextAlignment 方法在它内容布局域内改变标签内容的位置。通过 应用 setContentDisplay 方 法 并 指 定 ContentDisplay 常 量 ( 包 括 LFFT, RIGHT, CENTER, TOP, BOTTOM)来定义图形相对文本的位置。 2.2 设置字体 比较一下图 2-1 标签和图 2-2 标签,会注意图 2-1 字体更大些。这是因为示例 2-2 没有为标 签设定任何字体,默认了缺省字体。为了不使用缺三字体,需要使用 Label 类的 setFont 方 法。示例 2-3 代码片设置了标签 label1 的字体为 30 点阵、字体名字为 Arial。而 label2 设置 了大小为 32 点阵、字体名为 Cambria。 示例 2-3 字体设置 //Use a constructor of the Font class label1.setFont(new Font("Arial", 30)); //Use the font method of the Font class label2.setFont(Font.font("Cambria", 32)); 2.3 包装字体 有时候创建标签后,却必须在比希望的小的的空间进行渲染。也就是必须打碎(包装)文本 以适合布局区域要求。这是需要设定 setWrapText 方法为 true。如示例 2-4 所示 示例 2-4 文本包装 Label label3 = new Label("A label that needs to be wrapped"); label3.setWrapText(true); 当 label3 被增加到应用内容中时,将被渲染成图 2-3 所示 图 2-3 包装文本标签 假如标签的布局区域的长宽都被限定,那标签将不能完成渲染所需文本字符串。这时,可以 使用 Label 类的 setTextOverrun 方法和 OverrunStyle 类型来定义如何处理不能被正确处理的 字符串。详情可以查看 API 文档中关于 OverrunStyle 的类型。 2.4 应用效果 尽管标签内容是静态的,且不能编辑,但可以对之应用可视效果或转换。如示例 2-5 代码片 所示,把标签选择了 270 度,并垂直转换了它的位置。 示例 2-5 旋转标签 Label label2 = new Label ("Values"); label2.setFont(new Font("Cambria", 32)); label2.setRotate(270); label2.setTranslateY(50); 旋转和转化是 JavaFX API 中典型的转换。另外,可以在用户鼠标移过标签时实现放缩效果。 示例代码 2-6 对 label3 应用了放缩效果.当标签的 MOUSE_ENTERED 事件被触发时,setScaleX 和 setScaleY 方法的缩放因子设置为 1.5。当用户移除鼠标时,事件 MOUSE_EXITED 出现,缩 放因子设置为 1.0,标签渲染为原始状态。 示例 2-6 缩放效果 label3.setOnMouseEntered(new EventHandler() { @Override public void handle(MouseEvent e) { label3.setScaleX(1.5); label3.setScaleY(1.5); } }); label3.setOnMouseExited(new EventHandler() { @Override public void handle(MouseEvent e) { label3.setScaleX(1); label3.setScaleY(1); } }); 效果如图 2-4 所示。 图 2-4 标签缩放 3 Button(按钮) 按钮 Button 类通过 JavaFX API 使开发者处理用户点击行为。Button 类是 Labeled 类的扩展。 它可以显示文本、图片或二者同现。图 3-1 展示了按钮的的不同效果图。这部分讲学习到怎 么创建这些按钮类型。 图 3-1 按钮类型 3.1 创建按钮 可以使用三种构造器创建 JavaFX 应用程序的按钮控件,如示例 3-1 所示: 示例 3-1 创建按钮 //A button with an empty text caption. Button button1 = new Button(); //A button with the specified text caption. Button button2 = new Button("Accept"); //A button with the specified text caption and icon. Image imageOk = new Image(getClass().getResourceAsStream("ok.png")); Button button3 = new Button("Accept", new ImageView(imageOk)); 因为 Button 类扩展自 Labeled 类,也可以使用如下方法为不带图标和文本标题的按钮设 定相关内容:  setText(String text)方法:制定按钮的文本标题;  setGraphic(Node graphic)方法:指定按钮图标。 示例 3-2 展示了如何创建带图标而五标题的按钮。 示例 3-2 添加按钮图标 Image imageDecline = new Image(getClass().getResourceAsStream("not.png")); Button button5 = new Button(); button5.setGraphic(new ImageView(imageDecline)); 添加如上代码到应用中后,代码段产生的按钮效果如图 3-3 所示: 图 3-3 图标按钮 在示例 3-2 和图 3-2 中,图标是个 ImageView 对象。然而还可以使用其它图形对象,诸如 javafx.scene.shape 包中的图形元素对象。在定义好按钮的文本和图标后,可以使用 setGaphicTextGap 方法设置二者间的间隔。 缺省的按钮外观区别如下图 3-3 所示: 图 3-3 按钮状态 3.2 赋予行为 按钮的主要功能是点击时产生行为。使用 Button 类的 setAction 方法设定用户点击按钮的行 为。如示例 3-3 代码段所示: 示例 3-3 定义按钮行为 button2.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { label.setText("Accepted"); } }); ActionEvent 是由 EvenHandler 处理的一种事件类型。EvenHandler 对象提供了一个 handle 方 法来处理按钮触发的行为。示例 3-3 展示了如何覆盖方法 handle,以便用户点击按钮时可以 改变标签的标题文字。 根据需要,可以使用 Button 类来设置很多事件处理方法,以便产生特定行为或应用效果。 3.3 应用效果 由于 Button 类扩展自 Node 类,故可以应用 javafx.scene.effect 包中的任何效果来强化按钮外 貌。示例 3-4 中,DropShadow 效果在鼠标进入时应用到按钮上。 示例 3-4 应用 DropShadow 效果 DropShadow shadow = new DropShadow(); //Adding the shadow when the mouse cursor is on button3.addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler() { @Override public void handle(MouseEvent e) { button3.setEffect(shadow); } }); //Removing the shadow when the mouse cursor is off button3.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler() { @Override public void handle(MouseEvent e) { button3.setEffect(null); } }); 图 3-4 展示了按钮在鼠标进入和离开是的的状态 图 3-4 按钮 DropShadow 效果 3.4 式样化按钮 通过Skin类来使用CSS式样表增强按钮的可视化外貌风格。在JavaFX2.0中使用CSS和在HTML 中类似。因为都基于相同的 CSS 规范。 定义好 CSS 独立文件后,通过使用 setStyleClass 方法产生效果。这个方法继承自 Node 类, 并适用所有界面控件。另外,也可直接代码中应用 setStyle 方法直接定义按钮式样。如示例 3-5 和图 3-4 所示。 示例 3-5 式样按钮 button1.setStyle("-fx-font: 22 arial; -fx-base: #b6e7c9;"); 属性”-fx-font-size”设置了字体大小,”-fx-base”属性覆盖缺省颜色。具体结果如下图 3-5 所示: 图 3-5 css 按钮 4 RadioButton(单选按钮) 类 RadioButton 是类 ToggleButton 的特殊实现版。单选按钮控件只能选中或者不消。典型的 单选按钮应用是组成组,而同时只有一个按钮可选。这种行为有别与切换(toggle)按钮, 因为同组内所有切换按钮可以在“非选”状态。 图 4-1 展示了 RadioButton 的三种应用情况,且每组包含三个单选按钮。 图 4-1RadioButton 示例 继续学习下面的内容将学到更多的单选按钮的实现方式。 4.1 创建单选按钮 类 RadioButton 在包 javafx.scene.control 中,可以通过两个构造器来创建单选按钮。示 例 4-1 展示了两个单选按钮。无参数构造器创建了 rb1,并用 setText 方法设置了标题文本。 rb2 标题则有在构造器内生成。 示例 4-1 创建单选按钮 //A radio button with an empty string for its label RadioButton rb1 = new RadioButton(); //Setting a text label rb1.setText("Home"); //A radio button with the specified label RadioButton rb2 = new RadioButton("Calendar"); 可以调用 setSelected 方法并设置为 true 来明确选定单选按钮,如果需要检查一特定按钮是 否选中,可以应用 isSelected 方法判断。 由于 RadioButton 类扩展自 Labeled 类,因此不仅可以设定文本标题,还可以使用 setGraphic 方法指定图片。示例 4-2 演示了应用中图形单选按钮的实现方式。 示例 4-2 创建图形化单选按钮 Image image = new Image(getClass().getResourceAsStream("ok.jpg")); RadioButton rb = new RadioButton("Agree"); rb.setGraphic(new ImageView(image)); 4.2 单选按钮组 单选按钮典型应用是成组互斥项选择。ToggleGroup 对象提供了与之有关的单选按钮的引用, 并管理之,以便同时只能选择一项。示例 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-2 单选按钮组 4.3 单选按钮事件处理 典型情况下,当组中单选按钮选中后应用程序会执行一个行为。查看示例 4-4 代码段可以学 习如何选择单选按钮来改变相应的图标。 Example 4-4 Processing Action for Radio Buttons ImageView image = new ImageView(); rb1.setUserData("Home") rb2.setUserData("Calendar"); rb3.setUserData("Contacts"); final ToggleGroup group = new ToggleGroup(); group.selectedToggleProperty().addListener(new ChangeListener(){ public void changed(ObservableValue ov, Toggle old_toggle, Toggle new_toggle) { if (group.getSelectedToggle() != null) { final Image image = new Image( getClass().getResourceAsStream( group.getSelectedToggle().getUserData().toString() + ".jpg" ) ); icon.setImage(image); } } }); 用户数据被赋予每个单选按钮。ChangeListener对象负责检查组中选中的按钮。这是 通过使用 getSelectedToggle 方法来界定并调用 getUserData 方法来提取当前选中按钮的用户 数据,并把相应用图片文件名户户数据用于加载构建。 比如,当 rb3 选中时,getSelectedToggle 方法返回“rb3”,并且 getUserData 方法返回“Contracts”。 因此 getResourceAsStream 方法接收“Contracts.jpg”值,加载处理并显示。应用输出结果如 图 4-1 所示。 4.4 单选聚焦请求 在单选按钮组中,第一个按钮是缺少聚焦的。如果应用 setSelected 方法选中组中第二个按 钮,你将看到如下图 4-3 所示的情形 图 4-3 缺省焦点设置 第二个按钮选中,但第一个按钮人保持聚焦状态。为了改变上述缺省聚焦,使用 requestFocus 功能来改变聚焦模式。如示例 4-5 所示。 示例 4-5 请求聚焦 rb2.setSelected(true); rb2.requestFocus(); 当通过这样应用改变后,这些代码产生的结构如图 4-4 所示。 图 4-4 选择并聚焦 5 切换按钮(Toggle Button) ToggleButton 类代表着另一类有效按钮。两个或以上此类按钮可以联合成一组,并同时只能 有一个被选中或没有选择需要。图 5-1 展示了一组三个按钮联合成组的应用情况。这个应用 是用被选按钮指定颜色绘制矩形。 图 5-1 切换按钮 5.1 创建切换按钮 应用中 ToggleButton 类有三种创建切换按钮的构造器,根据需要使用。如示例 5-1 所示 示例 5-1 创建切换按钮 //A toggle button without any caption or icon ToggleButton tb1 = new ToggleButton(); //A toggle button with a text caption ToggleButton tb2 = new ToggleButton("Press me"); //A toggle button with a text caption and an icon Image image = new Image(getClass().getResourceAsStream("icon.png")); ToggleButton tb3 = new ToggleButton ("Press me", new ImageView(image)); ToggleButton 类扩展自 Labeled 类,因此可以设定文本标题、图标或是二者。即可以使用 Labeled 类的 setText 和 setGraghic 方法来指定切换按钮的文本和图形内容。 另外,代码中定义好了切换按钮后,还可以联合成组并设定特殊行为。 5.2 添加切换按钮到组 ToggleButton 类和 RadioButton 类实现类似,但是不像单选按钮,组中切换按钮不能强制至 少选择一个。就是说,单击选中按钮会导致非选中,而单击单选按钮组中选中按钮则没影响。 花点时间学习下示例 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); ToggleButton tb3 = new ToggleButton("Critical"); tb3.setToggleGroup(group); 示例 5-2 创建三个切换按钮,并添加到切换组中。切换按钮 tb1 调用 setSelected 方法以便应 用启动后选中。然而也可取消选中 Minor 切换按钮以达到组中没有选中情况。如图 5-2 所示。 图 5-2 三个切换按钮的组 典型情况下,使用一组单选按钮来赋予相应的特殊行为。接下来部分解释怎么使用切换按钮 改变矩形颜色。 5.3 设定行为 继承自 Node 类的 ToggleButton 类的 setUserData 方法有助于把任何选择项和特定值联系起来。 示例 5-3 中,用户数据就是指定那种色彩用于绘制矩形。 示例 5-3 设置切换按钮用户数据 tb1.setUserData(Color.LIGHTGREEN); tb2.setUserData(Color.LIGHTBLUE); tb3.setUserData(Color.SALMON); final Rectangle rect = new Rectangle(145, 50); final ToggleGroup group = new ToggleGroup(); group.selectedToggleProperty().addListener(new ChangeListener(){ public void changed(ObservableValue ov, Toggle toggle, Toggle new_toggle) { if (new_toggle == null) rect.setFill(Color.WHITE); else rect.setFill( (Color) group.getSelectedToggle().getUserData() ); } }); ChangeListener对象检查组中被选择按钮。如果没有选中,则矩形用白色绘制,如果 有选中的,则连续调用 getSelectedToggle 和 getUserData 方法返回值并绘制矩形。 例 如 , 如 果 用 户 选 择 了 tb2 ,则 setSelectedToggle().getUserData() 被 调 用 并 返 回 Color.LIGHTBLUE 值绘制矩形,如图 5-3 所示。 5.4 风格化切换按钮 可以为切换按钮用于 CSS 以强化应用效果。JavaFX2.0 中使用 CSS 和 HTML 中相似,因为基于 相同 CSS 规范。示例 5-4 使用了 setStyle 方法来改变切换按钮的“-fx-base”属性。 示例 5-4 应用 CSS tb1.setStyle("-fx-base: lightgreen;"); tb2.setStyle("-fx-base: lightblue;"); tb3.setStyle("-fx-base: salmon;"); 把如上的代码行增加到应用代码中,其展示的可视效果如图 5-4 所示。 有兴趣也可以尝试 ToggleButton 其它 CSS 属性,或者应用动画、转换、可视效果等。 6 选择框(CheckBox) CheckBox 类为应用中创建选择框提供了支持。尽管选择框和单选按钮类似,但它们不能被 组合到切换组中时同时只能选择一个。 图 6-1 展示了一个选择框应用情况——使应用工具栏中图标生效或失效。 图 6-1 选择框示例 6.1 创建选择框 示例 6-1 创建两个简单选择框 示例 6-1 创建选择框 //A checkbox without a caption CheckBox cb1 = new CheckBox(); //A checkbox with a string caption CheckBox cb2 = new CheckBox("Second"); cb1.setText("First"); cb1.setSelected(true); 一旦创建好选择框,就能通过使用相应 APIs 来改变。在示例 6-1 中 setText 方法定义了 c1 文 本标题。setSelected 方法设置 true,以便应用启动时 cb1 选择框被选中。 6.2 定义状态 选择框即可定义也可不定义。如果定义了可以进行选择或取消选择,但是如果没有定义,则 不能进行选中或取消选中。可以使用 CheckBox 类的 setSelected 和 setIndeterminate 方法联 合类指定选择框的状态。表 6-1 展示了基于 INDETERMINATE 和 SELECTED 属性的三种选择框 状态。 表 6-1 选择框状态 Property Values Checkbox Appearance INDETERMINATE = false SELECTED = false INDETERMINATE =false SELECTED = true INDETERMINATE = true SELECTED = true/false 在应用中可能需要使用选择框的三种状态来代表相应的混合状态的 UI 元素,例如 "Yes", "No", "Not Applicable." 。allowIndeterminate 属性决定选择框是否循环三种状态:selected, deselected, and undefined (选中、不选和未定义)。如果这个变量是 true,这个空间将循 环三种状态;如果是 false,则值应用选中和不选两种状态。下文将描述三个选择框并使用 两种状态起作用。 6.3 设置行为 示例 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] = new Image(getClass().getResourceAsStream(names[i] + ".png")); final ImageView icon = icons[i] = new ImageView(); final CheckBox cb = cbs[i] = new CheckBox(names[i]); cb.selectedProperty().addListener(new ChangeListener() { public void changed(ObservableValue ov, Boolean old_val, Boolean new_val) { icon.setImage(new_val ? image : null); } }); } 名为 names 数组使用循环创建了选择框数组和相应的图标数据。例如 cbs[0]第一个选择框被 赋予“Security ” 文 本 标 题 。 同 时 image[0] 接 收 “ security.png ” 作 为 文 件 名 供 方 法 getResourceStream 方法使用来创建图片。如果一个特殊的选择框被选中,则相应的图片被 赋予到相应图标。如果选择框取消选择,则图标接收 null 图片,并且图标也不会渲染。 图 6-2 展示了当 Security 和 Chart 选择框被选中和 Project 选择框取消选中的情况。 6.4 风格化选择框 图 6-2 中的选择框有一个缺省的外观,可以应用 setStyle 方法改变选择框的外观。如示例 6-3 所示。 示例 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 所示。 图 6-3 式样化选择框 为了给所有的选择框设定式样,可以采用如下处理过程:  创建一个.css 文件;  在 css 文件中创建选择框 CSS 类;  在选择框 CSS 类中定义所有需要的式样;  在 JavaFX 应用中,是式样表能被 setStyleClass 方法使用。 7 下拉选择框(Choice Box) 类 ChoiceBox 为快速在多选择项中选择提供了支持。通过图 7-1 评估下拉选择框的简要实现 情况。 图 7-1 下拉选择框 7.1 创建下拉框 示例 7-1 展示了创建三项下拉选择框的应用。 示例 7-1 创建下拉框 ChoiceBox cb = new ChoiceBox(FXCollections.observableArrayList( "First", "Second", "Third") ); 示例 7-1 展示了在 ChoiceBox 类的构造器内创建选择项并生成下拉选择框的情形。这些选择 项也可通过一个可见数组来指定。因此,作为可选性,可以使用空构造器创建下拉框,然后 使用 setItems 方法来设置选择项。如示例 7-2 所示。 示例 7-2 文本元素和分割符下拉框 ChoiceBox cb = new ChoiceBox(); cb.setItems(FXCollections.observableArrayList( "New Document", "Open ", new Separator(), "Save", "Save as") ); 注意,下拉框不仅可以包含文本元素,也能包括其它对象。比如在示例 7-2 中 Separator 控 件用来分割选择项。当这些代码集成到应用后,产生的输出效果如图 7-2 所示: 图 7-2 使用下拉框创建的菜单 在实际应用中,这些下拉选择框常用于构建多选择项列表。 7.2 设置选择框行为 图 7-3 展示了一个有五个选项的多选项应用情况。当选择相应语言时,相应的内容将被渲染 展现。 图 7-3 多选择列表 图 7-4 提供了一份代码段描述了选择框被选项是如何协调产生相应渲染效果的。 图 7-4 选定一个选择项 ChangeListener对象通过持续调用 getSelectionModel 和 selectedIndexProperty 方法 侦测被选项索引。getSelectionModel 方法返回选择项,selectedIndexProperty 方法返回 cb 选 择框的 SELECTED_INDEX 属性。这样,整数值作为索引定义了数组元素并为标签指定了相应 文本字符串。比如,若用户选择了第二项,相对应于“Spanish”,则 SELECTED_INDEX 被赋 予 1,并且“Hola”被选中,因此标签将渲染“Hola”。 通过为下拉框赋予提示框,可以构建 ChoiceBox 控件更多信息。一个提示框是 javafx.scene.contol 包中一个有效 UI 控件。提示框控件可应用与 JavaFX 的任意 UI 控件。 7.3 应用提示框 提示框类 Tooltip 预设提供了一个通过 setTooltip 方法很容易应用到选择框(或是其它任意控 件)提示框。如示例 7-3 所示。 示例 7-3 添加提示框 cb.setTooltip(new Tooltip("Select the language")); 典型应用是用户在 Tooltip 类的构造器内定义是文本。但是若应用程序的逻辑要求 UI 动态设 置文本,则可用空的构造器构建提示框,再通过 setText 方法设置相应文本即可。 提示框应用到 cb 选择框后,用户把鼠标移到选择框就可看到图片了,如图 7-5 所示。 图 7-5 应用提示框的下落选择框 为进一步强化应用,你可以用 CSS 特性或应用可视效果或转换来式样化相应的下来选择框。 读者可自行尝试。 8 文本域(Text Field) 类 TextField 是实现了接收和显示文本输入的用户界面控件。它提供了接收用户文本输入的 能力。随同另一个文本输入控件 PasswordField,都扩展自超类 TextInput。 图 8-1 展示了一个典型的带有标签的文本输入域。 图 8-1 标签和文本框 8.1 创建文本 在示例 8-1 中,文本框和标签一起表名在输入域应该输入的内容类型。 示例 8-1 创建文本框 Label label1 = new Label("Name:"); TextField textField = new TextField (); HBox hb = new HBox(); hb.getChildren().addAll(label1, textField); hb.setSpacing(10); 可以如示例 8-1 创建一个空的文本域,也可创建带特定文本数据的文本域。为了创建带预设 文本的文本框,使用如下构造方法:TextField(“Hello World!”)。在任何时候都可使用 getText 方法获取文本框的内容。 可以使用 TextInput 类的 setPrefColumnCount 方法设置文本域的大小,定义一次显示的最大 字符数。 8.2 用文本域构建用户界面 一般文本框对象(TextField)常被用于表单中创建多个文本域。图 8-2 应用显示了三个输入 域,并处理用户输入的数据。 图 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 TextField name = new TextField(); name.setPromptText("Enter your first name."); name.setPrefColumnCount(10); name.getText(); GridPane.setConstraints(name, 0, 0); grid.getChildren().add(name); //Defining the Last Name text field final TextField lastName = new TextField(); lastName.setPromptText("Enter your last name."); GridPane.setConstraints(lastName, 0, 1); grid.getChildren().add(lastName); //Defining the Comment text field final TextField comment = new TextField(); comment.setPrefColumnCount(15); 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 通过空构造器创建。 不像示例 8-1 创建方式,标签没有伴随文本框在代码片中出现。相反,文本框内的提示信息 通知用户输入什么类型数据。应用启动时,提示信息通过 setPromptText 方法来实现。示例 8-2 的代码段在应用中产生的效果如图 8-3 所示: 图 8-3 提示信息输入框 提示文本和输入文本的不同在于提示文本不能通过 getText 方法获取。 在实际应用中,输入到到文本框的数据将由应用逻辑需要的方式进行处理。下面部分将解释 如何使用输入框并产生用户响应。 8.3 处理文本数据 如前文所述,用户输入的文本数据可由继承自 TextInput 类的 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 for your comment!"); } else { label.setText("You have not left a comment."); } } }); //Setting an action for the Clear button clear.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { name.setText(""); lastName.setText(""); comment.setText(""); label.setText(null); } }); 当用户点击“Submit”按钮时,setOnAction 方法检查 comment 文本框。如果如果包含有非 空字符串,将有相应信息被渲染。否则,应用程序会通知用户注释消息缺失。如图 8-4 所示。 图 8-4 注释框空白 当用户点击 Clear 按钮时,三个输入框内容都被擦出。 下面浏览下文本框有用的几个方法:  Copy()——复制选中范围的内容到剪贴板,并保留选中内容;  Cut()——剪切选中内容到剪贴板,并移走选中内容;  Paste()——从剪切板复制内容到输入框,替换选中内容 9 密码输入框(Password Field) 类 PasswordField 类实现了一种特定的文本输入域。用户键入的字符被展示的星号隐藏。如 图 9-1 展示了带内部提示信息的密码框 图 9-1 提示密码框 9.1 创建密码框 看下示例 9-1 代码片的初始级的密码框的创建。 示例 9-1 创建密码框 PasswordField passwordField = new PasswordField(); passwordField.setPromptText("Your password"); 对于用户界面,可以用提示信息和密码框一起,也可增加一个标签提示。和 TextField 类一 样,PasswordField 类也提供 setText 方法在应用启动时在控件内来渲染文本。但是 setText 方 法指定的文本将被星号隐藏。缺省情况下,星号是替代显示字符。图 9-2 展示了带预定文本 的情形。 图 9-2 密码框 键入密码框内的文字也可以用 getText 的方法获取,并可进一步处理和授权。 9.2 评估密码框 花点时间审查下示例 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 PasswordField pb = new PasswordField(); pb.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { 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.setText(""); } }); hb.getChildren().addAll(label, pb); vb.getChildren().addAll(hb, message); 这个密码框的授权逻辑通过 setOnAction 方法来定义的。这个方法在密码框被提交时被调用, 创建一个新的 EventHandler 对象来处理这个输入的值。如果输入值和要求的不同,将会有相 应的信息响应并提示。如图 9-3 所示 图 9-3 密码不正确 如果输入值满足预订条件要求,将会提示确认信息。如图 9-4 所示 图 9-4 密码正确 基于安全的原因,输入值后再清除密码框是个好的实战。在示例 9-2 中,身份认证执行后, 密码框被设置为空值。 10 滚动条(Scroll Bar) 类 ScrollBar 是你可以在应用中创建一个能滚动且可视的面板。图 10-1 展示了滚动条的三个 区域:滑块和左右按钮(或上下按钮)以及轨道。 图 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 代码所示。 示例 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 = new ScrollBar(); final Image[] images = new Image[5]; final ImageView[] pics = new ImageView[5]; final VBox vb = new VBox(); DropShadow shadow = new DropShadow(); @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] = new Image(getClass().getResourceAsStream( "fw" +(i+1)+ ".jpg") ); final ImageView pic = pics[i] = new ImageView(images[i]); pic.setEffect(shadow); vb.getChildren().add(pics[i]); } sc.valueProperty().addListener(new ChangeListener() { public void changed(ObservableValue ov, Number old_val, Number new_val) { vb.setLayoutY(-new_val.doubleValue()); } }); stage.show(); } public static void main(String[] args) { launch(args); } } 初始代码行向场景图中添加了带图像和滚动条垂直框。垂直框的 Y 坐标随滚动条的 VALUE 属性值的改变而变。因此每次滑块移动,或点击按钮或点击轨道,这个垂直框也移动。如示 例 10-3 所示。 示例 10-3 可滚动垂直框 sc.valueProperty().addListener(new ChangeListener() { public void changed(ObservableValue ov, Number old_val, Number new_val) { vb.setLayoutY(-new_val.doubleValue()); } }); 编译并运行此应用,将会输出图 10-2 所示的结果。 图 10-2 滚动条示例 这个应用展示了滚动条类(ScrollBar)的典型应用。也可以定制该类来创建一个滚动域。像 于每个 UI 控件和节点一样,滚动条都可式样化改变它相应外观。 11 滚动面板(Scroll Pane) 滚动面板提供了一个可滚动的 UI 元素视图。这个控件使用户可以通过平移视图的方式滚动 内容。滚动面板的缺省设置下带有图像的情形如图 11-1 所示。 图 11-1 滚动面板 11.1 创建滚动面板 示例 11-1 展示了如何在应用中创建滚动面板 示例 11-1 用滚动面板看图 Image roses = new Image(getClass().getResourceAsStream("roses.jpg")); ScrollPane sp = new ScrollPane(); sp.setContent(new ImageView(roses)); setContent 方法设定滚动面板的内容,也可仅指定一个节点。为创建多组件滚动视图,使用 布局容器或 Group 类来实现。也可以指定 setPannable 方法为 true 值,通过点击和移动鼠标 来预览图片,滚动体的位置也随之改变。 11.2 设置滚动面板的滚动策略 ScrollBar 类提供了一种策略来决定何时出现滚动条:一直显示、从不显示或按需显示。可分 别使用 setHbarPolicy 和 setVbarPolicy 方法来设定相应滚动策略。在示例 11-2 中,垂直滚动 条会出现而水平的不会出现。 示例 11-2 设置水平和垂直滚动条策略 sp.setHbarPolicy(ScrollBarPolicy.NEVER); sp.setVbarPolicy(ScrollBarPolicy.ALWAYS); 结果如图 11-2 所示 图 11-2 无水平滚动条 11.3 调整滚动面板内组件 在设计用户界面是,可能需要调整组件,以便与滚动面板相匹配。通过设置方法 setFitToWidth 和 setFitToHeight 为 true 可以匹配特定纬度。 图 11-3 滚动面板包含一个单选按钮、一个文本框和一个密码框。内容尺寸超过了滚动面板 的大小,因此垂直滚动条出现。但是由于 setFitToWidth 被设置为 true 值,所以在宽度上内 容收缩时水平滚动条不会出现。 图 11-3 自适应宽度 缺省情况下,FIT_TO_WIDTH 和 FIT_TO_HEIGHT属性值为false,且可调整内容保持原始大小。 如果移走 setFitToWidth 代码,你将看到如图 11-4 所示的情形。 图示 11-4 缺省属性 类 ScrollBar 是你实现检索查阅,并能设置水平和垂直方向内容的当前、最小以及最大值。 这些开发者可在实践中学习具体应用。 11.4 带滚动面板应用示例 示例 11-3 使用了一个滚动面板显示一个带图的垂直框。ScrollPane 类的 VVALUE 属性有助于 标定当前展示的图片,并渲染相应图片文件名称内容。 示例 11-3 使用滚动板查看图片 package scrollpanesample; 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 = new ScrollPane(); final Image[] images = new Image[5]; final ImageView[] pics = new ImageView[5]; final VBox vb = new VBox(); final Label fileName = new Label(); final String [] imageNames = new String [] {"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("Scroll Pane"); box.getChildren().addAll(sp, fileName); VBox.setVgrow(sp, Priority.ALWAYS); fileName.setLayoutX(30); fileName.setLayoutY(160); for (int i = 0; i < 5; i++) { images[i] = new Image(getClass().getResourceAsStream(imageNames[i])); pics[i] = new ImageView(images[i]); pics[i].setFitWidth(100); pics[i].setPreserveRatio(true); vb.getChildren().add(pics[i]); } sp.setVmax(440); sp.setPrefSize(115, 150); sp.setContent(vb); sp.vvalueProperty().addListener(new ChangeListener() { public void changed(ObservableValue ov, Number old_val, Number new_val) { fileName.setText(imageNames[(new_val.intValue() - 1)/100]); } }); stage.show(); } public static void main(String[] args) { launch(args); } } 编译代码并运行此应用,产生的效果如下图 11-5 所示. 图 11-5 滚动图片 垂直滚动条的最大值与垂直框的高度一样。示例 11-4 的代码片渲染了当前展示图片文件的 名称。 示例 11-4 跟踪滚动面板垂直变化 sp.vvalueProperty().addListener(new ChangeListener() { public void changed(ObservableValue ov, Number old_val, Number new_val) { fileName.setText(imageNames[(new_val.intValue() - 1)/100]); } }); ImageView 对象限制图片高度为 100 像素。因此,当 new_val.intValue-1 除以 100 时的结果将 指定当前图片数组的索引。 在应用中,也可以改变观点条最大和最小值,以便动态更新用户界面。 12 列表视图(List View) ListView 类代表一个可滚动的列表项清单。图 12-1 展示了一个这样有效住宿类型清单。 图 12-1 简单列表视图 可自行使用 setItems 方法来定义列表清单。通应用 setCellFactory 方法也可创建一个列表项 视图。 12.1 创建列表视图 示例 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 可调整垂直列表 通过设置方向属性为 Oreintation.HORIZONTAL,可以设置 ListView 对象为水平方向。图 12-1 列 表调整为水平方向其展示的效果如图 12-3 所示。 图 12-3 水平列表清单 通过如下联合方法可以获得每个列表项的状态:  getSelectionModel().selectedIndexProperty() –返回选择项索引;  getSelectionModel().selectedItemProperty()-返回当前选择项;  getFocusModel().getFocusedIndex()-返回当前焦点项索引;  getFocusModel().getFocusedItem()-返回当前焦点项。 注意:选中和聚集状态是只读的,应用启动后,不能设置相应的选中或聚焦状态。 情面的代码示例描述了如何创建带有文本项的清单,然而,列表视图控件也具有包含 Node 对象的能力。 12.2 用数据组装列表视图 学习下下面的应用代码,如何用户单元格工厂生成列表项。代码如下示例 12-3 色彩模板列 表应用所示。 示例 12-3 创建单元格工厂 import javafx.application.Application; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Scene; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import javafx.util.Callback; public class Main extends Application { ListView list = new ListView(); ObservableList data = FXCollections.observableArrayList( "chocolate", "salmon", "gold", "coral", "darkorchid", "darkgoldenrod", "lightsalmon", "black", "rosybrown", "blue", "blueviolet", "brown"); @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); VBox.setVgrow(list, Priority.ALWAYS); list.setItems(data); list.setCellFactory(new Callback, ListCell>() { @Override public ListCell call(ListView list) { return new ColorRectCell(); } }); stage.show(); } static class ColorRectCell extends ListCell { @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); Rectangle rect = new Rectangle(100, 20); if (item != null) { rect.setFill(Color.web(item)); setGraphic(rect); } } } public static void main(String[] args) { launch(args); } } 单元格工厂生成 ListCell 对象。每个单元格和一个单一数据项关联,并渲染为列表的单一行。 单元格内容通过 setGraphic 方法设定,这个方法可以包含其它控件,如文本、图形或图片。 在此应用中,这个单元格是个矩形。 编译并运行应用代码,产生如图 12-4 所示内容。 图 12-4 色彩模板列表 列表可以滚动,并能选择和取消选择。也可自行扩展此应用,如用带色彩模板的文本标签等。 12.3 处理列表项选择 改变应用代码,如示例 12-4 所示,当列表特定项选中时可进行相应事件处理。 示例 12-4 处理列表项事件 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 = new ListView(); 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); VBox.setVgrow(list, Priority.ALWAYS); label.setLayoutX(10); label.setLayoutY(115); label.setFont(Font.font("Verdana", 20)); list.setItems(data); list.setCellFactory(new Callback, ListCell>() { @Override public ListCell call(ListView list) { return new ColorRectCell(); } }); list.getSelectionModel().selectedItemProperty().addListen er( new ChangeListener() { public void changed(ObservableValue ov, String old_val, String new_val) { label.setText(new_val); label.setTextFill(Color.web(new_val)); } }); stage.show(); } static class ColorRectCell extends ListCell { @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); Rectangle rect = new Rectangle(100, 20); if (item != null) { rect.setFill(Color.web(item)); setGraphic(rect); } } } public static void main(String[] args) { launch(args); } } selectedItemProperty 调用 addListener 方法创建一个新的 ChangeListener对象来处 理选中项的改变。例如,如果“淡紫色”项选中,标签将接受“darkorchid”标题并用相应 的颜色填充。改变后的应用输出如图 12-5 所示。 图 12-5 选中淡紫色 13 表格视图(Table View) 在 JavaFX SDK API 中有好几种类被设计为数据表格。应用中创建表格的重要的类是 TableView、 TableColumn 以及 TableCell。通过数据模型并应用单元格工厂将可组中成数据表。表格类提 供了内置的数据排序以及列调整能力。 图 13-1 展示了典型的数据表,表示地址簿的联系信息。 图 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 extends Application { private TableView table = new TableView(); public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { Scene scene = new Scene(new Group()); stage.setTitle("Table View Sample"); stage.setWidth(400); stage.setHeight(500); final Label label = new Label("Address Book"); label.setFont(new Font("Arial", 20)); TableColumn firstNameCol = new TableColumn("First Name"); TableColumn lastNameCol = new TableColumn("Last Name"); TableColumn emailCol = new TableColumn("Email"); table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); final VBox vbox = new VBox(); vbox.setSpacing(5); vbox.getChildren().addAll(label, table); vbox.setPadding(new Insets(10, 0, 0, 10)); ((Group) scene.getRoot()).getChildren().addAll(vbox); stage.setScene(scene); stage.show(); } } 表格控件由 TableView 类实例化。在示例 13-1 中,表格被加到 VBox 布局容器,但是,也可 直接添加到应用场景中。 示例 13-1 定义了三列来存储地址簿的如下信息:联系人的首名、后名以及电邮。列有 TableColumn 类创建。 TableView 类的 getColumn 方法把创建的列添加到表格。在应用中,你可以使用这种方法动 态添加或删除列。 编译运行结果如下图 13-2 所示: 图 13-2 无数据表格 通过调用 setVisible 方法来管理列的可视性。例如,如果应用逻辑需要隐藏电邮地址列,通 过调用 emailCol.setVisible(false)来实现任务。 例如,假设地址簿中的联系信息由两个 email 账号,然后你需要展现主要和次要的 email 地 址。那么创建两个子列,在 emailCol 上调用 getColumns 方法,如示例 13-2 所示。 示例 13-2 创建嵌套列 TableColumn firstEmailCol = new TableColumn("Primary"); TableColumn secondEmailCol = new TableColumn("Secondary"); emailCol.getColumns().addAll(firstEmailCol, secondEmailCol); 把这儿代码行添加到示例 13-1 中,编译运行结果如图 13-3 所示结果: 图 13-3 嵌套列表格 尽管表格添加到应用中,默认标题“No Content In table”依然会出现。这是因为没有定义数 据。可以通过使用 setPlacehoder 方法指定一个 Node 对象来代替空表中的标题性内容。 13.2 定义数据模型 当在 JavaFX 应用中创建表格式,最后的实践是实现一个类来定义数据模型,ing 提供方法和 字段域来与表更进一步协同工作。示例 13-3 创建了一个 Person 类来定义地址簿的数据 示例 13-3 创建数据模型类 public static class Person { private final SimpleStringProperty firstName; private final SimpleStringProperty lastName; private final SimpleStringProperty email; private Person(String fName, String lName, String email) { this.firstName = new SimpleStringProperty(fName); this.lastName = new SimpleStringProperty(lName); this.email = new SimpleStringProperty(email); } public String getFirstName() { return firstName.get(); } public void setFirstName(String fName) { firstName.set(fName); } public String getLastName() { return lastName.get(); } public void setLastName(String fName) { lastName.set(fName); } public String getEmail() { return email.get(); } public void setEmail(String fName) { email.set(fName); } } 字符串属性 firstName、lastName 和 email 的创建为了引用特定数据元素。 另外,get 和 set 方法因每个数据元素而提供。因此,例如 getFirstName 方法返回属性 firstName 的值,setFirstName 方法设定该属性值。 当数据模型是 Person 类的轮廓,你可创建一个 ObservableList 数组并定义许多你想在表中展 示的数据行。如示例 13-4 代码片所示。 示例 13-4 通过 Observable 列表定义表格数据 final ObservableList data = FXCollections.observableArrayList( new Person("Jacob", "Smith", "jacob.smith@example.com"), new Person("Isabella", "Johnson", "isabella.johnson@example.com"), new Person("Ethan", "Williams", "ethan.williams@example.com"), new Person("Emma", "Jones", "emma.jones@example.com"), new Person("Michael", "Brown", "michael.brown@example.com") ); 下一步是把数据和和表列关联。通过为每个数据元素定义的属性来完成这些关联。如示例 13-5 所示。 示例 13-5 数据列关联 firstNameCol.setCellValueFactory( new PropertyValueFactory("firstName") ); lastNameCol.setCellValueFactory( new PropertyValueFactory("lastName") ); emailCol.setCellValueFactory( new PropertyValueFactory("email") ); setCellValueFactory 方法为每列指定单元格。单元格工程由 PropertyValueFactory 类实现,并 通过表列 firstName、lastName 和 email 属性来引用相应 Person 类的数据。 在数据模型定义时,数据别添加并管理到相应列,也可通过 TableView 类的 setItems 方法把 数据添加到数据表:table.setItems(data)。 由于 ObservableList 对象可实现数据变化跟踪,不管数据何时变化,TableView 内容将自动更 新展示。 测试下示例 13-6 的应用代码。 示例 13-6 创建数据表并添加数据 import javafx.beans.property.SimpleStringProperty; import javafx.scene.control.cell.PropertyValueFactory; import javafx.application.Application; 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 SimpleStringProperty firstName; private final SimpleStringProperty lastName; private final SimpleStringProperty email; private Person(String fName, String lName, String email) { this.firstName = new SimpleStringProperty(fName); this.lastName = new SimpleStringProperty(lName); this.email = new SimpleStringProperty(email); } public String getFirstName() { return firstName.get(); } public void setFirstName(String fName) { firstName.set(fName); } public String getLastName() { return lastName.get(); } public void setLastName(String fName) { lastName.set(fName); } public String getEmail() { return email.get(); } public void setEmail(String fName) { email.set(fName); } } private TableView table = new TableView(); private final ObservableList data = FXCollections.observableArrayList( new Person("Jacob", "Smith", "jacob.smith@example.com"), new Person("Isabella", "Johnson", "isabella.johnson@example.com"), new Person("Ethan", "Williams", "ethan.williams@example.com"), new Person("Emma", "Jones", "emma.jones@example.com"), new Person("Michael", "Brown", "michael.brown@example.com") ); public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { Scene scene = new Scene(new Group()); stage.setTitle("Table View Sample"); stage.setWidth(400); stage.setHeight(500); final Label label = new Label("Address Book"); label.setFont(new Font("Arial", 20)); TableColumn firstNameCol = new TableColumn("First Name"); firstNameCol.setCellValueFactory( new PropertyValueFactory("firstName") ); TableColumn lastNameCol = new TableColumn("Last Name"); lastNameCol.setCellValueFactory( new PropertyValueFactory("lastName") ); TableColumn emailCol = new TableColumn("Email"); emailCol.setMinWidth(200); emailCol.setCellValueFactory( new PropertyValueFactory("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(new Insets(10, 0, 0, 10)); ((Group) scene.getRoot()).getChildren().addAll(vbox); stage.setScene(scene); stage.show(); } } 编译运行应用代码,展示结果如图 13-4 所示 图 13-4 带数据表格 13.3 添加新行 图 13-4 中 5 行数据,到目前它还是不能改变的。可以使用文本框来输入新值到相应列。文 本框控件能实现应用接收用户的输入。示例 13-7 创建三个文本框、并定义相应提示文本, 然后在创建添加按钮。 示例 13-7 使用文本框输入新项目到表格 final TextField addFirstName = new TextField(); addFirstName.setPromptText("First Name"); addFirstName.setMaxWidth(firstNameCol.getPrefWidth()); final TextField addLastName = new TextField(); addLastName.setMaxWidth(lastNameCol.getPrefWidth()); addLastName.setPromptText("Last Name"); final TextField addEmail = new TextField(); addEmail.setMaxWidth(emailCol.getPrefWidth()); addEmail.setPromptText("Email"); final Button addButton = new Button("Add"); addButton.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { data.add(new Person( addFirstName.getText(), addLastName.getText(), addEmail.getText() )); addFirstName.setText(""); addLastName.setText(""); addEmail.setText(""); } }); 当用户单击“Add”按钮时,键入输入框的值被包装成 Person 对象,并添加到 Observable 列表中。这样新的联系信息将出现在列表中。 测验一下示例 13-8 的应用代码。 示例 13-8 带文本输入框的表格 import javafx.application.Application; import javafx.beans.property.SimpleStringProperty; 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.TextField; import javafx.scene.control.cell.PropertyValueFactory; 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 StringProperty firstName; private final StringProperty lastName; private final StringProperty email; private Person(String fName, String lName, String email) { this.firstName = new SimpleStringProperty(fName); this.lastName = new SimpleStringProperty(lName); this.email = new SimpleStringProperty(email); } public String getFirstName() { return firstName.get(); } public void setFirstName(String fName) { firstName.set(fName); } public String getLastName() { return lastName.get(); } public void setLastName(String fName) { lastName.set(fName); } public String getEmail() { return email.get(); } public void setEmail(String fName) { email.set(fName); } } private TableView table = new TableView(); private final ObservableList data = FXCollections.observableArrayList( new Person("Jacob", "Smith", "jacob.smith@example.com"), new Person("Isabella", "Johnson", "isabella.johnson@example.com"), new Person("Ethan", "Williams", "ethan.williams@example.com"), new Person("Emma", "Jones", "emma.jones@example.com"), new Person("Michael", "Brown", "michael.brown@example.com") ); private HBox hb = new HBox(); public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { Scene scene = new Scene(new Group()); stage.setTitle("Table View Sample"); stage.setWidth(400); stage.setHeight(500); final Label label = new Label("Address Book"); label.setFont(new Font("Arial", 20)); TableColumn firstNameCol = new TableColumn("First"); firstNameCol.setCellValueFactory( new PropertyValueFactory("firstName") ); TableColumn lastNameCol = new TableColumn("Last"); lastNameCol.setCellValueFactory( new PropertyValueFactory("lastName") ); TableColumn emailCol = new TableColumn("Email"); emailCol.setMinWidth(200); emailCol.setCellValueFactory( new PropertyValueFactory("email") ); table.setItems(data); table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); final TextField addFirstName = new TextField(); addFirstName.setPromptText("First Name"); addFirstName.setMaxWidth(firstNameCol.getPrefWidth()); final TextField addLastName = new TextField(); addLastName.setMaxWidth(lastNameCol.getPrefWidth()); addLastName.setPromptText("Last Name"); final TextField addEmail = new TextField(); addEmail.setMaxWidth(emailCol.getPrefWidth()); addEmail.setPromptText("Email"); final Button addButton = new Button("Add"); addButton.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { data.add(new Person( addFirstName.getText(), addLastName.getText(), addEmail.getText() )); addFirstName.setText(""); addLastName.setText(""); addEmail.setText(""); } }); 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(new Insets(10, 0, 0, 10)); ((Group) scene.getRoot()).getChildren().addAll(vbox); stage.setScene(scene); stage.show(); } } 应用没有提供任何过滤检查,如 email 地址格式是否正确,是否允许空值等。这读者可自行 提供该功能。 图 13-5 演示了用户添加一行新数据的情形 图 13-5 添加联系信息 图 13-6 展示了点击“Add”按钮后,数据表的情形。 图 13-6 新添加数据 13.4 列数据排序 TableView 类提供了内建的列排序功能。用户通过点击列头类改变数据顺序。第一次点击升 序排列,第二次则降序排列,第三次则不再排序。缺省时不应用排序。 用户在数据表中可以多列排序,并指定每列排序的优先性。为了排序,再点击每列头时用户 按住 Shift 键,就能实现排序了。 图 13-7 中,第一列实现了升序排序,而第二列降序排列。注意第一列优先于第二列。 图 13-7 多列排序 开发者可以通过 setSortType 方法设置每列的优先性来排序。可以指定升序和降序类型。例 如 , 使 用 如 下 代 码 实 现 email 类的降序排 列: emailCol.setSortType(TableColumn.SortType.DESCENDING);。 也可通过添加和删除 TableView.sortOrderde Observable 列表的 TableColumn 实 例指定哪一列来排序。列表中顺序代表排列的优先性(如第零项有最高优先权)。 通过调用 setSortable(false)方法来禁止排序数据。 13.5 编辑表中数据 TableView 类不仅可以渲染表格数据,它还能实现对数据的编辑。用 TableView.edit(int row,TableColumn column)方法来实现数据编辑。也可以使用 TableCell 类来编辑表中的数 据。看下示例 13-9 的代码 示例 13-9 实现单元格编辑 class EditingCell extends TableCell { private TextField textField; public EditingCell() { } @Override public void startEdit() { super.startEdit(); if (textField == null) { createTextField(); } setText(null); setGraphic(textField); textField.selectAll(); } @Override public void cancelEdit() { super.cancelEdit(); setText((String) getItem()); setGraphic(null); } @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (empty) { setText(null); setGraphic(null); } else { if (isEditing()) { if (textField != null) { textField.setText(getString()); } setText(null); setGraphic(textField); } else { setText(getString()); setGraphic(null); } } } private void createTextField() { textField = new TextField(getString()); textField.setMinWidth(this.getWidth() - this.getGraphicTextGap()*2); textField.setOnKeyReleased(new EventHandler() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.ENTER) { commitEdit(textField.getText()); } else if (t.getCode() == KeyCode.ESCAPE) { cancelEdit(); } } }); } private String getString() { return getItem() == null ? "" : getItem().toString(); } } 在示例 13-9 中,createTextField 方法使用 textField 变量来分析输入序列,即当键入或移开输 入时,调用 commitEdit 和 cancelEdit 方法做相应的释放。 TableColumn 类的 setCellFactory 方法用于装配单元格工厂。定制单元格工厂的首要任务是根 据需要返回一个新的 TableCell 实例。示例 13-10 展示了如何实现列的单元格工厂。 示例 13-10 使用单元格工厂 Callback cellFactory = new Callback() { public TableCell call(TableColumn p) { return new EditingCell(); } }; firstNameCol.setCellFactory(cellFactory); lastNameCol.setCellFactory(cellFactory); emailCol.setCellFactory(cellFactory); 如示例 13-11 所示,用 TableColumn 类的 setOnEditCommit 方法来处理数据项的任何改变。 这个方法定义了一个修改数据项、检索新数据并替换 data 列表中相应的元素 示例 13-11 处理表中可编辑数据 //Enabling editing table.setEditable(true); //Modifying the firstName property firstNameCol.setOnEditCommit(new EventHandler>() { @Override public void handle(CellEditEvent t) { ((Person)t.getTableView().getItems().get( t.getTablePosition().getRow())).setFirstName(t.getNewValue()); } }); //Modifying the lastName property lastNameCol.setOnEditCommit(new EventHandler>() { @Override public void handle(CellEditEvent t) { ((Person)t.getTableView().getItems().get( t.getTablePosition().getRow())).setLastName(t.getNewValue()); } }); //Modifying the email property emailCol.setOnEditCommit(new EventHandler>() { @Override public void handle(CellEditEvent t) { ((Person)t.getTableView().getItems().get( t.getTablePosition().getRow())).setEmail(t.getNewValue()); } }); 在应用列表文件清单中可以找到地址簿应用的完整代码。 图 13-8 中,用户编辑最后 Michael Brown 的“last name”。为了编辑表的但与个·单元格, 用户输入了一个新值,然后处理 Enter 键。这个单元格直到 Enter 键按下才改变。这种行为 由实现 TextField 类决定的 图 13-8 编辑单元格 14 树视图(Tree View) 这部分中来学习在 JavaFX 应用中如何构建树结构视图,诸如向树添加条目、处理事件、单 元格工厂实现并应用定制树的单元格等。 包 javafx.scene.control 中的 TreeView 类提供了层次结构视图。在数视图中的最高层级 的对象称为“根”(root),根包含多项子项,每个子项也可有子项,没有子项的叫“叶节点”。 图 14-1 展示了一个有树视图应用概貌。 图 14-1 树图示例 14.1 创建树图 当在 JavaFX 应用中创建树型结构时,典型情况是实例化 TreeView 类,再定义几个 TreeItem 对象,使其中一个 TreeItem 为根节点,把根节点天叫道树型视图,其它节点添加到根节点 上。通过相应 TreeItem 构造器或调用 setGraphic 方法,可为每个树节点设定图标。图标推荐 大小是 16x16 的。但实际上,任何 Node 对象皆可为图标,并能完全互动。 示例 14-1 是一个简单树形视图的应用实现,带有根节点和五个叶子节点。 示例 14-1 创建树型视图 import javafx.application.Application; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class TreeViewSample extends Application { private final Node rootIcon = new ImageView( new Image(getClass().getResourceAsStream("folder_16.png")) ); public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("Tree View Sample"); TreeItem rootItem = new TreeItem ("Inbox", rootIcon); rootItem.setExpanded(true); for (int i = 1; i < 6; i++) { TreeItem item = new TreeItem ("Message" + i); rootItem.getChildren().add(item); } TreeView tree = new TreeView (rootItem); StackPane root = new StackPane(); root.getChildren().add(tree); primaryStage.setScene(new Scene(root, 300, 250)); primaryStage.show(); } } 树型的项在循环内创建并调用 getChildren 和 add 方法添加到根节点,也可用 addAll 方法替 换 add 方法实现一次性添加前面创建的树节点项。 当创建新的树视图对象时可在 TreeView 类的构造器内指定根节点,如示例 14-1 所示,或者 通过调用 TreeView 类的 setRoot 方法设置。 在根节点上调用 setExpanded 方法定义树视图的初始面貌。缺省情况下,所有 TreeItem 实例 是收缩折叠的,如果需要则必须人为展开。通过 setExpanded 方法设置 true 值后,树根在应 用启动时展开的情形如图 14-2 所示。 图 14-2 5 项树图 示例 14-1 创建了字符串项的简单树图。但是,树型结构可包含不同类型的节点项。 14.2 添加不同类型项 使用如下泛型 TreeItem 构造器,可使用数项 TreeItem(T value)来定义特定应用数据。T 值 可以指定为任何对象,如 UI 控件或定制组件。 示例 14-2 解释了如何创建带有字符串、选择框和单选框的树型结构的。 示例 14-2 创建不同元素树视图 import javafx.application.Application; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class TreeViewSample extends Application { private final Node rootIcon = new ImageView( new Image(getClass().getResourceAsStream("folder_16.png")) ); private final ToggleGroup group = new ToggleGroup(); TreeItem[] securityItems = new TreeItem[10]; TreeItem[] serverItems = new TreeItem[6]; public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("Tree View Sample"); TreeItem rootItem = new TreeItem("Settings", rootIcon); rootItem.setExpanded(true); TreeItem securityItem = new TreeItem ("Security"); securityItem.setExpanded(true); TreeItem serverItem = new TreeItem ("Server"); for (int i = 1; i < 9; i++) { TreeItem item = securityItems[i] = new TreeItem (new CheckBox("Option" + i)); } securityItem.getChildren().addAll(securityItems); for (int i = 1; i < 5; i++) { RadioButton rb = new RadioButton("Option" + i); rb.setToggleGroup(group); TreeItem item = serverItems[i] = new TreeItem (rb); } serverItem.getChildren().addAll(serverItems); rootItem.getChildren().addAll(securityItem, serverItem); TreeView tree = new TreeView(rootItem); StackPane root = new StackPane(); root.getChildren().add(tree); primaryStage.setScene(new Scene(root, 300, 250)); primaryStage.show(); } } 创建了三项 TreeItem对象构建树,包括根节点。“Security”项由选择框叶子项构成, “Server”项包含单选按钮叶子项。 编译并运行示例 14-2,其产生的输出界面如图 14-3 所示 图 14-3 字符串、选择框和单选按钮的树图 不像 TreeView 类,TreeItem 不是扩展自 Node 类。因此,不能应用可视效果或添加菜单到树 节点项上。但是可以使用单元工厂(Cell Factory,或细胞工厂)机制来克服这个障碍,并能 为树节点项按需定制相应行为。 14.3 实现单元工厂 单元工厂机制用于生成 TreeCell 实例,并代表树图中单一 TreeItem。使用单元工厂是非常有 用的,特别是对有大量数据操作的应用,诸如动态改变或按需添加等。比如一个展示给定公 司人力资源数据的应用,这可使用户修改雇员和新增雇员信息。 示例 14-3 创建了 Employee 类,并根据部门安排到一个组 示例 14-3 创建人力资源树视图 import java.util.Arrays; import java.util.List; import javafx.application.Application; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.beans.property.SimpleStringProperty; import javafx.scene.layout.VBox; public class TreeViewSample extends Application { private final Node rootIcon = new ImageView(new Image(getClass().getResourceAsStream("root.png"))); private final Image depIcon = new Image(getClass().getResourceAsStream("department.png")); List employees = Arrays.asList( new Employee("Ethan Williams", "Sales Department"), new Employee("Emma Jones", "Sales Department"), new Employee("Michael Brown", "Sales Department"), new Employee("Anna Black", "Sales Department"), new Employee("Rodger York", "Sales Department"), new Employee("Susan Collins", "Sales Department"), new Employee("Mike Graham", "IT Support"), new Employee("Judy Mayer", "IT Support"), new Employee("Gregory Smith", "IT Support"), new Employee("Jacob Smith", "Accounts Department"), new Employee("Isabella Johnson", "Accounts Department")); TreeItem rootNode = new TreeItem("MyCompany Human Resources", rootIcon); public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { rootNode.setExpanded(true); for (Employee employee : employees) { TreeItem empLeaf = new TreeItem(employee.getName()); boolean found = false; for (TreeItem depNode : rootNode.getChildren()) { if (depNode.getValue().contentEquals(employee.getDepartment( ))){ depNode.getChildren().add(empLeaf); found = true; break; } } if (!found) { TreeItem depNode = new TreeItem( employee.getDepartment(), new ImageView(depIcon) ); rootNode.getChildren().add(depNode); depNode.getChildren().add(empLeaf); } } stage.setTitle("Tree View Sample"); VBox box = new VBox(); final Scene scene = new Scene(box, 400, 300); scene.setFill(Color.LIGHTGRAY); TreeView treeView = new TreeView(rootNode); box.getChildren().add(treeView); stage.setScene(scene); stage.show(); } public static class Employee { private final SimpleStringProperty name; private final SimpleStringProperty department; private Employee(String name, String department) { this.name = new SimpleStringProperty(name); this.department = new SimpleStringProperty(department); } public String getName() { return name.get(); } public void setName(String fName) { name.set(fName); } public String getDepartment() { return department.get(); } public void setDepartment(String fName) { department.set(fName); } } } 示例 14-3 中每个 Employee 对象有两个属性:name 和 department。TreeItem 对象相应的雇 员都作为叶子而引用,而相应部门树项作为带子项的树节点。被创建的新部门名字,通过调 用 getDepartment 方法可被从 Employee 对象中检索到。 编译并运行这个应用,其展示的窗口界面如图 14-4 所示。 图 14-4 树型视图应用示例 和示例 14-3 对比,你可以预览树型视图和节点项,但是不能改变已存在的树项或添加新的 树项。示例 14-4 展示了带有元工厂的变更版应用实现。这个变更版具有改变雇员名字的能 力。 示例 14-4 元工厂实现版 import java.util.Arrays; import java.util.List; import javafx.application.Application; import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.TextField; import javafx.scene.control.TreeCell; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.util.Callback; import javafx.beans.property.SimpleStringProperty; import javafx.scene.layout.VBox; public class TreeViewSample extends Application { private final Node rootIcon = new ImageView(new Image(getClass().getResourceAsStream("root.png"))); private final Image depIcon = new Image(getClass().getResourceAsStream("department.png")); List employees = Arrays.asList( new Employee("Ethan Williams", "Sales Department"), new Employee("Emma Jones", "Sales Department"), new Employee("Michael Brown", "Sales Department"), new Employee("Anna Black", "Sales Department"), new Employee("Rodger York", "Sales Department"), new Employee("Susan Collins", "Sales Department"), new Employee("Mike Graham", "IT Support"), new Employee("Judy Mayer", "IT Support"), new Employee("Gregory Smith", "IT Support"), new Employee("Jacob Smith", "Accounts Department"), new Employee("Isabella Johnson", "Accounts Department")); TreeItem rootNode = new TreeItem("MyCompany Human Resources", rootIcon); public static void main(String[] args) { Application.launch(args); } @Override public void start(Stage stage) { rootNode.setExpanded(true); for (Employee employee : employees) { TreeItem empLeaf = new TreeItem(employee.getName()); boolean found = false; for (TreeItem depNode : rootNode.getChildren()) { if (depNode.getValue().contentEquals(employee.getDepartment())){ depNode.getChildren().add(empLeaf); found = true; break; } } if (!found) { TreeItem depNode = new TreeItem( employee.getDepartment(), new ImageView(depIcon) ); rootNode.getChildren().add(depNode); depNode.getChildren().add(empLeaf); } } stage.setTitle("Tree View Sample"); VBox box = new VBox(); final Scene scene = new Scene(box, 400, 300); scene.setFill(Color.LIGHTGRAY); TreeView treeView = new TreeView(rootNode); treeView.setEditable(true); treeView.setCellFactory(new Callback,TreeCell>(){ @Override public TreeCell call(TreeView p) { return new TextFieldTreeCellImpl(); } }); box.getChildren().add(treeView); stage.setScene(scene); stage.show(); } private final class TextFieldTreeCellImpl extends TreeCell { private TextField textField; public TextFieldTreeCellImpl() { } @Override public void startEdit() { super.startEdit(); if (textField == null) { createTextField(); } setText(null); setGraphic(textField); textField.selectAll(); } @Override public void cancelEdit() { super.cancelEdit(); setText((String) getItem()); setGraphic(getTreeItem().getGraphic()); } @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (empty) { setText(null); setGraphic(null); } else { if (isEditing()) { if (textField != null) { textField.setText(getString()); } setText(null); setGraphic(textField); } else { setText(getString()); setGraphic(getTreeItem().getGraphic()); } } } private void createTextField() { textField = new TextField(getString()); textField.setOnKeyReleased(new EventHandler() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.ENTER) { commitEdit(textField.getText()); } else if (t.getCode() == KeyCode.ESCAPE) { cancelEdit(); } } }); } private String getString() { return getItem() == null ? "" : getItem().toString(); } } public static class Employee { private final SimpleStringProperty name; private final SimpleStringProperty department; private Employee(String name, String department) { this.name = new SimpleStringProperty(name); this.department = new SimpleStringProperty(department); } public String getName() { return name.get(); } public void setName(String fName) { name.set(fName); } public String getDepartment() { return department.get(); } public void setDepartment(String fName) { department.set(fName); } } } TreeView 对象上调用的 setCellFactory 方法覆盖了 TreeCell 的实现,并重新定义了树图的节点 项,以 TextFieldTreeCellImpl 类来作为相应树节点项。TextFieldTreeCellImpl 类为每个树节点创 建了 TextField 对象并提供方法处理编辑事件。 注意:必须显式调用树形视图 setEnditable(true)方法来实现所有树节点的可编辑。 编译并运行示例 14-4 的应用,然后尝试点击树图中雇员项并改变名字。图 14-5 展示了可编 辑树型节点的情形。 图 14-5 改变雇员名字 14.4 按需添加树型节点项 改变一下前述的树图示例应用,以便人力资源项能添加新雇员。示例 14-5 中,使用加粗代 码行来表示引用参考。这些代码行添加到 “部门”树图项的上下文菜单中。当添加雇员菜 单项被选中时,新的树图项将被作为叶子添加到当前的部门项下。 使用 isLeaf 方法辨别部门树节点和雇员树节点。 示例 14-5 添加新节点 import java.util.Arrays; import java.util.List; import javafx.application.Application; import javafx.event.Event; import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.TextField; import javafx.scene.control.TreeCell; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.util.Callback; import javafx.beans.property.SimpleStringProperty; import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; import javafx.scene.layout.VBox; public class TreeViewSample extends Application { private final Node rootIcon = new ImageView(new Image(getClass().getResourceAsStream("root.png"))); private final Image depIcon = new Image(getClass().getResourceAsStream("department.png")); List employees = Arrays.asList( new Employee("Ethan Williams", "Sales Department"), new Employee("Emma Jones", "Sales Department"), new Employee("Michael Brown", "Sales Department"), new Employee("Anna Black", "Sales Department"), new Employee("Rodger York", "Sales Department"), new Employee("Susan Collins", "Sales Department"), new Employee("Mike Graham", "IT Support"), new Employee("Judy Mayer", "IT Support"), new Employee("Gregory Smith", "IT Support"), new Employee("Jacob Smith", "Accounts Department"), new Employee("Isabella Johnson", "Accounts Department")); TreeItem rootNode = new TreeItem("MyCompany Human Resources", rootIcon); public static void main(String[] args) { Application.launch(args); } @Override public void start(Stage stage) { rootNode.setExpanded(true); for (Employee employee : employees) { TreeItem empLeaf = new TreeItem(employee.getName()); boolean found = false; for (TreeItem depNode : rootNode.getChildren()) { if (depNode.getValue().contentEquals(employee.getDepartment())){ depNode.getChildren().add(empLeaf); found = true; break; } } if (!found) { TreeItem depNode = new TreeItem(employee.getDepartment(), new ImageView(depIcon) ); rootNode.getChildren().add(depNode); depNode.getChildren().add(empLeaf); } } stage.setTitle("Tree View Sample"); VBox box = new VBox(); final Scene scene = new Scene(box, 400, 300); scene.setFill(Color.LIGHTGRAY); TreeView treeView = new TreeView(rootNode); treeView.setEditable(true); treeView.setCellFactory(new Callback,TreeCell>(){ @Override public TreeCell call(TreeView p) { return new TextFieldTreeCellImpl(); } }); box.getChildren().add(treeView); stage.setScene(scene); stage.show(); } private final class TextFieldTreeCellImpl extends TreeCell { private TextField textField; private ContextMenu addMenu = new ContextMenu(); public TextFieldTreeCellImpl() { MenuItem addMenuItem = new MenuItem("Add Employee"); addMenu.getItems().add(addMenuItem); addMenuItem.setOnAction(new EventHandler() { public void handle(Event t) { TreeItem newEmployee = new TreeItem("New Employee"); getTreeItem().getChildren().add(newEmployee); } }); } @Override public void startEdit() { super.startEdit(); if (textField == null) { createTextField(); } setText(null); setGraphic(textField); textField.selectAll(); } @Override public void cancelEdit() { super.cancelEdit(); setText((String) getItem()); setGraphic(getTreeItem().getGraphic()); } @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (empty) { setText(null); setGraphic(null); } else { if (isEditing()) { if (textField != null) { textField.setText(getString()); } setText(null); setGraphic(textField); } else { setText(getString()); setGraphic(getTreeItem().getGraphic()); if ( !getTreeItem().isLeaf()&&getTreeItem().getParent()!= null ){ setContextMenu(addMenu); } } } } private void createTextField() { textField = new TextField(getString()); textField.setOnKeyReleased(new EventHandler() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.ENTER) { commitEdit(textField.getText()); } else if (t.getCode() == KeyCode.ESCAPE) { cancelEdit(); } } }); } private String getString() { return getItem() == null ? "" : getItem().toString(); } } public static class Employee { private final SimpleStringProperty name; private final SimpleStringProperty department; private Employee(String name, String department) { this.name = new SimpleStringProperty(name); this.department = new SimpleStringProperty(department); } public String getName() { return name.get(); } public void setName(String fName) { name.set(fName); } public String getDepartment() { return department.get(); } public void setDepartment(String fName) { department.set(fName); } } } 编译并运行这个应用,然后选择部门节点,并单击右键,上下文菜单出现,如图 14-6 所示: 图 14-6 上下文菜单添加节点项 当选择“Add Employee”菜单项,这个新纪录将被添加到部门节点下。如图 14-7 所示,并 给点击编辑这个名字。 图 14-7 新添加雇员 这部门内容中,相关 JavaFX 对象类包括:  TreeView  TreeItem  TreeCell  Cell  TextField 可以自行查阅相关 SDK API 文档。 15 组合下拉框(Combo Box) 这部分内容解释如何在 JavaFX 应用中使用组合下拉框。这里也将讨论可编辑和不可编 辑的下拉框,讲解如何跟踪下拉框的改变并处理相应事件,并解释如何使用元工厂来改变下 拉框的缺省实现。 下拉框是个典型的用户接口界面元素,使用户可以从多项中选择一项。当展示超过限制的的 许多项目时,下拉框将非常有用。因为可以在下拉列表中添加滚动条,不像选择框。如果数 量没有超过某一限制,开发人员可自行决定用下拉框还是选择框更适合需要。 通过使用 API 中 ComboBox 类来创建 JavaFX 应用中的下拉框。图 15-1 展示了带有两个下拉 框的应用示例。 图 15-1 带 2 个下拉框的应用 15.1 创建下拉框 创建下拉框时,必须实例化 ComboBox 类,并定义其项目为可察列表。就像其他 UI 控件一 样,如 ChoiceBox、ListView 和 TableView 一样。示例 15-1 展示了在构造器内设置选项的应 用情况。 示例 15-1 创建可察列表的下拉框 ObservableList options = FXCollections.observableArrayList( "Option 1", "Option 2", "Option 3" ); final ComboBox comboBox = new ComboBox(options); 另一个创建下拉框的方法是使用空构造器实例化,并调用 setItems 方法来设置下拉项,如下 代码所示:comboBox.setItems(options); 当下拉框添加到应用场景后,它在用户界面的情形如图 15-2 所示。 图 15-2 三项下拉框 任何时候,都可以向下拉框添加新选项。示例 15-2 代码实现了项下拉框再添加 3 选项的任 务演示。 示例 15-2 向下拉框天剑新项 comboBox.getItems().addAll( "Option 4", "Option 5", "Option 6" ); ComboBox 类提供了便捷特性和方法来应用下拉框。可用 setValue 方法指定下拉框中被选项。 当在 ComboBox 对象上调用 setValue 方法是,selectionModel 特性的被选项将改变其值,即 便值不在下来列表中。如列表项改变到清单中有的值,相应项将成为被选中项。相似的,可 用调用 getValue 方法获取被选中项。当用户选择一项后,selectionModel 特性的选中项和下 拉框的 value 特性都会更新为新值。 可以严格规定下拉框的可视行数。如下代码行使下拉框控件只显示 3 项: comboBox.setVisibleRowCount(3) 。因此下拉框中将有三项显示行可见,并出现滚动条。 如图 15-3 所示。 图 15-3 设置下拉可视数量 尽管 ComboBox 类有个泛型标注并能使用户组合不同类型项,但不要使用 Node(或任何子 类)作为相应下拉项。因为场景图概念暗示只有一个 Node 对象可以放置到应用场景中,选 中项被从下拉框列表中删除。当选择改变时,前面的选择项返回列表,新的选择项被删除。 为了阻止这种情况,使用元工厂机制,这个解决方案在 API 文档中有描述。当需要改变初始 行为或外貌时,这个元工厂机制非常有用。 下拉框应用示例描述了在典型的 email 界面中怎么使用下拉框。示例 15-3 就创建了这样的 界面,带有两个下拉框用,于选择 email 接收者和消息优先等级。 示例 15-3 创建下拉框并添加到场景图 import javafx.application.Application; import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.GridPane; import javafx.stage.Stage; public class ComboBoxSample extends Application { public static void main(String[] args) { launch(args); } final Button button = new Button ("Send"); final Label notification = new Label (); final TextField subject = new TextField(""); final TextArea text = new TextArea (""); String address = " "; @Override public void start(Stage stage) { stage.setTitle("ComboBoxSample"); Scene scene = new Scene(new Group(), 450, 250); final ComboBox emailComboBox = new ComboBox(); emailComboBox.getItems().addAll( "jacob.smith@example.com", "isabella.johnson@example.com", "ethan.williams@example.com", "emma.jones@example.com", "michael.brown@example.com" ); final ComboBox priorityComboBox = new ComboBox(); priorityComboBox.getItems().addAll( "Highest", "High", "Normal", "Low", "Lowest" ); priorityComboBox.setValue("Normal"); GridPane grid = new GridPane(); grid.setVgap(4); grid.setHgap(10); grid.setPadding(new Insets(5, 5, 5, 5)); grid.add(new Label("To: "), 0, 0); grid.add(emailComboBox, 1, 0); grid.add(new Label("Priority: "), 2, 0); grid.add(priorityComboBox, 3, 0); grid.add(new Label("Subject: "), 0, 1); grid.add(subject, 1, 1, 3, 1); grid.add(text, 0, 2, 4, 1); grid.add(button, 0, 3); grid.add (notification, 1, 3, 3, 1); Group root = (Group)scene.getRoot(); root.getChildren().add(grid); stage.setScene(scene); stage.show(); } } 示例 15-3 中的两个下拉框使用了 getItems 和 addAll 方法来添加选项。编译并运行示例代码, 请展示界面如图 15-4 所示。 图 1-4 Email 接收者和优先级 15.2 可编辑下拉框 典型情况下,邮件客户端支持从地址簿中选择接受者和输入新地址。这是一个可编辑的下拉 框将非常适合这个任务。使用 ComboBox 类的 setEditable(true)方法可使下拉框可编辑。使用 setPrompText 方法,可指定在没有选择时呈现在可变接口内的文本。测试下示例 15-4 应用 中改变代码。粗体代码行是针对示例 15-3 的添加的。 示例 15-4 在可编辑下拉框中处理新输入值 import javafx.application.Application; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.GridPane; import javafx.stage.Stage; public class ComboBoxSample extends Application { public static void main(String[] args) { launch(args); } final Button button = new Button ("Send"); final Label notification = new Label (); final TextField subject = new TextField(""); final TextArea text = new TextArea (""); String address = " "; @Override public void start(Stage stage) { stage.setTitle("ComboBoxSample"); Scene scene = new Scene(new Group(), 450, 250); final ComboBox emailComboBox = new ComboBox(); emailComboBox.getItems().addAll( "jacob.smith@example.com", "isabella.johnson@example.com", "ethan.williams@example.com", "emma.jones@example.com", "michael.brown@example.com" ); emailComboBox.setPromptText("Email address"); emailComboBox.setEditable(true); emailComboBox.valueProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue ov, String t, String t1) { address = t1; } }); final ComboBox priorityComboBox = new ComboBox(); priorityComboBox.getItems().addAll( "Highest", "High", "Normal", "Low", "Lowest" ); priorityComboBox.setValue("Normal"); button.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { if (emailComboBox.getValue() != null && !emailComboBox.getValue().toString().isEmpty()){ notification.setText("Your message was successfully sent" + " to " + address); emailComboBox.setValue(null); if (priorityComboBox.getValue() != null && !priorityComboBox.getValue().toString().isEmpty()){ priorityComboBox.setValue(null); } subject.clear(); text.clear(); } else { notification.setText("You have not selected a recipient!"); } } }); GridPane grid = new GridPane(); grid.setVgap(4); grid.setHgap(10); grid.setPadding(new Insets(5, 5, 5, 5)); grid.add(new Label("To: "), 0, 0); grid.add(emailComboBox, 1, 0); grid.add(new Label("Priority: "), 2, 0); grid.add(priorityComboBox, 3, 0); grid.add(new Label("Subject: "), 0, 1); grid.add(subject, 1, 1, 3, 1); grid.add(text, 0, 2, 4, 1); grid.add(button, 0, 3); grid.add (notification, 1, 3, 3, 1); Group root = (Group)scene.getRoot(); root.getChildren().add(grid); stage.setScene(scene); stage.show(); } } 除了编辑 email 下拉框外,代码片还实现了这个空间的事件处理。新输入值或选择值被存储 到 address 变量。当用户点击“Send”按钮,包含 email 地址的通知信息被展示。 图 15-5 显示了编辑一个地址到另一个地址的情形。 图 15-5 编辑 Email 地址 按下发送按钮后,所有控件返回到它们缺省状态。Clear 方法被 TextField 和 TextArea 对象调 用,并且 null 值被设置到下拉框的选择项。图 15-6 展示了发送后的情形。 图 15-6 发送后界面 15.3 下拉框应用元工厂 使用元工厂机制改变下拉框的默认行为和外观。示例 15-5 创建了元工厂并应用到优先级下 拉框来高亮度显示优先级 示例 15-5 下拉框元工厂应用 import javafx.application.Application; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.GridPane; import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.util.Callback; public class ComboBoxSample extends Application { public static void main(String[] args) { launch(args); } final Button button = new Button ("Send"); final Label notification = new Label (); final TextField subject = new TextField(""); final TextArea text = new TextArea (""); String address = " "; @Override public void start(Stage stage) { stage.setTitle("ComboBoxSample"); Scene scene = new Scene(new Group(), 450, 250); final ComboBox emailComboBox = new ComboBox(); emailComboBox.getItems().addAll( "jacob.smith@example.com", "isabella.johnson@example.com", "ethan.williams@example.com", "emma.jones@example.com", "michael.brown@example.com" ); emailComboBox.setPromptText("Email address"); emailComboBox.setEditable(true); emailComboBox.valueProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue ov, String t, String t1) { address = t1; } }); final ComboBox priorityComboBox = new ComboBox(); priorityComboBox.getItems().addAll( "Highest", "High", "Normal", "Low", "Lowest" ); priorityComboBox.setValue("Normal"); priorityComboBox.setCellFactory( new Callback, ListCell>() { @Override public ListCell call(ListView param) { final ListCell cell = new ListCell() { { super.setPrefWidth(100); } @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (item != null) { setText(item); if (item.contains("High")) { setTextFill(Color.RED); } else if (item.contains("Low")){ setTextFill(Color.GREEN); } else { setTextFill(Color.BLACK); } } else { setText(null); } } }; return cell; } }); button.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { if (emailComboBox.getValue() != null && !emailComboBox.getValue().toString().isEmpty()){ notification.setText("Your message was successfully sent" + " to " + address); emailComboBox.setValue(null); if (priorityComboBox.getValue() != null && !priorityComboBox.getValue().toString().isEmpty()){ priorityComboBox.setValue(null); } subject.clear(); text.clear(); } else { notification.setText("You have not selected a recipient!"); } } }); GridPane grid = new GridPane(); grid.setVgap(4); grid.setHgap(10); grid.setPadding(new Insets(5, 5, 5, 5)); grid.add(new Label("To: "), 0, 0); grid.add(emailComboBox, 1, 0); grid.add(new Label("Priority: "), 2, 0); grid.add(priorityComboBox, 3, 0); grid.add(new Label("Subject: "), 0, 1); grid.add(subject, 1, 1, 3, 1); grid.add(text, 0, 2, 4, 1); grid.add(button, 0, 3); grid.add (notification, 1, 3, 3, 1); Group root = (Group)scene.getRoot(); root.getChildren().add(grid); stage.setScene(scene); stage.show(); } } 元工厂生成 ListCell 对象。每个元与一单个下拉框项相关。通过 setPrefWidth 方法设置么个 下拉框项的宽。updateItem 方法设置红色为高亮项,绿色为低亮项,正常项为黑色。 图 15-7 展示了示例示例 15-5 应用元工厂后的效果图 图 15-7 优先级下拉框变更版 另外,通过应用 CSS 或可视效果,可以进一步增强下拉框的外观。 本部分教程相关 API 内容包括:  ComboBox  ComboBoxBase  ListView  ListCell  Button 读者可以自行查阅相关文档介绍。 16 分隔符(Separator) Separator 类在 JavaFX API 中表示水平或垂直的分割线,它分割应用的用户界面元素并不产生 任何行为。但是可以式样化并能应用效果,甚至动画。缺省情况下,分隔符是水平的,应用 setOrientation 方法你可改变的方向。 16.1 创建分隔符 示例 16-1 代码片创建了一个水平分隔符和一个垂直分隔符。 示例 16-1 垂直和水平分隔符 //Horizontal separator Separator separator1 = new Separator(); //Vertical separator Separator separator2 = new Separator(); separator2.setOrientation(Orientation.VERTICAL); 分隔符类 Separator 扩展自 Node 类。因此,分隔符继承了所有的 Node 示例变量。 典型情况下,分隔符用于分割一组 UI 控件。学习下示例 16-2 的代码片,它把春天月份和夏 天月份的选择框分割开了 示例 16-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); 以上代码段添加到应用中,产生的效果如图 16-1 所示 图 16-1 选择框和分隔符 分隔符可以占据整个水平或垂直方向。setMaxWidth 方法用户定义特定宽度。setValignment 方法指定布局空间内分割线的垂直位置。相似的,用 setHalignment 方法可以设置分隔线水 平位置。 在示例 16-2 中,通过专用方法 add(index,node)把分隔符被增加到垂直方框内。在应用中, 在 UI 创建后或 UI 动态改变后可以应用这种方法来应用分隔符。 16.2 应用界面添加分隔符 如前文所提,分隔符可被用于 UI 控件组的分割。也可以使用分隔符结构化用户界面。通过 应用分隔符,图 16-2 展示了天气预报的渲染情况。 图 16-2 通过分隔符结构化天气预报 对于图 16-2 展示的应用,分隔符用于分割标签和图片。看看示例 16-3 的代码情况。 示例 16-3 天气预报中的分隔符应用 import javafx.application.Application; import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.geometry.VPos; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.*; 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 { Label caption = new Label("Weather Forecast"); Label friday = new Label("Friday"); Label saturday = new Label("Saturday"); Label sunday = new Label("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 = new GridPane(); grid.setPadding(new Insets(10, 10, 10, 10)); grid.setVgap(2); grid.setHgap(5); scene.setRoot(grid); Image cloudImage = new Image(getClass().getResourceAsStream("cloud.jpg")); Image sunImage = new Image(getClass().getResourceAsStream("sun.jpg")); caption.setFont(Font.font("Verdana", 20)); GridPane.setConstraints(caption, 0, 0); GridPane.setColumnSpan(caption, 8); grid.getChildren().add(caption); final Separator sepHor = new Separator(); sepHor.setValignment(VPos.CENTER); GridPane.setConstraints(sepHor, 0, 1); GridPane.setColumnSpan(sepHor, 7); grid.getChildren().add(sepHor); friday.setFont(Font.font("Verdana", 18)); GridPane.setConstraints(friday, 0, 2); GridPane.setColumnSpan(friday, 2); grid.getChildren().add(friday); final Separator sepVert1 = new Separator(); sepVert1.setOrientation(Orientation.VERTICAL); sepVert1.setValignment(VPos.CENTER); sepVert1.setPrefHeight(80); GridPane.setConstraints(sepVert1, 2, 2); GridPane.setRowSpan(sepVert1, 2); grid.getChildren().add(sepVert1); saturday.setFont(Font.font("Verdana", 18)); GridPane.setConstraints(saturday, 3, 2); GridPane.setColumnSpan(saturday, 2); grid.getChildren().add(saturday); final Separator sepVert2 = new Separator(); sepVert2.setOrientation(Orientation.VERTICAL); sepVert2.setValignment(VPos.CENTER); sepVert2.setPrefHeight(80); GridPane.setConstraints(sepVert2, 5, 2); GridPane.setRowSpan(sepVert2, 2); grid.getChildren().add(sepVert2); sunday.setFont(Font.font("Verdana", 18)); GridPane.setConstraints(sunday, 6, 2); GridPane.setColumnSpan(sunday, 2); grid.getChildren().add(sunday); final ImageView cloud = new ImageView(cloudImage); GridPane.setConstraints(cloud, 0, 3); grid.getChildren().add(cloud); final Label t1 = new Label("16"); t1.setFont(Font.font("Verdana", 20)); GridPane.setConstraints(t1, 1, 3); grid.getChildren().add(t1); final ImageView sun1 = new ImageView(sunImage); GridPane.setConstraints(sun1, 3, 3); grid.getChildren().add(sun1); final Label t2 = new Label("18"); t2.setFont(Font.font("Verdana", 20)); GridPane.setConstraints(t2, 4, 3); grid.getChildren().add(t2); final ImageView sun2 = new ImageView(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.show(); } public static void main(String[] args) { launch(args); } } 这个应用用了两个水平分隔符和一个垂直分隔符,并且在容器 GridPane 中使分隔符跨行和 列。应用中,也可以预先设置分隔符的长度(宽度或高度),以便用户界面动态改变时做相 应变化。也可以应用 CSS 改变分隔符的外观。 16.3 式样化分隔符 为了应用式样到示例 16-3 的所有分隔符,可以创建 CSS 文件并存在应用主类所在的相同包 下。示例 16-4 演示了 CSS 应用到分隔符的式样文件 示例 16-4 分隔符式样化 /*controlStyle.css */ .separator{ -fx-background-color: #e79423; -fx-background-radius: 2; } 通关 Scene 类的 getStylesheets 方法可以在应用中式样化分隔符。如示例 16-5 所示 示例 16-5 应用中式样化应用 scene.getStylesheets().add("separatorsample/controlStyle.css"); 图 16-3 展示了天气预报应用式样化分隔符的情况 图 16-3 式样化分隔符 本部分教程相关对象文档包括:  Separator  JavaFX CSS Specification 读者可自行查阅相关内容描述。 17 滑动器(Slider) 滑动器类(Slider)代表带有范围数值展示和交互的控件。这个控件由滑轨和可拖动滑块构 成。同时也可以包含刻度线和刻度标识,以便提示数值的范围。图 17-1 展示了典型的滑动 器,并标识了主要元素构成。 图 17-1 滑动器元素构成 17.1 创建滑动器 花点时间来查阅下示例 17-1 的代码片,这些代码生成的滑动器如图 17-1 所示。 示例 17-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 方法指定滑动器的 当前值,且总是小于最大值且大于最小值。在应用启动时,使用该方法定义滑块的位置。 两个 Boolean 方法,即 setShowTickMarks 和 setShowTickLabels 用来定义滑动器的可视外观。 在示例 17-1,刻度和标签是可用的。另外,主刻度间的单位距离是 50,次刻度和主刻度间 的数值规定为 5。可以把 setSnapToTicks 方法设置为 true,以便保持滑块赋值和刻度一致。 setBlockIncrement 方法定义了滑块移动时的距离。在示例 17-1 中,这个值是 10,这意味着 每次用户点击滑轨,滑块朝点击位置移动 10 个单位的距离。 17.2 在图形应用中使用滑动器 现在来检验一下图 17-2 的相关内容。这个应用有三个滑动器来编辑图片的渲染特征。每个 滑动器调整一个特定可视化特征:透明度、sepia 值以及扩展因子。 图 17-2 三个滑动器 此应用的源代码如示例 17-2 所示 示例 17-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 = new Slider(0, 1, 1); final Slider sepiaTone = new Slider(0, 1, 1); final Slider scaling = new Slider (0.5, 1, 1); final Image image = new Image(getClass().getResourceAsStream( "cappuccino.jpg") ); final Label opacityCaption = new Label("Opacity Level:"); final Label sepiaCaption = new Label("Sepia Tone:"); final Label scalingCaption = new Label("Scaling Factor:"); final Label opacityValue = new Label( Double.toString(opacityLevel.getValue())); final Label sepiaValue = new Label( Double.toString(sepiaTone.getValue())); final Label scalingValue = new Label( Double.toString(scaling.getValue())); final static Color textColor = Color.WHITE; final static SepiaTone sepiaEffect = new SepiaTone(); @Override public void start(Stage stage) { Group root = new Group(); Scene scene = new Scene(root, 600, 400); stage.setScene(scene); stage.setTitle("Slider Sample"); scene.setFill(Color.BLACK); GridPane grid = new GridPane(); grid.setPadding(new Insets(10, 10, 10, 10)); grid.setVgap(10); grid.setHgap(70); final ImageView cappuccino = new ImageView (image); cappuccino.setEffect(sepiaEffect); GridPane.setConstraints(cappuccino, 0, 0); GridPane.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(new ChangeListener() { public void changed(ObservableValue 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 void changed(ObservableValue 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 void changed(ObservableValue 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.show(); } public static void main(String[] args) { launch(args); } } ImageView 对象的透明度属性依据第一个滑动器(opacityLevel)的变化而做一致性改变; SepiaTone 效果随着 sepiaTone 滑块改变而改变;第三个滑动器定义了图片放缩因子,随滑块 的变动而改变 setScaleX 和 setScaleY 方法的值,已达到相应效果。 示例 17-3 中的代码片演示了把滑动器 getValue 方法返回 double 值转换为字符串的方法。并 应用了格式化来渲染滑动器值为 float 数值,且带有 2 为小数位。 示例 17-3 格式化滑动器值 scalingValue.setText((Double.toString(value)).format("%.2f", value)); 接下来,读者可以自行应用可视效果或 CSS 来增强滑动器的外观。 本节内容包括的 API 文档主要有:  Slider  SepiaTone 读者可自行查阅相关进一步描述介绍。 18 进度条和进度指示器(Progress Bar & Progress Indicator) ProgressIndicator 类和他的直接子类 ProgressBar 提供了指示特别任务处理以及完成处理情况 侦测的能力。在 ProgressBar 类可视化完整的处理过程时,ProgressIndicator 类可视化一个动 态的变化饼图。如图 18-1 所示。 图 18-1 进度条以及指示符 18.1 创建进度控件 用示例 18-1 的代码片,来插入进度控件到 JavaFX 应用中。 示例 18-1 实现进度条和进度指示符 ProgressBar pb = new ProgressBar(0.6); ProgressIndicator pi = new ProgressIndicator(0.6); 也可以用布带参数的构造器来创建进度控件,这样你可以用 setProgress 方法来赋值。另外, 可以用 ProgressBarBuilder 类来初始化进度控件,此控件由方法来构建和处理进度。详情可 以查阅相应 API 文档。 有时候应用程序不能决定完整的任务完成时间。在这样的例子中,进度控件保留在一个未确 定的模式中,直到任务长度能决定了为止。图 18-2 展示了依赖于不同进度变量值的不同进 度控件的状态。 图 18-2 进度控件不同状态 示例 18-2 展示了图 18-2 的应用程序代码。 示例 18-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 extends Application { 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("Progress Controls"); for (int i = 0; i < values.length; i++) { final Label label = labels[i] = new Label(); label.setText("progress:" + values[i]); final ProgressBar pb = pbs[i] = new ProgressBar(); pb.setProgress(values[i]); final ProgressIndicator pin = pins[i] = new ProgressIndicator(); pin.setProgress(values[i]); final HBox hb = hbs[i] = new HBox(); hb.setSpacing(5); hb.setAlignment(Pos.CENTER); hb.getChildren().addAll(label, pb, pin); } final VBox vb = new VBox(); vb.setSpacing(5); vb.getChildren().addAll(hbs); scene.setRoot(vb); stage.show(); } public static void main(String[] args) { launch(args); } } 进度变量 0 和 1 之间的正数代表进度百分比。例如 0.4 代表 40%。变量的负值表示进度处于 未定模式中。可用 isIndeterminate 方法来检查进度控件是否处理这种模式中。 18.2 在用户界面中指示进度 图 18-2 一下子展示了简化渲染进度控件所有可能的状态。在实际应用中,进度值可以通过 其它 UI 元素获得。 研究下示例 18-3 的代码,学习如何基于滑块位置设置进度条和进度指示符的值。 示例 18-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 extends Application { @Override public void start(Stage stage) { Group root = new Group(); Scene scene = new Scene(root); stage.setScene(scene); stage.setTitle("Progress Controls"); final Slider slider = new Slider(); slider.setMin(0); slider.setMax(50); final ProgressBar pb = new ProgressBar(0); final ProgressIndicator pi = new ProgressIndicator(0); slider.valueProperty().addListener(new ChangeListener() { public void changed(ObservableValue 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); } } 编译运行这个示例代码,其产生的界面如图 18-3 所示。 图 18-3 滑块指示进度 ChangeListener 对象决定滑动器值的改变,并计算进度条和进度指示符的进度值,以便进度 控件的值是在 0.0 和 1.0 之间。 本节内容相关 API 主要是:  ProgressBar  ProgressIndicator 读者可以进一步查询相应 API 文档,来了解更多内容。 19 超链接(Hyperlink) Hyperlink 类代表了另一类型的标签控件,其主要用于格式化文本为超链接。图 19-1 演示了 缺省超链接实现的三中状态。 图 19-1 超链接控件的三种状态 19.1 创建超链接 示例 19-1 代码片可以产生一个超链接。代码如下 示例 19-1 典型超链接 Hyperlink link = new Hyperlink(); link.setText("http://example.com"); link.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { System.out.println("This link is clicked"); } }); 实例方法 setText 定义了超链接的标题。由于超链接类时 Labeled 类的扩展,你可以设置特别 字体和标题填充色。setOnAction 方法设定特定行为,以便任何时点击超链接而被调用,类 似于 Button 控件的控制方式。在示例 19-1 中,这个行为局限于打印字符串。但是,在具体 应用中,你可能希望实现更通常的任务。 19.2 连接本地内容 图 19-2 的应用程序,渲染了本地目录的图片。 图 19-2 查看图片 此应用的源代码如示例 19-2 所示 示例 19-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 = new ScrollPane(); final Hyperlink[] hpls = new Hyperlink[captions.length]; final Image[] images = new Image[imageFiles.length]; public static void main(String[] args) { Application.launch(args); } @Override public void start(Stage stage) { Scene scene = new Scene(new Group()); stage.setTitle("Hyperlink Sample"); stage.setWidth(300); stage.setHeight(200); selectedImage.setLayoutX(100); selectedImage.setLayoutY(10); for (int i = 0; i < captions.length; i++) { final Hyperlink hpl = hpls[i] = new Hyperlink(captions[i]); final Image image = images[i] = new Image( getClass().getResourceAsStream(imageFiles[i]) ); hpl.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { selectedImage.setImage(image); } }); } final Button button = new Button("Refresh links"); button.setOnAction(new EventHandler() { @Override public void handle(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(); } } 这个应用在循环内创建了四个超链接对象。当用户点击相应超链接时,调用 setOnAction 方 法定义的行为。此例中,来自图片数组的相应的图片被设置到 selectedImage 变量。当用户 点击超链接时,将成为已受访连接。可使用类 Hyperlink 类的 setVisited 方法来刷新连接。示 例 19-3 的代码片即能完成此任务。 示例 19-3 刷新超链接 final Button button = new Button("Refresh links"); button.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { for (int i = 0; i < captions.length; i++) { hpls[i].setVisited(false); selectedImage.setImage(null); } } }); 当点击时,刷新按钮使所有超链接都回到未受访状态,如图 19-3 所示 图 19-3 未受访超链接 因为 Hyperlink 类是 Labeled 类的扩展,不但可设置文本标题,还可设置图片。下面应用中 使用文本标题以及图片来创建超链接并实现远程 HTML 页面的加载。 19.3 连接远程内容 通过嵌入 WebView 浏览器到应用场景中,就可实现 HTML 内容的渲染。WebView 组件提供 了基本的页面浏览功能。此对象完成页面渲染,并支持用户界面交互,族如何导航连接和 JavaScript 命令等。 研究下示例 19-4 的应用程序源代码。此例中创建了四个带文本标题和图片的超链接。点击 超链接时,相应的值作为 URL 被传递给嵌入浏览器,然后完成页面渲染。 示例 19-4 加载远程页面 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 = new String[]{ "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 = new Hyperlink[captions.length]; final Image[] images = new Image[imageFiles.length]; public static void main(String[] args){ launch(args); } @Override public void start(Stage stage) { VBox vbox = new VBox(); Scene scene = new Scene(vbox); stage.setTitle("Hyperlink Sample"); stage.setWidth(570); stage.setHeight(550); selectedImage.setLayoutX(100); selectedImage.setLayoutY(10); final WebView browser = new WebView(); final WebEngine webEngine = browser.getEngine(); for (int i = 0; i < captions.length; i++) { final Hyperlink hpl = hpls[i] = new Hyperlink(captions[i]); final Image image = images[i] = new Image(getClass().getResourceAsStream(imageFiles[i])); hpl.setGraphic(new ImageView (image)); hpl.setFont(Font.font("Arial", 14)); final String url = urls[i]; hpl.setOnAction(new EventHandler() { @Override public void handle(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 循环中创建,和示例 19-2 中类似。超链接的行为集从 urls 数组中传递相应的 URL 给嵌入浏览器的 WebEngine 对象。 编译运行此应用代码,其产生的效果如图 19-4 所示 图 19-4 远程加载 oracle 页面 本部分内容涉及到的 API 文档包括:  Hyperlink  Labeled  WebView  WebEngine 读者可进一步查阅相关文档来了解更近一步信息。 20 提示框(Tooltip) Tooltip 类代表一个普通的 UI 组件,常用于显示 UI 控件的附加信息,即当鼠标悬停于控件上 时,提示信息将展示出来。通过调用 setTooltip 方法,提示信息可以被设置到任意控件上。 提示框有两种状态: activated and showing 。当提示框激活后,鼠标移到控件上。当 提示框处于 showing 状态时,就会实际展示相应信息。一个展示的提示框也可被激活。另外, 在提示框激活和展现间通常有些延迟现象。 图 20-1,展示了带提示框的密码输入框。 图 20-1 密码框带提示 20.1 创建提示框 学习下示例 20-1 的代码片,此例中创建了带提示框的密码输入框,如前图所示。 示例 20-1 为密码框添加提示框 final PasswordField pf = new PasswordField(); final Tooltip tooltip = new Tooltip(); tooltip.setText( "\nYour password must be\n" + "at least 8 characters in length\n" + ); pf.setTooltip(tooltip); 包 javafx.scene.control 中的 UI 控件都有 setTooltip 方法来添加提示框。可在 Tooltip 构造器内 定义一个文本标题,或使用 setText 方法完成。 由于 Tooltip 是个 Labeled 类的扩展,不仅可以添加文本标题,还可添加图标。示例 20-2 的 代码片就添加了一个图标到提示密码框。 示例 20-2 提示框添加图标 Image image = new Image( getClass().getResourceAsStream("warn.png") ); tooltip.setGraphic(new ImageView(image)); 在应用程序中添加以上代码片,编译完整的代码并运行,其它结果如图 20-2 所示。 图 20-2 带图标提示框 提示框不仅能包括附加或辅助信息,也可以呈现数据。 20.2 在提示框中表现应用数据 图 20-3 所示的应用中,使用了提示框中代表的数据来计算酒店住宿的总成本。 图 20-3 计算旅店价格 每个选择框都伴随一个提示框。每个提示框展示了特定预定项的价格。如果用户选择一个选 择框,相应的值会被增加到总成本。如果取消选项,则相应值被从总成本中扣除 看下应用的源代码,如示例 20-3 所示 示例 20-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 = new String[]{ "Accommodation (BB)", "Half Board", "Late Check-out", "Extra Bed" }; final static Integer[] rates = new Integer[]{ 100, 20, 10, 30 }; final CheckBox[] cbs = new CheckBox[rooms.length]; final Label total = new Label("Total: $0"); Integer sum = 0; public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { Scene scene = new Scene(new Group()); stage.setTitle("Tooltip Sample"); stage.setWidth(300); stage.setHeight(150); total.setFont(new Font("Arial", 20)); for (int i = 0; i < rooms.length; i++) { final CheckBox cb = cbs[i] = new CheckBox(rooms[i]); final Integer rate = rates[i]; final Tooltip tooltip = new Tooltip("$" + rates[i].toString()); tooltip.setFont(new Font("Arial", 16)); cb.setTooltip(tooltip); cb.selectedProperty().addListener(new ChangeListener() { public void changed(ObservableValue ov, Boolean old_val, Boolean new_val) { if (cb.isSelected()) { sum = sum + rate; } else { sum = sum - rate; } total.setText("Total: $" + 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(new Insets(20, 10, 10, 20)); ((Group) scene.getRoot()).getChildren().add(root); stage.setScene(scene); stage.show(); } } 示例 20-4 中代码行在示例 20-3 中用来创建提示框,并附一个文本标题。标题的整数值被转 成了字符串值。 示例 20-4 设置提示框值 final Tooltip tooltip = new Tooltip("$" + rates[i].toString()) 也可通过应用 CSS 来改变提示框的可是外观。 21 HTML 编辑器(HTML Editor) HTMLEditor 控件是个功能完整的富文本编辑器。其实现是基于 HTML5 编辑特征的文档,并 包括如下编辑功能:  文本格式化处理:包括错字体、斜字体、下划线、删除线风格;  段落设置:格式化、字体、大小;  前景背景设置;  文本缩进;  项目符合和编号列表;  添加水平标尺;  复制和粘贴文本块; 图 21-1 展示了 JavaFX 应用中富文本编辑器的情形。 图 21-1 HTML 编辑器 类 HTMLEditor 以一个 HTML 字符的形式来表示编辑的内容。例如,在图 21-1 编辑器中输入 的 内 容 , 由 下 列 字 符 表 示 “

Heading

Text, some text
”。 由于 HTMLEditor 类是由 Node 类的扩展而来,因此可以对其实例应用可是效果或转换。 21.1 添加 HTML 编辑器 像其它 UI 控件一样,HTMLEditor 组件必须添加到应用场景中才能生效。可如示例 21-1 所示 直接添加到场景中,或通过布局容器实现。 示例 21-1 应用 HTML 编辑器 import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.web.HTMLEditor; import javafx.stage.Stage; public class HTMLEditorSample extends Application { @Override public void start(Stage stage) { stage.setTitle("HTMLEditor Sample"); stage.setWidth(400); stage.setHeight(300); final HTMLEditor htmlEditor = new HTMLEditor(); htmlEditor.setPrefHeight(245); Scene scene = new Scene(htmlEditor); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } } 编译运行以上代码,产生的界面如图 21-2 所示。 图 21-2 初始化 HTMLEditor 组件视图 组件提供了格式化工具栏,但不能切换其可见性。通过应用 CSS 式样表,可以定制编辑器的 外观,如示例 21-2 所示。 示例 21-2 式样化 HTML 编辑器 htmlEditor.setStyle( "-fx-font: 12 cambria;" + "-fx-border-color: brown; " + "-fx-border-style: dotted;" + "-fx-border-width: 2;" ); 当以上代码行添加到示例 21-1 时,编辑器将变成图 21-3 所示的情形。 图 21-3 应用 CSS 后的 HTMLEditor 应用式样后改变了组件的边框和格式化工具栏的字体。 HTMLEditor 类提供了一个方法来定义编辑域的内容,以便在应用启动时调用,即使用 setHtmlText 方法来设置 HTMLEditor 初始化内容。如示例 21-3 所示。 示例 21-3 设置文本内容 private final String INITIAL_TEXT = "Lorem ipsum dolor sit " + "amet, consectetur adipiscing elit. Nam tortor felis, pulvinar " + "in scelerisque cursus, pulvinar at ante. Nulla consequat" + "congue lectus in sodales. Nullam eu est a felis ornare " + "bibendum et nec tellus. Vivamus non metus tempus augue auctor " + "ornare. Duis pulvinar justo ac purus adipiscing pulvinar. " + "Integer congue faucibus dapibus. Integer id nisl ut elit " + "aliquam sagittis gravida eu dolor. Etiam sit amet ipsum " + "sem."; htmlEditor.setHtmlText(INITIAL_TEXT); 图 21-4 演示了调用 setHTMLText 方法设置编辑器内容的情形。 图 21-4 带预定义内容的 HTMLEditor 字符串中可以使用 HTML 标签,以便对初始内容应用特别格式化处理。 21.2 使用 HTML Editor 构建用户界面 在 JavaFX 应用中可以应用 HTMLEditor 控件实现典型的用户界面(UIs)。例如,可以实现即 时消息服务、电邮客户端甚至内容管理系统等。 示例 21-4 向 Email 客户端添加 HTMLEditor import javafx.application.Application; import javafx.collections.FXCollections; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.GridPane; import javafx.scene.layout.VBox; import javafx.scene.web.HTMLEditor; import javafx.stage.Stage; public class HTMLEditorSample extends Application { @Override public void start(Stage stage) { stage.setTitle("Message Composing"); stage.setWidth(500); stage.setHeight(500); Scene scene = new Scene(new Group()); final VBox root = new VBox(); root.setPadding(new Insets(8, 8, 8, 8)); root.setSpacing(5); root.setAlignment(Pos.BOTTOM_LEFT); final GridPane grid = new GridPane(); grid.setVgap(5); grid.setHgap(10); final ChoiceBox sendTo = new ChoiceBox(FXCollections.observableArrayList( "To:", "Cc:", "Bcc:") ); sendTo.setPrefWidth(100); GridPane.setConstraints(sendTo, 0, 0); grid.getChildren().add(sendTo); final TextField tbTo = new TextField(); tbTo.setPrefWidth(400); GridPane.setConstraints(tbTo, 1, 0); grid.getChildren().add(tbTo); final Label subjectLabel = new Label("Subject:"); GridPane.setConstraints(subjectLabel, 0, 1); grid.getChildren().add(subjectLabel); final TextField tbSubject = new TextField(); tbTo.setPrefWidth(400); GridPane.setConstraints(tbSubject, 1, 1); grid.getChildren().add(tbSubject); root.getChildren().add(grid); final HTMLEditor htmlEditor = new HTMLEditor(); htmlEditor.setPrefHeight(370); root.getChildren().addAll(htmlEditor, new Button("Send")); final Label htmlLabel = new Label(); htmlLabel.setWrapText(true); scene.setRoot(root); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } } 用户接口包括一个选择接受类型的选择框、两个文本框(输入地址和主体)、一个标签、一 个编辑器和“Send”按钮。UI 控件受控于 Grid 和 VBox 布局容器,并安排于应用场景中。 编译运行此应用,其运行展示结果如图 21-5 所示。 图 21-5 Email 客户端用户界面 可调用 setPrefWidth 和 setPrefHeight 方法来设定 HTMLEditor 对象的宽和高,或不做限定。 示例 21-4 指定了该组件的高度,它的宽度有 VBox 布局容器限定。在文本额你让超过编辑区 域是,垂直滚动条就会出现。 21.3 获取 HTML 内容 使用 JavaFXHTMLEditor 控件,可以编辑并设置初始内容。另外,还能以 HTML 格式获得输入 和编辑的文本。示例 21-5 展示了这种应用实现。 示例 21-5 检索 HTML 代码 import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.VBox; import javafx.scene.web.HTMLEditor; import javafx.stage.Stage; public class HTMLEditorSample extends Application { private final String INITIAL_TEXT = "Lorem ipsum dolor sit " + "amet, consectetur adipiscing elit. Nam tortor felis, pulvinar " + "in scelerisque cursus, pulvinar at ante. Nulla consequat" + "congue lectus in sodales. Nullam eu est a felis ornare " + "bibendum et nec tellus. Vivamus non metus tempus augue auctor " + "ornare. Duis pulvinar justo ac purus adipiscing pulvinar. " + "Integer congue faucibus dapibus. Integer id nisl ut elit " + "aliquam sagittis gravida eu dolor. Etiam sit amet ipsum " + "sem."; @Override public void start(Stage stage) { stage.setTitle("HTMLEditor Sample"); stage.setWidth(500); stage.setHeight(500); Scene scene = new Scene(new Group()); VBox root = new VBox(); root.setPadding(new Insets(8, 8, 8, 8)); root.setSpacing(5); root.setAlignment(Pos.BOTTOM_LEFT); final HTMLEditor htmlEditor = new HTMLEditor(); htmlEditor.setPrefHeight(245); htmlEditor.setHtmlText(INITIAL_TEXT); final TextArea htmlCode = new TextArea(); htmlCode.setWrapText(true); ScrollPane scrollPane = new ScrollPane(); scrollPane.getStyleClass().add("noborder-scroll-pane"); scrollPane.setContent(htmlCode); scrollPane.setFitToWidth(true); scrollPane.setPrefHeight(180); Button showHTMLButton = new Button("Produce HTML Code"); root.setAlignment(Pos.CENTER); showHTMLButton.setOnAction(new EventHandler() { @Override public void handle(ActionEvent arg0) { htmlCode.setText(htmlEditor.getHtmlText()); } }); root.getChildren().addAll(htmlEditor, showHTMLButton, scrollPane); scene.setRoot(root); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } } HTMLEditor 对象的 getHTMLText 方法源于编辑过的内容并表示为 HTML 字符串。信息传递到 文本域以便查看、赋值和粘贴生成的 HTML 代码。图 21-6 展示了文本的 HTML 代码示例。 图 21-6 获取 HTML 内容 类似情况,你也可获取 HTML 代码并保存为文件,或者是否发送到 WebView 对象来同步编 辑器和嵌入浏览器中的内容。看看示例 21-6 是如何实现这个任务的。 示例 21-6 在浏览器中渲染编辑的 HTML 内容 import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.VBox; import javafx.scene.web.HTMLEditor; import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javafx.stage.Stage; public class HTMLEditorSample extends Application { private final String INITIAL_TEXT = "Lorem ipsum dolor sit " + "amet, consectetur adipiscing elit. Nam tortor felis, pulvinar " + "in scelerisque cursus, pulvinar at ante. Nulla consequat" + "congue lectus in sodales. Nullam eu est a felis ornare " + "bibendum et nec tellus. Vivamus non metus tempus augue auctor " + "ornare. Duis pulvinar justo ac purus adipiscing pulvinar. " + "Integer congue faucibus dapibus. Integer id nisl ut elit " + "aliquam sagittis gravida eu dolor. Etiam sit amet ipsum " + "sem."; @Override public void start(Stage stage) { stage.setTitle("HTMLEditor Sample"); stage.setWidth(500); stage.setHeight(500); Scene scene = new Scene(new Group()); VBox root = new VBox(); root.setPadding(new Insets(8, 8, 8, 8)); root.setSpacing(5); root.setAlignment(Pos.BOTTOM_LEFT); final HTMLEditor htmlEditor = new HTMLEditor(); htmlEditor.setPrefHeight(245); htmlEditor.setHtmlText(INITIAL_TEXT); final WebView browser = new WebView(); final WebEngine webEngine = browser.getEngine(); ScrollPane scrollPane = new ScrollPane(); scrollPane.getStyleClass().add("noborder-scroll-pane"); scrollPane.setStyle("-fx-background-color: white"); scrollPane.setContent(browser); scrollPane.setFitToWidth(true); scrollPane.setPrefHeight(180); Button showHTMLButton = new Button("Load Content in Browser"); root.setAlignment(Pos.CENTER); showHTMLButton.setOnAction(new EventHandler() { @Override public void handle(ActionEvent arg0) { webEngine.loadContent(htmlEditor.getHtmlText()); } }); root.getChildren().addAll(htmlEditor, showHTMLButton, scrollPane); scene.setRoot(root); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } } 从 htmlEditor 组件接收到 HTML 代码,并加载到 WebEngine 对象,由它来设定嵌入浏览器的 内容。用户每次点击浏览器中“Load Content”按钮,编辑后的内容将更新。图 21-7 演示了 示例 21-6 的行为。 图 21-7 浏览器中加载内容 读者可以尝试使用 Text 组件增加一个非编辑的文本内容到 UI 中,做相关处理。 本部分教程相关的 API 对象有:  HTMLEditor  WebView  WebEngine  Label  Button  TextField  ChoiceBox  ScrollPane 读者可以自行查阅相关详细信息。 22 标题窗格和折叠(Titled Pane and Accordion) 标题窗格就是带标题的面板,能打开和关闭,并且它可封装任何 Node 类控件或图片以及组 元素一起添加到布局容器。 标题窗格通过折叠控件可以实现组化管理,即可一次创建多个窗格并同时展示,如图 22-1 所示,展示了折叠控件和三个标题窗格的组合应用情形。 图 22-1 折叠标题窗格组 通过使用 SDK 中 Accordion 和 TitlePane 类来实现应用中相应需要。 22.1 创建标题窗格 创建标题窗格控件,带有标题和一些内容。为实现此目标,可以使用带两个参数的 TitlePane 构造器,或者是用 setText 和 setContent 方法实现。两个方法如示例 22-1 所示。 示例 22-1 声明一个标题窗格对象 //using a two-parameter constructor TitledPane tp = new TitledPane("My Titled Pane", new Button("Button")); //applying methods TitledPane tp = new TitledPane(); tp.setText("My Titled Pane"); tp.setContent(new Button("Button")); 编译运行带有以上代码片的应用,将产生图 22-2 的类似内容。 图 22-2 带按钮的标题窗格 标题窗格被重设为与预订内容大小一致。也增加多行文本并计算结果,如图 22-3 所示。 图 22-3 带一些文本的标题窗格 不要显式设置标题窗格的最小、最大或预定高度,因为这可能会在关闭或打开是导致不可预 料的行为。 示例 22-2 代码片中,通过 GridPane 布局容器增加了好几个控件到标题窗格中。 示例 22-2 带布局容器的标题窗格 TitledPane gridTitlePane = new TitledPane(); GridPane grid = new GridPane(); grid.setVgap(4); grid.setPadding(new Insets(5, 5, 5, 5)); grid.add(new Label("First Name: "), 0, 0); grid.add(new TextField(), 1, 0); grid.add(new Label("Last Name: "), 0, 1); grid.add(new TextField(), 1, 1); grid.add(new Label("Email: "), 0, 2); grid.add(new TextField(), 1, 2); gridTitlePane.setText("Grid"); gridTitlePane.setContent(grid); 编译运行带有上述代码片的程序,将输入图 22-4 所示的界面 图 22-4 包含多个控件的标题窗格 可以自行规定标题窗格打开关闭的方式。缺省情况下,所有标题窗格都是可折叠的,并是动 态的。如果实际应用禁止关闭标题窗格,可以使用 setCollapsible 方法设定 false 来实现。也 应用 setAnimated 方法设定 false 来使动画打开失效。示例 22-3 代码片实现了这种任务要求。 示例 22-3 调整标题窗格风格样式 TitledPane tp = new TitledPane(); //prohibit closing tp.setCollapsible(false); //prohibit animating tp.setAnimated(false); 22.2 把标题窗格添加到折叠控件中 应用中,可以把标题窗格作为独立元素,或通过 Accordion 空间来把它们联合成组。不要显 式设置折叠控件的最大、最小或预订高度之,因为这可能导致在标题窗格打开时难料的行为。 增加多个标题窗格到折叠控件中和增加切换按钮到切换组里雷氏:同一时间,只能有一个标 题窗格能打开。示例 22-4 代码创建了三个标题窗格,并加到折叠控件中。 示例 22-4 折叠控件和三个标题面板 import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.Accordion; import javafx.scene.control.TitledPane; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.paint.Color; import javafx.stage.Stage; public class TitledPaneSample extends Application { final String[] imageNames = new String[]{"Apples", "Flowers", "Leaves"}; final Image[] images = new Image[imageNames.length]; final ImageView[] pics = new ImageView[imageNames.length]; final TitledPane[] tps = new TitledPane[imageNames.length]; public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { stage.setTitle("TitledPane"); Scene scene = new Scene(new Group(), 80, 180); scene.setFill(Color.GHOSTWHITE); final Accordion accordion = new Accordion (); for (int i = 0; i < imageNames.length; i++) { images[i] = new Image(getClass().getResourceAsStream(imageNames[i] + ".jpg")); pics[i] = new ImageView(images[i]); tps[i] = new TitledPane(imageNames[i],pics[i]); } accordion.getPanes().addAll(tps); accordion.setExpandedPane(tps[0]); Group root = (Group)scene.getRoot(); root.getChildren().add(accordion); stage.setScene(scene); stage.show(); } } 在循环内创建了三个标题窗格。每个标题窗格的内容为一个 ImageView 对象。标题窗格通过 使用 getPanes 和 addAll 方法添加到折叠控件中。也可使用 add 方法替代 addAll 方法来添加 单一标题窗格。 缺省情况下,所有的标题窗格在应用启动时是关闭的。示例 22-4 中的 setExpandedPane 方法 设定了带苹果图片的标题窗格是打开的。如图 22-5 所示。 图 22-5 折叠标题面板 22.3 处理带标题窗体的折叠事件 可以使用标题窗格和折叠控件来表现应用中不同的数据。示例 22-5 创建了一个单独的标题 窗格,并带有 GridPane 布局容器和折叠容器组合的三个标题窗格。独立的标题窗格包含电 邮客户端的 UI 元素。折叠控件使选中的图片出现在网格标题窗格的附件域中。 示例 22-5 折叠控件监听器的实现 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.Accordion; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.control.TitledPane; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.paint.Color; import javafx.stage.Stage; public class TitledPaneSample extends Application { final String[] imageNames = new String[]{"Apples", "Flowers", "Leaves"}; final Image[] images = new Image[imageNames.length]; final ImageView[] pics = new ImageView[imageNames.length]; final TitledPane[] tps = new TitledPane[imageNames.length]; final Label label = new Label("N/A"); public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { stage.setTitle("TitledPane"); Scene scene = new Scene(new Group(), 800, 250); scene.setFill(Color.GHOSTWHITE); // --- GridPane container TitledPane gridTitlePane = new TitledPane(); GridPane grid = new GridPane(); grid.setVgap(4); grid.setPadding(new Insets(5, 5, 5, 5)); grid.add(new Label("To: "), 0, 0); grid.add(new TextField(), 1, 0); grid.add(new Label("Cc: "), 0, 1); grid.add(new TextField(), 1, 1); grid.add(new Label("Subject: "), 0, 2); grid.add(new TextField(), 1, 2); grid.add(new Label("Attachment: "), 0, 3); grid.add(label,1, 3); gridTitlePane.setText("Grid"); gridTitlePane.setContent(grid); // --- Accordion final Accordion accordion = new Accordion (); for (int i = 0; i < imageNames.length; i++) { images[i] = new Image(getClass().getResourceAsStream(imageNames[i] + ".jpg")); pics[i] = new ImageView(images[i]); tps[i] = new TitledPane(imageNames[i],pics[i]); } accordion.getPanes().addAll(tps); accordion.expandedPaneProperty().addListener(new ChangeListener() { public void changed(ObservableValue ov, TitledPane old_val, TitledPane new_val) { if (new_val != null) { label.setText(accordion.getExpandedPane().getText() + ".jpg"); } } }); HBox hbox = new HBox(10); hbox.setPadding(new Insets(20, 0, 0, 20)); hbox.getChildren().setAll(gridTitlePane, accordion); Group root = (Group)scene.getRoot(); root.getChildren().add(hbox); stage.setScene(scene); stage.show(); } } 当用户打开折叠控件中的标题窗格时,折叠控件的 expandedPanedProperty 就改变了。监听 器 ChangeListener 对象被通知相应改变,并且折叠控件中的展开的标题窗格被用去构建一个 附件的文件名。这个文件名以文本方式被设置到相应的 Label 中。 图 22-6 展示了这个应用启动后的情形。由于标题窗格没有选中项,附件标识包含“N/A”。 图 22-6 标题窗格应用示例 如果展开 Leaves 标题窗格,附件标签将包含“Leaves.jpg”,如图 22-7 所示。 图 22-7 Leaves 标题窗格展开情形 由于 TitlePane 和 Accordion 类都扩展自 Node 类,故可以对其应用可视效果或转换。也可通 过 CSS 样式表来改变相应控件的风格。 本部分教程中关联到的 API 文档有:  TitledPane  Accordion  Label  GridPane  TextField 读者可以自行查阅相关内容介绍。 23 菜单(Menu) 这部分介绍如何创建菜单以及菜单栏、添加菜单项、分组菜单项、创建子菜单以及设置内容 菜单。 在应用中,通过如下几个 JavaFX API 来构建菜单:  MenuBar  MenuItem o Menu o CheckMenuItem o RadioMenuItem o Menu o CustomMenuItem . SeparatorMenuItem  ContextMenu 图 23-1 展示了带有典型菜单栏的应用情形。 图 23-1 带菜单栏以及三项菜单的应用 23.1 构建 JavaFX 应用菜单 菜单是个响应用户请求的行为化的事项清单。菜单可见时,用户可选择一个菜单项。用户点 击菜单项后,菜单回到隐藏模式。通过使用菜单,可以节约应用中用户界面控件。 菜单栏中的菜单典型情况是分组应用。一般编码模式是:申明菜单栏,定义菜单分类并把菜 单项分类到组配成菜单。在 JavaFX 应用中构建菜单时,主要使用如下菜单项相关类:  MenuItem – 创建行为化选项  Menu – 创建子菜单  RadioButtonItem –创建互斥选项  CheckMenuItem – 创建选择项,可在选中与否中切换 为了在分类内分割菜单项,需要使用 SeparaorMenuItem 类。 菜单栏中菜单一般按类别组织起来,并定位在窗口顶端,为重要的 UI 元素留下布景空间。 由于一些原因,如果不需要为菜单栏分配 UI 的可见部分,可以使用内容菜单,以便用户可 以用鼠标点击打开。 23.2 创建菜单栏 尽管菜单栏可以放置到用户界面的其它位置,典型情况下是放置在用户界面的顶部,并保护 一个或多个菜单项。菜单来动态适应应用窗口的改变。缺省时,添加到菜单栏的每个菜单项 表现为一个文本按钮。 假如有这样一个应用,渲染关于植物的的参考信息,如名字、图片和简介。可以创建三个菜 单类:File、Edit 和 View,并为它们配置菜单项。示例 23-1 所示就是这样的一个应用,来看 下代码。 示例 23-1 菜单示例应用 import java.util.AbstractMap.SimpleEntry; import java.util.Map.Entry; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.effect.DropShadow; import javafx.scene.effect.Effect; import javafx.scene.effect.Glow; import javafx.scene.effect.SepiaTone; 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 MenuSample extends Application { final PageData[] pages = new PageData[] { new PageData("Apple", "The apple is the pomaceous fruit of the apple tree, species Malus " + "domestica in the rose family (Rosaceae). It is one of the most " + "widely cultivated tree fruits, and the most widely known of " + "the many members of genus Malus that are used by humans. " + "The tree originated in Western Asia, where its wild ancestor, " + "the Alma, is still found today.", "Malus domestica"), new PageData("Hawthorn", "The hawthorn is a large genus of shrubs and trees in the rose " + "family, Rosaceae, native to temperate regions of the Northern " + "Hemisphere in Europe, Asia and North America. " + The name hawthorn was " + "originally applied to the species native to northern Europe, " + "especially the Common Hawthorn C. monogyna, and the unmodified " + "name is often so used in Britain and Ireland.", "Crataegus monogyna"), new PageData("Ivy", "The ivy is a flowering plant in the grape family (Vitaceae) native to + " eastern Asia in Japan, Korea, and northern and eastern China. " + "It is a deciduous woody vine growing to 30 m tall or more given " + "suitable support, attaching itself by means of numerous small " + "branched tendrils tipped with sticky disks.", "Parthenocissus tricuspidata"), new PageData("Quince", "The quince is the sole member of the genus Cydonia and is native to " + "warm-temperate southwest Asia in the Caucasus region. The " + "immature fruit is green with dense grey-white pubescence, most " + "of which rubs off before maturity in late autumn when the fruit " + "changes color to yellow with hard, strongly perfumed flesh.", "Cydonia oblonga") }; final String[] viewOptions = new String[] { "Title", "Binomial name", "Picture", "Decsription" }; final Entry[] effects = new Entry[] { new SimpleEntry("Sepia Tone", new SepiaTone()), new SimpleEntry("Glow", new Glow()), new SimpleEntry("Shadow", new DropShadow()) }; final ImageView pic = new ImageView(); final Label name = new Label(); final Label binName = new Label(); final Label description = new Label(); public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { stage.setTitle("Menu Sample"); Scene scene = new Scene(new VBox(), 400, 350); scene.setFill(Color.OLDLACE); MenuBar menuBar = new MenuBar(); // --- Menu File Menu menuFile = new Menu("File"); // --- Menu Edit Menu menuEdit = new Menu("Edit"); // --- Menu View Menu menuView = new Menu("View"); menuBar.getMenus().addAll(menuFile, menuEdit, menuView); ((VBox) scene.getRoot()).getChildren().addAll(menuBar); stage.setScene(scene); stage.show(); } private class PageData { public String name; public String description; public String binNames; public Image image; public PageData(String name, String description, String binNames) { this.name = name; this.description = description; this.binNames = binNames; image = new Image(getClass().getResourceAsStream(name + ".jpg")); } } } 不像其他 UI 控件,Menu 类和其它 MemuItem 扩展类,不是扩展自 Node 类,因此不能直接 添加到应用场景中,并且保持不可见,直到通过 getMenus 方法添加到菜单栏为止。 图 23-2 菜单栏加入到应用中 可以通过键盘的箭头来导航菜单项。但是,当你选择一个菜单项时,没有行为执行,因为菜 单行为还没定义。 23.3 添加菜单项 通过添加如下项来设置 File 菜单的功能:  Shuffle – 加载关于植物的参考信息  Clear – 删除参考信息并清理场景  Separator – 分割菜单项  Exit – 退出应用 示例 23-2 中粗体行代码通过使用 MenuItem 类创建了 Shuffle 菜单,并增加图像组件到应用 场景。MenuItem 类创建带文本和图像行为项,在用户点击时,行为执行的是 setOnAction 方法定义的内容,与 Button 类相似。 示例 23-2 为 Shuffle 添加内容 import java.util.AbstractMap.SimpleEntry; import java.util.Map.Entry; import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.effect.DropShadow; import javafx.scene.effect.Effect; import javafx.scene.effect.Glow; import javafx.scene.effect.SepiaTone; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.TextAlignment; import javafx.stage.Stage; public class MenuSample extends Application { final PageData[] pages = new PageData[] { new PageData("Apple", "The apple is the pomaceous fruit of the apple tree, species Malus " +"domestica in the rose family (Rosaceae). It is one of the most " +"widely cultivated tree fruits, and the most widely known of " +"the many members of genus Malus that are used by humans. " +"The tree originated in Western Asia, where its wild ancestor, " +"the Alma, is still found today.", "Malus domestica"), new PageData("Hawthorn", "The hawthorn is a large genus of shrubs and trees in the rose " + "family, Rosaceae, native to temperate regions of the Northern " + "Hemisphere in Europe, Asia and North America. " + "The name hawthorn was " + "originally applied to the species native to northern Europe, " + "especially the Common Hawthorn C. monogyna, and the unmodified " + "name is often so used in Britain and Ireland.", "Crataegus monogyna"), new PageData("Ivy", "The ivy is a flowering plant in the grape family (Vitaceae) native" +" to eastern Asia in Japan, Korea, and northern and eastern China." +" It is a deciduous woody vine growing to 30 m tall or more given " +"suitable support, attaching itself by means of numerous small " +"branched tendrils tipped with sticky disks.", "Parthenocissus tricuspidata"), new PageData("Quince", "The quince is the sole member of the genus Cydonia and is native" +" to warm-temperate southwest Asia in the Caucasus region. The " +"immature fruit is green with dense grey-white pubescence, most " +"of which rubs off before maturity in late autumn when the fruit " +"changes color to yellow with hard, strongly perfumed flesh.", "Cydonia oblonga") }; final String[] viewOptions = new String[] { "Title", "Binomial name", "Picture", "Decsription" }; final Entry[] effects = new Entry[] { new SimpleEntry("Sepia Tone", new SepiaTone()), new SimpleEntry("Glow", new Glow()), new SimpleEntry("Shadow", new DropShadow()) }; final ImageView pic = new ImageView(); final Label name = new Label(); final Label binName = new Label(); final Label description = new Label(); private int currentIndex = -1; public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { stage.setTitle("Menu Sample"); Scene scene = new Scene(new VBox(), 400, 350); scene.setFill(Color.OLDLACE); name.setFont(new Font("Verdana Bold", 22)); binName.setFont(new Font("Arial Italic", 10)); pic.setFitHeight(150); pic.setPreserveRatio(true); description.setWrapText(true); description.setTextAlignment(TextAlignment.JUSTIFY); shuffle(); MenuBar menuBar = new MenuBar(); final VBox vbox = new VBox(); vbox.setAlignment(Pos.CENTER); vbox.setSpacing(10); vbox.setPadding(new Insets(0, 10, 0, 10)); vbox.getChildren().addAll(name, binName, pic, description); // --- Menu File Menu menuFile = new Menu("File"); MenuItem add = new MenuItem("Shuffle", new ImageView(new Image("src/menusample/new.png"))); add.setOnAction(new EventHandler() { public void handle(ActionEvent t) { shuffle(); vbox.setVisible(true); } }); menuFile.getItems().addAll(add); // --- Menu Edit Menu menuEdit = new Menu("Edit"); // --- Menu View Menu menuView = new Menu("View"); menuBar.getMenus().addAll(menuFile, menuEdit, menuView); ((VBox) scene.getRoot()).getChildren().addAll(menuBar, vbox); stage.setScene(scene); stage.show(); } private void shuffle() { int i = currentIndex; while (i == currentIndex) { i = (int) (Math.random() * pages.length); } pic.setImage(pages[i].image); name.setText(pages[i].name); binName.setText("(" + pages[i].binNames + ")"); description.setText(pages[i].description); currentIndex = i; } private class PageData { public String name; public String description; public String binNames; public Image image; public PageData(String name, String description, String binNames) { this.name = name; this.description = description; this.binNames = binNames; image = new Image(getClass().getResourceAsStream(name + ".jpg")); } } } 在用户选择 Shuffle 菜单项时,通过相应的处理,shuffle 方法在 setOnAction 中被调用。 Clear 菜单项用于檫出应用布景内容。可以通过带 GUI 元素的 VBox 容器不可见来实现。如图 23-3 所示。 示例 23-3 创建 Clear 菜单 MenuItem clear = new MenuItem("Clear"); clear.setAccelerator(KeyCombination.keyCombination("Ctrl+ X")); clear.setOnAction(new EventHandler() { public void handle(ActionEvent t) { vbox.setVisible(false); } }); MenuItem 的实现是开发者能设定加速器,即组合键来实现菜单相同的功能。对于 Clear 菜 单,用户既可以选择菜单来时功能操作,也可通过 Ctrl 和 X 键来模拟。 Exit 菜单用于关闭应用窗口,通过设置 System.exit(0)为菜单行为来实现。如示例 23-4 所示。 示例 23-4 创建退出菜单 MenuItem exit = new MenuItem("Exit"); exit.setOnAction(new EventHandler() { public void handle(ActionEvent t) { System.exit(0); } }); 使用 getItems 方法来为 File 菜单天剑新创建的菜单项,如示例 23-5 所示。也可创建分隔符 并添加到菜单,以便可视化分割 Exit 菜单项。 示例 23-5 添加菜单项 menuFile.getItems().addAll(add, clear, new SeparatorMenuItem(), exit); 通过添加示例 23-2、3、4、5 代码到菜单示例应用中,然后编译运行。选择 Shuffle 菜单加 载不同植物参考。然后分别使用菜单项。图 23-3 展示了 Clear 菜单选中时的情形。 图 23-3 File 菜单选择情况 在 View 菜单下,可以隐藏和展示参考信息元素。通过实现 CreateMenuItem 方法并在 start 方法内创建 CheckMenuItem 对象调用它。然后把新创建的选择菜单项天骄到 View 菜单。如 示例 23-6 所示: 示例 23-6 应用 CheckMenuItem 创建切换选项 // --- Creating four check menu items within the start method CheckMenuItem titleView = createMenuItem ("Title", name); CheckMenuItem binNameView = createMenuItem ("Binomial name", binName); CheckMenuItem picView = createMenuItem ("Picture", pic); CheckMenuItem descriptionView = createMenuItem ("Description", description); menuView.getItems().addAll(titleView, binNameView, picView, descriptionView); ... // The createMenuItem method private static CheckMenuItem createMenuItem (String title, final Node node){ CheckMenuItem cmi = new CheckMenuItem(title); cmi.setSelected(true); cmi.selectedProperty().addListener(new ChangeListener() { public void changed(ObservableValue ov, Boolean old_val, Boolean new_val) { node.setVisible(new_val); } }); return cmi; } CheckMenuItem 类扩展自 MenuItem 类,它能在选中与否间切换状态。选中时将菜单项标记 为选中。 示例 23-6 创建了四个 CheckMenuItem 对象并处理属性 selectedProperty 的变换情况。例如, 当用户取消选中 picView 项时,setVisible 方法介绍 false 值,植物图片不可见。把这些代码 片天叫道应用中,编译运行应用,可以体验下选中与取消的情况。图 23-4 展示了这样应用 的情形。 图 23-4 使用选择菜单项 23.4 创建子菜单 对于编辑(Edit)菜单,定义了两个菜单项:图片效果和非效果(Picture Effect and No Effects)。图片效果菜单设计成带三个子项的菜单,非效果菜单为删除选择效果并回复到原 始图片状态。 用 RadioMenuItem 类来创建子菜单,添加单选按钮到切换组来实现排他性选择。如示例 23-7 所示。 示例 23-7 创建带单选按钮的菜单 //Picture Effect menu Menu menuEffect = new Menu("Picture Effect"); final ToggleGroup groupEffect = new ToggleGroup(); for (Entry effect : effects) { RadioMenuItem itemEffect = new RadioMenuItem(effect.getKey()); itemEffect.setUserData(effect.getValue()); itemEffect.setToggleGroup(groupEffect); menuEffect.getItems().add(itemEffect); } //No Effects menu final MenuItem noEffects = new MenuItem("No Effects"); noEffects.setOnAction(new EventHandler() { public void handle(ActionEvent t) { pic.setEffect(null); groupEffect.getSelectedToggle().setSelected(false); } }); //Processing menu item selection groupEffect.selectedToggleProperty().addListener(new ChangeListener() { public void changed(ObservableValue ov, Toggle old_toggle, Toggle new_toggle) { if (groupEffect.getSelectedToggle() != null) { Effect effect = (Effect) groupEffect.getSelectedToggle().getUserData(); pic.setEffect(effect); } } }); //Adding items to the Edit menu menuEdit.getItems().addAll(menuEffect, noEffects); SetUserData 方法为特定单选按钮定义了可视效果。当切换组中一项被选中,相应的相关背 影到图片。当非效果菜单被选中,setEffect 方法设定 null 值,并且没有效果应用到图片。 图 23-5 展示了阴影效果选择情况。 图 23-5 带三个单选项的子菜单。 当 DropShadow 效果应用到图片是,它看起来如图 23-6 所示。 图 23-6 效果图 没有效果菜单被选中时,可以使用 MenuItem 类的 setDisable 方法使非效果菜单失效。变更 示例 23-7 代码为示例 23-8 代码,如下所示: 示例 23-8 失效菜单项 Menu menuEffect = new Menu("Picture Effect"); final ToggleGroup groupEffect = new ToggleGroup(); for (Entry effect : effects) { RadioMenuItem itemEffect = new RadioMenuItem(effect.getKey()); itemEffect.setUserData(effect.getValue()); itemEffect.setToggleGroup(groupEffect); menuEffect.getItems().add(itemEffect); } final MenuItem noEffects = new MenuItem("No Effects"); noEffects.setDisable(true); noEffects.setOnAction(new EventHandler() { public void handle(ActionEvent t) { pic.setEffect(null); groupEffect.getSelectedToggle().setSelected(false); noEffects.setDisable(true); } }); groupEffect.selectedToggleProperty().addListener(new ChangeListener() { public void changed(ObservableValue ov, Toggle old_toggle, Toggle new_toggle) { if (groupEffect.getSelectedToggle() != null) { Effect effect = (Effect) groupEffect.getSelectedToggle().getUserData(); pic.setEffect(effect); noEffects.setDisable(false); } else { noEffects.setDisable(true); } } }); menuEdit.getItems().addAll(menuEffect, noEffects); 当没有 RadioMenuItem 选项被选时,非效果菜单失效,如图 23-7 所示。当用户选择一个可 视化效果时,非效果菜单又有效了。 图 23-7 效果菜单无效 23.5 添加上下文菜单 当没有用户界面放置宫内需求时,可以使用上下文菜单,即鼠标右键单击是窗口弹出式菜单。 弹出菜单可以包含多个菜单项。 在菜单示例应用中,为图片设定了一个弹出菜单应变用户能复制图片。 使用 ContextMenu 类来定义上下文菜单,如示例 23-9 所示。 示例 23-9 定义上下文菜单 final ContextMenu cm = new ContextMenu(); MenuItem cmItem1 = new MenuItem("Copy Image"); cmItem1.setOnAction(new EventHandler() { public void handle(ActionEvent e) { Clipboard clipboard = Clipboard.getSystemClipboard(); ClipboardContent content = new ClipboardContent(); content.putImage(pic.getImage()); clipboard.setContent(content); } }); cm.getItems().add(cmItem1); pic.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler() { @Override public void handle(MouseEvent e) { if (e.getButton() == MouseButton.SECONDARY) cm.show(pic, e.getScreenX(), e.getScreenY()); } }); 当用户右击 ImageView 对象时,show 方法被调用来展示上下文菜单。 setOnAction 方法定义了复制图片(Copy Image)菜单项,它创建了 Clipboard 对象,并把图 片作为它的内容。图 23-8 展示了用户选择复制图片是的情形。 图 23-8 使用上下文菜单 你可以尝试把这个复制图片粘贴到一个图形编辑器,或其他文档中看看效果。 为了增强功能,你可以添加更多菜单项到上下文菜单中,并指定不同的行为。也可用 CustomMenuItem 类创建自定义菜单。用这个类,你可以嵌入任意的菜单节点内,并指定按 钮或滑动器作为菜单项等。 本部内容涉及的 API 内容有:  Menu  MenuItem  RadioMenuItem  CheckMenuItem  ContextMenu  SeparatorMenuItem  CustomMenuItem 读者可以根据需要进一步查阅相关内容。 24 定 制 界 面 控 件 (Customization of UI Controls) 这部分描述 UI 控件定制特性,并总结 Oracle 提供的方法和技巧,以帮助你改变 UI 控件的外 观和行为。 在示例应用工程 UIControlSamples 中,将学到如何定制控件:应用 CSS、重定义缺省行为、 使 用 元 工 厂 。 对大多数具体案例,应用任务要求的的独特性是不要 用 javafx.scene.control 包的类来实现,而是通过扩展 Control 来发明自己控件。 24.1 应用 CSS 可以通过重定义 JavaFX Caspian 式样表来改变 UI 控件外观。具体可参考案例“ Skinning JavaFX Applications with CSS”来进一步了解详情。 在 JavaFX 论坛上有些特定事务经常被问到。由于 Tooltip 类没有任何属性或方法来改变提示 框的缺省颜色,却依然可改变.tooltip CSS 类的-fx-background-color 属性来实现,如示例 24-1 所示。 示例 24-1 改变提示框背景色 .tooltip { -fx-background-color: linear-gradient(#e2ecfe, #99bcfd); } .page-corner { -fx-background-color: linear-gradient(from 0% 0% to 50% 50%,#3278fa,#99bcfd); } .page-corner CSS 类定义了提示框右下角颜色。当把示例 24-1 这些代码增加到 TooltipSample 的式样表并应用到布景中,提示框将变为蓝色。如图 24-1 所示效果情形。 图 24-1 蓝色背景提示框 注意,当你改变提示框缺省式样时,新的外观将应用到应用中的所有提示框。 另外一个流行的设计任务是改变控件的标记。例如,CheckBox 类缺省样式定义了传统的标 记来标识选中状态。通过 CSS 你可以重新定义标记图形,以及颜色,如示例 24-2 所示: 示例 24-2 改变 Checkbox 标记 .check-box .mark { -fx-shape: "M2,0L5,4L8,0L10,0L10,2L6,5L10,8L10,10L8,10L5,6L2,10L0,10 L0,8L4,5L0,2L0,0Z"; } .check-box:selected .mark { -fx-background-color: #0181e2; } -fx-shape 属性设定了新的 SVG 路径标记,-fx-background-color 属性定义了颜色。把这些使用 改变应用到相应的应用中,它产生的效果如图 24-2 所示。 图 24-2 ComboBoxSample 勾选框改变 许多开发者询问如何克服 TableView 和 ListView 控件的可视样式的限制。缺省时,控件中所 有行都展示,不管是否为空。通过正确的 CSS 设置,可以为空行设定特定的颜色。示例 24-3 实现了控件 TableView 的这个任务需求。 示例 24-3 为 TableView 空行设定颜色 .table-row-cell:empty { -fx-background-color: lightyellow; } .table-row-cell:empty .table-cell { -fx-border-width: 0px; } 第一个 CSS 样式定义了所有空行——不管奇偶行,为淡黄色背景。当 table-row-cell 是空的 时,第二个 CSS 语句删除绘制在表格右手边垂直边框。 当示例 24-3 的 CSS 样式应用到 TableViewSample 应用中,这个地址簿表格如图 24-3 所示: 图 24-3 TableViewSample 带色空行处理 甚至可以为空单元格背景色设置 null 值,以便式样表使用缺省的背景色。如图 24-4 所示的 情形 图 24-4 TableViewSample 空背景色空行处理 可以设置 UI 控件的更多 CSS 属性来个它们的图形、颜色和应用效果。有兴趣的具体可以参 考“ JavaFX CSS Reference Guide”来学更多内容的 CSS 和相应类。 24.2 改变缺省行为 许多开发者要求特定的 API 来限制文本域的输入,例如,只允许数字等。示例 24-4 提供了 一个只允许数字的文本框应用示例。 示例 24-4 禁止字符输入 import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.stage.Stage; public class CustomTextFieldSample extends Application { final static Label label = new Label(); @Override public void start(Stage stage) { Group root = new Group(); Scene scene = new Scene(root, 300, 150); stage.setScene(scene); stage.setTitle("Text Field Sample"); GridPane grid = new GridPane(); grid.setPadding(new Insets(10, 10, 10, 10)); grid.setVgap(5); grid.setHgap(5); scene.setRoot(grid); final Label dollar = new Label("$"); GridPane.setConstraints(dollar, 0, 0); grid.getChildren().add(dollar); final TextField sum = new TextField() { @Override public void replaceText(int start, int end, String text) { if (!text.matches("[a-z, A-Z]")) { super.replaceText(start, end, text); } label.setText("Enter a numeric value"); } @Override public void replaceSelection(String text) { if (!text.matches("[a-z, A-Z]")) { super.replaceSelection(text); } } }; sum.setPromptText("Enter the total"); sum.setPrefColumnCount(10); GridPane.setConstraints(sum, 1, 0); grid.getChildren().add(sum); Button submit = new Button("Submit"); GridPane.setConstraints(submit, 2, 0); grid.getChildren().add(submit); submit.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { label.setText(null); } }); GridPane.setConstraints(label, 0, 1); GridPane.setColumnSpan(label, 3); grid.getChildren().add(label); scene.setRoot(grid); stage.show(); } public static void main(String[] args) { launch(args); } } 为重定义 TextField 类的缺省实现,必须覆盖继承制 TextInputControl 类的 replaceText 和 replaceSelection 方法。 当用户试图在 Sum 文本域输入字母时,不会有任何出现,且会出现警告消息,如图 24-5 所 示。 图 24-5 试图键入字母 但当用户输入数字是,就会出现在输入域内,如图 24-6 所示 图 24-6 输入数字 24.3 单元格工厂实现 通过使用单元格工厂机制,可以实现 4 种 UI 控件的外观和行为的定制化处理。元工厂可以 应用的 4 类控件为: TableView, ListView, TreeView 和 ComboBox。一个单元 格工厂用于生产单元格示例,来代表这些控件的任何单一细目。 Cell 类扩展自 Labeled 类,提供了所有典型用例需要的属性和方法:显示和编辑文本。但是 当应用需要在列表或表格中展示图片对象时,你可以用 graphic 属性来放置任何 Node 对象 与单元格内。 例如,示例 24-5 的代码片为列表视图创建了一个单元格工程,并在 updateItem 方法内重定 义了单元格内容,以便清单展示不同颜色的矩形。 示例 24-5 ListView 控件单元格工厂实现 list.setCellFactory(new Callback, ListCell>() { Override public ListCell call(ListView list) { return new ColorRectCell(); } }); ... static class ColorRectCell extends ListCell { @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); Rectangle rect = new Rectangle(100, 20); if (item != null) { rect.setFill(Color.web(item)); setGraphic(rect); } else { setGraphic(null); } } } 图 24-7 展示了定制列表的外观 图 24-7 色块矩形列表清单 这部分教程使广泛用了单元格工厂机制来定制 UI 控件。表格 24-1 总结了代码模板,以便在 应用中来应用实现单元格工厂。 表格 24-1 单元格工厂编码模式 控件 编码模式 ListView,ComboBox list.setCellFactory(new Callback, ListCell>() { @Override public ListCell call(ListView list) { //cell implementation } }); TableView column.setCellFactory(new Callback() { public TableCell call(TableColumn p) { //cell implementation } }); TreeView tree.setCellFactory(new Callback, TreeCell>(){ @Override public TreeCell call(TreeView p) { //cell implementation } }); 为了评估更多的单元格工厂用例,可以参考前面的相应控件介绍。 至此,关于 JavaFX 内置控件的介绍就结束了。读者可以自行做相关的进一步深入研究和应 用开发。并祝学习快乐。
还剩160页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享pdf获得金币 ] 18 人已下载

下载pdf

pdf贡献者

pxwp

贡献于2012-05-14

下载需要 15 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf