SmartLogic Logo (443) 451-3001

The SmartLogic Blog

SmartLogic is a web and mobile product development studio based in Baltimore. Contact us for help building your product or visit our website to learn more about what we do.

Creating Your First Custom SkinnableComponent in Flex 4

July 19th, 2008 by

It took me a couple of days to get to my next Flex 4 example, but here we finally are. I wanted to try making a component which had optional SkinParts, so I came up with the following example (get the source). For those who don’t know, Flex 4 targets Flash Player 10 so you’ll need that in order to run the SWF.

In this example we will build a component called QuestionAndAnswer which will include a text field containing a question, a check box, and a text field containing an answer to the question. The check box and answer are both optional, so if the Skin file doesn’t include those SkinParts, they won’t be a part of the view. If they are included, then clicking the check box will show the text field containing the answer. Let’s see what the code looks like.

QuestionAndAnswer.as

package com.smartlogicsolutions.flex.component {
	import flash.events.Event;
	import flex.component.CheckBox;
	import flex.component.TextView;
	import flex.core.Flags32;
	import flex.core.SkinnableComponent;

	[DefaultProperty("content")]
	[SkinStates("question", "answer")]

	/**
	 * Component to demonstrate optional SkinParts.
	 *
	 * @langversion ActionScript 3.0
	 * @author Greg Jastrab <greg@smartlogicsolutions.com
	 */
	public class QuestionAndAnswer extends SkinnableComponent {

		/* --- Variables --- */

		[Bindable]
		public var content:*;
		protected var answerVal:String;
		protected var flags:Flags32;
		protected var questionVal:String;
		protected var toggleText:String;

		// Skin invalidation flags
		protected static const onQuestionFlag:uint	= 1 << 0;
		protected static const onAnswerFlag:uint	= 1 << 1;

		/* === Variables === */

		/* --- Skin Parts --- */

		[SkinPart]
		public var questionText:TextView;

		[SkinPart(required="false")]
		public var answerText:TextView;

		[SkinPart(required="false")]
		public var toggleAnswer:CheckBox;

		/* --- Constructor --- */

		public function QuestionAndAnswer() {
			super();
			flags = new Flags32();
			answerVal = questionVal = "";
			toggleLabel = "Toggle Answer"
		}

		/* === Constructor === */

		/* --- Functions --- */

		override protected function getUpdatedSkinState():String {
			return onAnswer ? "answer" : "question";
		}

		override protected function partAdded(partName:String, instance:*):void {
			super.partAdded(partName, instance);

			if(instance == toggleAnswer) {
				toggleAnswer.addEventListener(Event.CHANGE, onToggleAnswer);
			}
		}

		override protected function partRemoved(partName:String, instance:*):void {
			super.partRemoved(partName, instance);

			if(instance == toggleAnswer) {
				toggleAnswer.removeEventListener(Event.CHANGE, onToggleAnswer);
			}
		}

		/* === Functions === */

		/* --- Event Handlers --- */

		private function onToggleAnswer(evt:Event):void {
			onAnswer = toggleAnswer.selected;
		}

		/* === Event Handlers === */

		/* --- Public Accessors --- */

		[Bindable("answerChanged")]
		public function get answer():String { return answerVal; }

		public function set answer(value:String):void {
			answerVal = value;
			dispatchEvent(new Event("answerChanged"));
		}

		[Bindable("onAnswerChanged")]
		public function get onAnswer():Boolean { return flags.isSet(onAnswerFlag); }

		public function set onAnswer(value:Boolean):void {
			if(!flags.update(onAnswerFlag, value))
				return;
			dispatchEvent(new Event("onAnswerChanged"));
			invalidateSkinState();
		}

		[Bindable("questionChanged")]
		public function get question():String { return questionVal; }

		public function set question(value:String):void {
			questionVal = value;
			dispatchEvent(new Event("questionChanged"));
		}

		[Bindable("toggleLabelChanged")]
		public function get toggleLabel():String { return toggleText; }

		public function set toggleLabel(value:String):void {
			toggleText = value;
			dispatchEvent(new Event("toggleLabelChanged"));
		}

		/* === Public Accessors === */
	}
}

Things to note here:

  • [SkinPart] declarations: this metadata tag describes what properties are visually placed within the Skin file
  • the partAdded and partRemoved functions: get called whenever the view adds a SkinPart to the display list
    • I’m attaching/removing the event listener for when the CheckBox changes in these functions, but it’s more appropriate to do so in the attachBehaviors and removeBehaviors functions. I I think this is alright for my example, but this was easier to code since the CheckBox is only optionally added – perhaps someone from the SDK team can give better insight on how it should be done to optional skin parts?
  • the call to invalidateSkinStates in the onAnswer getter function: the component dictates when states should change, so call invalidateSkinStates() to indicate the state has changed
  • the getUpdatedSkinState function: using properties within the component, determines what the current state should be

