如何用mxml继承状态?

时间:2010-10-18 14:20:30

标签: flex flex4 mxml states

我有以下面板组件,名为AdvancedPanel with 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中重新声明编辑状态,如下所示:

  <!-- 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会显示一个带有“Go to edit”按钮的空面板。但是当我点击它时,“额外编辑按钮”变得可见,但是控制栏内的“在编辑中显示”按钮没有。

当CustomAdvancedPanel为空,没有重新声明的状态和“额外编辑按钮”时,面板工作正常。

我认为这是因为在AdvancedPanel中声明的State对象与CustomAdvancedPanel不同,因此状态不同,即使它们具有相同的名称。然而。我不能在CustomAdvancedPanel中使用AdvancedPanel的状态而不用(重新)在mxml中声明它们。

有没有办法实现这种状态重用?或者有更好的方法来获得相同的结果吗?

6 个答案:

答案 0 :(得分:2)

我建议你使用Spark的皮肤架构来实现你的目标。因为皮肤状态是在主机组件中继承的,所以您可以将所有逻辑放在OOP方式中。但皮肤仍然会包含重复的代码:(无论如何,它比所有组件的重复代码更好。

所以我们的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)

AFAIK组件的状态不会交叉到继承的组件。想一想 - 如果是这种情况(如果你可以继承状态)那么只要你想扩展一个组件,它就会让生活变得非常复杂;你必须要知道所有继承的状态而不是踩到他们的脚趾。

答案 2 :(得分:0)

我认为它是OO编程的限制,但不确定究竟是什么。我不是Flex专家,但我从面向对象的编程角度考虑过这个问题,而这就是我认为发生的事情:

首先考虑在创建对象时,Flex(或任何OO语言)会自动创建该对象的副本及其父对象的私有副本,从而创建其父对象的私有副本,依此类推整个对象树。这可能听起来很奇怪,但作为一个例子,当你在构造函数中编写super()时,你正在调用父类的构造函数。

Flex拥有所谓的“属性”。这相当于Java中使用公共getter和setter方法的私有成员字段(变量)。当你宣布

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

你实际上在说

states = xyz

反过来又相当于AS

setStates(xyz)

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

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

那么为什么不在重新声明状态的基本示例中允许includeIn?我不知道。要么它是一个错误,要么更有可能是一个合法的语言/ OO原因,它为什么它永远不会工作。

我的解释可能并不完全准确。就我所理解的事情而言。考虑到有问题的Button是超类的一部分,我自己也不知道为什么会发生这种情况。一些有趣的测试将是:

  1. 将点击处理程序移动到实际的公共方法而不是内联。
  2. 将super.currentState ='edit'添加到点击处理程序。
  3. 如果您想了解有关所有这些继承内容的更多信息,请在ActionScript或Flex中编写一些简单的类,其中一个类继承另一个类,并运行各种函数调用以查看发生的情况。

答案 3 :(得分: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>

答案 4 :(得分:0)

Assaf Lavie是对的,如果自定义组件具有其父级状态,则会非常混乱。我会说考虑使用皮肤:

答案 5 :(得分: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);
                }
            }
        }

这是有效的,因为在创建组件时,states变量设置两次,一次由基类设置,一次由扩展类设置。这种解决方法只是将它们组合在一起。