JavaFX Script语言教程「建议收藏」

JavaFX Script语言教程「建议收藏」JavaFXScript™(下文中称为JavaFX)语言是一种声明式的静态类型编程语言。它具有第一级函数(first-classfunctions)、声明式的语法、列表推导(list-comprehensions)及基于依赖关系的增量式求值(incrementaldependency-basedevaluation)等特征。JavaFX脚本式语言特别适用于Java2Dswi…

大家好,又见面了,我是你们的朋友全栈君。

JavaFX Script™ (下文中称为JavaFX)语言是一种声明式的静态类型编程语言。它具有第一级函数(first-class functions)、声明式的语法、列表推导(list-comprehensions)及基于依赖关系的增量式求值(incremental dependency-based evaluation)等特征。JavaFX 脚本式语言特别适用于Java2D swing GUI组件,它能够简单地创建图形界面。

译者注:第一级函数指函数被当作对象对待,可以在运行时赋值、传递和返回。详见wikipedia上的解释

译者注:列表推导指一种在函数语言中的表达式,它表示了在一个或者多个列表的成员(被选择的)进行某种操作的结果。它被称为“syntactic sugar”,即为开发者提供了便捷的多种函数的应用组合。详见FOLDC对list comprehension的解释

在本教程中,你将使用NetBeans™ IDE 5.5(下面简称为IDE)通过建立简单的“Hello World”应用来学习如何使用JavaFX语言。本文也讨论在JavaFX语言中可用的各种GUI组件,并提供示例代码,讨论JavaFX组件与 Swing GUI组件相比的差异。

为了让NetBeans初级用户获得更多关于使用NetBeans IDE的信息,这里提供了在 NetBeans网站上的支持和文档

前提条件

为了更好地理解教程,你需要安装NetBeans IDE,并熟知此IDE的一些基本部分。如果你希望获得对IDE基本功能的了解,请参考IDE在线帮助中的基本功能部分。

当然,你也要基本熟悉Java编程语言,尤其是在Swing和Java2D方面。

系统要求

这里假设你的系统符合在NetBeans IDE 5.5Release Notes中对运行IDE而提出的系统要求

本教程所需软件

在你开始本教程之前,你需要安装如下软件:

内容

建立一个JavaFX项目

首先,你需要按照下面的步骤来建立你的第一个JavaFX项目。

  1. 在IDE主菜单中选择File > New Project。
  2. 在New Project wizard中,在Categories(分类)窗口中选择General,接着在Projects窗口中选择Java Application。

    New project window

  3. 点击Next。
  4. 在Project Name栏中输入JavaFXapp
  5. 在Project Location处,点击Browse来为项目指定保存位置。
  6. 取消对Set as Main Project和Create Main Class的选择。

    Image for specifying name and location

  7. 点击Finish。
    JavaFXapp项目将被建立并显示在项目窗体中,如下图所示:

    Image with new JavaFXapp project displayed in Projects window

top

创建第一个JavaFX程序

现在,你可以在IDE中建立你的第一个JavaFX HelloWorld程序。

  1. 右键点击JavaFXapp >Source Packages节点,选择New > File/Folder.
    New File wizard将出现。

    Creating a new JavaFX file for the first time.

  2. 在Categories窗体中选择Other,然后在File Types窗体中选择JavaFX File。点击Next.

    注意: 在完成本次对JavaFX文件的初始创建后,当你从项目窗口的main project节点或者Source Packages节点通过右键菜单点击New时,JavaFX文件类型应该出现在可用文件类型列表中。

    Choosing JavaFX file type in New File dialog

  3. 在新建JavaFX文件向导的Project Name和Project Location页面,在File Name栏中输入HelloWord,在Folder栏中输入src
    值得注意的是:Created File输入栏中的内容会随着文件名而更新。

    New JavaFX filename and location

  4. 点击Finish。
    这样,HelloWorld.fx节点便被添加到了项目窗口,HelloWorld.fx文件将在Source editor中打开.

    HelloWorld.fx created

  5. 复制并粘贴如下代码到HelloWorld.fx中。
            
    import javafx.ui.*;

    Frame {
    title: "Hello World JavaFX"
    width: 200
    height: 50
    content: Label {
    text: "Hello World"
    }
    visible: true
    }