On to the Skin files. com.smartlogicsolutions.flex.skin.QASkin.mxml will place all of the SkinParts, while com.smartlogicsolutions.flex.skin.QSkin.mxml will only place the questionText.

QASkin.mxml

<?xml version="1.0" encoding="utf-8"?>
<Skin xmlns="http://ns.adobe.com/mxml/2009" layout="flex.layout.VerticalLayout">

	<Style>
		.text {
			fontFamily: "Arial";
			fontSize: 11pt;
			color: #000000;
		}
	</Style>

	<states>
		<State name="question" />
		<State name="answer" />
	</states>

	<content>
		<TextView id="questionText" text="{data.question}" styleName="text" color.answer="#0000ff" />

		<CheckBox id="toggleAnswer" label="{data.toggleLabel}" />

		<TextView id="answerText" includeIn="answer" styleName="text" text="{data.answer}" />
	</content>

</Skin>

The includeIn property replaces the old AddChild syntax in states, so includeIn="answer" says to only include the answerText in the answer state.

QSkin.mxml

<?xml version="1.0" encoding="utf-8"?>
<Skin xmlns="http://ns.adobe.com/mxml/2009" layout="flex.layout.VerticalLayout">

	<Style>
		.text {
			fontFamily: "Arial";
			fontSize: 11pt;
			color: #000000;
		}
	</Style>

	<states>
		<State name="question" />
		<State name="answer" />
	</states>

	<content>
		<TextView id="questionText" text="{data.question}" styleName="text" color.answer="#0000ff" />
	</content>

</Skin>

And finally, the application file:

DemoQuestionAnswer.mxml

<?xml version="1.0" encoding="utf-8"?>
<Application xmlns="http://ns.adobe.com/mxml/2009"
			 xmlns:sls="com.smartlogicsolutions.flex.component.*"
			 layout="flex.layout.HorizontalLayout">

	<Style>
		QuestionAndAnswer { skinZZ: ClassReference("com.smartlogicsolutions.flex.skin.QASkin"); }
		.question { skinZZ: ClassReference("com.smartlogicsolutions.flex.skin.QSkin"); }
	</Style>

	<sls:QuestionAndAnswer id="qa"
   						   question="What is the coolest new language?"
					       answer="Flex 4, duh!" />

	<sls:QuestionAndAnswer id="q" styleName="question" question="Only A Question" />

</Application>

This places 2 QuestionAndAnswer components horizontally in the Application, giving one the QASkin and the other the QSkin skins. Compile and run and you should see:

DemoQuestionAnswer Screenshot Checkbox not selected

DemoQuestionAnswer Screenshot Checkbox not selected

And after you click the CheckBox:

DemoQuestionAnswer Screenshot with Checkbox selected

DemoQuestionAnswer Screenshot with Checkbox selected

  • http://cksachdev.blogspot.com Chetan Sachdev

    Nice, Have you got how to register for event, Say on click of a button I need alert. Looking into it.

  • http://www.iamdeepa.com Deepa Subramaniam

    Regarding attachBehaviors/removeBehaviors vs. partAdded/partRemoved:

    Since some components don’t have parts, attachBehaviors/removeBehaviors is for the adding and removing of event listeners to the skin object itself. When a component has parts, adding and removing of event listeners should happen in the partAdded/partRemoved methods.

    Remember, all of these methods are optional and should only be overridden when you need to do extra work in those methods.

  • http://www.ChikaraDev.com Greg Lafrance

    The Flex 4 sample apps I have tried in this blog have not compiled for me thus far. Would it be possible for you to bring them up to speed with the current SDK so they work without having to change anything? I’ve learned a bit from trying to get them to work, but this one on skins I ultimately could not get to work as I get an error that the “data” property is unknown.

  • Gary

    In addition to extending partAdded and partRemoved functions, you can add event listeners to any visual component by following;
    1. Add an event listener function to the component class. Ex: onDragEnter
    2. In the skin file, specify this function by {hostComponent.functionName(event)}. Ex: dragEnter=”{hostComponent.onDragEnter(event)}”
    This way you can speed up the development but if you want to remove this event listener, then you have to override partRemoved anyway. It just offers a quick solution.

  • http://alvijee.blogspot.com Tahir Azeem Alvi

    Hi,

    How i skin or apply the Css to Alert in Flex 4?

    Thanks

Greg Jastrab was a developer and project manager at SmartLogic from 2006 to 2011. Follow @gjastrab on twitter

Greg Jastrab's posts