Flex 3 — Аннулирование в обработчике событий show прерывает жизненный цикл

#apache-flex

#apache-flex

Вопрос:

Просто интересно, сталкивался ли кто-нибудь с этим и имеет хорошее исправление.

Вот как воспроизвести:

Создайте навигатор вкладок (или viewstack, что угодно) и добавьте пару вкладок.

На вашей вкладке добавьте обработчик событий show. Внутри обработчика событий вызывайте invalidateProperties() и invalidateDisplayList() на одном из дочерних элементов вашей вкладки. Поставьте точку останова в дочерних commitProperties() и updateDisplayList(). Вы заметите, что updateDisplayList() вызывается перед commitProperties(), что приводит к некорректному поведению.

Я заметил эту проблему при настройке dataProvider DataGrid из обработчика show. Установка dataProvider приводит к тому, что сетка делает недействительными как свойства, так и DisplayList, сначала вызывается updateDisplayList() , затем commitProperties(), что приведет к тому, что сетка не обновит строки.

Похоже, корень проблемы заключается в том, что событие show отправляется из цикла LayoutManagers validateDisplayList(), поэтому аннулирование дочернего объекта из обработчика show приводит к немедленному вызову его updateDisplayList() .

Я знаю, что могу использовать callLater() внутри обработчика show или нескольких других хакерских решений, но я бы предпочел исправить корень проблемы, поскольку я не хочу находить / исправлять эту проблему каждый раз, когда кто-то использует событие show и происходят плохие вещи.

Я рассматриваю возможность изменения UIComponent.setVisible(), который отправляет событие show, и использования callLater() в dispatchEvent(), чтобы событие show не отправлялось в середине цикла проверки, если у кого-нибудь нет идеи получше.

 <mx:Script>
    <![CDATA[
        import mx.controls.Label;

        private var tabLabel:Label;
        private function onCreationComplete():void
        {
            var ifactory:IFactory = TestLabel;
            tabLabel = Label(ifactory.newInstance());
            tab1.addChild(tabLabel);
        }


        private function onTab1Show():void
        {
            tabLabel.invalidateProperties();
            tabLabel.invalidateDisplayList();
        }

    ]]>
</mx:Script>

<mx:Component id="TestLabel">
    <mx:Label text="Test">
        <mx:Script>
            <![CDATA[

                override protected function commitProperties():void
                {
                    super.commitProperties();   
                }
                override protected function updateDisplayList(w:Number, h:Number):void
                {
                    super.updateDisplayList(w, h);                  
                }

            ]]>
        </mx:Script>
    </mx:Label>
</mx:Component>

<mx:TabNavigator height="200" width="200" creationComplete="onCreationComplete()">
    <mx:Canvas id="tab1" height="100%" width="100%" label="Tab 1" show="onTab1Show()" />
    <mx:Canvas height="100%" width="100%" label="Tab 2" />
</mx:TabNavigator>
  

Комментарии:

1. Да, это было бы необычно, однако это работает. Кроме того, учитывая, что эта ошибка повлияет на любые дочерние элементы любого компонента, который поддерживает событие show, это огромная проблема, и я не знаю ни одного способа исправить это глобально, не меняя SDK немного.

2. Покажите нам какой-нибудь код для воспроизведения, и я посмотрю.

3. Вы хотите, чтобы я также добавил в него точки останова для вас? Вы должны выполнить перерыв в commitProperties() и updateDisplayList(), чтобы просто увидеть проблему. Я не думаю, что код в этом случае необходим, поскольку эту проблему легко воспроизвести, и это можно сделать с ЛЮБЫМ компонентом, который поддерживает событие show. Просто добавьте дочерний элемент и обработчик show, сделайте дочерний элемент недействительным внутри обработчика show и посмотрите порядок, в котором он вызывает commitProperties и updateDisplayList для дочернего элемента.

4. Я не хочу тратить время на создание образца с нуля, который продемонстрирует проблему, с которой вы столкнулись. Если вам нужна помощь, помогите мне помочь вам.

5. лол, вот так. Просто вставьте это в приложение и поместите свои точки останова в переопределения для метки. Обратите внимание на порядок их вызова при переключении на tab1.

Ответ №1:

Я решил, что лучшим решением этой проблемы была небольшая настройка LayoutManager. Он должен быть достаточно умен, чтобы знать при проверке объекта, если какой-либо из предыдущих этапов недействителен.

Эта проблема может проявляться несколькими способами. По существу, это может привести к аннулированию объектов из методов measure() или updateDisplayList(). Например, если событие отправляется из функции measure(), а обработчик события приводит к аннулированию размера и свойств объектов, функция measure() может быть запущена до commitProperties(), что может привести к повреждению компонента.