top

运行第一个JavaFX程序

现在让我们使用IDE运行第一个JavaFX程序。

  1. 从项目窗口的JavaFXapp节点上点击右键选择Properties。

    edit project properties

  2. 在Categories窗体中选择Run。
  3. 在Arguments(参数)栏中输入HelloWorld,点击OK。

    Edit the Run properties of the JavaFXapp project

  4. 回到项目窗口的JavaFXapp节点上点击右键选择Run Project。

    Run the JavaFXapp project

    运行的程序将显示在如下窗口:

    First HelloWorld window

庆贺一下吧!到此为止,你已经完成了第一个JavaFX程序!

top

比较声明式语法和过程式语法

正像我们在前一节所看到的,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程序,它和前面的代码具有相同的效果。

下面列举出在声明式方法和过程式方法中实际发生了什么:

  1. 调用Frame类构造方法建立新的Frame。
  2. 给Frame的title、width、visible和content属性赋值。
  3. 在赋值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
};

此程序运行结果如下图:

HelloWorld window #2

如果model对象的saying属性被修改为:

        model.saying = "Goodbye Cruel World!";

那么视图将自动随之改变,如下图:

view changes to goodbye

这里值得注意的是:在上面的示例中,通过将JavaFX bind操作符应用于modelsaying属性,从而实现了对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
};

此程序运行结果如下图:

JavaFX Script语言教程「建议收藏」

如果你在TextField中输入其它的文字,并按下Enter,那么窗体的标题将相应地改变:

JavaFX Script语言教程「建议收藏」

在本例中,TextField的value属性被更新为用户输入的文字(通过TextField类实现)。而与此同时,modelsaying属性也被更新为相同值。因为赋值给窗体的title属性的表达式依赖于modelsaying属性,因此modelsaying属性的变化导致了表达式被重新求值、窗体的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配置选项对应的属性。这里提供了一个使用EmptyBorderGridPanel的简单示例。正如你所期待的那样,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
};

程序运行结果如下图:

JavaFX Script语言教程「建议收藏」

在点击按钮多次后,将出现如下效果:

JavaFX Script语言教程「建议收藏」

需要注意的:
Button
action和mnemonic(助记码)属性的解释将在下面给出..

在本例中,GridPanel被配置为单列、双行、并在行间放置10个像素的垂直间隔,而这些工作仅仅通过为columnsrowsvgap属性赋值来完成。如果你希望在列间建立一个间隔的话,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

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的textmnemonicitems的属性值。

正如你所认为的那样,text属性的类型是Stringmnemonic属性却是KeyStroke类型的。它的值FKeyStroke类的一个枚举值。在JavaFX的属性初始化程序上下文中,该属性的静态类(和在Java类中的静态字段相似)的枚举值能够在没有类型名限制的情况下被访问(而在别处,你不得不把F写为F:KeyStroke)。

这里唯一的菜单项是一个MenuItem类实例,它具有值为”Exit”的text属性和值为Xmnemonic属性。而它的accelerator属性也被赋值了。注意:在声明中的类型名Accelerator被省略了。这在JavaFX中是允许的。如果类型名没有提供,该属性的静态类型将被使用,在本例中accelerator属性的静态类型是Accelerator。另外,acceleratormodifierkeyStroke属性都使用枚举值进行了初始化。

最后,MenuItemaction属性是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
}

程序执行结果如下图:

JavaFX Script语言教程「建议收藏」

如果你通过编程删去购物项(cart items):

       delete cart.items;

程序将显示如下结果:

JavaFX Script语言教程「建议收藏」

在上面的示例中,嵌入的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 ?操作符间接引用。可能听起来有些饶舌,我们可以在下面的讲解中慢慢理解这段话的含义。例如:

        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。

