如何使用mxml继承状态?

9
我有一个名为AdvancedPanel的面板组件,带有controlBarContent属性:
<!-- AdvancedPanel.mxml -->
<s:Panel>
  <s:states>
    <s:State name="normal" />
    <s:State name="edit" />
  </s:states>
  <s:controlBarContent>
    <s:Button 
      includeIn="edit"
      label="Show in edit"
      />
    <s:Button 
      label="Go to edit"
      click="{currentState='edit'}"
      />
  </s:controlBarContent>
</s:Panel>

我创建了第二个面板,称为CustomAdvancedPanel,基于AdvancedPanel,因为我不想重新声明controlBarContent。
<!-- CustomAdvancedPanel.mxml -->
<local:AdvancedPanel>
  <s:Button includeIn="edit" label="Extra edit button" />
</local:AdvancedPanel>

这样做不起作用,因为CustomAdvancedPanel中的“edit”状态未按照编译器的要求声明。我必须在CustomAdvancedPanel.mxml中重新声明edit状态如下:

  <!-- CustomAdvancedPanel.mxml with edit state redeclared -->
    <local:AdvancedPanel>
      <local:states>
        <s:State name="normal" />
        <s:State name="edit" />
      </local:states>
      <s:Button includeIn="edit" label="Extra edit button" />
    </local:AdvancedPanel>

在应用程序组件中使用CustomAdvancedPanel会显示一个空面板和“转到编辑”按钮。但是当我点击它时,“额外的编辑按钮”变为可见,但控制栏内的“在编辑中显示”按钮不可见。
当CustomAdvancedPanel为空时,没有重新声明状态和“额外编辑按钮”,该面板运行正常。
我认为这是因为在AdvancedPanel中声明的State对象与CustomAdvancedPanel不同,所以状态不同,即使它们具有相同的名称。然而,我不能在mxml中使用AdvancedPanel的状态来内置CustomAdvancedPanel而不重新声明它们。
是否有任何方法实现这种状态重用?或者有没有更好的方法来获得相同的结果?

+1 对于措辞得当的问题,并提供示例。 - JeffryHouser
6个回答

