flex - unidos - requisitos para cobrar una herencia
Cómo heredar estados con mxml? (6)
"¿O hay una mejor manera de obtener el mismo resultado?"
Como usted lo solicitó, y debido a que no presentó un caso claro en cuanto a la necesidad del componente CustomAdvancedPanel adicional, poner el "botón de edición adicional" en el componente AdvancedPanel es la solución más simple.
<!-- 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>
Tengo el siguiente componente de panel llamado AdvancedPanel con 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>
Creé un segundo panel, llamado CustomAdvancedPanel basado en AdvancedPanel, ya que no quiero redeclarar el controlBarContent
<!-- CustomAdvancedPanel.mxml -->
<local:AdvancedPanel>
<s:Button includeIn="edit" label="Extra edit button" />
</local:AdvancedPanel>
Esto no funciona, porque el estado ''editar'' en CustomAdvancedPanel no se declara de acuerdo con el compilador. Debo redeclarar el estado de edición en CustomAdvancedPanel.mxml de la siguiente manera:
<!-- 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>
El uso del CustomAdvancedPanel dentro de un componente de aplicación muestra un panel vacío con el botón "Ir a editar". Pero cuando hago clic en él, el "botón de edición adicional" se vuelve visible, pero el botón "Mostrar en edición" dentro de la barra de control no.
Cuando CustomAdvancedPanel está vacío, sin estados redeclarados y "Botón de edición adicional", el panel funciona bien.
Creo que es porque el objeto State declarado en AdvancedPanel no es lo mismo que CustomAdvancedPanel, por lo que el estado es diferente, incluso si tienen el mismo nombre. Sin embargo. No puedo usar los estados de AdvancedPanel dentro de CustomAdvancedPanel sin (re) declararlos en mxml.
¿Hay alguna forma de lograr este tipo de reutilización de estado? ¿O hay una mejor manera de obtener el mismo resultado?
AFAIK el estado del componente no se cruza con los componentes heredados. Piénselo: si ese fuera el caso (si pudiera heredar estados), la vida sería realmente complicada siempre que quiera extender un componente; tendrías que estar al tanto de todos los estados heredados y no ponerte de puntillas.
Assaf Lavie tiene razón, sería muy confuso si un componente personalizado tuviera los estados de sus padres. Yo diría que consideres usar máscaras:
Creo que es una limitación de la programación OO, pero no estoy seguro de qué exactamente. No soy experto en Flex, pero lo pensé desde un punto de vista de programación orientada a objetos y esto es lo que creo que sucede:
Primero considere que cuando crea un objeto, Flex (o cualquier lenguaje OO) crea automáticamente una copia de ese objeto Y una copia privada de su objeto padre, que a su vez crea una copia privada de su objeto padre y así sucesivamente hasta el objeto completo árbol. Puede sonar raro, pero como ejemplo de esto, cuando escribes super () en un constructor estás llamando al constructor de la clase padre.
Flex tiene lo que llama "propiedades". Esto es el equivalente de lo que en Java sería un campo de miembro privado (variable) con un método getter y setter público. Cuando declaras
<local:states>xyz</local:states>
estás diciendo efectivamente
states = xyz
que a su vez es el AS equivalente a decir
setStates(xyz)
La parte importante, y esta es una regla general sobre las propiedades, es que setStates es un método público, cualquiera puede llamarlo. Sin embargo, la matriz de estados en sí es privada. Si no declara uno, CustomAdvancedPanel no tiene propiedad de estados. Tampoco tiene un método setStates o getStates. Sin embargo, como setStates / getStates son públicos, los hereda de AdvancedPanel para que funcionen como si tuvieran estos métodos. Cuando llama a uno de estos métodos (obtiene o establece la matriz de estados), en realidad llama al método donde existe , que está en su objeto principal, AdvancedPanel. Cuando AdvancedPanel ejecuta el método, el valor de la matriz de estados en AdvancedPanel se lee o establece. Esta es la razón por la que cuando no redeclara ningún estado en CustomAdvancedPanel, todo funciona perfectamente: cree que está configurando y obteniendo la matriz de estados en CustomAdvancedPanel pero, de hecho, detrás de las escenas está operando en la matriz states en el objeto principal AdvancedPanel, que es perfectamente bien y bien.
Ahora redefine la matriz de estados en CustomAdvancedPanel: ¿qué está pasando? Recuerde que declarar una propiedad en Flex es como declarar una variable privada de nivel de clase y getters y setters públicos. Por lo tanto, le está dando a CustomAdvancedPanel una matriz privada llamada estados y getters y setters públicos para obtener / establecer esa matriz. Estos getters y setters anularán los de AdvancedPanel. Así que ahora su aplicación interactuará con CustomAdvancedPanel de la misma manera, pero detrás de las escenas ya no está operando en los métodos / variables de AdvancedPanel, sino más bien en los que ha declarado en CustomAdvancedPanel. Esto explica por qué cuando cambia el estado de CustomAdvancedPanel, la parte que se hereda de AdvancedPanel no reacciona, ya que su visualización está vinculada a la matriz de estados en AdvancedPanel, que todavía existe de forma independiente.
Entonces, ¿por qué no se permite el includeIn en el ejemplo básico en el que no se redeclaran los estados? No lo sé. O es un error, o tal vez más probable, hay un idioma legítimo / OO por lo que nunca podría funcionar.
Es posible que mi explicación no sea totalmente precisa. Eso es lo que entiendo las cosas. Yo mismo no sé por qué eso sucedería realmente teniendo en cuenta que el botón en cuestión es parte de la superclase. Un par de pruebas interesantes serían:
- mueva el manejador de clic a un método público real en lugar de en línea.
- agregue super.currentState = ''edit'' al manejador de clics.
Si desea obtener más información acerca de todo este material de herencia, escriba algunas clases simples en ActionScript o Flex con una clase heredando otra y ejecute varias llamadas a funciones para ver qué sucede.
Le sugiero que use la arquitectura de despellejamiento de Spark para obtener sus objetivos. Debido a que los estados de la piel se heredan en el componente de host, puede colocar toda la lógica en modo OOP. Pero las máscaras aún contendrán código duplicado :( De todos modos, es mejor que duplicar el código de todo el componente.
Entonces nuestro Panel Avanzado se verá así:
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();
}
}
}
Y para CustomAdvancedPanel:
package
{
import spark.components.supportClasses.ButtonBase;
public class CustomAdvancedPanel extends AdvancedPanel
{
[SkinPart(required="true")]
public var extraEditButton:ButtonBase;
}
}
Por supuesto, puede heredar de la clase Panel pero hice el código de muestra más simple.
Y las pieles:
<?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>
Y:
<?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>
Por supuesto, la forma políticamente correcta es usar máscaras. Sin embargo, para aquellos que realmente solo quieren la herencia del estado de fuerza bruta para las clases de MXML aquí hay un trabajo que he encontrado.
Para que este método funcione, la clase MXML extendida debe declarar exactamente los mismos estados de la clase MXML base, ni más ni menos, todos con nombres idénticos.
Luego, en la clase extendida, inserte el siguiente método:
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);
}
}
}
Esto funciona porque a medida que se crea el componente, la variable de estados se establece dos veces, una vez por la clase base y una vez por la clase extendida. Esta solución simplemente los combina.