运行程序,显示如下:

JavaFX Script语言教程「建议收藏」

在点击超链接两次后,程序显示变化为:

JavaFX Script语言教程「建议收藏」

top

Group Panel(分组面板),Simple Label(简单标签)和TextField(文本栏)

本节将使用一个非常简单的示例讲解JavaFX的Group Panel、Simple Label和TextField类。

JavaFX GroupPanel类封装了Java.net上的GroupLayout类。GroupLayout是一个强大的布局管理器,它将面板的内容表现为平行的水平、垂直分组的集合。在JavaFX中,这些平行的分组被简单地称为RowColumn。当你声明一个GroupPanel时,你也可以针对每个水平、垂直的组件分组来声明其Row和Column对象。然后,在添加组件时便可以将相应的Row和Column对象赋值给组件的rowcolumn属性。GroupPanel按照当前外观风格准则在组件之间自动地插入间隔。通过声明Row或者Column对象的alignmentresizable属性,你能够控制在行或者列中的组件对齐和行或者列是否可调整大小。

JavaFX TextField类封装了Swing的JFormattedTextField。它具有一个value属性,无论在焦点位于此文本框或者移到其它组件时,只要用户按下Enter,该属性值都将被更新。通过将数字赋值给它的columns,你可以控制它的宽度。而通过赋值LEADINGCENTERTRAILING给它的horizontalAligment属性,你还可以控制它的水平对齐。TextField类具有两个值为函数的属性,它们允许你执行基于用户交互的行为:actiononChange。如果你将一个函数或者操作赋值给action属性,无论何时用户按下Enter键,此函数或者操作都会被调用。如果你将一个函数或者操作赋值给onChange属性,当文本栏的value发生变化时,这个的函数或者操作将被调用。

JavaFX SimpleLabel类封装了Swing的JLabel类。SimpleLabel与Label的不同之处在于它不支持超链接和首选大小(preferred size)。

下面显示了一个示例:

JavaFX Script语言教程「建议收藏」

下面是示例的代码:

        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的声明中,四个变量(firstNameRowlastNameRowlabelsColumnfieldsColumn)被声明为rows和columns属性,即将两行和两列分别赋值给GroupPanel的rowscolumns属性。最后,正如你所见到的,label和TextField被赋值为GroupPanel的elements属性。从label和TextField的声明可以看出,它们的rowcolumn也被相应地赋值。

top

Button(按钮)

JavaFX Button类封装了Swing的JButton组件。为了讲解如何使用Button,让我们从Swing教程中重建一个简单的示例:

JavaFX Script语言教程「建议收藏」

        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
}

点击左侧按钮后,程序出现以下变化:

JavaFX Script语言教程「建议收藏」

示例程序中共有三个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。TabPlacementTabLayout类定义了一些枚举值(TOP、LEFT、BOTTOM、RIGHT 、WRAP、SCROLL),我们可以将这些值相应地赋值给TabbedPane的tabPlacementtabLayout属性,从而能够控制tab的位置和布局。TabbedPane的selectedIndex属性表示了当前显示哪个tab。

程序运行如下图:

JavaFX Script语言教程「建议收藏」

值得注意的是:在示例中第四个tab被选择了,这是因为模型的selectedTab属性被初始化为3。在本例中,TabbedPane的selectedIndex属性也随之更新,因为它被绑定到了模型的selectedTab属性上。

对模型的tabPlacement属性作出如下修改:

        model.tabPlacement = BOTTOM;

tab将移动窗体的下方:

JavaFX Script语言教程「建议收藏」

对模型的selectedTab属性作出如下修改:

        model.selectedTab = 0;

这将导致第一个tab被选择:

JavaFX Script语言教程「建议收藏」

对模型的tabCount属性作出如下修改:

        model.tabCount = 20;

这将导致15个新建的tab被添加到TabbedPane:

JavaFX Script语言教程「建议收藏」

修改模型的tabLayout:

        model.tabLayout = SCROLL;

程序运行效果如下图:

JavaFX Script语言教程「建议收藏」

修改模型的tabCount:

        model.tabCount = 2;

结果只保留了前两个tab:

JavaFX Script语言教程「建议收藏」

top

ListBox(列表框)

JavaFX ListBox类提供了Swing JList组件的功能,但不同的是它提供了一个声明式接口。

为了演示其用法,我们还是从Swing教程的ListDemo出发重建一个简单示例:

在这个示例中,ListBox包含一个雇员姓名列表。如果点击“Fire”按钮,被选择的雇员将从列表中移除。如果在列表下方的文本框中输入新姓名,那么“Hire”按钮将变为可用状态。如果此时按下“Hire”按钮,这个新的姓名将被添加到列表。

这个示例也演示了如何使用BorderPanelFlowPanel。 一个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”按钮两次后,程序将改变为下图:

JavaFX Script语言教程「建议收藏」

如果在文本栏中输入新的名字,则程序将发生改变:

JavaFX Script语言教程「建议收藏」

接着点击“Hire”按钮:

JavaFX Script语言教程「建议收藏」

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具有两个属性:weightcontentweight属性决定了当分割窗体被调整大小时应有多少的空间分配给它(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中作出选择都将影响到其关联对象。

听起来难免比较抽象,还是让我们看下面的示例程序初始化界面和源代码吧:

JavaFX Script语言教程「建议收藏」

        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),将出现下面的变化:

JavaFX Script语言教程「建议收藏」

如果打开菜单,你将看到它也发生了同样的变化:

JavaFX Script语言教程「建议收藏」

top

ComboBoxes(下列选择框)

JavaFX ComboBox与Swing JComboBox组件相关。我们将在上一个示例中添加两个组件来演示如何使用ComboBox。示例代码如下:

JavaFX Script语言教程「建议收藏」

        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赋予下拉列表项。ComboBoxCelltext属性决定了下拉列表单元的外观。当然,你还可以建立风格化文本或者图片作为内容的下拉列表项:将包含这些风格化文本或者图片的HTML代码赋值给text属性(就像示例中左下方的ComboBox展示的那样)。ComboBoxselection属性决定了哪个列表项被选择。将一个整数(从0开始)索引赋值到这个属性,将使这个索引对应位置的列表项被选中。在用户选择列表项的同时,被选择的列表项的索引值将被隐含地赋值给selection属性。在上例中的两个ComboBox中,selection属性都被绑定到同一个模型属性。同样的,ComboBox中的列表项(cells)也从同一个模型属性“投影”出来。作为结果,你能够通过ComboBox、listbox、button groups来选择被显示的图片。

如果打开第二个ComboBox,示例程序将变为:

JavaFX Script语言教程「建议收藏」

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
}

上面的代码运行结果如下:

JavaFX Script语言教程「建议收藏」

为了构造Tree,我们将一个返回TreeCell对象的表达式被赋值给它的root(根)属性。TreeCell代表了Tree的一行。你可以将一组TreeCell对象赋值给它的cells属性来描述某个TreeCell的子单元(child cells)。另外,每个TreeCell都具有一个决定其外观的text属性。你也可以将HTML代码赋值给text属性来建立一个风格化文本或者图片作为内容的TreeCell。

接下来,让我们重建一个Swing教程中的示例(GenealogyExample),它显示了某人的后代或者父辈情况。

当我们运行这个示例后,程序将显示以下:

JavaFX Script语言教程「建议收藏」

如果在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将形如下图:

JavaFX Script语言教程「建议收藏」

在点击“Show Ancestors”后,Clement将成为根,他的双亲将显示在他的下面:

JavaFX Script语言教程「建议收藏」

top

Tables(表格)

JavaFX Table类封装了Swing JTable组件。我们在这里通过对Swing教程示例(SimpleTableDemo)进行微小的修改来示范如何使用Table

JavaFX Script语言教程「建议收藏」

