0

我正在远离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;
    }
}

当我在 using 中添加控件时vgParameters.addElement(control)comboBoxMultiSelect无法正确呈现。plusIcon按钮内部最初_btnMultiple没有正确定位,但在大约 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;
    }

}
4

3 回答 3

1

突出的一件事是您的updateDisplayList(). 如您所知,这是您的组件应该调整其子对象的大小和位置(和/或进行任何程序绘图)的地方。

但与其直接设置子对象的宽度/高度,不如使用 Flex 生命周期方法之一:setActualSize()setLayoutBoundsSize(). setLayoutBoundsSize()与火花组件一起使用。

当您设置 Flex 组件的宽度/高度时,该组件将使其自身失效,以便在下一个更新周期中重新渲染它。但是,由于您尝试在其中呈现组件,updateDisplayList()因此应注意不要使此方法中的子对象无效。

setActualSize()和方法设置 Flex 组件的setLayoutBoundsSize()宽度/高度,但不会使组件无效。

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);
}
于 2012-06-16T00:00:30.557 回答
1

我将尝试为您提供一个示例,说明我们在上面的评论中讨论的 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" width="100%" />
    <s:Button id="toggleButton" width="20"
              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.
  • 请注意,类型toggleButtonIEventDispatcher: 从宿主组件的角度来看,所有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")
}
于 2012-06-16T08:40:58.847 回答
0

非常感谢你们的回答!解释概念时对细节的考虑和关注非常棒!特雷斯比恩!

@RIAstar 但是,由于已经存在大量代码,因此更改我的架构(将视觉元素与行为元素分开)将强制对代码进行大量重构,并且对于尚未明确请求的功能而言,成本会很高。(能够在运行时更改的控件的可视化表示)这当然很有趣,我将把它添加到未来的版本中。

也就是说,我认为我已经能够找到解决问题的方法。我决定以@SunilD. 的建议为基础,即在重新添加控件之前对其进行验证。我知道这是一个 hack,但人类并不完美,因此代码也不是。;-)

查看控件时,我注意到只有按钮在渲染图像时出现问题。所以为了测试,我添加和删除了一个带有图标的按钮实例,我看到了同样的行为(不管comboBoxMultiSelect是如何实现的)我还注意到,在第一次创建按钮时我没有看到按钮执行此操作。那么,当按钮从显示列表中移除时,为什么不直接重建按钮呢?

我最终连接comboBoxMultiSelectFlexEvent.REMOVE事件,销毁按钮引用,创建一个新的,然后用 AddChild() 重新添加它。下面是对事件的解释。

“使用 removeChild()、removeChildAt()、removeElement() 或 removeElementAt() 方法将组件作为内容子项从容器中移除时调度。如果使用rawChildren.removeChild() 或 rawChildren.removeChildAt() 方法,事件不会被调度。

仅当有一个或多个相关侦听器附加到调度对象时才会调度此事件。”

果然,这修复了图标显示不正确并解释了发生的情况。由于某种原因,按钮在重新添加时需要多个渲染事件来应用其样式。还有其他人能够复制这种行为吗?

I guess the real question now is "what is the best way to remove and add-back-in a button to the display list, so that its embedded icon is unaffected?"

于 2012-06-19T16:54:22.450 回答