До сих пор я лишь немного занимался разработкой Flex, но предпочел программный подход к созданию элементов управления, а не файлы mxml, потому что (и пожалуйста, поправьте меня, если я ошибаюсь!) Я понял, что вы можете ' У меня есть оба варианта, то есть функциональность класса должна быть в отдельном файле класса ActionScript, но содержащиеся в нем элементы объявлены в mxml.
По-видимому, нет большой разницы в производительности, но программная привязка данных кажется несколько менее тривиальной. Я посмотрел, как компилятор mxml преобразует выражения привязки данных. Результатом является куча сгенерированных обратных вызовов и намного больше строк, чем в представлении mxml. Итак, вот вопрос: есть ли способ программно связать данные, не вызывающий серьезных страданий?





Не бойтесь MXML. Отлично подходит для разметки видов. Если вы пишете свои собственные компоненты многоразовый, то написание их в ActionScript иногда может дать вам немного больше контроля, но для одноразовых представлений MXML намного лучше. Это более лаконично, привязки очень легко настраиваются и т. д.
Однако привязки в чистом ActionScript не должны доставлять особых хлопот. Это никогда не будет так просто, как в MXML, где многие вещи делаются за вас, но это можно сделать без особых усилий.
У вас есть BindingUtils и его методы bindSetter и bindProperty. Я почти всегда использую первое, так как обычно хочу поработать или звонить invalidateProperties, когда значения меняются, я почти никогда не хочу просто установить свойство.
Что вам нужно знать, так это то, что эти два возвращают объект типа ChangeWatcher, если вы по какой-то причине хотите удалить привязку, вы должны удерживать этот объект. Это то, что делает привязку вручную в ActionScript немного менее удобной, чем в MXML.
Начнем с простого примера:
BindingUtils.bindSetter(nameChanged, selectedEmployee, "name");
Это устанавливает привязку, которая будет вызывать метод nameChanged при изменении свойства name объекта в переменной selectedEmployee. Метод nameChanged получит новое значение свойства name в качестве аргумента, поэтому оно должно выглядеть так:
private function nameChanged( newName : String ) : void
Проблема с этим простым примером заключается в том, что после того, как вы настроили эту привязку, она будет срабатывать каждый раз, когда свойство указанного объекта изменяется. Значение переменной selectedEmployee может измениться, но привязка все еще установлена для объекта, на который переменная указывала ранее.
Есть два способа решить эту проблему: либо оставить ChangeWatcher, возвращенный BindingUtils.bindSetter, и вызвать на нем unwatch, когда вы хотите удалить привязку (а затем вместо этого настроить новую привязку), либо привязать к себе. Сначала я покажу вам первый вариант, а затем объясню, что я имею в виду, говоря о себе.
currentEmployee может быть преобразован в пару геттер / сеттер и реализован следующим образом (показан только сеттер):
public function set currentEmployee( employee : Employee ) : void {
if ( _currentEmployee != employee ) {
if ( _currentEmployee != null ) {
currentEmployeeNameCW.unwatch();
}
_currentEmployee = employee;
if ( _currentEmployee != null ) {
currentEmployeeNameCW = BindingUtils.bindSetter(currentEmployeeNameChanged, _currentEmployee, "name");
}
}
}
Что происходит, так это то, что когда свойство currentEmployee установлено, оно проверяет, было ли предыдущее значение, и если да, то удаляет привязку для этого объекта (currentEmployeeNameCW.unwatch()), тогда он устанавливает частную переменную, и если новое значение не было, null устанавливает новая привязка для свойства name. Что наиболее важно, он сохраняет ChangeWatcher, возвращенный вызовом привязки.
Это базовый шаблон привязки, и я думаю, он отлично работает. Однако есть уловка, с помощью которой можно сделать это немного проще. Вместо этого вы можете привязаться к себе. Вместо того, чтобы настраивать и удалять привязки каждый раз при изменении свойства currentEmployee, вы можете попросить систему привязки делать это за вас. В обработчике creationComplete (или конструкторе, или, по крайней мере, когда-то раньше) вы можете настроить привязку следующим образом:
BindingUtils.bindSetter(currentEmployeeNameChanged, this, ["currentEmployee", "name"]);
Это устанавливает привязку не только к свойству currentEmployee на this, но и к свойству name на этом объекте. Таким образом, при любом изменении будет вызываться метод currentEmployeeNameChanged. Нет необходимости сохранять ChangeWatcher, потому что привязку никогда не придется удалять.
Второе решение работает во многих случаях, но я обнаружил, что первое иногда необходимо, особенно при работе с привязками в классах без представления (поскольку this должен быть диспетчером событий, а currentEmployee должен быть привязан к нему для работай).
Код делает несколько вещей: он следит за тем, чтобы мы не обновлялись, если значение не другое, он обновляет переменную и наблюдателя, а также позволяет полностью сбросить все, установив для свойства значение null (он изящно обрабатывает как случай, когда переменная имеет значение NULL, и когда для нее установлено значение NULL, и при необходимости не отслеживает и просматривает). Я уверен, что его можно было бы написать по-другому, но он должен делать все эти вещи, и я не уверен, что вы могли бы написать его намного короче, чем он есть, без потери функциональности.
Разве второй метод не будет продолжать связывать данные, даже если компонент больше не виден, но все еще находится в памяти?
Один из способов разделить MXML и ActionScript для компонента на отдельные файлы - это сделать что-то похожее на код модели ASP.Net 1.x. В этой модели декларативная часть (в данном случае MXML) является подклассом императивной части (ActionScript). Итак, я мог бы объявить код для такого класса:
package CustomComponents
{
import mx.containers.*;
import mx.controls.*;
import flash.events.Event;
public class MyCanvasCode extends Canvas
{
public var myLabel : Label;
protected function onInitialize(event : Event):void
{
MyLabel.text = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.";
}
}
}
... и такая разметка:
<?xml version = "1.0" encoding = "utf-8"?>
<MyCanvasCode xmlns = "CustomComponents.*"
xmlns:mx = "http://www.adobe.com/2006/mxml"
initialize = "onInitialize(event)">
<mx:Label id = "myLabel"/>
</MyCanvasCode>
Как видно из этого примера, недостатком этого подхода является то, что вам нужно объявлять элементы управления, такие как myLabel, в обоих файлах.
Хорошая причина для этого в том, что это не нарушает представления о гибком дизайне. Если вас не слишком заботит представление дизайна (как я), то с наследованием другим способом справиться намного проще. Вам не нужно предварительно объявлять (и синхронизировать) дочерние элементы mxml в вашем классе .as. Также это кажется более "правильным". Возьмите визуал и добавьте к нему функциональность.
Не делайте этого ... это удвоит количество классов, необходимых для представления.
есть способ, который я обычно использую для совместного использования mxml и сценария действия: все мои компоненты mxml наследуются от класса сценария действия, куда я добавляю более сложный код. Затем вы можете обратиться к прослушивателям событий, реализованным в этом классе, в файле mxml.
С уважением,
Рут
Он существует на сегодняшний день. :)
Я только что выпустил свой проект привязки данных ActionScript с открытым исходным кодом: http://code.google.com/p/bindage-tools
BindageTools - альтернатива BindingUtils (смотрите там игру слов?), Которая использует свободный API, в котором вы объявляете свои привязки данных в стиле конвейера:
Bind.fromProperty(person, "firstName")
.toProperty(firstNameInput, "text");
Двусторонние привязки:
Bind.twoWay(
Bind.fromProperty(person, "firstName"),
Bind.fromProperty(firstNameInput, "text"));
Явное преобразование и проверка данных:
Bind.twoWay(
Bind.fromProperty(person, "age")
.convert(valueToString()),
Bind.fromProperty(ageInput, "text")
.validate(isNumeric()) // (Hamcrest-as3 matcher)
.convert(toNumber()));
И т.д. На сайте есть еще много примеров. Есть много других функций - приходите посмотреть. --Мэтью
Обновлено: обновленные API
Действительно потрясающая запись! Быстрый вопрос. Разве не было бы более простым подходом к первому решению просто проверить, имеет ли значение currentEmployeeNameCW значение null? Если он не равен нулю, значит привязка существует, поэтому вызовите currentEmployeeNameCW.unwatch (). Похоже на более обобщенное и все же очень емкое решение.