Привязка данных, или способность создавать непосредственную связь между двумя переменными, является одной из ключевых возможностей языка программирования 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 для наглядности.
|