Translated by Liu Dan (
cleverpig) and Tian Le (Tin)
本文檔將討論在JavaFX語言中可用的各種GUI組件,并提供示例代碼,討論JavaFX組件與Swing GUI組件相比的差異。
比較聲明式語法和過程式語法
正像我們在前一節所看到的,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)。
top
增加動態行為
目前,我們編寫的“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
到一個增量式求值表達式。這種表達式將使用條件邏輯、迭代器、選擇等產生任意復雜度的動態內容,而動態內容的表達式仍然保有其可聲明性。
top
學習更多的JavaFX GUI組件
本節將討論在JavaFX語言中可用的多種不同GUI組件,并給出展示其用途的示例程序,通過比較JavaFX組件與Swing GUI組件的方式討論它們的差異。
top
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 Border類和其相應的Swing Border類的對應表格:
top
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
)。
這里唯一的菜單項是一個text屬性和值為X
的mnemonic
屬性。而它的accelerator
屬性也被賦值了。注意:在聲明中的類型名Accelerator
被省略了。這在JavaFX中是允許的。如果類型名沒有提供,該屬性的靜態類型將被使用,在本例中accelerator
屬性的靜態類型是Accelerator。另外,accelerator
的modifier
和keyStroke
屬性都使用枚舉值進行了初始化。
最后,MenuItem
的action
屬性是function
類型的(即指它的值為function,而非對象)。在本例中,action
屬性是一個內聯(inline)的operation
,它調用了一些Java代碼。
top
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)(公式中涉及到其它單元格中的數值)的單元格;當你在其它單元格中輸入數據時,那些包含此公式的表格值將被自
動更新。
top
在HTML中的圖片
JavaFX的Label類實際上封裝了一個特定的JEditorPane類,后者使用了一個支持利用Java類裝載器(Java class loader)從JAR文件裝載圖片的、共享的圖片緩存。這樣就可以使用HTML <img>
元素引用那些與應用打包在一起的圖片資源。
超鏈接
Label類也支持HTML超鏈接:將指定的URL作為HTML <a>
元素的href
屬性嵌入到label。
我們使用JavaFX #
操作符來建立這樣的URL。#
操作符生成一個字符串化對象引用(stringified object reference),此引用指向它的操作數,而操作數又可以使用JavaFX ?
操作符間接引用??赡苈犉饋碛行埳啵覀兛梢栽谙旅娴闹v解中慢慢理解這段話的含義。例如:
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。
運行程序,顯示如下:
在點擊超鏈接兩次后,程序顯示變化為:
top
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
也被相應地賦值。
top
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文件一起打包的圖片資源。
top
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:
top
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”按鈕:
top
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中。
top
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),將出現下面的變化:
如果打開菜單,你將看到它也發生了同樣的變化:
top
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,示例程序將變為:
top
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將成為根,他的雙親將顯示在他的下面:
top
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部件,因此你無需給它添加滑動面板。
top
Text Components(文本組件)
我們在這里通過對Swing教程示例進行微小的修改來示范如何使用文本組件:
JavaFX文本組件與Swing組件之間的對應關系如下:
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."]
}
}]
}
}]
}
}
top
Spinners(微調控制器)和Sliders(滑動條)
JavaFX Spinner和Slider類與Swing組件之間對應關系如下:
讓我們通過建立一個展示攝氏和華氏之間換算關系的、簡單的應用來演示如何使用它們吧:
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華氏度就是這樣做的。
top
相關資源
top
關于譯者
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文檔。
top
凡是有該標志的文章,都是該blog博主Caoer(草兒)原創,凡是索引、收藏
、轉載請注明來處和原文作者。非常感謝。