|
Привязка данных, или способность создавать непосредственную связь между двумя переменными, является одной из ключевых возможностей языка программирования JavaFX Script. Этот урок начинается со связывания двух простых переменных и переходит к более сложному связыванию: переменной и значения функции или выражения. Как только вы поймете идею, познакомьтесь с Applying Data Binding to UI Objects, уроком в Building GUI Applications with JavaFX, в котором показано, каким мощным инструментом может быть механизм привязки данных при построении приложений JavaFX.
Триггер замены представляет собой блок кода, который связан с переменной: когда переменная изменяется, код автоматически выполняется. Приводится практический пример триггера замены. |
Содержание
Зарезервированное слово bind связывает значение некоторой переменной со значением связываемого выражения. Связывамое выражение может быть простой переменной основного типа, объектом, функцией или каким-либо выражением. В следующих частях приводятся примеры для каждого из них.
В большинстве реальных ситуаций в программировании вы будете использовать привязку данных для синхронизации графического интерфейса пользователя (GUI) приложения с лежащими в его основе данными. (Программирование GUI обсуждается в JavaFX GUI Tutorial; здесь же мы рассматриваем основные принципы с некоторыми примерами.)
Давайте начнем с простого: следующий скрипт привязывает переменную x к переменной y, изменяет значение переменной x, а затем печатает значение y. Поскольку переменные связаны, значение переменной y автоматически обновляется.
var x = 0;
def y = bind x;
x = 1;
println(y); // y now equals 1
x = 47;
println(y); // y now equals 47
|
Обратите внимание, что мы объявили переменную y как def. Тем самым, никакой код не может прямо присвоить значение переменной y, и в то же время значение y разрешается менять с использованием оператора bind. Используйте точно такое же соглашение, применяя привязку к объекту (напомним, что мы ввели объект Address в уроке Using Objects):
var myStreet = "1 Main Street";
var myCity = "Santa Clara";
var myState = "CA";
var myZip = "95050";
def address = bind Address {
street: myStreet;
city: myCity;
state: myState;
zip: myZip;
};
println("address.street == {address.street}");
myStreet = "100 Maple Street";
println("address.street == {address.street}");
|
При изменении значения myStreet переменная street объекта address изменяется:
address.street == 1 Main Street
address.street == 100 Maple Street
|
Обратите внимание, что изменение значения myStreet на самом деле вызывает создание нового объекта Address и затем переназначение значения переменной address. Чтобы отслеживать изменения без создания нового объекта Address, следует применить привязку прямо к переменным экземпляра класса:
def address = bind Address {
street: bind myStreet;
city: bind myCity;
state: bind myState;
zip: bind myZip;
};
|
Первый bind (тот, который стоит непосредственно перед Address) можно опустить, если вы применяете явную привязку к переменным экземпляра класса:
def address = Address {
street: bind myStreet;
city: bind myCity;
state: bind myState;
zip: bind myZip;
};
|
В предыдущих уроках вы познакомились с функциями, но есть еще одно различие, о котором вы должны узнать: bound функции по сравнению с non-bound функциями.
Рассмотрим следующую функцию, которая создает и возвращает объект класса Point:
var scale = 1.0;
bound function makePoint(xPos : Number, yPos : Number) : Point {
Point {
x: xPos * scale
y: yPos * scale
}
}
class Point {
var x : Number;
var y : Number;
}
|
Такая функция известна как bound функция, т.к. ей предшествует зарезервированное слово bound.
Примечание: Зарезервированное слово bound не замещает слово bind; оба этих слова используются сообща как описано ниже.
Добавим следующий код, чтобы вызвать эту функцию и протестировать привязку данных:
var scale = 1.0;
bound function makePoint(xPos : Number, yPos : Number) : Point {
Point {
x: xPos * scale
y: yPos * scale
}
}
class Point {
var x : Number;
var y : Number;
}
var myX = 3.0;
var myY = 3.0;
def pt = bind makePoint(myX, myY);
println(pt.x);
myX = 10.0;
println(pt.x);
scale = 2.0;
println(pt.x);
|
Получим следующий результат:
Проанализируем этот скрипт небольшими фрагментами.
Следующий код:
var myX = 3.0;
var myY = 3.0;
def pt = bind makePoint(myX, myY);
println(pt.x);
|
инициализирует переменные скрипта myX и myY значениями 3.0. Эти значения затем передаются в качестве параметров функции makePoint, которая создает и возвращает новый объект класса Point. Зарезервированное слово bind, помещенное прямо перед вызовом функции makePoint, привязывает только что созданный объект класса Point (pt) к результату функции makePoint.
Затем код:
myX = 10.0;
println(pt.x);
|
меняет значение myX на 10.0 и выводит значение pt.x. Вывод показывает, что значение pt.x теперь также равно 10.0.
Наконец, следующий код:
scale = 2.0;
println(pt.x);
|
меняет значение scale и снова выводит значение pt.x. Значение pt.x теперь равно 20.0. Однако, если мы уберем зарезервированное слово bound от этой функции (тем самым сделав ее non-bound функцией), результат будет следующий:
Причина этого заключается в том, что non-bound функции вызываются повторно только тогда, когда один из их параметров меняется. Поскольку scale не является параметром функции, изменение его значения не приводит к новому вызову функции.
Оператор bind можно также использовать с выражениями for. Рассмотрим этот случай подробнее. Для начала определим две последовательности и выведем значения их элементов:
var seq1 = [1..10];
def seq2 = bind for (item in seq1) item*2;
printSeqs();
function printSeqs() {
println("First Sequence:");
for (i in seq1){println(i);}
println("Second Sequence:");
for (i in seq2){println(i);}
}
|
Последовательность seq1 содержит 10 элементов (числа от 1 до 10). Последовательность seq2 также содержит 10 элементов; эти элементы имели бы те же самые значения, что и элементы seq1, но мы применили к каждому элементу выражение item*2, так что их значения удвоились.
Следовательно, результат такой:
First Sequence:
1
2
3
4
5
6
7
8
9
10
Second Sequence:
2
4
6
8
10
12
14
16
18
20
|
Мы можем привязать две последовательности при помощи слова bind, помещенного непосредственно перед словом for.
def seq2 = bind for (item in seq1) item*2;
|
Возникает вопрос: если последовательность seq1 каким-либо образом изменится, все ли элементы последовательности seq2 изменятся или только некоторые из них? Мы можем протестировать это, если добавим один элемент со значением 11 в конец seq1, а затем напечатаем значения обеих последовательностей и посмотрим, что изменилось.
var seq1 = [1..10];
def seq2 = bind for (item in seq1) item*2;
insert 11 into seq1;
printSeqs();
function printSeqs() {
println("First Sequence:");
for (i in seq1){println(i);}
println("Second Sequence:");
for (i in seq2){println(i);}
}
|
Результат следующий:
First Sequence:
1
2
3
4
5
6
7
8
9
10
11
Second Sequence:
2
4
6
8
10
12
14
16
18
20
22
|
Вывод показывает, что добавление значения 11 в конец seq1 не влияет на первые 10 элементов последовательности seq2; новый элемент автоматически добавляется в конец seq2 и имеет значение 22.
Триггеры замены представляют собой произвольные блоки кода, которые ассоциируются с переменной и выполняются всякий раз, когда значение переменной меняется. Основной синтаксис показан в следующем примере: определяется значение переменной password и к переменной присоединяется триггер; когда значение password меняется, триггер распечатывает сообщение с новым значением этой переменной:
var password = "foo" on replace oldValue {
println("\nALERT! Password has changed!");
println("Old Value: {oldValue}");
println("New Value: {password}");
};
password = "bar";
|
Результат этого примера показан ниже:
ALERT! Password has changed!
Old Value:
New Value: foo
ALERT! Password has changed!
Old Value: foo
New Value: bar
|
В этом примере триггер срабатывает два раза: первый раз, когда переменная password инициализируется значением "foo", и второй раз, когда ее значение становится "bar". Обратите внимание, что в переменной oldValue сохраняется значение переменной до срабатывания триггера. Вы можете назвать эту переменную любым именем; мы использовали oldValue для наглядности.
|