Flex: существует ли безболезное связывание программных данных?

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

Похоже, что разница в производительности невелика, но программная обработка данных выглядит несколько менее тривиальной. Я посмотрел, как компилятор mxml преобразует выражения привязки данных. Результатом является куча вызванных обратных вызовов и намного больше строк, чем в представлении mxml. Итак, вот вопрос: есть способ делать привязку данных программно, что не связано с миром боли?

Ответы

Ответ 1

Не бойтесь 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 должен быть привязкой для работы).

Ответ 2

Он существует на сегодняшний день.:)

Я только что выпустил проект привязки данных 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()));

Etc. На сайте есть много примеров. Там тоже много других функций - посмотрите. --Matthew

Изменить: обновленные API

Ответ 3

Один из способов разделить 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 в обоих файлах.

Ответ 4

существует способ, которым я обычно использую mxml и action script вместе: все мои компоненты mxml наследуют от класса action script, где я добавляю более сложный код. Затем вы можете обратиться к прослушивателям событий, реализованным в этом классе в файле mxml.

Привет,

Ruth