JavaFX 8:Chapter 2 属性和绑定(1)

来源:《Learn JavaFX 8: Building User Experience and Interfaces with Java 8》

在这一章中,您到学到:

  • 什么是JavaFX中的属性
  • 如何去创造属性对象并且使用它
  • JavaFX中属性类的层次结构
  • 如何处理属性对象的失效和改变事件
  • 什么是JavaFX中的绑定,并且如何使用单向绑定和双向绑定
  • 关于JavaFX中高级绑定和低级绑定API

本章讨论Java和JavaFX中的属性和绑定。如果您有使用JavaBeans API进行属性和绑定的经验,那么您可以跳过讨论Java中的属性和绑定的前几节,并从“理解JavaFX中的属性”一节开始。

什么是属性?

一个Java类可以包含两种类型的成员:字段和方法。字段表示对象的状态,它们被声明为private。公共方法,例如访问器或getter和setter,用于读取和修改私有字段。简单地说,对其部分或全部私有字段具有公共访问器的Java类称为Java bean,而访问器定义了bean的属性。Java bean的属性允许用户自定义其状态、行为或两者。

Java bean是可观察的,它们支持在属性更改时发送通知。当Java bean的公共属性发生更改时,将会向所有感兴趣的侦听器发送通知。

本质上,Java bean定义了可重用的组件,构建器工具可以组装这些组件来创建Java应用程序。这为第三方开发Java bean打开了大门,并使它们可供其他人重用。

属性可以是只读、只写或者读写。只读的属性有getter但没有setter。只写的属性有setter但没有getter。读写的属性有一个getter和一个setter。

Java IDEs和其他构建工具(例如,GUI布局构建器)使用自检来获取bean的属性列表,并允许您在设计时操作这些属性。Java bean既可以是可视化的,也可以是非可视化的。bean的属性在构建工具或者编程中被使用。

JavaBeans API提供了一个类库,通过java.bean包和命名约定来创建并使用Java bean。下面是一个具有读写的name属性的Person bean的示例。getName()方法(getter)返回name字段的值。setName()方法(setter)设置name字段的值:

// Person.java
package com.jdojo.binding;

public class Person {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

按照约定,getter和setter方法的名称是通过将属性的名称(第一个字母是大写的)分别附加到单词get和set之后来创建的。getter方法不应该接受任何参数,它的返回类型应该与字段的类型相同。setter方法应该接受一个参数,它的类型应该与字段的类型相同,并且它的返回类型应该是void。

下面的代码片段以编程的方式操作Person bean的name属性:

Person p = new Person();
p.setName("John Jacobs");
String name = p.getName();

一些面向对象的编程语言,例如C#,提供了第三种类型的类成员,称为属性(property)。属性用于从类外部读取、写入和计算私有字段的值。C#允许你用Name属性声明Person类,如下所示:

// C# version of the Person class
public class Person {
    private string name;

