自定义复合控件在添加回 VGROUP 后仅 0.5-1 秒无法正确呈现
Posted
技术标签:
【中文标题】自定义复合控件在添加回 VGROUP 后仅 0.5-1 秒无法正确呈现【英文标题】:Custom Composite Control not rendering correctly for only 0.5-1 sec after being added back into a VGROUP 【发布时间】:2012-06-19 00:47:28 【问题描述】:我正在远离 MXML,并在 ActionScript 中构建了一个自定义组件控件。
我的控件显示正确。问题出现在我从显示列表中删除它并使用 .addElement(control) 方法重新添加它之后。
这是再次添加它的代码。
private function displayParameters(parameters:ArrayCollection):void
for(var index:int = 0; index<parameters.length; index++)
if(parameters[index] is ReportControl)
var control:ReportControl = parameters[index] as ReportControl;
control.percentWidth = 100;
vgParameters.addElement(control);
ReportControl
是comboBoxMultiSelect
的基类,如下所示。 ReportControl
在图形上没有什么特别之处,它仅作为其具体实现(多态)的编程接口。
public class comboBoxMultiSelect extends ReportControl
[Embed("../Assets/Icons/plus-16.png")]
private var plusIcon:Class;
[Embed("../Assets/Icons/minus-16.png")]
private var minusIcon:Class;
private var expanded:Boolean = false;
private var buttonIconChanged:Boolean = false;
private var _drp:ComboBox;
private var _btnMultiple:Button;
private var _horizontalGroup:HGroup;
private var _multiSelector:ReportGridSelector;
private var _multiSelection:Boolean = true;
private var bMultiSelectionChanged:Boolean = false;
public function ToggleExpanded():void
expanded = !_expanded;
buttonIconChanged = true;
invalidateSize();
invalidateProperties();
invalidateDisplayList();
public function comboBoxMultiSelect()
super();
override protected function createChildren():void
super.createChildren();
if(!_horizontalGroup)
_horizontalGroup = new HGroup();
_horizontalGroup.gap = 0;
_horizontalGroup.percentWidth = 100;
_horizontalGroup.height = ReportControl.SIZE_DEFAULT_HEIGHT;
addChild(_horizontalGroup);
if(!_drp)
_drp = new ComboBox();
_drp.text = GuiText;
_drp.percentWidth = 100;
_drp.height = ReportControl.SIZE_DEFAULT_HEIGHT;
_horizontalGroup.addElement(_drp);
if(!_btnMultiple && _multiSelection)
_btnMultiple = new Button;
_btnMultiple.setStyle("icon", plusIcon);
_btnMultiple.width = 20;
_btnMultiple.height = ReportControl.SIZE_DEFAULT_HEIGHT;
_btnMultiple.visible = true;
_btnMultiple.addEventListener(MouseEvent.CLICK,
function(event:MouseEvent):void
ToggleExpanded();
);
_horizontalGroup.addElement(_btnMultiple);
override protected function commitProperties():void
super.commitProperties();
if(buttonIconChanged)
if(_expanded==true)
_btnMultiple.setStyle("icon", minusIcon);
else
_btnMultiple.setStyle("icon", plusIcon);
buttonIconChanged = false;
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
super.updateDisplayList(unscaledWidth, unscaledHeight);
_horizontalGroup.width = unscaledWidth;
_horizontalGroup.height = unscaledHeight;
override protected function measure():void
super.measure();
measuredMinWidth = measuredWidth = ReportControl.SIZE_DEFAULT_WIDTH;
//minimum size //default size
if(_expanded==true)
measuredMinHeight= measuredHeight = 200;
else
measuredMinHeight= measuredHeight =
ReportControl.SIZE_DEFAULT_HEIGHT;
当我使用vgParameters.addElement(control)
重新添加控件时,comboBoxMultiSelect
无法正确呈现。按钮 _btnMultiple
内的 plusIcon
最初定位不正确,但在大约 0.5-1 秒后迅速自行纠正。
我很确定问题出在 comboBoxMultiSelect 之内,只是不确定如何强制图标保持在同一个位置。
在我辛勤工作之后,这很烦人,有人知道我做错了什么吗?
谢谢:)
更新 -----> 这是 ReportControl 代码
[Event (name= "controlChanged", type="Reporting.ReportControls.ReportControlEvent")]
[Event (name= "controlIsNowValid", type="Reporting.ReportControls.ReportControlEvent")]
public class ReportControl extends UIComponent
private var _guiText:String;
private var _amfphpArgumentName:String;
private var _reportResult:ReportResult;
private var _sequence:int;
private var _reportId:int;
private var _controlConfiguration:ReportParameterVO;
private var _isValid:Boolean = false;
internal var _selection:Object;
/**
* SIZE_DEFAULT_HEIGHT = 22
*/
internal static const SIZE_DEFAULT_HEIGHT:int = 22;
/**
* SIZE_DEFAULT_WIDTH = 150
*/
internal static const SIZE_DEFAULT_WIDTH:int = 150;
public function get ControlConfiguration():ReportParameterVO
return _controlConfiguration;
public function set ControlConfiguration(value:ReportParameterVO):void
_controlConfiguration = value;
_guiText = (value ? value.GuiText:"");
_amfPHPArgumentName = (value ? value.AMFPHP_ArgumentName: "");
_sequence = (value ? value.Sequence : null);
_reportId = (value ? value.ReportId : null);
public function get IsValid():Boolean
return _isValid;
public function get ReportID():int
return _reportId;
public function get Sequence():int
return _sequence;
public function get ControlRepResult():ReportResult
return _reportResult;
public function set ControlRepResult(value:ReportResult):void
_reportResult = value;
internal function set Selection(value:Object):void
_selection = value;
internal function get Selection():Object
return _selection;
public function get ParameterSelection():Object
return _selection;
public function get GuiText():String
return _guiText;
public function get AmfPHPArgumentName():String
return _amfPHPArgumentName;
public function ReportControl()
//TODO: implement function
super();
public function dispatchControlChanged():void
this.dispatchEvent(new ReportControlEvent(ReportControlEvent.CONTROL_CHANGED, this, true));
public function dispatchControlIsNowValid():void
this.dispatchEvent(new ReportControlEvent(ReportControlEvent.CONTROL_IS_NOW_VALID, this, true));
public function addSelfToValueObject(valueObject:Object):Object
valueObject[AmfPHPArgumentName] = _selection;
return valueObject;
【问题讨论】:
请问您为什么“逐渐远离 MXML”?创建这个简单的复合类可能要花费您几个小时,如果您用 MXML 编写它并让框架为您解决困难,这将花费您几分钟。这其中的收获在哪里?如果您想将布局 (mxml) 与行为 (as) 分开,如果您愿意,我可以向您展示如何做到这一点,尽管它并不能真正回答您的问题。 我们需要对未来可能需要的“X”个控制类型进行某种形式的抽象。而且我觉得用 MXML 来做这件事(虽然肯定会更快)会在一定程度上失去控制。另外,我们正在学习 flex,所以还有什么比深入了解组件生命周期更好的方法 了解组件生命周期是一回事,抽象是另一回事。我不完全理解您在这种情况下所说的抽象是什么意思,但我觉得您应该看看 Spark 皮肤架构,它允许您编写一个可以采用多种形式(作为皮肤)的主机组件。这是我的一个旧答案,它显示了这个概念的一个非常简单的例子:***.com/questions/9930740/what-is-the-hostcomponent/…。 (不幸的是,它非常简单,没有任何行为。)大多数 Spark 组件都是用这个概念构建的。 对不起,我应该更清楚一点,我所说的抽象是指 SuperClass-->SubClass 关系。在这种情况下,ReportControl-->comboBoxMultiSelect。每个控件的视觉表示将标准化,(相同的列,字体大小等......)。但是每个控件的功能可能会有很大的不同。本质上,我们正在构建的是一个临时报告引擎,其中每个报告都有自己的“参数配置”。此问题中的类最终将用作报告的选择器。 我已经看到你的基类了。这是我在之前评论中解释的完美候选人。让您的 ReportControl 扩展 SkinnableComponent 而不是 UIComponent,将 ComboBoxMultiSelect 的可视化表示分离为基于 mxml 的外观类,并在扩展 ReportControl 的基于 as 的宿主组件中定义组件的属性和行为。它将消除必须手动管理布局并添加更多抽象的痛苦。 【参考方案1】:我将尝试举一个例子来说明我在上面的 cmets 中讨论过的 Spark 蒙皮架构的含义。这不是您问题的直接答案,但我认为您可能会觉得它很有趣。为了简洁起见,我必须使它比你的组件更简单一些,因为你似乎已经为你的问题去掉了一些代码,所以我不知道它应该做什么。
这将是一个组件,可让您通过单击按钮在正常状态和展开状态之间切换。首先,我们将创建皮肤类。通常你会先创建主机组件,但这样解释会更容易。
<!-- my.skins.ComboBoxMultiSelectSkin -->
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
height.normal="25" height.expanded="200">
<fx:Metadata>
[HostComponent("my.components.ComboBoxMultiSelect")]
</fx:Metadata>
<s:states>
<s:State name="normal" />
<s:State name="expanded" />
</s:states>
<s:layout>
<s:HorizontalLayout gap="0" />
</s:layout>
<s:ComboBox id="comboBox" />
<s:Button id="toggleButton"
icon.normal="@Embed('../Assets/Icons/plus-16.png')"
icon.expanded="@Embed('../Assets/Icons/minus-16.png')"/>
</s:Skin>
因此,我们已经完全设置了组件的外观和布局。你觉得你的头痛消失了吗?我觉得这很优雅。我们有两种状态,组件的高度将调整为当前选定的状态,Button 的图标也会调整。切换状态的方式和时间是组件行为,将在宿主组件中定义。
现在让我们用普通的 ActionScript 创建那个宿主组件。为此,我们将扩展 SkinnableComponent(请注意,如果这将扩展 SkinnableComponent 而不是 UIComponent,它也可以扩展您的 ReportControl)。
[SkinState("normal")]
[SkinState("expanded")]
public class ComboBoxMultiSelect extends SkinnableComponent
[SkinPart(required="true")]
public var toggleButton:IEventDispatcher;
[SkinPart(required="true")]
public var comboBox:ComboBox;
private var expanded:Boolean;
override protected function partAdded(partName:String, instance:Object):void
super.partAdded(partName, instance);
switch (instance)
case toggleButton:
toggleButton.addEventListener(MouseEvent.CLICK, handleToggleButtonClick);
break;
case comboBox:
comboBox.addEventListener(IndexChangeEvent.CHANGE, handleComboSelection);
break;
private function handleToggleButtonClick(event:MouseEvent):void
toggleExpanded();
private function handleComboSelection(event:IndexChangeEvent):void
//handle comboBox selection
protected function toggleExpanded():void
expanded = !expanded;
invalidateSkinState();
override protected function getCurrentSkinState():String
return expanded ? "expanded" : "normal";
好的,这里还有很多事情要做。
首先查看SkinState
元数据声明:当皮肤类分配给组件时,编译器将检查该皮肤是否实现了所需的状态。
那么SkinPart
声明:宿主组件上的属性名称必须与皮肤类中标签的id完全匹配。由于required
设置为true
,编译器将检查这些组件是否真的存在于皮肤中。如果您想要可选的皮肤部件,请将其设置为false
。
注意toggleButton
的类型是IEventDispatcher
:从宿主组件的角度来看,所有toggleButton
要做的就是调度CLICK 事件。这意味着我们现在可以使用<s:Image id="toggleButton" source="..." />
创建一个皮肤,并且整个事情将继续以相同的方式工作。看看这有多强大?
因为不是立即分配 skinpart 属性,所以我们覆盖了 partAdded()
方法,该方法将在组件可用时执行。在大多数情况下,这是您连接事件侦听器的地方。
在toggleExpanded()
方法中,我们像您问题中的组件一样切换布尔值,但是我们只会使皮肤状态无效。这将导致皮肤调用getCurrentSkinState()
方法并将其状态更新为返回的任何值。
等等!您有一个工作组件,其行为很好地分离到一个动作脚本类中,您不必担心布局的复杂性。如果您希望创建具有相同行为的组件,但它应该水平扩展而不是垂直扩展:只需创建一个调整 width
而不是 height
的新皮肤并将其分配给相同的主机组件。
哦,等等!我差点忘了告诉你如何将皮肤分配给组件。你可以内联:
<c:ComboBoxMultiSelect skinClass="my.skins.ComboBoxMultiSelectSkin" />
或通过样式:
@namespace c "my.components.*";
c|ComboBoxMultiSelect
skinClass: ClassReference("my.skins.ComboBoxMultiSelectSkin")
【讨论】:
【参考方案2】:突出的一点是您对updateDisplayList()
的实现。如您所知,这是您的组件应该调整其子对象的大小和位置(和/或进行任何程序绘图)的地方。
但与其直接设置子对象的宽度/高度,不如使用 Flex 生命周期方法之一:setActualSize()
或 setLayoutBoundsSize()
。将setLayoutBoundsSize()
与火花组件一起使用。
当您设置 Flex 组件的宽度/高度时,该组件将使其自身失效,以便在下一个更新周期中重新渲染它。但是由于您尝试在 updateDisplayList()
中呈现组件,因此您应该注意不要使此方法中的子对象无效。
setActualSize()
和 setLayoutBoundsSize()
方法设置 Flex 组件的宽度/高度,但不会使组件无效。
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
super.updateDisplayList(unscaledWidth, unscaledHeight);
_horizontalGroup.setLayoutBoundsSize(unscaledWidth, unscaledHeight);
// if you wanted to position objects, you would set their x/y coordinates
// here with the move() or setLayoutBoundsPosition() methods
注意,看起来一些子对象也在 createChildren()
中调整大小......在这种情况下,基本 Flex 组件是什么并不清楚(ReportControl
扩展了什么类?
以这种方式可能消除该渲染故障。与直接设置宽度/高度属性相比,它肯定会执行更少的代码。
[编辑]
这可能是与HGroup
的交互,这在此组件中是不必要的。虽然我认为以这种方式制作组件很有趣,但它可能更乏味......这就是@RIAStar 明智地指出另一种方法的原因。
一些进一步的想法,如果你想继续走这条路:
1) 看看您在 createChildren()
中所做的尺寸调整 - 例如,HGroup
被赋予了百分比宽度,但在 updateDisplayList()
中被赋予了固定宽度(这可能是红鲱鱼,但我不会设置百分比宽度)。
2) 您可能会在移除组件或重新添加组件之前欺骗组件进行自我验证。一种可能是浪费时间的怪异预感。
3) 从您的组件中删除“HGroup”。这有点不必要:布局要求很简单,只需几行 Actionscript 即可。随着布局要求变得更加复杂,您的里程会有所不同!
在createChildren()
中,将组合框和按钮直接添加到UIComponent
。然后在updateDisplayList()
中调整大小并定位它们,如下所示:
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
super.updateDisplayList(unscaledWidth, unscaledHeight);
var padding:Number = 10;
var gap:Number = 0;
// make the ComboBox consume all of the width execpt for 20px and gap + padding
var availableWidth:Number = unscaledWidth - 20 - gap - (2*padding);
_drp.setLayoutBoundsSize(availableWidth, unscaledHeight); // combo box 100% width
_btnMultiple.setLayoutBoundsSize(20, unscaledHeight); // button is 20px wide
// now position them ...
// probably should not use 0, rather calculate a Y coordinate that centers them
// in the unscaledHeight
_drp.setLayoutBoundsPosition(padding, 0);
_btnMultiple.setLayoutBoundsPosition(unscaledWidth - padding - 20, 0);
【讨论】:
谢谢 Sunil D,我会试试的。ReportControl
基类直接扩展 UIComponent。并包含最简单的简单 getter 和 setter。我会发布它的代码。
我用更多想法编辑了我的答案。但是,您可能需要考虑@RIAStar 的明智建议 :)
我看到您的帖子并尝试手动定位控件而不是 HGroup。我让它正确显示,但我仍然看到图标问题。我已经进行了一些测试,并且能够将这种奇怪的行为限制为只是一个带有图标的按钮,该按钮被删除并重新添加到显示列表中。我认为当一个按钮被重新添加时,它的样式需要应用多个渲染事件。【参考方案3】:
非常感谢两位的回答!解释概念时对细节的考虑和关注非常棒!特雷斯比恩!
@RIAstar 然而,由于已经存在大量代码,更改我的架构(将视觉元素与行为元素分开)将强制对代码进行大量重构,并且对于尚未实现的功能将花费很多明确要求。 (能够在运行时更改的控件的可视化表示)这当然很有趣,我将把它添加到未来的版本中。
也就是说,我想我已经找到了解决问题的方法。我决定以@SunilD. 的建议为基础,即在重新添加控件之前对其进行验证。我知道这是一个 hack,但人类并不完美,因此代码也不是。 ;-)
查看控件时,我注意到只有按钮在呈现其图像时出现问题。所以为了测试,我添加并删除了 JUST 一个带有图标的按钮实例,我看到了相同的行为! (不管comboBoxMultiSelect 是如何实现的)我ALSO 注意到在第一次创建按钮时我没有看到它执行此操作。那么,当按钮从显示列表中移除时,为什么不直接重建按钮呢?
我最终将comboBoxMultiSelect
连接到FlexEvent.REMOVE
事件,销毁按钮引用,创建一个新引用,然后使用 AddChild() 将其重新添加。下面是对事件的解释。
"当组件作为内容从容器中移除时调度 通过使用 removeChild()、removeChildAt()、removeElement() 或 removeElementAt() 方法。如果组件从 使用 rawChildren.removeChild() 将容器作为非内容子项 或 rawChildren.removeChildAt() 方法,事件不会被调度。
只有在有一个或多个相关事件时才会调度此事件 附加到调度对象的侦听器。”
果然,这修复了图标显示不正确并解释了发生的情况。出于某种原因,按钮在重新添加时需要多个渲染事件来应用其样式。还有其他人能够复制这种行为吗?
我想现在真正的问题是“在显示列表中删除和重新添加按钮的最佳方法是什么,使其嵌入的图标不受影响?”
【讨论】:
以上是关于自定义复合控件在添加回 VGROUP 后仅 0.5-1 秒无法正确呈现的主要内容,如果未能解决你的问题,请参考以下文章