#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;
}
}