    public string Name {
        get { return name; }
        set { name = value; }
    }
}

在C#中,下面的代码片段使用Name属性操作name私有字段,它相当于前面显示的Java版本的代码:

Person p = new Person();
p.Name = "John Jacobs";
string name = p.Name;

如果属性的访问器用于执行返回和设置字段值的例行工作,C#提供了一个紧凑的格式来定义这样的属性。在这种情况下,甚至不需要声明私有字段。你可以用C#重写Person类,如下所示:

// C# version of the Person class using the compact format
public class Person {
    public string Name { get; set; }
}

那么,什么是属性?属性是类的公共的可访问特征,它影响类的状态和行为。即使一个属性是公开可访问的,它的使用(读写)调用隐藏的实际实现的方法来访问数据。属性是可观察的,因此当其值发生变化时,相关方会得到通知。

本质上,属性定义了对象的公共状态,可以读、写和观察。与C#等其他编程语言不同,Java中的属性在语言级别上并不受支持。Java对属性的支持来自JavaBeans API和设计模式。有关Java中属性的更多详细信息,请参考JavaBeans规范,该规范可以从http://www.oracle.com/technetwork/java/javase/overview/spec-136004.html下载。

除了简单的属性(如Person bean的name属性)之外,Java还支持索引、绑定和约束属性。索引属性是使用索引访问的值数组。索引属性是使用数组的数据类型实现的。绑定属性发生更改时,会向所有侦听器发送通知。约束性属性是一个绑定属性,侦听器可以在其中否决更改。

什么是绑定?

在编程中,绑定这个术语被使用在许多不同的语境中。在这里,我想在数据绑定的语境中定义它。数据绑定定义了程序中数据元素(通常是变量)之间的关系,以保持它们的同步。在GUI应用程序中,数据绑定经常用于同步数据模型中的元素与相应的UI元素。

考虑下面的语句,假设x,y,z是数值变量:

x = y + z;

上面的语句定义了x、y和z之间的绑定关系。当它被执行时,x的值与y的值和z的值的总和同步。绑定还有一个时间因素。在上面的语句中,x的值绑定到y的值和z的值的总和,并且在语句执行时有效。x的值可能不是执行在上述语句前后的y的值和z的值的总和。

有时需要绑定保持一段时间。思考下面的语句,它使用listPrice、discounts和taxes定义了一个绑定:

soldPrice = listPrice - discounts + taxes;

在本例中,您希望使绑定永远有效,以便在listPrice、discounts和taxes发生变化时正确地计算销售价格。

在上面的绑定中,listPrice、discounts和taxes被称为依赖项,可以说soldPrice被listPrice、discounts和taxes绑定。

要使绑定正常地工作,必须在其依赖项发生更改时通知绑定。支持绑定的编程语言提供了一种用依赖项注册的侦听器机制。当依赖项变为无效或发生更改时,将通知所有的侦听器。绑定可以在接收到此类通知时将自身与其依赖项进行同步。

绑定可以是eager binding或lazy binding。在eager binding中,被绑定的变量在其依赖项更改后立即重新计算。在lazy binding中,当绑定变量的依赖项发生变化时,不会立即重新计算。而是在下次读取时再重新计算。lazy binding比eager binding性能更好。

绑定可以是单向的,也可以是双向的。单向绑定只在一个方向上工作,依赖项中的更改被传播到绑定变量上。双向绑定是在两个方向上工作。在双向绑定中,被绑定的变量和依赖项保持彼此的值同步。通常,双向绑定只定义在两个变量之间。例如,双向绑定 x = y 和 y = x 声明了x和y的值总是相同的。

在数学上,不可能在多个变量之间定义一个双向绑定。在上面的例子中,销售价格定义的是单向绑定。如果您希望使其成为双向绑定,那么当销售价格发生变化时,计算标价、折扣和税金的值的可能性就不是唯一的,它们有无限的可能性。

带有GUI的应用程序为用户提供UI小部件,例如文本字段、复选框和按钮,用于操作数据。UI小部件中显示的数据必须与底层数据模型同步,反之亦然。在这种情况下,需要双向绑定来保持UI和数据模型同步。

理解JavaBeans中的绑定支持

在讨论Java FX属性和绑定之前,让我们先简要介绍一下JavaBeans API中的绑定支持。如果以前使用过JavaBeans API,可以跳过本节。

Java从早期版本中就开始支持bean属性的绑定。表2-1显示了一个Employee bean,它有两个属性,名称和工资。

表2-1. 具有名称和工资两个属性的Employee bean

// Employee.java
package com.jdojo.binding;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Employee {
    private String name;
    private double salary;
    private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
 
    public Employee() {
        this.name = "John Doe";
        this.salary = 1000.0;
    }

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
 
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double newSalary) {
        double oldSalary = this.salary;
        this.salary = newSalary;
 
        // Notify the registered listeners about the change
        pcs.firePropertyChange("salary", oldSalary, newSalary);
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        pcs.removePropertyChangeListener(listener);
    }
 
    @Override
    public String toString() {
        return "name = " + name + ", salary = " + salary;
    }
}

Employee bean的两个属性都是读写的。salary属性也是一个绑定属性,它的setter访问器在salary发生变化时生成属性改变的通知。

感兴趣的侦听器可以使用addPropertyChangeListener()方法和removePropertyChangeListener()方法注册或注销属性改变的通知。PropertyChangeSupport类是JavaBeans API的一部分,它促进了属性更改监听器的注册和删除以及属性更改通知的触发。

任何对基于工资变化同步值感兴趣的一方都需要向Employee bean注册,并在收到更改通知时采取必要的操作。

