Ok, so I think I am working off an unintended path for skins. But here it is. I am building an AIR app using the Flex 4 sdk. I though I would be cute and start with a panel as the base for my UI. I just needed to add a couple buttons to the title bar of the panel ( titlewindow like). So I thought hey this will be great I will just extend PanelSkin and add my buttons. This of course didnt work. I can extend the skin and as long as I don't add any visual controls everything is fine. As soon as I add a group or rect, then I override the default content of the parent and the PanelSkin goes away.
So I set up my buttons in the declarations block and am trying to find out when the right place in the skin's lifecycle is to add them in. Normally I'd say this is in createChildren, but we are not supposed to use that in Flex 4. I tried updateDisplay but I can't seem to get a reference to the actual entity I want to add the buttons to. Not to mention that I can't use addChild and if I use addElement then the app wont launch.
So the real question is how do I make this work. I am sure no one intended this workflow, but I want it. I know I could copy the code from PanelSkin to start my own, but thats not right. What if you guys fix a bug in PanelSkin or something, I just want to position a couple controls in the corner of your panel.
That being said, am I perhaps going at this the wrong way? Should I be using composition instead of inheritence?
I'd appreciate any help.
Thanks,
=Sim
Simeon - Excellent, excellent question.
Subclassing skins (or any MXML component for that matter) is not a supported or recommended practice in Flex. Instead, there are two options available for your panel skin workflow: copy/paste, or compositing. With copy/paste, you would copy the packaged PanelSkin.mxml file into a new skin file and augment it as you see fit (add in your buttons, etc). With compositing, basically you put a PanelSkin inside your custom skin and proxy out the parts.
Check out the attached examples. PanelSkinTest.mxml instantiates a Spark Panel where a custom skin is used for that Panel. The custom skin, MyPanelSkin.mxml, composites in a default Spark PanelSkin and proxies out the expected parts of a Panel.
Hope this makes sense and is helpful.
Cheers,
Deepa Subramaniam
Flex Framework Engineer
Gr, attaching files doesn't seem to be working too well for me. Instead, here is the code:
PanelSkinTest.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768">
<s:layout>
<s:VerticalLayout />
</s:layout>
<s:Panel title="Default Skin" width="200" height="200">
<s:layout>
<s:VerticalLayout />
</s:layout>
<s:Button label="Button"/>
<s:CheckBox label="CheckBox" />
</s:Panel>
<s:Panel title="Custom Skin" width="200" height="200" skinClass="MyPanelSkin">
<s:layout>
<s:VerticalLayout />
</s:layout>
<s:Button label="Button"/>
<s:CheckBox label="CheckBox" />
</s:Panel>
</s:Application>
MyPanelSkin.mxml
<?xml version="1.0" encoding="utf-8"?>
<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/halo"
alpha.disabled="0.5">
<fx:Script>
<![CDATA[
import spark.primitives.SimpleText;
import spark.components.Group;
public function get titleField():SimpleText
{
return pSkin.titleField;
}
public function get contentGroup():Group
{
return pSkin.contentGroup;
}
]]>
</fx:Script>
<s:states>
<mx:State name="normal" />
<mx:State name="disabled" />
</s:states>
<skins:PanelSkin xmlns:skins="spark.skins.default.*" id="pSkin" left="0" right="0" top="0" bottom="0" />
<s:Button top="7" right="7" width="18" height="18" />
</s:Skin>
Hey Deepa,
Thanks for your response. This doe work and is very close to what I had in my own testing.
The problem comes when you decide to make a component out of the extended Panel. You can extend Panel and define your own skinParts for the button, but the partAdd function never fires for the skinParts declared in the parent.
Panel defines these 2 skin Parts in particular
[SkinPart(required="true")]
public var titleField:SimpleText;
[SkinPart(required="true")]
public var contentGroup:Group;
And I have used the class below to extend Panel and work on my skin.
TitleWindow.as
package view.components
{
import flash.events.Event;
import flash.events.MouseEvent;
import spark.components.Button;
import spark.components.Panel;
import spark.primitives.SimpleText;
[Event(name="close")]
public class TitleWindow extends Panel
{
[SkinPart(required="true")]
public var closeButton:Button;
public function TitleWindow()
{
super();
}
override protected function partAdded(partName:String, instance:Object) : void
{
super.partAdded(partName, instance);
if ( partName == "closeButton" ) {
Button(instance).addEventListener(MouseEvent.CLICK, onCloseClick);
}
if ( partName == "titleField" ) {
SimpleText(titleField).text = title;
}
}
protected function onCloseClick(event:MouseEvent) : void
{
dispatchEvent(new Event("close") );
}
override public function set title(value:String) : void
{
super.title = value;
if ( titleField ) {
titleField.text = value;
}
}
}
}
TitleWindowSkin.as
<?xml version="1.0" encoding="utf-8"?>
<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/halo" xmlns:default="spark.skins.default.*">
<fx:Script>
<![CDATA[
import spark.primitives.SimpleText;
import spark.components.Group;
public function get contentGroup():Group {
return p.contentGroup;
}
public function get titleField():SimpleText {
return p.titleField;
}
]]>
</fx:Script>
<s:states>
<mx:State name="normal" />
</s:states>
<default:PanelSkin id="p" top="0" bottom="0" left="0" right="0" />
<s:Button id="closeButton" skinClass="view.skins.CloseButtonSkin" right="10" top="{p.titleField.height/3}" />
</s:Skin>
In the code above the partAdd if block for titleField never gets called.
Any clue on how I can get the skinAdd method to execute for skinParts declared in the parent object?
Thanks, it was great seeing you again at FlashCamp.
=Sim
Simeon,
You're mixing and matching what deepa said was possible. You cannot extend Skin classes in MXML, as she said, yet you're extending the model portion of the component in TitleWindow.as.
I believe what you want to be doing in your example - to create a TitleWindow class - (and deepa, please correct me if I'm leading him in the wrong direction) is have TitleWindow extend SkinnableContainer, and proxy the contentGroup via the Panel you add in the skin.
I'll try to play around w/ this as an example to see if I'm right and reply further when I have the chance. Otherwise, hopefully deepa will have the chance soon to shed some guidance.
Hi gjastrab
Thanks for your comments. I think you are wrong though, and it goes back to something Deepa and I talked about at FlashCamp.
I understand that it is not the intended behaviour to extend and existing skin through inheritience. I don't have a problem with that which is why I created a new skin and included the panelskin inside thereby creating a new skin and using the old skin through composition.
However to think that I could not extend panel to add behaviors just seems wrong. I understand that the lifecycle of a skin as that it should not be extended. But even if I created my skin completely from scratch I would still be extending Panel to get the built in behavours. I have done this with buttons and other classes with no problem.
The real issue is about wether or not the partAdd method should be called for skinParts that are defined in the parent. And I think this may be a bug. In our discussion last week, Deepa mentioned that the bug may already be fixed so I am just looking for confirmation on that.
Thanks again for your comments though.
simeon
You're misinterpreting what I said - I don't think my post made it sound like I think you cannot extend components to add behavior - but I also didn't fully understand what you were trying to do. Initially I was thinking that your mixing of inheritance w/ composition in the skin wouldn't & shouldn't work, but now I see your point.
My guess at how the framework currently works (although I haven't gotten the chance to dig into it deeply enough yet to fully understand this) is that for now you'd have to copy in the Panel skin code to your custom skin, and augment it manually to add the close button. I do agree this is a shortcoming, but I also think it'd be weird for public getters defined in a Skin class that compose extended sub-parts should be passed into partAdded. Although, maybe not.
I'm interested in seeing what the framework team has to say about this.
Although, I just finally tried running your example, and it worked fine for me. The partAdded successfully fired for the titleBar. I put in a trace and saw it when debugging. Attached is a screenshot, the code I ran, and the SWF as well. I built this code using revision 7584 of the SDK trunk.
I've run into the same issue. Here's a simple workaround...
In the extended component, paste the following:
protected override function findSkinParts() : void
{
repairSkinParts();
super.findSkinParts();
}
protected function repairSkinParts():void
{
var desc:XML = describeType(this);
for each (var variable:XML in desc.elements("variable"))
{
var varName:String = variable.@name;
for each (var metadata:XML in variable.elements("metadata"))
{
var metaName:String = metadata.@name;
if (metaName == "SkinPart")
{
for each (var arg:XML in metadata.elements("arg"))
{
var required:Boolean = false;
if (arg.@key == "required")
if (arg.@value == "true")
required = true;
}
skinParts[varName] = required;
}
}
}
}
...basically repairs the protected skinParts object so that it includes skin parts in the current component AND its ancestors.
I'm trying out Simeon's demo of this here, and am having issues with styling. Most obviously, the backgroundColor style is not being set for the PanelSkin within his TitleWindowSkin. There is no backgroundColor property on the PanelSkin's _nonInhertingStyles object, and so the background of the TitleWindow ends up being black. While other properties do have values set, attempting to change them via CSS has no effect. Styling for Panels is fine; the problem only occurs for TitleWindows.
I have seen this problem building against two versions of the SDK: the one packaged with Flash Builder Beta 2, and 4.0.0.10485_mpl. Is this a known framework bug?
North America
Europe, Middle East and Africa
Asia Pacific