如何用mxml继承状态?

发布于 2024-09-27 14:42:29 字数 1603 浏览 1 评论 0原文

我有以下名为 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 将显示一个带有“转到编辑”按钮的空面板。但是当我单击它时,“额外编辑按钮”变得可见,但 controlBar 内的“在编辑中显示”按钮不可见。

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

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

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

I have the following panel component called 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>

I created a second panel, called CustomAdvancedPanel based on the AdvancedPanel since I don't want to redeclare the controlBarContent

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

This doesn't work, because the 'edit' state in CustomAdvancedPanel isn't declared according to the compiler. I have to redeclare the edit state in CustomAdvancedPanel.mxml as follows:

  <!-- 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>

Using the CustomAdvancedPanel inside an application component shows an empty panel with the "Go to edit" button. But when I click it, the "Extra edit button" becomes visible, but the "Show in edit" button inside the controlBar doesn't.

When the CustomAdvancedPanel is empty, without redeclared states and "Extra edit button" the panel works just fine.

I think it is because the State object declared in AdvancedPanel isn't the same as CustomAdvancedPanel, so the state is different, even if they have the same name. However. I can't use the states of AdvancedPanel inside CustomAdvancedPanel without (re)declare them in mxml.

Is there any way to achieve this kind of state-reuse? Or is there a better way to obtain the same result?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

纸短情长 2024-10-04 14:42:29

我建议您使用 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>

I suggest you to use Spark's skinning architecture to obtain your goals. Because skin states are inherited in host component you can place all the logic in OOP way. But skins will still contain duplicate code :( Anyway it is better than duplicate code of all the component.

So our AdvancedPanel will look like the following:

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();
        }
    }
}

And for CustomAdvancedPanel:

package
{
    import spark.components.supportClasses.ButtonBase;

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

Of course you can inherit from Panel class but I made sample code more simple.

And the skins:

<?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>

And:

<?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>
十年不长 2024-10-04 14:42:29

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

AFAIK the component's state does not cross over to inherited components. Think about it - if that were the case (if you could inherit states) then it would make life really complicated whenever you want to extend a component; you would have to be aware of all inherited states and not step on their toes.

无声静候 2024-10-04 14:42:29

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

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

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

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

你实际上是

states = xyz

在说,AS 相当于说,

setStates(xyz)

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

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

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

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

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

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

I reckon it's a limitation of OO programming, but not sure what exactly. I'm no Flex expert but I thought about it from an object-oriented programming point of view and here's what I think happens:

First consider that when you create an object, Flex (or any OO language) automatically creates a copy of that object AND a private copy of its parent object, which in turn creates a private copy of its parent object and so on up the entire object tree. That might sound weird but as an example of this, when you write super() in a constructor you are calling the constructor of the parent class.

Flex has what it calls "properties". This is the equivalent of what in Java would be a private member field (variable) with a public getter and setter method. When you declare

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

you are effectively saying

states = xyz

which in turn is the AS equivalent of saying

setStates(xyz)

The important part, and this is a general rule about properties, is that setStates is a public method, anyone can call this. However the states array itself is private. If you don't declare one, CustomAdvancedPanel has no states property. Neither does it have a setStates or getStates method. However as setStates/getStates are public, it inherits them from AdvancedPanel so it funcions as if it has these methods. When you call one of these methods (get or set the states array), it actually calls the method where it exists, which is in its parent object, AdvancedPanel. When AdvancedPanel executes the method the value of the states array in AdvancedPanel itself is read or set. This is why when you don't redeclare any states in CustomAdvancedPanel everything works perfectly - you think you are setting and getting the states array in CustomAdvancedPanel but in fact behind the scenes you are operating on the states array in the AdvancedPanel parent object, which is perfectly fine and good.

Now you redefine the states array in CustomAdvancedPanel - what is happening? Remember that declaring a property in Flex is like declaring a private class-level variable and public getters and setters. So you are giving CustomAdvancedPanel a private array called states and public getters and setters to get/set that array. These getters and setters will override the ones from AdvancedPanel. So now your application will interact with CustomAdvancedPanel the same way but behind the scenes you are no longer operating on the methods/variables of AdvancedPanel but rather on the ones you have declared in CustomAdvancedPanel itself. This explains why when you change the state of CustomAdvancedPanel, the part that is inherited from AdvancedPanel does not react, since its display is linked to the states array in AdvancedPanel, which still exists independently.

So why isn't the includeIn allowed in the basic example where you don't redeclare the states? I don't know. Either it's a bug, or perhaps more likely, there's a legitimate language/OO reason why it could never work.

It's possible that my explanation is not totally accurate. That's as far as I understand things. I myself don't know why that would really happen considering the Button in question is part of the superclass. A couple of interesting tests would be:

  1. move the click handler into an actual public method instead of inline.
  2. add super.currentState='edit' to the click handler.

If you want to learn more about all this inheritance stuff, write some simple classes in ActionScript or Flex with one class inheriting another, and run various function calls to see what happens.

心的憧憬 2024-10-04 14:42:29

“或者有更好的方法来获得相同的结果吗?”

既然您问了,并且由于您没有明确说明是否需要额外的 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>

"Or is there a better way to obtain the same result?"

Since you asked, and because you didn't make a clear case as to the need for the extra CustomAdvancedPanel component, putting the "Extra edit button" in the AdvancedPanel component is the simplest solution.

<!-- 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>
恰似旧人归 2024-10-04 14:42:29

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

Assaf Lavie is right, it would be very confusing, if a custom component had its parent's states. i'd say consider using skins:

坏尐絯℡ 2024-10-04 14:42:29

当然政治正确的做法是使用皮肤。然而,对于那些真正只想强制 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);
                }
            }
        }

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

Of course the politically correct way is to use skins. However, for those who really just want to brute force state inheritance for MXML classes here is a work around that I have found.

For this method to work, the extending MXML class should declare exactly the same states of the base MXML class, no more and no fewer, all with identical names.

Then in the extending class insert the following method:

        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);
                }
            }
        }

This works because as the component is created the states variable is set twice, once by the base class, and once by the extending class. This workaround just combines them together.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文