Событие show отправляется из updateDisplayList() UIComponent, поэтому оно подвержено этому сбою.

В итоге я немного изменил validateSize() и validateDisplayList(). Добавлен фрагмент кода для проверки недопустимых флагов предыдущих этапов и повторного аннулирования объекта при необходимости. Также добавлен флаг, заставляющий LayoutManager немедленно запускать другой цикл, если объект повторно становится недействительным из-за вышеуказанного условия.

К вашему сведению, обычно не рекомендуется изменять SDK, но это можно сделать для каждого проекта, скопировав файл SDK в соответствующую структуру папок в вашем проекте и выполнив чистку проекта. Файл в вашем проекте будет использоваться, а не в SDK.

 private function validateSize():void
{
    // trace("--- LayoutManager: validateSize --->");

    //SDK Mod - Storage for items to be re-invalidated.
    var reInvalidate:Array = [];

    var obj:ILayoutManagerClient = ILayoutManagerClient(invalidateSizeQueue.removeLargest());
    while (obj)
    {
        // trace("LayoutManager calling validateSize() on "   Object(obj));


        //SDK Mod - Check if we need to record this item due to invalid dependencies.
        if (obj is UIComponent)
        {
            if (UIComponent(obj).mx_internal::invalidatePropertiesFlag == true)
            {
                //Record the invalid item.
                reInvalidate.push(obj);
                //Set flag so LayoutManager immediately runs another cycle
                recycleImmediately = true;
            }
        }

        obj.validateSize();
        if (!obj.updateCompletePendingFlag)
        {
            updateCompleteQueue.addObject(obj, obj.nestLevel);
            obj.updateCompletePendingFlag = true;
        }

        // trace("LayoutManager validateSize: "   Object(obj)   " "   IFlexDisplayObject(obj).measuredWidth   " "   IFlexDisplayObject(obj).measuredHeight);

        obj = ILayoutManagerClient(invalidateSizeQueue.removeLargest());
    }

    //Re-invalidate any items with invalid dependencies.
    while (reInvalidate.length > 0)
        invalidateSize(ILayoutManagerClient(reInvalidate.shift()));

    if (invalidateSizeQueue.isEmpty())
    {
        // trace("Measurement Queue is empty");

        invalidateSizeFlag = false;
    }

    // trace("<--- LayoutManager: validateSize ---");
}
private function validateDisplayList():void
{

    // trace("--- LayoutManager: validateDisplayList --->");

    //SDK Mod - Storage for items to be re-invalidated.
    var reInvalidate:Array = [];

    var obj:ILayoutManagerClient = ILayoutManagerClient(invalidateDisplayListQueue.removeSmallest());
    while (obj)
    {
        // trace("LayoutManager calling validateDisplayList on "   Object(obj)   " "   DisplayObject(obj).width   " "   DisplayObject(obj).height);



        //SDK Mod - Check if we need to record this item due to invalid dependencies.
        if (obj is UIComponent)
        {
            if (UIComponent(obj).mx_internal::invalidatePropertiesFlag == true ||
                UIComponent(obj).mx_internal::invalidateSizeFlag == true)
            {
                //Record the invalid item.
                reInvalidate.push(obj);
                //Set flag so LayoutManager immediately runs another cycle
                recycleImmediately = true;
            }
        }

        obj.validateDisplayList();
        if (!obj.updateCompletePendingFlag)
        {
            updateCompleteQueue.addObject(obj, obj.nestLevel);
            obj.updateCompletePendingFlag = true;
        }

        // trace("LayoutManager return from validateDisplayList on "   Object(obj)   " "   DisplayObject(obj).width   " "   DisplayObject(obj).height);

        // Once we start, don't stop.
        obj = ILayoutManagerClient(invalidateDisplayListQueue.removeSmallest());
    }

    //Re-invalidate any items with invalid dependencies.
    while (reInvalidate.length > 0)
        invalidateDisplayList(ILayoutManagerClient(reInvalidate.shift()));

    if (invalidateDisplayListQueue.isEmpty())
    {
        // trace("Layout Queue is empty");

        invalidateDisplayListFlag = false;
    }

    // trace("<--- LayoutManager: validateDisplayList ---");
}
  

Затем внутри doPhasedInstantiation()
.
.
.

 if (invalidatePropertiesFlag ||
        invalidateSizeFlag ||
        invalidateDisplayListFlag)
    {
        //SDK Mod - Check if we re-invalidated any items durring the cycle. 
        if (recycleImmediately == false)
        {
            //No items were re-invalidated, default behavior.
            callLaterObject.callLater(doPhasedInstantiation);
        }
        else
        {
            //We re-invalidated items durring the current cycle, run another cycle immediately and bail out.
            recycleImmediately = false;
            doPhasedInstantiation();
            return;
        }
    }