自定义复合控件在添加回 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);
      
   

ReportControlcomboBoxMultiSelect 的基类,如下所示。 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 事件。这意味着我们现在可以使用&lt;s:Image id="toggleButton" source="..." /&gt; 创建一个皮肤,并且整个事情将继续以相同的方式工作。看看这有多强大? 因为不是立即分配 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 秒无法正确呈现的主要内容,如果未能解决你的问题,请参考以下文章

C# 自定义控件VS用户控件

Listview 添加第二项后仅显示一项

自定义控件的子控件支持设计模式

VS2012 C#设计程序界面时以下这种按钮怎么添加

MFC如何添加自定义控件

AVPlayerViewController 播放速度的自定义控制