本文档将讨论在JavaFX语言中可用的各种GUI组件,并提供示例代码,讨论JavaFX组件与Swing GUI组件相比的差异。
前提条件
如果你从前没有阅读过JavaFX脚本语言起步教程,那么我们强烈推荐你在继续阅读本文档之前首先阅读JavaFX脚本语言起步教程。
你应该具有熟知Java编程语言,尤其是在Swing和Java2D方面。
内容
比较声明式语法和过程式语法
正像我们在前一节所看到的,JavaFX语言提供了一种声明式语法来表达结构和用户接口组件的内容。为了帮助你理解,让我们以纯过程的方式重写上面的程序,就像我们在编写Swing程序时经常做的那样:
var win = new Frame(); win.title = "Hello World JavaFX"; win.width = 200; var label = new Label(); label.text = "Hello World"; win.content = label; win.visible = true;
上面的源代码同样也是一个有效的JavaFX程序,它和前面的代码具有相同的效果。
下面列举出在声明式方法和过程式方法中实际发生了什么:
- 调用Frame类构造方法建立新的Frame。
- 给Frame的title、width、visible和content属性赋值。
- 在赋值content属性的过程中,调用了Label类的构造方法建立一个新的Label,并且为它的text属性赋值。
尽管上面的代码是一个非常简单的示例,但根据第一个示例和上例的比较不难看出,采用声明式语法编写程序会使代码更加清楚易懂。
因此,声明式编程使用简单的表达方式建立应用程序。在上面的第一个示例中,表达式的根(root)往往是一个对象分配表达式(构造器),它生成了组成程序的对象图表(object graph)。
增加动态行为
目前,我们编写的“Hello World”程序并没有动态行为。为了使用JavaFX建立一个具有动态行为的图形用户接口组件,你可以建立这样的图形用户接口:它的属性依赖于其它对象的属性值。而这些其它的对象将成为应用状态的表象(representation),即它们代表了应用的状态。由于这个GUI组件的属性依赖于其它对象的属性,因此此GUI组件将在你修改其它对象时自动地“反射”变化。在这里,这个GUI组件通常被称为视图(View),而其它对象被称为模型(Model)。下面是“Hello World”程序的模型/视图版本。
class HelloWorldModel { attribute saying: String; } var model = HelloWorldModel { saying: "Hello World" }; var win = Frame { title: "Hello World JavaFX" width: 200 content: Label { text: bind model.saying } visible: true };
此程序运行结果如下图:
如果model
对象的saying
属性被修改为:
model.saying = "Goodbye Cruel World!";
那么视图将自动随之改变,如下图:
这里值得注意的是:在上面的示例中,通过将JavaFX bind
操作符应用于model
的saying
属性,从而实现了对label的text
属性的初始化。在这里,bind
操作符标识了增量式(incremental)更新。这意味着无论model.saying
的值何时改变,label的text
属性都将被更新为相同值。
在例如Buttons、CheckBoxes、TextFields这些输入部件中,处于模型属性和GUI组件属性之间的关联可以是双向的。
请考虑以下示例:
class HelloWorldModel { attribute saying: String; } var model = HelloWorldModel { saying: "Hello World" }; var win = Frame { title: bind "{model.saying} JavaFX" width: 200 content: TextField { value: bind model.saying } visible: true };
此程序运行结果如下图:
如果你在TextField中输入其它的文字,并按下Enter
,那么窗体的标题将相应地改变:
在本例中,TextField的value
属性被更新为用户输入的文字(通过TextField
类实现)。而与此同时,model
的saying
属性也被更新为相同值。因为赋值给窗体的title
属性的表达式依赖于model
的saying
属性,因此model
的saying
属性的变化导致了表达式被重新求值、窗体的title
属性被更新。
注意:你能够将任意组件的属性bind
到一个增量式求值表达式。这种表达式将使用条件逻辑、迭代器、选择等产生任意复杂度的动态内容,而动态内容的表达式仍然保有其可声明性。
学习更多的JavaFX GUI组件
本节将讨论在JavaFX语言中可用的多种不同GUI组件,并给出展示其用途的示例程序,通过比较JavaFX组件与Swing GUI组件的方式讨论它们的差异。
- Border(边框)和Layout Manager(布局管理器)
- Menu(菜单)
- Label(标签)
- Group Panel(分组面板),Simple Label(简单标签)和TextField(文本栏)
- Button(按钮)
- ListBox(列表框)
- SplitPane(分割窗体)
- RadioButton(单选按钮)、RadioButtonMenuItem(单选按钮菜单项)、ToggleButton(开关按钮)和ButtonGroup(按钮分组)
- ComboBox(下拉选择框)
- Tree(树形)
- Table(表格)
- TextComponent(文本组件)
- Spinner(微调控制器)和Slider(滑动条)
Border(边框)和Layout Manager(布局管理器)
在JavaFX语言中,使用Border和Layout Manager也采用声明的方式。每个Swing/AWT Layout Manager都被封装在一个JavaFX类中,这个类使用指定的Layout Manager来实例化JPanel。被添加到JPanel上的组件被声明为这个JavaFX类的属性。每个Swing的Border类型也同样被封装在一个JavaFX类中,这个类具有与Swing Border配置选项对应的属性。这里提供了一个使用EmptyBorder和GridPanel的简单示例。正如你所期待的那样,JavaFX的EmptyBorder
对应着javax.swing.border.EmptyBorder
,而GridPanel
则对应着java.awt.GridLayout
。
class ButtonClickModel { attribute numClicks: Number; } var model = new ButtonClickModel(); var win = Frame { width: 200 content: GridPanel { border: EmptyBorder { top: 30 left: 30 bottom: 30 right: 30 } rows: 2 columns: 1 vgap: 10 cells: [Button { text: "I'm a button!" mnemonic: I action: operation() { model.numClicks++; } }, Label { text: bind "Number of button clicks: {model.numClicks}" }] } visible: true };
程序运行结果如下图:
在点击按钮多次后,将出现如下效果:
需要注意的:对
Button
的
action
和mnemonic(助记码)属性的解释将在下面给出..
在本例中,GridPanel被配置为单列、双行、并在行间放置10个像素的垂直间隔,而这些工作仅仅通过为columns
、rows
和vgap
属性赋值来完成。如果你希望在列间建立一个间隔的话,GridPanel还提供hgap
属性。与此同时,一个30像素宽的空白边框被设置在GridPanel的四边。
通过把button和label赋值到cells
属性,我们可以在GridPanel上添加按钮和标签。GridPanel
通过从其基类JPanel中增加或者删除组件的方式来对在cells
属性上的插入或者删除作出回应。
JavaFX支持的其它Layout Manager也采用同样的方式。下面给出这些Layout Manager在JavaFX、Swing中的对应表格:
JavaFX部件 | Layout Manager |
---|---|
GridPanel |
GridLayout |
GridBagPanel |
GridBagLayout |
FlowPanel |
FlowLayout |
BorderPanel |
BorderLayout |
Box |
BoxLayout |
StackPanel |
Romain Guy's StackLayout |
CardPanel |
CardLayout |
GroupPanel |
org.jdesktop.layout.GroupLayout |
下面是JavaFX Border类和其相应的Swing Border类的对应表格:
JavaFX Border | Swing Border |
---|---|
EmptyBorder |
EmptyBorder |
LineBorder |
LineBorder |
BevelBorder |
BevelBorder |
SoftBevelBorder |
SoftBevelBorder |
MatteBorder |
MatteBorder |
TitledBorder |
TitledBorder |
Menu(菜单)
让我们在上一个示例的基础上添加一个简单的菜单条。新代码如下:
import java.lang.System; class ButtonClickModel { attribute numClicks: Number; } var model = new ButtonClickModel(); Frame { width: 200 menubar: MenuBar { menus: Menu { text: "File" mnemonic: F items: MenuItem { text: "Exit" mnemonic: X accelerator: { modifier: ALT keyStroke: F4 } action: operation() { System.exit(0); } } } } content: GridPanel { border: EmptyBorder { top: 30 left: 30 bottom: 30 right: 30 } rows: 2 columns: 1 vgap: 10 cells: [Button { text: "I'm a button!" mnemonic: I action: operation() { model.numClicks++; } }, Label { text: bind "Number of button clicks: {model.numClicks}" }] } visible: true }
在程序执行后,按下ALT+F
组合键将出现如下情形:
正如你所见,我们通过将一个MenuBar
类赋值到窗口的menubar
属性建立了一个菜单条。你可以通过增加menubar的menus
来为menubar增加menu。在本例中,我们只添加了一个menu,但任何能够返回Menu
对象列表的表达式都能够在这里使用。
为了定义菜单,需要赋值menu的text
、mnemonic
、items
的属性值。
正如你所认为的那样,text
属性的类型是String
。 mnemonic
属性却是KeyStroke类型的。它的值F
是KeyStroke
类的一个枚举值。在JavaFX的属性初始化程序上下文中,该属性的静态类(和在Java类中的静态字段相似)的枚举值能够在没有类型名限制的情况下被访问(而在别处,你不得不把F
写为F:KeyStroke
)。
这里唯一的菜单项是一个MenuItem类实例,它具有值为"Exit"的text
属性和值为X
的mnemonic
属性。而它的accelerator
属性也被赋值了。注意:在声明中的类型名Accelerator
被省略了。这在JavaFX中是允许的。如果类型名没有提供,该属性的静态类型将被使用,在本例中accelerator
属性的静态类型是Accelerator。另外,accelerator
的modifier
和keyStroke
属性都使用枚举值进行了初始化。
最后,MenuItem
的action
属性是function
类型的(即指它的值为function,而非对象)。在本例中,action
属性是一个内联(inline)的operation
,它调用了一些Java代码。
Label(标签)
JavaFX的Label class支持HTML内容。通过使用Label
,你可以利用HTML和CSS建立风格化文本(styled text)和图片,它非常类似编写典型的Web应用。另外,通过使用JavaFX内嵌表达式,你能够在Swing应用中建立动态HTML内容,这就像在编写Web页面时使用JSTL或者Velocity等工具一样容易。
请阅读下面的购物卡示例:
class Item { attribute id: String; attribute productId: String; attribute description: String; attribute inStock: Boolean; attribute quantity: Number; attribute listPrice: Number; attribute totalCost: Number; } attribute Item.totalCost = bind quantity*listPrice; class Cart { attribute items: Item*; attribute subTotal: Number; } operation sumItems(itemList:Item*) { var result = 0.00; for (item in itemList) { result += item.totalCost; } return result; } attribute Cart.subTotal = bind sumItems(items); var cart = Cart { items: [Item { id: "UGLY" productId: "D100" description: "BullDog" inStock: true quantity: 1 listPrice: 97.50 }, Item { id: "BITES" productId: "D101" description: "Pit Bull" inStock: true quantity: 1 listPrice: 127.50 }] }; Frame { content: Label { text: bind "<html> <h2 align='center'>Shopping Cart</h2> <table align='center' border='0' bgcolor='#008800' cellspacing='2' cellpadding='5'> <tr bgcolor='#cccccc'> <td><b>Item ID</b></td> <td><b>Product ID</b></td> <td><b>Description</b></td> <td><b>In Stock?</b></td> <td><b>Quantity</b></td> <td><b>List Price</b></td> <td><b>Total Cost</b></td> <td> </td> </tr> { if (sizeof cart.items == 0) then "<tr bgcolor='#FFFF88'><td colspan='8'><b>Your cart is empty.</b></td></tr>" else foreach (item in cart.items) "<tr bgcolor='#FFFF88'> <td>{item.id}</td> <td>{item.productId}</td> <td>{item.description}</td> <td>{if item.inStock then "Yes" else "No"}</td> <td>{item.quantity}</td> <td align='right'>{item.listPrice}</td> <td align='right'>{item.totalCost}</td> <td> </td> </tr>" } <tr bgcolor='#FFFF88'> <td colspan='7' align='right'> <b>Sub Total: ${cart.subTotal}</b> </td> <td> </td> </tr> </table> </html>" } visible: true }
程序执行结果如下图:
如果你通过编程删去购物项(cart items):
delete cart.items;
程序将显示如下结果:
在上面的示例中,嵌入的JavaFX表达式(在代码中显示为粗体)动态地建立HTML表格行和单元格的内容。当表达式所依赖的对象发生改变时,标签的HTML内容也随之动态地更新。
上面的示例非常有趣,因为它演示了如何使用表达式来定义属性值。Item
类的totalCost
属性和Cart
类的subTotal
属性都被绑定到了计算其数值的表达式。无论何时,只要这些表达式所依赖的对象发生变化,其关联的属性值也将自动地被重新计算并更新。这让我们想到了常见的电子表格:它含有包含公式(formulas)(公式中涉及到其它单元格中的数值)的单元格;当你在其它单元格中输入数据时,那些包含此公式的表格值将被自动更新。
在HTML中的图片
JavaFX的Label类实际上封装了一个特定的JEditorPane类,后者使用了一个支持利用Java类装载器(Java class loader)从JAR文件装载图片的、共享的图片缓存。这样就可以使用HTML <img>
元素引用那些与应用打包在一起的图片资源。
超链接
Label类也支持HTML超链接:将指定的URL作为HTML <a>
元素的href
属性嵌入到label。
我们使用JavaFX #
操作符来建立这样的URL。#
操作符生成一个字符串化对象引用(stringified object reference),此引用指向它的操作数,而操作数又可以使用JavaFX ?
操作符间接引用。可能听起来有些饶舌,我们可以在下面的讲解中慢慢理解这段话的含义。例如:
var a = 20; var b = #a; assert b instanceof String; // passes var c = (Number) ?b; assert a == c; // passes
Label类的HTML渲染器在HTML <a href=url>
上下文中识别这样的URL,并处理鼠标在具有URL的元素上进行的点击,如果URL的值指向一个函数或者操作的话,它将调用该函数或者操作。
例如,这里使用超链接标签代替按钮来重写前面的按钮点击示例:
class ButtonClickModel { attribute numClicks: Number; } var model = new ButtonClickModel(); Frame { width: 200 content: GridPanel { border: EmptyBorder { top: 30 left: 30 bottom: 30 right: 30 } rows: 2 columns: 1 vgap: 10 cells: [Label { text: bind "<html> <a href='{#(operation() {model.numClicks++;})}'> I'm a hyperlink! </a> </html>" }, Label { text: bind "Number of clicks: {model.numClicks}" }] } visible: true };
上面示例中的粗体部分建立了一个新的operation
,它将增加模型的numClicks
属性值。
并且这里用到了前面讲到的URL创建方式:应用#
操作符来生成URL,而URL指向嵌入在HTML标识中的operation。
运行程序,显示如下:
在点击超链接两次后,程序显示变化为:
Group Panel(分组面板),Simple Label(简单标签)和TextField(文本栏)
本节将使用一个非常简单的示例讲解JavaFX的Group Panel、Simple Label和TextField类。
JavaFX GroupPanel类封装了Java.net上的GroupLayout类。GroupLayout是一个强大的布局管理器,它将面板的内容表现为平行的水平、垂直分组的集合。在JavaFX中,这些平行的分组被简单地称为Row和Column。当你声明一个GroupPanel时,你也可以针对每个水平、垂直的组件分组来声明其Row和Column对象。然后,在添加组件时便可以将相应的Row和Column对象赋值给组件的row
和column
属性。GroupPanel按照当前外观风格准则在组件之间自动地插入间隔。通过声明Row或者Column对象的alignment
和resizable
属性,你能够控制在行或者列中的组件对齐和行或者列是否可调整大小。
JavaFX TextField类封装了Swing的JFormattedTextField。它具有一个value
属性,无论在焦点位于此文本框或者移到其它组件时,只要用户按下Enter,该属性值都将被更新。通过将数字赋值给它的columns
,你可以控制它的宽度。而通过赋值LEADING
、CENTER
、TRAILING
给它的horizontalAligment
属性,你还可以控制它的水平对齐。TextField类具有两个值为函数
的属性,它们允许你执行基于用户交互的行为:action
和onChange
。如果你将一个函数或者操作赋值给action
属性,无论何时用户按下Enter键,此函数或者操作都会被调用。如果你将一个函数或者操作赋值给onChange
属性,当文本栏的value
发生变化时,这个的函数或者操作将被调用。
JavaFX SimpleLabel类封装了Swing的JLabel类。SimpleLabel与Label的不同之处在于它不支持超链接和首选大小(preferred size)。
下面显示了一个示例:
下面是示例的代码:
class Model { attribute firstName: String; attribute lastName: String; } var model = Model { firstName: "Joe" lastName: "Smith" }; Frame { content: GroupPanel { var firstNameRow = Row { alignment: BASELINE } var lastNameRow = Row { alignment: BASELINE } var labelsColumn = Column { alignment: TRAILING } var fieldsColumn = Column { alignment: LEADING resizable: true } rows: [firstNameRow, lastNameRow] columns: [labelsColumn, fieldsColumn] content: [SimpleLabel { row: firstNameRow column: labelsColumn text: "First Name:" }, TextField { row: firstNameRow column: fieldsColumn columns: 25 value: bind model.firstName }, SimpleLabel { row: lastNameRow column: labelsColumn text: "Last Name:" }, TextField { row: lastNameRow column: fieldsColumn columns: 25 value: bind model.lastName }] } visible: true };
上面的示例中关于布局的代码显示为蓝色。本示例中的布局由两行(一行用于first name,另一行用于last name)、两列(一列用于标签,另一列用于文本栏)组成。在GroupPanel的声明中,四个变量(firstNameRow
、lastNameRow
、labelsColumn
和fieldsColumn
)被声明为rows和columns属性,即将两行和两列分别赋值给GroupPanel的rows
和columns
属性。最后,正如你所见到的,label和TextField被赋值为GroupPanel的elements
属性。从label和TextField的声明可以看出,它们的row
和column
也被相应地赋值。
Button(按钮)
JavaFX Button类封装了Swing的JButton组件。为了讲解如何使用Button,让我们从Swing教程中重建一个简单的示例:
class ButtonDemoModel { attribute buttonEnabled: Boolean; } var model = ButtonDemoModel { buttonEnabled: true }; Frame { title: "ButtonDemo" content: FlowPanel { content: [Button { text: "Disable middle button" verticalTextPosition: CENTER horizontalTextPosition: LEADING icon: Image { url: "http://java.sun.com/docs/books/tutorial/uiswing/examples/components/ButtonDemoProject/src/components/images/right.gif" } mnemonic: D toolTipText: "Click this button to disable the middle button" enabled: bind model.buttonEnabled action: operation() { model.buttonEnabled = false; } }, Button { text: "Middle button" icon: Image { url: "http://java.sun.com/docs/books/tutorial/uiswing/examples/components/ButtonDemoProject/src/components/images/middle.gif" } verticalTextPosition: BOTTOM horizontalTextPosition: CENTER mnemonic: M toolTipText: "This middle button does nothing when you click it." enabled: bind model.buttonEnabled }, Button { text: "Enable middle button" icon: Image { url: "http://java.sun.com/docs/books/tutorial/uiswing/examples/components/ButtonDemoProject/src/components/images/left.gif" } mnemonic: E toolTipText: "Click this button to enable the middle button" action: operation() { model.buttonEnabled = true; } enabled: bind not model.buttonEnabled }] } visible: true }
点击左侧按钮后,程序出现以下变化:
示例程序*有三个button,其中每个button的enabled
属性都绑定到模型对象的buttonEnabled
属性。当你通过触发左侧和右侧按钮的action
修改此属性时,这三个button的状态都将发生变化。
我们通过将Image对象赋值给button的icon
属性为按钮增添了图片。
JavaFX Image
对象具有一个url
属性,你可以将一个包含了指向图片资源的URL作为其值。JavaFX具有内建的图片缓存,它支持使用Java class loader从JAR文件装载图片。因此,我们能够通过“file:
URL”轻松地访问和JAR文件一起打包的图片资源。
TabbedPane(页签窗体)
为了演示如何使用TabbedPane,让我们定义下面的具有TabbedPane组件相应属性的模型类:Model
。
class Model { attribute tabPlacement: TabPlacement; attribute tabLayout: TabLayout; attribute tabCount: Integer; attribute selectedTab: Integer; }
现在,让我们从上面的模型出发设计一个TabbedPane示例。
var model = Model { tabPlacement: TOP tabLayout: WRAP selectedTab: 3 tabCount: 5 }; Frame { height: 300 width: 400 content: TabbedPane { tabPlacement: bind model.tabPlacement tabLayout: bind model.tabLayout tabs: bind foreach (i in [1..model.tabCount]) Tab { title: "Tab {i}" toolTipText: "Tooltip {i}" } selectedIndex: bind model.selectedTab } visible: true }
上面以粗体显示的代码展示了在TabbedPane和模型之间的依赖关系。在完成编码后,TabbedPane的外观将随着模型的修改而改变。
我们通过将一组Tab对象赋值给TabbedPane的tabs
属性的方式将Tab添加到TabbedPane。TabPlacement和TabLayout类定义了一些枚举值(TOP、LEFT、BOTTOM、RIGHT 、WRAP、SCROLL),我们可以将这些值相应地赋值给TabbedPane的tabPlacement
和tabLayout
属性,从而能够控制tab的位置和布局。TabbedPane的selectedIndex
属性表示了当前显示哪个tab。
程序运行如下图:
值得注意的是:在示例中第四个tab被选择了,这是因为模型的selectedTab
属性被初始化为3
。在本例中,TabbedPane的selectedIndex
属性也随之更新,因为它被绑定到了模型的selectedTab
属性上。
对模型的tabPlacement属性作出如下修改:
model.tabPlacement = BOTTOM;
tab将移动窗体的下方:
对模型的selectedTab属性作出如下修改:
model.selectedTab = 0;
这将导致第一个tab被选择:
对模型的tabCount属性作出如下修改:
model.tabCount = 20;
这将导致15个新建的tab被添加到TabbedPane:
修改模型的tabLayout:
model.tabLayout = SCROLL;
程序运行效果如下图:
修改模型的tabCount:
model.tabCount = 2;
结果只保留了前两个tab:
ListBox(列表框)
JavaFX ListBox类提供了Swing JList组件的功能,但不同的是它提供了一个声明式接口。
为了演示其用法,我们还是从Swing教程的ListDemo出发重建一个简单示例:
在这个示例中,ListBox包含一个雇员姓名列表。如果点击“Fire”按钮,被选择的雇员将从列表中移除。如果在列表下方的文本框中输入新姓名,那么“Hire”按钮将变为可用状态。如果此时按下“Hire”按钮,这个新的姓名将被添加到列表。
这个示例也演示了如何使用BorderPanel和FlowPanel。一个BorderPanel最多包括五个组件,这五个组件将被放置在面板的上方、左侧、下方、右侧或者*。它会垂直拉伸左侧、右侧的组件,水平拉伸上方、下方的组件,而位于*的组件将向垂直、水平两个方向伸展。FlowPanel包括了一个按照从左到右的顺序放置组件的列表,就像在段落中文本一样。而且本示例还展示了如何使用RigidArea:一种用于在其它组件之间创建空白的、不可见的填充器组件。
class EmployeeModel { attribute employees: String*; attribute selectedEmployee: Number; attribute newHireName: String; } var model = EmployeeModel { employees: ["Alan Sommerer", "Alison Huml", "Kathy Walrath", "Lisa Friendly", "Mary Campione", "Sharon Zakhour"] }; Frame { title: "ListBox Example" content: BorderPanel { center: ListBox { selection: bind model.selectedEmployee cells: bind foreach (emp in model.employees) ListCell { text: emp } } bottom: FlowPanel { content: [Button { text: "Fire" action: operation() { delete model.employees[model.selectedEmployee]; } }, RigidArea { width: 5 }, TextField { columns: 30 value: bind model.newHireName }, RigidArea { width: 5 }, Button { text: "Hire" enabled: bind model.newHireName.length() > 0 action: operation() { insert model.newHireName after model.employees[model.selectedEmployee]; model.newHireName = ""; if (sizeof model.employees == 1) { model.selectedEmployee = 0; } else { model.selectedEmployee++; } } }] } } visible: true }
上面示例中的粗体代码用于创建ListBox。我们通过将一组ListCell对象赋值到ListBox的cells
属性来创建ListBox。而cells就取值于模型的雇员列表。因此,当从模型中添加或者删除雇员时,相应的单元格将被添加到ListBox或者从ListBox中删除。当单元格被渲染时,你为ListCell的text
属性所赋的值也将被显示出来。尽管在本示例中没有必要,但你仍然可以将一些HTML代码赋值给ListCell的text
属性,从而建立一个风格化文本和(或者)图片来作为单元格的内容。
ListBox的selection
属性包含了被选择单元格的索引。在本例中,它被绑定到模型的selectedEmployee
属性,因此当在列表中改变被选项时,模型的selectedEmployee
属性也将更新。与此同时,如果selectedEmployee
属性被更新,列表的被选项也会作出相应改变。这正是示例中“Hire”按钮的action
所做的。
在点击“Fire”按钮两次后,程序将改变为下图:
如果在文本栏中输入新的名字,则程序将发生改变:
接着点击“Hire”按钮:
splitPane(分割窗体)
JavaFX SplitPane类基于一个自定义Java组件,而不是Swing的JSplitPane类。与JSplitPane不同的是:它能够包括多个组件。而和JSplitPane一样是:你可以通过为它所包含的组件赋值来控制它的朝向和空间的数量。让我们看看下面的示例:
这个示例由一个水平分割的窗体和两个组件组成。左侧的组件是一个ListBox,它占据了30%的空间。而右侧的组件是一个包含CenterPanel(一种含有一个位于其*区域的组件的面板)的ScrollPane。CenterPanel包含了一个SimpleLabel,后者用于显示与被选择的列表项相关的图片。
class ExampleModel { attribute imageFiles: String*; attribute selectedImageIndex: Number; attribute selectedImageUrl: String; } var model = ExampleModel { var: self imageFiles: ["Bird.gif", "Cat.gif", "Dog.gif", "Rabbit.gif", "Pig.gif", "dukeWaveRed.gif", "kathyCosmo.gif", "lainesTongue.gif", "left.gif", "middle.gif", "right.gif", "stickerface.gif"] selectedImageUrl: bind "http://java.sun.com/docs/books/tutorial/uiswing/examples/components/SplitPaneDemoProject/src/components/images/{self.imageFiles[self.selectedImageIndex]}" }; Frame { title: "SplitPane Example" height: 400 width: 500 content: SplitPane { orientation: HORIZONTAL content: [SplitView { weight: 0.30 content: ListBox { selection: bind model.selectedImageIndex cells: bind foreach (file in model.imageFiles) ListCell { text: bind file } } }, SplitView { weight: 0.70 content: ScrollPane { view: CenterPanel { background: white content: SimpleLabel { icon: Image {url: bind model.selectedImageUrl} } } } }] } visible: true }
示例中的粗体代码是与splitPane相关的。正如你见到的那样,splitPane的orientation
属性被赋值为HORIZONTAL
。通过将一组SplitView对象赋值给content
属性,我们便为splitPane添加了组件。每个SplitView具有两个属性:weight
和content
。weight
属性决定了当分割窗体被调整大小时应有多少的空间分配给它(SplitView)。而你为content
的赋值将显示在splitPane中。
RadioButton(单选按钮)、RadioButtonMenuItem(单选按钮菜单项)、ToggleButton(开关按钮)和ButtonGroup(按钮分组)
JavaFX RadioButton类封装了Swing的JRadioButton组件。RadioButtonMenuItem类封装了Swing的JRadioButtonMenuItem组件。而ToggleButton类封装了Swing的JToggleButton组件。
在这些组件之间、以及它们与单项选择的列表框(ListBox)、下拉列表框(ComboBox)、页签面板(TabbedPane)、卡片面板(CardPanel)之间具有很强的相似性,即所有这些组件都提供了从选项列表中挑选其中一个选项的能力。
RadioButtons、RadioButtonMenuItems和ToggleButtons都与一个使用JavaFX ButtonGroup类的选项列表相关,而JavaFX ButtonGroup类与Swing ButtonGroup相对应。与Swing类不同的是,JavaFX ButtonGroup提供了一个和单选列表框相似的选择模型。ButtonGroup的selection
属性保存了一个用来控制被选择按钮的数字索引。如果你为此属性赋值,那么索引值为此值的按钮将被选择,而其它按钮也将取消选择。如果你选择某个按钮,这个按钮的索引将隐含地被赋值给ButtonGroup的selection
属性。
为了演示,让我们对前面一节的示例进行扩展,使其包含ButtonGroup。首先在菜单中放置一组RadioButtonMenuItems作为菜单项。接着,将一组RadioButtons放置到一个四列的GridPanel中。最后,在一个单列GridPanel中放置一组 ToggleButtons。每个按钮的分组都将像前一节示例中的ListBox的cells
那样从同一个模型中“投影”出来,并且它们的ButtonGroup的selection
属性也像ListBox的selection
属性那样被绑定到同一个模型属性。你在ListBox、Menu、RadioButton、ToggleButton中作出选择都将影响到其关联对象。
听起来难免比较抽象,还是让我们看下面的示例程序初始化界面和源代码吧:
class ExampleModel { attribute imageFiles: String*; attribute selectedImageIndex: Number; attribute selectedImageUrl: String; } var model = ExampleModel { var: self imageFiles: ["Bird.gif", "Cat.gif", "Dog.gif", "Rabbit.gif", "Pig.gif", "dukeWaveRed.gif", "kathyCosmo.gif", "lainesTongue.gif", "left.gif", "middle.gif", "right.gif", "stickerface.gif"] selectedImageUrl: bind "http://java.sun.com/docs/books/tutorial/uiswing/examples/components/SplitPaneDemoProject/src/components/images/{self.imageFiles[self.selectedImageIndex]}" }; Frame { menubar: MenuBar { menus: Menu { text: "File" mnemonic: F var buttonGroup = ButtonGroup { selection: bind model.selectedImageIndex } items: foreach (imageName in model.imageFiles) RadioButtonMenuItem { buttonGroup: buttonGroup text: imageName } } } title: "RadioButton/ToggleButton Example" height: 400 width: 500 content: BorderPanel { top: GridPanel { rows: sizeof model.imageFiles / 4 columns: sizeof model.imageFiles % 4 var buttonGroup = ButtonGroup { selection: bind model.selectedImageIndex } cells: foreach (imageName in model.imageFiles) RadioButton { buttonGroup: buttonGroup text: imageName } } right: GridPanel { rows: sizeof model.imageFiles columns: 1 var buttonGroup = ButtonGroup { selection: bind model.selectedImageIndex } cells: foreach (imageName in model.imageFiles) ToggleButton { buttonGroup: buttonGroup text: imageName } } center: SplitPane { orientation: HORIZONTAL content: [SplitView { weight: 0.30 content: ListBox { selection: bind model.selectedImageIndex cells: bind foreach (imageName in model.imageFiles) ListCell { text: bind imageName } } }, SplitView { weight: 0.70 content: ScrollPane { view: CenterPanel { background: white content: SimpleLabel { icon: Image {url: bind model.selectedImageUrl} } } } }] } } visible: true }
示例中的橙色代码是与ButtonGroups相关的。正如你所见到的,我们通过将button的buttonGroup
属性设置为指定的ButtonGroup,将一组button添加到了buttonGroup中。
如果点击在ListBox中的“Pig.gif”(或者选择“Pig.gif”的RadioButton或ToggleButton),将出现下面的变化:
如果打开菜单,你将看到它也发生了同样的变化:
ComboBoxes(下列选择框)
JavaFX ComboBox与Swing JComboBox组件相关。我们将在上一个示例中添加两个组件来演示如何使用ComboBox。示例代码如下:
class ExampleModel { attribute imageFiles: String*; attribute selectedImageIndex: Number; attribute selectedImageUrl: String; } var model = ExampleModel { var: self imageFiles: ["Bird.gif", "Cat.gif", "Dog.gif", "Rabbit.gif", "Pig.gif", "dukeWaveRed.gif", "kathyCosmo.gif", "lainesTongue.gif", "left.gif", "middle.gif", "right.gif", "stickerface.gif"] selectedImageUrl: bind "http://java.sun.com/docs/books/tutorial/uiswing/examples/components/SplitPaneDemoProject/src/components/images/{self.imageFiles[self.selectedImageIndex]}" }; Frame { menubar: MenuBar { menus: Menu { text: "File" mnemonic: F var buttonGroup = ButtonGroup { selection: bind model.selectedImageIndex } function makeRadioButton(buttonGroup, imageName) { return RadioButtonMenuItem { buttonGroup: buttonGroup text: imageName }; } items: foreach (imageName in model.imageFiles) makeRadioButton(buttonGroup, imageName) } } title: "RadioButton/ToggleButton/ComboBox Example" height: 400 width: 500 content: BorderPanel { top: GridPanel { rows: sizeof model.imageFiles / 4 columns: sizeof model.imageFiles % 4 var buttonGroup = ButtonGroup { selection: bind model.selectedImageIndex } cells: foreach (imageName in model.imageFiles) RadioButton { buttonGroup: buttonGroup text: imageName } } right: GridPanel { rows: sizeof model.imageFiles columns: 1 var buttonGroup = ButtonGroup { selection: bind model.selectedImageIndex } cells: foreach (imageName in model.imageFiles) ToggleButton { buttonGroup: buttonGroup text: imageName } } center: SplitPane { orientation: HORIZONTAL content: [SplitView { weight: 0.30 content: ListBox { selection: bind model.selectedImageIndex cells: bind foreach (imageName in model.imageFiles) ListCell { text: bind imageName } } }, SplitView { weight: 0.70 content: BorderPanel { top: ComboBox { selection: bind model.selectedImageIndex cells: bind foreach (imageName in model.imageFiles) ComboBoxCell { text: bind imageName } } center: ScrollPane { view: CenterPanel { background: white content: SimpleLabel { icon: Image {url: bind model.selectedImageUrl} } } } } }] } bottom: FlowPanel { alignment: LEADING content: ComboBox { selection: bind model.selectedImageIndex cells: bind foreach (imageName in model.imageFiles) ComboBoxCell { text: bind "<html> <table> <tr> <td> <img src='http://java.sun.com/docs/books/tutorial/uiswing/examples/components/SplitPaneDemoProject/src/components/images/{imageName}' height='32' width='32'></img> </td> <td> {imageName} </td> </tr> </table> </html>" } } } } visible: true }
上例中有关ComboBox的代码表示为粗体。我们通过将一组ComboBoxCell对象赋值给ComboBox的cells
属性,来为ComboBox赋予下拉列表项。ComboBoxCell
的text
属性决定了下拉列表单元的外观。当然,你还可以建立风格化文本或者图片作为内容的下拉列表项:将包含这些风格化文本或者图片的HTML代码赋值给text
属性(就像示例中左下方的ComboBox展示的那样)。ComboBox
的selection
属性决定了哪个列表项被选择。将一个整数(从0开始)索引赋值到这个属性,将使这个索引对应位置的列表项被选中。在用户选择列表项的同时,被选择的列表项的索引值将被隐含地赋值给selection
属性。在上例中的两个ComboBox中,selection
属性都被绑定到同一个模型属性。同样的,ComboBox
中的列表项(cells)也从同一个模型属性“投影”出来。作为结果,你能够通过ComboBox、listbox、button groups来选择被显示的图片。
如果打开第二个ComboBox,示例程序将变为:
Trees(树形)
JavaFX Tree类提供了一个封装了Swing JTree组件的声明式接口。首先,让我们一起通过建立一个没有动态行为的简单示例来了解Tree
的用法:
Frame { height: 400 width: 300 content: Tree { root: TreeCell { text: "Tree" cells: [TreeCell { text: "colors" cells: [TreeCell { text: "<html><font color='blue'>blue</font></html>" }, TreeCell { text: "<html><font color='red'>red</font></html>" }, TreeCell { text: "<html><font color='green'>green</font></html>" }] }, TreeCell { text: "food" cells: [TreeCell { text: "hot dogs" }, TreeCell { text: "pizza" }, TreeCell { text: "ravioli" }] }] } } visible: true }
上面的代码运行结果如下:
为了构造Tree
,我们将一个返回TreeCell对象的表达式被赋值给它的root
(根)属性。TreeCell代表了Tree的一行。你可以将一组TreeCell对象赋值给它的cells
属性来描述某个TreeCell的子单元(child cells)。另外,每个TreeCell都具有一个决定其外观的text
属性。你也可以将HTML代码赋值给text
属性来建立一个风格化文本或者图片作为内容的TreeCell。
接下来,让我们重建一个Swing教程中的示例(GenealogyExample),它显示了某人的后代或者父辈情况。
当我们运行这个示例后,程序将显示以下:
如果在Tree中选择某人并点击某个单选按钮中,那么这个被选择的人将成为Tree的根。Tree将根据选择将此人的父辈或者后代显示在其子节点中。
下面是示例代码。其中与Tree有关的代码以粗体显示。TreeCell
具有一个selected
属性(Boolean类型),它决定了自身是否被选择。与此同时,如果你通过程序将一个Boolean值赋值给这个属性的话,相应的TreeCell
将依照selected
属性值被选择或者取消选择。
在示例中,由于家谱是一个递归的数据结构,于是我们需要使用一个能够被递归调用的表达式来定义TreeCell的cells
属性。请注意:在这里我们使用了bind lazy
操作符而不是在初始化cells
属性中使用的bind
,因为它标识了lazy式求值。这意味着直到它左侧表达式第一次被访问到时,其右侧表达式才被求值。因此,对descendantTree()
和ancestorTree()
函数的递归调用并非马上执行,而是直到你展开Tree中的某个节点,Tree要求访问子节点的cells
时才被执行。
class GeneologyModel { attribute people: Person*; attribute selectedPerson: Person; attribute showDescendants: Boolean; } class Person { attribute selected: Boolean; attribute father: Person; attribute mother: Person; attribute children: Person*; attribute name: String; } // By defining these triggers I can populate the model // by just assigning the mother and father attributes of a Person trigger on Person.father = father { insert this into father.children; } trigger on Person.mother = mother { insert this into mother.children; } // Create and populate the model var model = GeneologyModel { var jack = Person { selected: true name: "Jack (great-granddaddy)" } var jean = Person { name: "Jean (great-granny)" } var albert = Person { name: "Albert (great-granddaddy)" } var rae = Person { name: "Rae (great-granny)" } var paul = Person { name: "Paul (great-granddaddy)" } var josie = Person { name: "Josie (great-granny)" } var peter = Person { father: jack mother: jean name: "Peter (grandpa)" } var zoe = Person { father: jack mother: jean name: "Zoe (grandma)" } var simon = Person { father: jack mother: jean name: "Simon (grandpa)" } var james = Person { father: jack mother: jean name: "James (grandpa)" } var bertha = Person { father: albert mother: rae name: "Bertha (grandma)" } var veronica = Person { father: albert mother: rae name: "Veronica (grandma)" } var anne = Person { father: albert mother: rae name: "Anne (grandma)" } var renee = Person { father: albert mother: rae name: "Renee (grandma)" } var joseph = Person { father: paul mother: josie name: "Joseph (grandpa)" } var isabelle = Person { father: simon mother: veronica name: "Isabelle (mom)" } var frank = Person { father: simon mother: veronica name: "Frank (dad)" } var louis = Person { father: simon mother: veronica name: "Louis (dad)" } var laurence = Person { father: james mother: bertha name: "Laurence (dad)" } var valerie = Person { father: james mother: bertha name: "Valerie (mom)" } var marie = Person { father: james mother: bertha name: "Marie (mom)" } var helen = Person { father: joseph mother: renee name: "Helen (mom)" } var mark = Person { father: joseph mother: renee name: "Mark (dad)" } var oliver = Person { father: joseph mother: renee name: "Oliver (dad)" } var clement = Person { father: laurence mother: helen name: "Clement (boy)" } var colin = Person { father: laurence mother: helen name: "Colin (boy)" } people: [jack, jean, albert, rae, paul, josie, peter, zoe, simon, james, bertha, anne, renee, joseph, frank, louis, laurence, valerie, marie, helen, mark, oliver, clement, colin] selectedPerson: jack showDescendants: true }; // Tree generation functions: operation geneologyTree(p:Person, showDescendants:Boolean) { if (showDescendants) { return descendantTree(p); } else { return ancestorTree(p); } } function descendantTree(p:Person) { return TreeCell { selected: bind p.selected text: bind p.name cells: bind lazy foreach (c in p.children) descendantTree(c) }; } function ancestorTree(p:Person) { return TreeCell { selected: bind p.selected text: bind p.name cells: bind lazy foreach (a in [p.father, p.mother]) ancestorTree(a) }; } Frame { title: "Genology Example" height: 300 width: 300 content: BorderPanel { top: FlowPanel { var buttonGroup = new ButtonGroup() content: [RadioButton { buttonGroup: buttonGroup text: "Show Descendants" selected: model.showDescendants onChange: operation(newValue:Boolean) { if (newValue) { var selectedPerson = model.people[selected]; if (selectedPerson <> null) { model.selectedPerson = selectedPerson; } model.showDescendants = true; } } }, RadioButton { buttonGroup: buttonGroup text: "Show Ancestors" onChange: operation(newValue:Boolean) { if (newValue) { var selectedPerson = model.people[selected]; if (selectedPerson <> null) { model.selectedPerson = selectedPerson; } model.showDescendants = false; } } }] } center: Tree { showRootHandles: true root: bind geneologyTree(model.selectedPerson, model.showDescendants) } } visible: true }
当所有节点都被展开并且选择“Clement”节点时,Tree将形如下图:
在点击“Show Ancestors”后,Clement将成为根,他的双亲将显示在他的下面:
Tables(表格)
JavaFX Table类封装了Swing JTable组件。我们在这里通过对Swing教程示例(SimpleTableDemo)进行微小的修改来示范如何使用Table
:
创建示例表格的代码如下:
class Person { attribute firstName: String; attribute lastName: String; attribute sport: String; attribute numYears: Number; attribute vegetarian: Boolean; attribute selected: Boolean; } class TableDemoModel { attribute people: Person*; } var model = TableDemoModel { people: [Person { firstName: "Mary" lastName: "Campione" sport: "Snowboarding" numYears: 5 vegetarian: false }, Person { firstName: "Alison" lastName: "Huml" sport: "Rowing" numYears: 3 vegetarian: true }, Person { firstName: "Kathy" lastName: "Walrath" sport: "Knitting" numYears: 2 vegetarian: false }, Person { firstName: "Sharon" lastName: "Zakhour" sport: "Speed reading" numYears: 20 vegetarian: true }, Person { firstName: "Philip" lastName: "Milne" sport: "Pool" numYears: 10 vegetarian: false }] }; Frame { height: 120 width: 500 title: "SimpleTableDemo" content: Table { columns: [TableColumn { text: "First Name" }, TableColumn { text: "Last Name" }, TableColumn { text: "Sport" width: 100 }, TableColumn { text: "# of Years" alignment: TRAILING }, TableColumn { text: "Vegetarian" alignment: CENTER }] cells: bind foreach (p in model.people) [TableCell { text:bind p.firstName selected: bind p.selected }, TableCell { text:bind p.lastName }, TableCell { text: bind p.sport }, TableCell { text: bind "{p.numYears}" }, TableCell { text: bind if p.vegetarian then "Yes" else "No" toolTipText: bind "{p.firstName} {p.lastName} {if not p.vegetarian then "eats" else "does not eat"} meat" }] } visible: true }
上例与table相关的代码表示为粗体。为了建立Table
,我们需要将一组TableColumn对象赋值给Table
的columns
属性,并把一组TableCell对象赋值给它的cells
属性。在上例中,由于我们把五个TableColumn
赋值给了Table
,所以table中显示了五列。同理,由于我们为每个person赋值了五个TableCell
(分别对应person的5个属性),从而使每个person的信息正好完整地显示在每一行。TableColumn
的text
属性决定了列头部单元格的内容。它的width
和alignment
属性决定了该列的首选宽度和水平对齐。
由于JavaFX Table
是一个ScrollableWidget部件,因此你无需给它添加滑动面板。
Text Components(文本组件)
我们在这里通过对Swing教程示例进行微小的修改来示范如何使用文本组件:
JavaFX文本组件与Swing组件之间的对应关系如下:
JavaFX部件 | Swing组件 |
---|---|
TextField |
JFormattedTextField |
PasswordField |
JPasswordField |
TextArea |
JTextArea |
EditorPane |
JEditorPane |
TextPane |
JTextPane |
class TextSamplerModel { attribute textFieldInput: String?; } var model = TextSamplerModel { }; Frame { title: "Text Sampler" visible: true content: SplitPane { orientation: HORIZONTAL content: [SplitView { weight: 0.5 content: BorderPanel { top: GridBagPanel { border: CompoundBorder { borders: [TitledBorder { title: "Text Fields" }, EmptyBorder { top: 5 left: 5 bottom: 5 right: 5 }] } cells: [GridCell { anchor: EAST gridx: 0 gridy: 0 content: SimpleLabel { text: "TextField: " } }, GridCell { anchor: WEST fill: HORIZONTAL weightx: 1 gridx: 1 gridy: 0 content: TextField { action: operation(value:String) { model.textFieldInput = value; } } }, GridCell { anchor: EAST gridx: 0 gridy: 1 insets: {top: 2} content: SimpleLabel { text: "PasswordField: " } }, GridCell { gridx: 1 gridy: 1 fill: HORIZONTAL weightx: 1 insets: {top: 2} content: PasswordField { action: operation(value:String) { model.textFieldInput = value; } } }, GridCell { anchor: WEST weightx: 1.0 gridx: 0 gridy: 2 gridwidth: 2 fill: HORIZONTAL content: SimpleLabel { border: EmptyBorder { top: 10 } text: bind if model.textFieldInput == null then "Type text and then Return in a field" else "You typed /"{model.textFieldInput}/"" } }] } center: BorderPanel { border: CompoundBorder { borders: [TitledBorder { title: "Plain Text" }, EmptyBorder { top: 5 left: 5 bottom: 5 right: 5 }] } center: TextArea { font: new Font("Serif", Font.ITALIC, 16) lineWrap: true wrapStyleWord: true text: "This is an editable TextArea that has been initialized with its text attribute. A text area is a /"plain/" text component, which means that although it can display text in any font, all of the text is in the same font" } } } }, SplitView { weight: 0.5 content: SplitPane { border: CompoundBorder { borders: [TitledBorder { title: "Styled Text" }, EmptyBorder { top: 5 left: 5 bottom: 5 right: 5 }] } orientation: VERTICAL content: [SplitView { weight: 0.5 content: EditorPane { opaque: true preferredSize: {height: 250 width: 250} contentType: HTML editable: false text: "<html> <body> <img src='http://java.sun.com/docs/books/tutorial/uiswing/examples/components/SplitPaneDemoProject/src/components/images/dukeWaveRed.gif' width='64' height='64'> This is an uneditable <code>EditorPane</code>, which was <em>initialized</em> with <strong>HTML</strong> text <font size='-2'>but not from</font> a <font size='+2'>URL</font>. <p> An editor pane uses specialized editor kits to read, write, display, and edit text of different formats. </p> <p> The Swing text package includes editor kits for plain text, HTML, and RTF. </p> <p> You can also develop custom editor kits for other formats. </p> </body></html>" } }, SplitView { weight: 0.5 content: TextPane { preferredSize: {height: 250 width: 250} editable: true content: ["This is an editable TextPane, another styled text component, which supports embedded icons.../n", Image {url: "http://java.sun.com/docs/books/tutorial/uiswing/components/example-swing/images/Pig.gif"}, "/n...and embedded components.../n", Button { contentAreaFilled: false icon: Image {url: "http://java.sun.com/docs/books/tutorial/uiswing/components/example-swing/images/sound.gif"} }, "/nTextPane is a subclass of EditorPane that uses a StyledEditorKit and StyledDocument,/n and provides cover methods for interacting with those objects."] } }] } }] } }
Spinners(微调控制器)和Sliders(滑动条)
JavaFX Spinner和Slider类与Swing组件之间对应关系如下:
JavaFX部件 | Swing组件 |
---|---|
Spinner |
JSpinner |
让我们通过建立一个展示摄氏和华氏之间换算关系的、简单的应用来演示如何使用它们吧:
class Temp { attribute celsius: Number; attribute farenheit: Number; attribute showCelsius: Boolean; attribute showFarenheit: Boolean; } trigger on Temp.celsius = value { farenheit = (9/5 * celsius + 32); } trigger on Temp.farenheit = value { celsius = ((farenheit - 32) * 5/9); } Frame { var temp = Temp { farenheit: 32 showFarenheit: true showCelsius: true } height: 300 width: 400 title: "Temperature" content: Box { orientation: VERTICAL content: [FlowPanel { content: [CheckBox { text: "Show Celsius" selected: bind temp.showCelsius }, RigidArea { width: 20 }, CheckBox { text: "Show Farenheit" selected: bind temp.showFarenheit }] }, Slider { visible: bind temp.showCelsius min: -100 max: 100 border: TitledBorder {title: "Celsius"} value: bind temp.celsius minorTickSpacing: 5 majorTickSpacing: 10 paintTicks: true paintLabels: true labels: [SliderLabel { value: 0 label: SimpleLabel { text: "0" } }, SliderLabel { value: 100 label: SimpleLabel { text: "100" } }] }, Slider { visible: bind temp.showFarenheit border: TitledBorder {title: "Farenheit"} min: -148 max: 212 paintTicks: true minorTickSpacing: 5 majorTickSpacing: 10 value: bind temp.farenheit paintLabels: true labels: [SliderLabel { value: 0 label: SimpleLabel { text: "0" } }, SliderLabel { value: 32 label: SimpleLabel { text: "32" } }, SliderLabel { value: 212 label: SimpleLabel { text: "212" } }] }, FlowPanel { alignment: LEADING content: [SimpleLabel { visible: bind temp.showCelsius alignmentX: 1 text: "Celsius:" }, Spinner { visible: bind temp.showCelsius min: -100 max: 100 value: bind temp.celsius }, RigidArea { width: 20 }, SimpleLabel { visible: bind temp.showFarenheit alignmentX: 1 text: "Farenheit:" }, Spinner { visible: bind temp.showFarenheit min: -148 max: 212 value: bind temp.farenheit }] }] } visible: true }
示例代码中与Spinner和Slider相关的部分以粗体表示。Spinner和Slider都具有min
和max
属性,这些属性决定了它们的取值范围,而value
属性则是其当前取值。
在上面示例采用摄氏温度的Spinner和Slider中,它们的value
属性绑定为模型的celsius
属性。而在采用华氏温度的Spinner和Slider中,value
属性绑定为模型的farenheit
属性。并且在模型的celsius
和farenheit
属性上定义了触发器,无论这两个属性值中哪个发生变化,都将相应地更新另一个属性。因此,无论移动slider或者修改spinner,相关的数据都将发生变化。
例如,如果我们将温度设置为88华氏度:
Slider还具有一些决定如何显示浮标行的属性。另外,通过将一组SliderLabel赋值给Slider的labels
,我们可以为特定的数值加标签。在本例中,冰点(freezing)和沸点(boiling)、0华氏度就是这样做的。
相关资源
关于译者
cleverpig:BJUG成员,Java社区——Matrix与Java共舞负责人之一,曾参与Buffalo的文档工作、Fielding的《Architectural Styles and the Design of Network-based Software Architectures》中文化研究(还要感谢Tin、Nicholas的大力相助),关注一切新技术,业余时间研究Guru并准备得道升天,但是苦于没有得法,目前还在苦苦追寻……
Tin:中文名“田乐”,BJUG成员,现就职于Sina。曾经在Java Web项目中担任软件架构师和Web设计,注重使用轻量级解决方案和敏捷方法。目前主要做基于Javascript的RIA开发,喜欢研究新技术并进行思考,业余时间继续关注Java和Ruby,并与朋友一起翻译Selenium文档。
译文Feedback
欢迎一切友人的Feedback!!
加入中国Openjfx用户讨论组:China Openjfx User Group