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>

我基于 AdvancedPanel 创建了第二个面板,称为 CustomAdvancedPanel,因为我不想重新声明 controlBarContent

<!-- CustomAdvancedPanel.mxml -->
<local:AdvancedPanel>
  <s:Button includeIn="edit" label="Extra edit button" />
</local:AdvancedPanel>

这不起作用,因为 CustomAdvancedPanel 中的“编辑”状态没有根据编译器声明。我必须在 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”按钮的空面板。但是当我单击它时,“额外的编辑按钮”变得可见,但 controlBar 内的“在编辑中显示”按钮不可见。

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

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

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

4

6 回答 6

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>
于 2011-03-30T15:33:31.167 回答
1

AFAIK 组件的状态不会跨越到继承的组件。想想看——如果是这样的话(如果你可以继承状态),那么每当你想扩展一个组件时,它会让生活变得非常复杂;您必须了解所有继承的状态,而不是踩到它们的脚趾。

于 2010-10-18T23:04:01.910 回答
0

我认为这是 OO 编程的限制,但不确定到底是什么。我不是 Flex 专家,但我是从面向对象编程的角度考虑的,我认为会发生以下情况:

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

Flex 有它所谓的“属性”。这相当于 Java 中具有公共 getter 和 setter 方法的私有成员字段(变量)。当你声明

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

你实际上是在说

states = xyz

这反过来又相当于说

setStates(xyz)

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

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

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

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

  1. 将点击处理程序移动到实际的公共方法而不是内联方法中。
  2. 将 super.currentState='edit' 添加到点击处理程序。

如果您想了解有关所有这些继承的更多信息,请在 ActionScript 或 Flex 中编写一些简单的类,其中一个类继承另一个类,然后运行各种函数调用以查看会发生什么。

于 2010-10-20T15:28:40.333 回答
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>
于 2010-11-09T09:03:18.673 回答
0

Assaf Lavie 是对的,如果自定义组件具有其父级的状态,那将非常令人困惑。我想说考虑使用皮肤:

于 2011-04-11T05:46:59.103 回答
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);
                }
            }
        }

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

于 2013-10-27T16:07:00.420 回答