表2-2显示了如何为Employee bean注册工资变化的通知。下面的输出显示工资变化通知只触发了两次,而setSalary()方法被调用了三次。这毫无问题,因为第二次调用setSalary()方法时使用了与第一次调用相同的工资数额,并且PropertyChangeSupport类足够智能,可以检测到这一点。该示例还展示了如何使用JavaBeans API绑定变量。雇员的税款是按百分比计算的。在JavaBeans API中,属性更改通知被用于绑定变量。

表2-2. 一个EmployeeTest类,被用于测试Employee Bean的工资变化

// EmployeeTest.java
package com.jdojo.binding;

import java.beans.PropertyChangeEvent;

public class EmployeeTest {
    public static void main(String[] args) {
        final Employee e1 = new Employee("John Jacobs", 2000.0);
 
        // Compute the tax
        computeTax(e1.getSalary());
 
        // Add a property change listener to e1
        e1.addPropertyChangeListener(EmployeeTest::handlePropertyChange);

        // Change the salary
        e1.setSalary(3000.00);
        e1.setSalary(3000.00); // No change notification is sent.
        e1.setSalary(6000.00);
    }
 
    public static void handlePropertyChange(PropertyChangeEvent e) {
        String propertyName = e.getPropertyName();

        if ("salary".equals(propertyName)) {
            System.out.print("Salary has changed. ");
            System.out.print("Old:" + e.getOldValue());
            System.out.println(", New:" + e.getNewValue());
            computeTax((Double)e.getNewValue());
        }
    }
 
    public static void computeTax(double salary) {
        final double TAX_PERCENT = 20.0;
        double tax = salary * TAX_PERCENT/100.0;
        System.out.println("Salary:" + salary + ", Tax:" + tax);
    }
}
Salary:2000.0, Tax:400.0
Salary has changed. Old:2000.0, New:3000.0
Salary:3000.0, Tax:600.0
Salary has changed. Old:3000.0, New:6000.0
Salary:6000.0, Tax:1200.0

理解JavaFX中的属性

JavaFX通过属性和绑定APIs支持属性、事件和绑定。JavaFX中的属性支持是JavaBeans属性的巨大飞跃。

JavaFX中的所有属性都是可观察的,可以观察到它们的失效和值的变化,可以是读写属性或是只读属性,所有的读写属性都支持绑定。

在JavaFX中,属性可以表示单个值或者是值的集合,本章涵盖了表示单个值的属性,我们将在第三章讨论表示值的集合的属性。

在JavaFX中,属性是对象。每种类型的属性都有一个属性类层次结构。例如,IntegerProperty、DoubleProperty和StringProperty类分别表示int、double和String类型的属性。这些属性类是抽象的。它们有两种实现类:一种表示读写属性,另一种表示只读属性的包装器。例如,SimpleDoubleProperty类和ReadOnlyDoubleWrapper类是具体的实现类,它们的对象分别代表读写属性和只读属性。

下面是一个如何创建初始值为100的IntegerProperty类对象的例子:

IntegerProperty counter = new SimpleIntegerProperty(100);

属性类分别提供两对getter和setter方法:get()、set() 和 getValue()、 setValue()。get()和set()方法分别获取和设置属性的值。对于基本类型的属性,它们使用基本类型的值。例如,对于IntegerProperty,get()方法的返回值类型和set()方法的参数类型都是int。getValue()和setValue()方法用于对象类型,例如,对于IntegerProperty来说,返回值类型和参数类型均为Integer。

对于引用类型的属性,例如 StringProperty 和 ObjectProperty<T>, 两对 getter 和 setter 都是使用对象类型。也就是说,StringProperty 的 get() 和 getValue() 方法都将返回一个 String, set() 和 setValue() 方法都将接受一个 String 参数。对于基本类型的自动装箱,使用哪个版本的 getter 和 setter 并不重要。getValue() 和 setValue() 方法的存在是为了帮助您根据对象类型编写泛型代码。

下面的代码片段使用 IntegerProperty 及其 get() 和 set() 方法。counter属性是一个读写属性,因为它是 SimpleIntegerProperty 类的对象:

IntegerProperty counter = new SimpleIntegerProperty(1);
int counterValue = counter.get();
System.out.println("Counter:" + counterValue);

counter.set(2);
counterValue = counter.get();
System.out.println("Counter:" + counterValue);
Counter:1
Counter:2