2
我建议你使用Spark的皮肤架构来实现你的目标。因为皮肤状态会在宿主组件中继承,你可以以面向对象的方式将所有逻辑放置在其中。但是皮肤仍然会包含重复的代码 :( 不管怎样,这比所有组件的重复代码要好。所以我们的AdvancedPanel将如下所示:
package
{
    import flash.events.MouseEvent;

    import spark.components.supportClasses.ButtonBase;
    import spark.components.supportClasses.SkinnableComponent;

    [SkinState("edit")]
    [SkinState("normal")]
    public class AdvancedPanel extends SkinnableComponent
    {
        [SkinPart(required="true")]
        public var goToEditButton:ButtonBase;
        [SkinPart(required="true")]
        public var showInEditButton:ButtonBase;

        private var editMode:Boolean;

        override protected function getCurrentSkinState():String
        {
            return editMode ? "edit" : "normal";
        }

        override protected function partAdded(partName:String, instance:Object):void
        {
            super.partAdded(partName, instance);
            if (instance == goToEditButton)
                goToEditButton.addEventListener(MouseEvent.CLICK, onGoToEditButtonClick);
        }

        override protected function partRemoved(partName:String, instance:Object):void
        {
            super.partRemoved(partName, instance);
            if (instance == goToEditButton)
                goToEditButton.removeEventListener(MouseEvent.CLICK, onGoToEditButtonClick);
        }

        private function onGoToEditButtonClick(event:MouseEvent):void
        {
            editMode = true;
            invalidateSkinState();
        }
    }
}

对于CustomAdvancedPanel:

package
{
    import spark.components.supportClasses.ButtonBase;

    public class CustomAdvancedPanel extends AdvancedPanel
    {
        [SkinPart(required="true")]
        public var extraEditButton:ButtonBase;
    }
}

当然,您可以继承Panel类,但我编写的示例代码更简单。
还有皮肤:
<?xml version="1.0" encoding="utf-8"?>
<!-- AdvancedPanelSkin.mxml -->
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">
    <fx:Metadata>
        [HostComponent("AdvancedPanel")]
    </fx:Metadata>
    <s:states>
        <s:State name="normal" />
        <s:State name="edit" />
    </s:states>
    <s:Panel left="0" right="0" top="0" bottom="0">
        <s:controlBarContent>
            <s:Button id="showInEditButton" label="Show in edit" includeIn="edit" />
            <s:Button id="goToEditButton" label="Go to edit" />
        </s:controlBarContent>
    </s:Panel>
</s:Skin>

并且:

<?xml version="1.0" encoding="utf-8"?>
<!-- CustomAdvancedPanelSkin.mxml -->
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">
    <fx:Metadata>[HostComponent("CustomAdvancedPanel")]</fx:Metadata>
    <s:states>
        <s:State name="normal" />
        <s:State name="edit" />
    </s:states>
    <s:Panel left="0" right="0" top="0" bottom="0">
        <s:Button includeIn="edit" label="Extra edit button" id="extraEditButton" />
        <s:controlBarContent>
            <s:Button id="showInEditButton" label="Show in edit" includeIn="edit" />
            <s:Button id="goToEditButton" label="Go to edit" />
        </s:controlBarContent>
    </s:Panel>
</s:Skin>

1
据我所知,组件的状态不会传递到继承的组件中。想一想 - 如果是这种情况(如果您可以继承状态),那么每当您想要扩展组件时,它都会使生活变得非常复杂;您必须了解所有继承的状态,并且不要踩到它们的脚趾头。

1
在CustomAdvancedPanel中添加<mx:DataGrid dataProvider="{new ArrayCollection(states)}" />,而不需要重新声明states,可以返回AdvancedPanel的三个状态(normal、edit、disabled)。然而,在mxml中无法使用它们,因为编译器会说它们不存在,但是正如datagrid所建议的那样,它们确实存在。 - Treur

0
“或者有更好的方法来获得相同的结果吗?”
既然你提出了问题,而且因为你没有清楚地说明需要额外的CustomAdvancedPanel组件,把“额外编辑按钮”放在AdvancedPanel组件中是最简单的解决方案。
<!-- AdvancedPanel.mxml -->
<s:Panel>
  <s:states>
    <s:State name="normal"/>
    <s:State name="edit"/>
  </s:states>
  <s:Button includeIn="edit" label="Extra edit button"/>
  <s:controlBarContent>
    <s:Button 
      includeIn="edit"
      label="Show in edit"/>
    <s:Button 
      label="Go to edit"
      click="{currentState='edit'}"/>
  </s:controlBarContent>
</s:Panel>

0

我认为这是面向对象编程的限制,但我不确定具体是什么。虽然我不是 Flex 专家,但我从面向对象编程的角度考虑过,并且这是我认为会发生的情况:

首先考虑当您创建一个对象时,Flex(或任何面向对象的语言)会自动创建该对象的副本和其父对象的私有副本,进而创建其父对象的私有副本,以此类推直到整个对象树。这听起来可能很奇怪,但例如在构造函数中使用 super() 您实际上是在调用父类的构造函数。

Flex 有所谓的“属性”。这相当于 Java 中具有公共 getter 和 setter 方法的私有成员字段(变量)。当您声明它们时,它们只是对象的属性,在内部被视为 get/set 函数对待。

<local:states>xyz</local:states>

你实际上是在说

states = xyz

这实际上相当于AS的说法

setStates(xyz)

重要的部分,这是关于属性的一般规则,setStates是一个公共方法,任何人都可以调用它。然而,states数组本身是私有的。如果你不声明一个,CustomAdvancedPanel就没有states属性。它也没有setStates或getStates方法。然而,由于setStates/getStates是公共的,它从AdvancedPanel继承了它们,所以它的功能就像它有这些方法一样。当你调用其中一个方法(获取或设置状态数组)时,它实际上调用存在的方法,在其父对象AdvancedPanel中。当AdvancedPanel执行该方法时,读取或设置AdvancedPanel本身的状态数组的值。这就是为什么当你在CustomAdvancedPanel中不重新声明任何状态时,一切都能完美地工作 - 你认为你正在设置和获取CustomAdvancedPanel中的状态数组,但实际上在幕后你正在操作AdvancedPanel父对象中的状态数组,这是完全正常和好的。

现在你在CustomAdvancedPanel中重新定义了states数组 - 发生了什么?请记住,在Flex中声明属性就像声明一个私有类级变量和公共的getter和setter一样。因此,您为CustomAdvancedPanel提供了一个名为states的私有数组,并提供了公共的获取器和设置器来获取/设置该数组。这些getter和setter将覆盖AdvancedPanel中的getter和setter。因此,现在您的应用程序将与CustomAdvancedPanel进行相同的交互,但在幕后,您不再使用AdvancedPanel的方法/变量,而是使用您在CustomAdvancedPanel中声明的内容。这就解释了为什么当您更改CustomAdvancedPanel的状态时,从AdvancedPanel继承的部分不会响应,因为它的显示与AdvancedPanel中的states数组相关联,该数组仍然独立存在。

那么为什么在不重新声明states的基本示例中不允许includeIn呢?我不知道。可能是一个bug,或者更有可能的是,由于某种合理的语言/OO原因,它永远无法工作。

我自己也不知道为什么这会发生,考虑到所涉及到的Button是超类的一部分。一些有趣的测试包括:

将点击处理程序移入实际的公共方法而不是内联。 在单击处理程序中添加 super.currentState='edit'。 如果想要学习更多有关继承的知识,请编写一些简单的 ActionScript 或 Flex 类,其中一个类继承另一个类,并运行各种函数调用以了解发生了什么。

0
当然,政治正确的方法是使用皮肤。但是,对于那些真的只想为MXML类强制实现状态继承的人,我找到了一个解决方法。
要让这种方法起作用,扩展的MXML类应该声明与基本MXML类完全相同的状态,既不多也不少,并且所有状态的名称都相同。
然后,在扩展类中插入以下方法:
        override public function set states(value:Array):void
        {
            if(super.states == null || super.states.length == 0)
            {
                super.states  = value;

                for each (var state:State in value)
                {
                    state.name = "_"+state.name;
                }
            }
            else
            {
                for each (var state:State in value)
                {
                    state.basedOn = "_"+state.name;
                    super.states.push(state);
                }
            }
        }

这个方法的原理是,在组件创建时,状态变量被设置了两次,一次是由基类设置的,另一次是由扩展类设置的。这个解决方法只是将它们合并在一起。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接