创建示例表格的代码如下:

        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对象赋值给Tablecolumns属性,并把一组TableCell对象赋值给它的cells属性。在上例中,由于我们把五个TableColumn赋值给了Table,所以table中显示了五列。同理,由于我们为每个person赋值了五个TableCell(分别对应person的5个属性),从而使每个person的信息正好完整地显示在每一行。TableColumntext属性决定了列头部单元格的内容。它的widthalignment属性决定了该列的首选宽度和水平对齐。

由于JavaFX Table是一个ScrollableWidget部件,因此你无需给它添加滑动面板。

top

Text Components(文本组件)

我们在这里通过对Swing教程示例进行微小的修改来示范如何使用文本组件:

JavaFX Script语言教程「建议收藏」

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."]
}

}]
}
}]
}
}

top

Spinners(微调控制器)和Sliders(滑动条)

JavaFX SpinnerSlider类与Swing组件之间对应关系如下:

JavaFX部件 Swing组件
Spinner JSpinner
 

 

让我们通过建立一个展示摄氏和华氏之间换算关系的、简单的应用来演示如何使用它们吧:

JavaFX Script语言教程「建议收藏」

        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都具有minmax属性,这些属性决定了它们的取值范围,而value属性则是其当前取值。

在上面示例采用摄氏温度的Spinner和Slider中,它们的value属性绑定为模型的celsius属性。而在采用华氏温度的Spinner和Slider中,value属性绑定为模型的farenheit属性。并且在模型的celsiusfarenheit属性上定义了触发器,无论这两个属性值中哪个发生变化,都将相应地更新另一个属性。因此,无论移动slider或者修改spinner,相关的数据都将发生变化。

例如,如果我们将温度设置为88华氏度:

JavaFX Script语言教程「建议收藏」

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

译文Feedback

欢迎一切友人的Feedback!!

加入中国Openjfx用户讨论组:China Openjfx User Group

联系译者:cleverpig@matrix.org.cn

top

 

转载于:https://www.cnblogs.com/lanzhi/archive/2008/01/30/6470825.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/151812.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 关于禅道迁移服务器

    关于禅道迁移服务器

  • WebService接口测试

    WebService接口测试WebService的理解WebService就是Web服务的意思,对应的应用层协议为SOAP(相当于HTTP协议),可理解为远程调用技术。 特点:客户端发送的请求主体内容(请求报文)的格式为XML格式接口返回的响应主体内容(响应报文)的格式为XML格式WSDL的介绍就是WebService接口对应的WSDL文件,该文件通过xml格式说明如何调用,可以看作WebService的接口文档(使用说明书…

  • 最简单也最难——怎样获取到Android控件的高度「建议收藏」

    最简单也最难——怎样获取到Android控件的高度

  • TraCI4Matlab的安装教程「建议收藏」

    TraCI4Matlab的安装使用教程安装1、下载安装Sumo2、下载安装TraCI4Matlab3、设置环境变量4、添加traci4matlab.jar路径5、将javaclasspath.txt复制至Matlab路径中6、配置Matlab路径安装Matlab有联合Sumo的插件traci4Matlab,网上还没有中文版的安装教程,走过的弯路,后来研究者尽可能少走。1、下载安装Sumo百度搜索sumo,点击进入官网,如图1:根据自己电脑系统进行下载:软件占的空间较少,按照提示安装完成即可:2

  • 取两个列表的交集_js两个数组取差集

    取两个列表的交集_js两个数组取差集js没有直接的方式来获取两个列表的交集,需要自己写一个函数,如下:functionjiaoji(arr_1,arr_2){//先对arr_1进行一次深拷贝,防止对原两个列表造成影响 lettemp_list=$.extend([],arr_1) arr_1.forEach(function(value){//如果arr_1的元素不在arr_2中,那么去…

  • urdf 学习笔记一

    urdf 学习笔记一学习写urdf有几个地方需要注意1.一定要记住它使用的是右手坐标系。x正方向朝左,y正方向向内,z轴正方向朝上2.构建树结构,即写link和joint3.每个link的参考坐标系都在它的底

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号