使用只读属性有点棘手。ReadOnlyXXXWrapper类包装了两个XXX类型的属性:一个是只读的,另一个是读写的。两个属性都是同步的。它的getReadOnlyProperty()方法返回一个ReadOnlyXXXProperty对象。

下面的代码片段展示了如何创建一个只读的Integer属性。idWrapper属性是读写的,而id属性是只读的。当idWrapper中的值发生变化时,id中的值也会自动发生变化:

ReadOnlyIntegerWrapper idWrapper = new ReadOnlyIntegerWrapper(100);
ReadOnlyIntegerProperty id = idWrapper.getReadOnlyProperty();

System.out.println("idWrapper:" + idWrapper.get());
System.out.println("id:" + id.get());

// Change the value
idWrapper.set(101);

System.out.println("idWrapper:" + idWrapper.get());
System.out.println("id:" + id.get())
idWrapper:100
id:100
idWrapper:101
id:101

通常,包装器属性用作类的私有实例变量,类可以在内部更改属性。它的一个方法返回包装器类的只读属性对象,因此相同的属性对于外部世界是只读的。

您可以使用表示单个值的七种类型的属性。这些属性的基类被命名为XXXProperty,只读基类被命名为ReadOnlyXXXProperty,包装类被命名为ReadOnlyXXXWrapper。表2-1列出了每种类型的XXX值。

表2-1. 包装单个值的属性类列表

属性对象封装了三条信息: 

  • 包含它的bean的引用
  • 一个名字
  • 一个值

在创建属性对象时,您可以提供以上三条信息中的全部或不提供。具体的属性类,例如SimpleXXXProperty和ReadOnlyXXXWrapper,提供了四个构造函数,允许您提供这三个信息的组合。以下是SimpleIntegerProperty类的构造函数:

SimpleIntegerProperty()
SimpleIntegerProperty(int initialValue)
SimpleIntegerProperty(Object bean, String name)
SimpleIntegerProperty(Object bean, String name, int initialValue)

初始值的默认值取决于属性的类型。对于数值类型,它为零;对于布尔类型,它为假;对于引用类型,它为空。

属性对象既可以是bean的一部分,也可以是独立的对象。指定的bean是对包含该属性的bean对象的引用。对于独立的属性对象,它可以为null,它的默认值为null。

属性的名字是它的名称。如果未提供,则默认为空字符串。

下面的代码片段创建一个属性对象作为bean的一部分,并设置所有三个值。SimpleStringProperty类构造函数的第一个参数是this,它是Person bean的引用,第二个参数“name”是属性的名称,第三个参数“Li”是属性的值:

public class Person {
     private StringProperty name = new SimpleStringProperty(this, "name", "Li"); 
        // More code goes here...
}

每个属性类都有getBean()和getName()方法,分别返回bean的引用和属性名。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaFX 属性是一种特殊的字段,可以在属性值发生变化时自动更新视图。JavaFX 属性有以下几个特点: 1. 可观察性:JavaFX 属性是可观察的,可以注册监听器来监听属性值的变化。 2. 绑定性:JavaFX 属性可以绑定到其他属性或表达式上,当被绑定属性或表达式的值发生变化时,绑定属性的值也会随之变化。 3. 可写性:JavaFX 属性可以是可写的或只读的,可写属性的值可以通过 set 方法来修改,只读属性的值只能在创建时指定,之后不可修改。 4. 类型安全:JavaFX 属性具有类型安全性,因为属性的类型是在编译时确定的。 以下是一个简单的 JavaFX 属性示例: ``` import javafx.beans.property.*; public class Person { private StringProperty name = new SimpleStringProperty(); private IntegerProperty age = new SimpleIntegerProperty(); public String getName() { return name.get(); } public void setName(String name) { this.name.set(name); } public StringProperty nameProperty() { return name; } public int getAge() { return age.get(); } public void setAge(int age) { this.age.set(age); } public IntegerProperty ageProperty() { return age; } } ``` 在上面的示例中,Person 类具有两个 JavaFX 属性:name 和 age。name 属性是一个字符串属性,age 属性是一个整数属性。这些属性都具有 get 和 set 方法,以及对应的属性对象(nameProperty 和 ageProperty)。可以使用属性对象来注册监听器、绑定属性和获取属性值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值