Tuesday, May 29, 2012

C++ Namespaces

Back in the day when C was the only kid in town, there was a default namespace. This global default namespace is what I would argue was the primitive Java package. Packages are a way of dividing up code; not only for visual organization but functional organization. There is no way to know that com.packageOne.myClass's toString() method could be different from com.packageTwo.myClass's toString() method without that com.whatever coming before it.

With the advent of C++, the standard namespace is std, and many C++ programmers use the line using namespace std to declare the standard namespace as std. std contains functions such as cout and others. The developer, however, can create a namespace called myStd and define a completely DIFFERENT cout function and have no clashes.

How do we do this wonderful piece of programming? Below is an example a header file containing a bunch of defined namespaces:

 1 #ifndef MILITARY_RANK_H
 2 #define MILITARY_RANK_H
 3 
 4 namespace Captain {
 5  char * getMilitaryRank(){
 6   return "Captain";
 7  }
 8 }
 9 
10 namespace Commander {
11  char * getMilitaryRank(){
12   return "Commander";
13  }
14 }
15 
16 namespace LieutenantCommander {
17  char * getMilitaryRank(){
18   return "Lieutenant Commander";
19  }
20 }
21 
22 namespace Lieutenant {
23  char * getMilitaryRank(){
24   return "Lieutenant";
25  }
26 }
27 
28 #endif


The above code defines four different namespaces; each namespace contains the same function definition, but the returned char * for each function is different. It is this simple to make a namespace, by the way. Below, I present my main implementation:

 1 #include <iostream>
 2 #include "MilitaryRank.h"
 3 
 4 namespace Captain {
 5  char * getOrders(){
 6   return "Number one, stand down red alert.";
 7  }
 8 }
 9 
10 int main(){
11  std::cout << Captain::getMilitaryRank() << std::endl;
12  std::cout << Captain::getOrders() << std::endl;
13  std::cout << Commander::getMilitaryRank() << std::endl;
14  std::cout << LieutenantCommander::getMilitaryRank() << std::endl;
15  std::cout << Lieutenant::getMilitaryRank() << std::endl;
16  char something;
17  std::cin >> something;
18 }


Notice that I define the same Captain namespace again at the top, and define a function in it. C++ is smart enough to recognize that both of these functions are in the same namespace, Captain. This is powerful, because it lets you create multiple .cpp or .h files that share the same namespace (like classes that share the same package in Java). The namespaces are referenced directly, and the scope resolution operation (::) is used to get reference to those methods. Notice that I omit the using namespace std and opted to call its functions directly. If I wanted to, I could have used using nsmespace Captain and for the Captain::function calls, I could have just called them without using the scope resolution operator

Lastly, here is the output:


Namespaces are something that C++ developers should take advantage of. They can  make large projects much cleaner. Note, however, that you should never, ever, ever have a program contain std::cin, because buffer overflow is one of the easiest way for hackers to exploit your program.

Tuesday, May 22, 2012

My C++ Interface

In my young days (not too long ago mind you), I started out with C++. I have always had a fondness for the language, despite the simplicity and support for Java. I wanted to write a simple post, so I decided to make a cute little interface and implement it in C++. C++ does not have actual interfaces like Java; however, a careful programmer can create pure virtual classes that can behave like interfaces. Implementing classes have the responsibility of overriding those virtual functions so that they are meaningful.
To implement the interface, the sub class would have to be an extension. In the land of Java, multiple inheritance is so frowned upon that the language prevents the developer from even doing it; C++ doesn't have such a restriction. Therefore, one can follow the same design patterns in Java (if they are careful).

A Brief Demonstration

In C++, virtual functions can allow late binding. This means that I can create classes that are implementations of the interface and later assign those instances to function arguments or pointers of type IMyInterface. The following code set will demonstrate that:
#ifndef IMYINTERFACE_H
#define IMYINTERFACE_H
using namespace std;

class IMyInterface{
public:
    virtual char * returnContent(){
        return "";
    }

};

#endif
The above class is a standard C++ class called IMyInterface. Clearly, it is labeled as an interface. There is only one function defined in the class, using the keyword virtual. It returns a type of char *, which is kind of like a String (for this example, I wanted to avoid as much of the STL as possible, despite the fact that I declare it as my default namespace at the top of the page).

The following code samples are implementations of that interface (in reality, extensions of a class):
#ifndef IMPLEMENTOR_H
#define IMPLEMENTOR_H
#include "IMyInterface.h"
using namespace std;

class Implementor: public IMyInterface{
private:
    int a;
public:
    Implementor(){
        a = 0;
    }

    char * returnContent(){
        return "This is [Implementor]";
    }

    static Implementor * getInstance(){
        return new Implementor();
    }

    void setA(int a){
        this->a = a;
    }
};

#endif

#ifndef ANOTHERDEMONSTRATION_H
#define ANOTHERDEMONSTRATION_H
#include "IMyInterface.h"
using namespace std;

class AnotherDemonstration : public IMyInterface{
public:
    char * returnContent() {
        return "This is [AnotherDemonstration]";
    }
};

#endif
The first code snippet has some useless traffic, but notice that it does override the virtual function. The latter class has no excessive traffic, and overrides the virtual function. The following is the run-time snippet that I am going to execute:
#include <iostream>
#include "Implementor.h"
#include "IMyInterface.h"
#include "AnotherDemonstration.h"
using namespace std;

    void displayable(IMyInterface * );

    int main(){
        AnotherDemonstration myThirdClass;
        Implementor * anotherImpl = Implementor::getInstance();
        displayable(anotherImpl);
        displayable(&myThirdClass);

    }

    void displayable(IMyInterface * object){
        cout << object->returnContent() << endl;
    }
The above snippet does the following:
  1. The first two lines create our instances of the implementations of IMyInterface; both are separate instances of their respective types, but are sub classes of their common parent type.
  2. The third line calls a function that accepts a point to an IMyInterface instance. This function calls the interface method IMyInterface::returnContent(). At execution time, late binding will occur calling the instance's implementations version of the function.
  3. The third line passes anotherImpl into the function.
  4. myThirdClass is not a type of IMyInterface *. Therefore, I pass in an address reference using the ampersand operator. This address reference will serve as the pointer to the instance myThirdClass.
I added a statement to hold up execution of the code so that I can get a print-screen of the execution:

When developing C++, keeping little tricks like the above in mind can save quite a bit of code. Don't fear the pointer, embrace the pointer (since all Java developers do, implicitly).

Friday, May 18, 2012

My Own Button Bar!

As a bit of fun, I decided to create my own flex button bar component. This button bar component is designed for implementation with any type of Flex button; but not only that, it can accept images or other types of IVisualElements as buttons. The following explains the implementation of the code. This button bar can be used in web, desktop, and mobile Flex applications.

The Interfaces

Two interfaces are defined for this little library project. IButtonBar should be clear enough to understand. It defines the underlying characters of my bar. I give the user the ability to set the height and width of the buttons, as well as the skin class for the buttons.
package com.bars.interfaces {
    import mx.collections.ListCollectionView;

    /**
     * Interface for ButtonBars. This defines a generic set of properties that are expected.
     * @author Charles
     */
    public interface IButtonBar {
        /**
         * Used for button rendering. At a minimum, this should contain some sort of data object that
         * has a label property or a property defined in <code>IButtonBar::labelField</code> for a label
         * and an option skinClass property for button skin rendering.
         * @param list
         */
        function set dataProvider(list:ListCollectionView):void;
        /**
         * Get a reference to the dataProvider.
         * @return
         */
        function get dataProvider():ListCollectionView;
        /**
         * Sets the selected index of the last button.
         * @param index
         */
        function set selectedIndex(index:int):void;
        /**
         * Index of the last clicked button or set index.
         * @return
         */
        function get selectedIndex():int;
        /**
         * Return the content associated to the button.
         * @return
         */
        function get selectedItem():Object;
        /**
         * The object property in the data provider that is to be label for the button.
         * Implementors of this interface should have the default value be "label"
         * @param value
         */
        function set labelField(value:String):void;
        /**
         * The object property designated for the button label.
         * @return
         */
        function get labelField():String;
        /**
         * The height for each of the buttons.
         * @param value
         */
        function set buttonHeight(value:Number):void;
        /**
         * The height for each of the buttons.
         * @param return
         */
        function get buttonHeight():Number;
        /**
         * The width for each of the buttons.
         * @param value
         */
        function set buttonWidth(value:Number):void;
        /**
         * The width for each of the buttons.
         * @param return
         */
        function get buttonWidth():Number;
        /**
         * Add the class reference for the button skins.
         * @param value
         * @return
         */
        function set skinClass(value:Class):void;
        /**
         * Get the class reference for the skin.
         * @return
         */
        function get skinClass():Class;

    }
}

The second interface is IButtonFactory. This interface defines a single method to that is intended for the creation of IVisualElements. Buttons implement the IVisualElement interface, but many other components implement that interface. The importance of this factory interface will be reveal later.

Button Factories

IButtonFactory components are used in the implementation of the IButtonBar; thus, I will cover the two implementations I have created for this demonstration. These factories are simple enough and won't require much explanation:

ButtonFactory

package com.bars.implementations {
    import com.bars.interfaces.IButtonFactory;

    import mx.core.IVisualElement;

    import spark.components.Button;

    public class ButtonFactory implements IButtonFactory {
        public static const instance:IButtonFactory = new ButtonFactory();

        public function ButtonFactory() {
            if (instance) {
                throw(new Error("[ButtonFactory] is singleton"));
            }
        }

        public static function getInstance():IButtonFactory {
            return instance;
        }

        public function makeButton(label:String, height:Number, width:Number, skinClass:Class):IVisualElement {
            var button:Button = new Button();
            button.label = label;

            if (skinClass != null) {
                button.setStyle("skinClass", skinClass);
            }

            if (height < 0) {
                button.percentHeight = 100;
            } else {
                button.height = height;
            }

            if (width < 0) {
                button.percentWidth = 100;
            } else {
                button.width = width;
            }

            return button;
        }
    }
}

RadioButtonFactory

package com.bars.implementations {
    import com.bars.interfaces.IButtonFactory;

    import mx.core.IVisualElement;

    import spark.components.RadioButton;

    public class RadioButtonFactory implements IButtonFactory {
        public static const instance:RadioButtonFactory = new RadioButtonFactory();

        public function RadioButtonFactory() {
            if (instance) {
                throw(new Error("[RadioButtonFactory] is singleton"));
            }
        }

        public static function getInstance():IButtonFactory {
            return instance;
        }

        public function makeButton(label:String, height:Number, width:Number, skinClass:Class):IVisualElement {
            var button:RadioButton = new RadioButton();
            button.label = label;

            if (skinClass != null) {
                button.setStyle("skinClass", skinClass);
            }

            if (height < 0) {
                button.percentHeight = 100;
            } else {
                button.height = height;
            }

            if (width < 0) {
                button.percentWidth = 100;
            } else {
                button.width = width;
            }

            return button;
        }
    }
}
Both classes make buttons, the first a typical spark button and the latter a radio button. Properties such as height, width, and label as well as the style skinClass are set. The key about this factory is that it returns an IVisualElement. Therefore, this factory interface can be used to create non-button components. Also notice that these button factories are singletons. There is no need to create multiple instances of this class, so I chose to make these factory immutable singletons that produce buttons. If the bar that uses this factory is implemented all over an application, there will only be one ButtonFactory and/or RadioButtonFactory used.

Implementation of the Button Bar

ButtonBarBase is, as its name-stake indicates, a base class implementation of the IButtonBar interface. The intent of this class is to be generic enough for sub-classes to use, and if needed, strategically override methods. Notice that this class extends Group; this is for layout support as well as IViewPort support. If a background is needed for this bar, it can be wrapped in a SkinnableContainer, which DOESN'T have IViewPort support. Below is the code in its entirety:
package com.bars.implementations {
    import com.bars.events.ButtonBarEvent;
    import com.bars.interfaces.IButtonBar;
    import com.bars.interfaces.IButtonFactory;
    
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.utils.getDefinitionByName;
    
    import mx.collections.ArrayCollection;
    import mx.collections.IList;
    import mx.collections.ListCollectionView;
    import mx.collections.XMLListCollection;
    import mx.core.IVisualElement;
    import mx.events.CollectionEvent;
    import mx.events.FlexEvent;
    
    import spark.components.Group;

    [Event(name="buttonBarChange", type="com.bars.events.ButtonBarEvent")]
    [Event(name="buttonBarClick", type="com.bars.events.ButtonBarEvent")]
    [Event(name="widthUpdated", type="com.bars.events.ButtonBarEvent")]
    [Event(name="heightUpdated", type="com.bars.events.ButtonBarEvent")]
    /**
     * Base class implementation of the ButtonBarBase class.
     * @author Charles
     */
    public class ButtonBarBase extends Group implements IButtonBar {
        protected var _dataProvider:ListCollectionView;
        protected var _selectedIndex:int = -1;
        protected var _selectedItem:Object;
        protected var _labelField:String = "label";
        protected var _buttonHeight:Number = -1;
        protected var _buttonWidth:Number = -1;
        protected var _skinClass:Class;

        protected var buttonFactory:IButtonFactory;
        protected var internalDataProvider:ArrayCollection;
        
        private var isCreated:Boolean = false;

        public function ButtonBarBase(buttonFactory:IButtonFactory) {
            this.buttonFactory = buttonFactory;
            internalDataProvider = new ArrayCollection();
            addEventListener(FlexEvent.CREATION_COMPLETE, renderComponent);
            super();
        }

        /**
         * Implementation adds a CollectionEvent.COLLECTION_CHANGE event to the IList parameter. This will allow the component to dynamically change
         * when the data provider is changed.
         * @param list
         */
        public function set dataProvider(list:ListCollectionView):void {
            if (_dataProvider != null && _dataProvider != list) {
                _dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, redrawComponentButtons);
            }

            if (_dataProvider == list) {
                return;
            }

            _dataProvider = list;
            
            if(isCreated){
                redrawComponentButtons();
            }
                
            _dataProvider.addEventListener(CollectionEvent.COLLECTION_CHANGE, redrawComponentButtons);
        }

        public function get dataProvider():ListCollectionView {
            return _dataProvider;
        }

        public function set selectedIndex(index:int):void {
            if (_selectedItem != index && index < internalDataProvider.length && index > -1) {
                _selectedIndex = index;
                _selectedItem = internalDataProvider[_selectedIndex].content;
                dispatchEvent(new ButtonBarEvent(ButtonBarEvent.CHANGE));
            } else if (index == -1) {
                _selectedIndex = index;
                _selectedItem = null;
                dispatchEvent(new ButtonBarEvent(ButtonBarEvent.CHANGE));
            }
        }

        [Bindable("buttonBarChange")]
        public function get selectedIndex():int {
            return _selectedIndex;
        }

        [Bindable("buttonBarChange")]
        public function get selectedItem():Object {
            return _selectedItem;
        }

        public function set labelField(value:String):void {
            _labelField = value;
        }

        public function get labelField():String {
            return _labelField;
        }

        public function set buttonHeight(value:Number):void {
            if (value >= 0 && value != _buttonHeight) {
                _buttonHeight = value;
                setButtonProperty("height", value);
                dispatchEvent(new ButtonBarEvent("heightUpdated"));
            }
        }

        [Bindable("heightUpdated")]
        public function get buttonHeight():Number {
            return _buttonHeight;
        }

        public function set buttonWidth(value:Number):void {
            if (value >= 0 && _buttonWidth != value) {
                _buttonWidth = value;
                setButtonProperty("width", value);
                dispatchEvent(new ButtonBarEvent("widthUpdated"));
            }
        }

        [Bindable("widthUpdated")]
        public function get buttonWidth():Number {
            return _buttonWidth;
        }

        public function set skinClass(value:Class):void {
            _skinClass = value;
        }

        public function get skinClass():Class {
            return _skinClass;
        }
        
        protected function setButtonProperty(propertyName:String, value:Number):void{
            for each(var object:Object in internalDataProvider){
                object.button[propertyName] = value;
            }
            
            invalidateSize();
        }

        protected function redrawComponentButtons(event:Event=null):void {
            for each (var object:Object in internalDataProvider) {
                object.button.removeEventListener(MouseEvent.CLICK, clickHandler);
            }

            internalDataProvider.removeAll();
            constructView();
        }

        protected function constructView():void {
            var button:IVisualElement;

            if (dataProvider == null) {
                return;
            }

            var actualLabel:String = "";

            for each (var object:Object in dataProvider) {
                if (object != null && object is XML) {
                    var labelContent:Object = (object as XML).elements(labelField)[0];
                    actualLabel = labelContent ? labelContent.toString() : "";
                } else if (object != null && dataProvider is ArrayCollection) {
                    actualLabel = object[labelField];
                }

                button = buttonFactory.makeButton(actualLabel, buttonHeight, buttonWidth, skinClass);
                button.addEventListener(MouseEvent.CLICK, clickHandler);
                internalDataProvider.addItem({ button: button, content: object });
            }

            addComponentsAndRedraw();
        }

        protected function addComponentsAndRedraw():void {
            this.removeAllElements();

            for each (var object:Object in internalDataProvider) {
                addElement(object.button);
            }

            invalidateSize();
            invalidateDisplayList();
        }

        protected function clickHandler(event:MouseEvent):void {
            var button:IVisualElement = event.currentTarget as IVisualElement;
            if (button != null) {
                for (var index:int = 0; index < internalDataProvider.length; index++) {
                    if (internalDataProvider[index].button == button) {
                        selectedIndex = index;
                        break;
                    }
                }
            }

            dispatchEvent(new ButtonBarEvent(ButtonBarEvent.CLICK));
        }
        
        private function renderComponent(event:FlexEvent):void{
            removeEventListener(FlexEvent.CREATION_COMPLETE, renderComponent);
            isCreated = true;
            constructView();
        }
    }
}

The class does the following:
  • The constructor accepts an argument of type IButtonFactory; this is the principal component used for button creation. It also initializes an internal data provider array collection as well as add a FlexEvent.CREATION_COMPLETE event to the class. The reason why this creation complete event is added is to prevent component drawing until all of the component parameters had a chance to be set.
  • The dataProvider method relies on the isCreated state. This state exists so that the component is not needlessly redrawn (an optimization). The dataProvider is set into a protected class variable, and if the component is created, the redrawComponentButtons method will be called so that the component can be reconstructed. Also notice that an event listener is added to the passed in ListCollectionView; this event will notify this object that dataProvider has changed, which will also order the redrawing of this component.
  • Setting the selected index will do one of three things:
    1. If the index set in is the current index and not -1, the function does nothing.
    2. If the index set in is not -1 and different, then the selectedItem is updated and a change event is dispatched.
    3. If the index set in is -1 and different, then the selectedItem is set to null and a change event is dispatched.
    The change event does two things: objects composing this view component will be notified that there is a change, and the selectedItem getter method will be called to update components that bind to the selectedItem getter. The selectedIndex getter also behaves identically, except instead of returning the content associated to the button, it returns the index of that content.
  • Button height and width setting is a bindable feature. Both the buttonHeight setter and the buttonWidth setter will update each button's width and height, dispatch an event that causes a binding to the height and width getters, and can be listened for in objects using this object. I use a generic method setButtonProperty to change the height and width properties..
  • redrawComponentButtons is used as both a listener function and something to be called by the class. This function tears down the internal dataprovider by removing the created buttons events. This is done to prevent memory leaks. This method calls constructView.
  • constructView builds the component. This is called when the data provider value is set when the component's creation complete event has already been called, or when the component's creation complete event is called. This method processes either an XMLListCollection or an ArrayCollection of objects. The labelField property is used to fetch the appropriate label from the content provided, defaulting to "label" for the label field. Also notice the use of the button factory in this component. This button factory will actually create the components, eliminating the need for subclasses to have to override this method for their own buttons.
  • addComponentsAndRedraw adds the elements to this object, and invalidated the size of the component and the display list.
  • The clickHandler handles button clicking. It uses the internal data provider to match the button that was clicked; and if there is a match, the selectedItem and selectedIndex are updated. Appropriate events are dispatched because of this. This is the only method is a genuine candidate for override in subclasses.
The beauty of this base class is that is does all of the work. This will be shown below...

MobileButtonBar

The MobileButtonBar is a subclass of ButtonBarBase. The whole class is as follows:

package com.bars.implementations {

    public class MobileButtonBar extends ButtonBarBase {
        public function MobileButtonBar() {
            super(ButtonFactory.getInstance());
        }
    }
}

Notice that the constructor doesn't have any parameters, and that the ButtonFactory instance is passed in to the base class. This is a simple and clean example of the power of the base class implementation.

MobileRadioButtonBar

This implementation of the ButtonBarBase required an override of the clickHandler method; however, most of the implementation is left to the parent class. Notice that this uses the RadioButtonFactory instance.

package com.bars.implementations {
    import com.bars.events.ButtonBarEvent;

    import flash.events.MouseEvent;

    import spark.components.RadioButton;

    public class MobileRadioButtonBar extends ButtonBarBase {
        public function MobileRadioButtonBar() {
            super(RadioButtonFactory.getInstance());
        }

        override protected function clickHandler(event:MouseEvent):void {
            var button:RadioButton=event.currentTarget as RadioButton;
            if (button != null) {
                for (var index:int=0; index < internalDataProvider.length; index++) {
                    var radioButton:RadioButton=internalDataProvider[index].button as RadioButton;

                    if (radioButton == button) {
                        selectedIndex=index;
                    } else if (radioButton.selected) {
                        radioButton.selected=false;
                    }
                }
            }

            dispatchEvent(new ButtonBarEvent(ButtonBarEvent.CLICK));
        }
    }
}


Application of the Button Bar

Below is an application of the button bar in mxml:

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:implementations="com.bars.implementations.*"
        title="HomeView">
    <fx:Declarations>
    </fx:Declarations>

    <fx:Script>
        <![CDATA[
            import avmplus.getQualifiedClassName;

            import flash.utils.getDefinitionByName;

            import mx.collections.ArrayCollection;
            import mx.collections.XMLListCollection;
            
            [Bindable]
            private var dataItems:ArrayCollection = new ArrayCollection(
                [{ label: "Hello", selection: "Red" }, { label: "This", selection: "Blue" }, { label: "Is", selection: "Yellow" }, { label: "A", selection: "Green" }, { label: "Button", selection: "Blue" }, { label: "Bar", selection: "Violate" }]
                );

            public function get dataItemsXML():XMLListCollection {
                var dataItems:XMLListCollection = new XMLListCollection();
                dataItems.addItem(<content><label>Test</label><something>George</something></content>);
                dataItems.addItem(<content><label>Two</label><something>Peter</something></content>);

                return dataItems;
            }

            [Bindable]
            private var heightValue:Number = 150;

            [Bindable]
            private var widthValue:Number = 150;

            public function change():void {
                dataItems.addItem({ label: "ThisWasAdded", selection: "indigo" });
            }

            public function resize():void {
                heightValue = 150;
                widthValue = 550;
            }
        ]]>
    </fx:Script>
    <s:Group width="100%"
             height="100%">
        <s:layout>
            <s:VerticalLayout/>
        </s:layout>

        <s:Group>
            <s:layout>
                <s:HorizontalLayout/>
            </s:layout>

            <s:Scroller width="300"
                        height="500">
                <implementations:MobileButtonBar id="firstBar"
                                                 buttonHeight="{heightValue}"
                                                 buttonWidth="{widthValue}"
                                                 dataProvider="{dataItemsXML}"
                                                 skinClass="{CobaltButtonSkin}">
                    <implementations:layout>
                        <s:VerticalLayout/>
                    </implementations:layout>
                </implementations:MobileButtonBar>
            </s:Scroller>
            <s:Scroller width="300"
                        height="500">
                <implementations:MobileRadioButtonBar id="secondBar"
                                                      buttonHeight="{heightValue}"
                                                      buttonWidth="{widthValue}"
                                                      dataProvider="{dataItems}">
                    <implementations:layout>
                        <s:VerticalLayout/>
                    </implementations:layout>
                </implementations:MobileRadioButtonBar>
            </s:Scroller>
        </s:Group>
        <s:Button label="change"
                  click="change()"/>
        <s:Button label="resize"
                  click="resize()"/>
        <s:HGroup width="100%">
            <s:Label text="first bar"/>
            <s:Label text="{firstBar.selectedItem.something}"/>
        </s:HGroup>
        <s:HGroup width="100%">
            <s:Label text="second bar"/>
            <s:Label text="{secondBar.selectedItem.selection}"/>
        </s:HGroup>
    </s:Group>
</s:View>

This implementation is in a mobile application. Below is an image from that running application:

Wednesday, May 2, 2012

Some Fancy Skinning

Using Flex 4 skins, a single GUI component does not need to be extended to change appearance. If the developer creates a clever underlying object extending either the SkinnableContainer or SkinnableComponent Flex components, a highly extensible and easy to use visual component can be created. Demonstrated in this post is something I like to call a "Fancy Component", which demonstrates how a single underlying component can be reused with different skins.

The first code snippet is the definition of the FancyComponent object, which is an extension of the Flex SkinnableContainer. There are three SkinParts defined, all of which are required to render this component. The skin parts are s:Image, s:TextArea, and s:Button. Pay attention to the names of these variables, as they will be important later on.

The FancyComponent overrides a function of the parent called partAdded. This is the principal function responsible for adding skin parts to this container. After calling the parent implementation, notice that I use if-else logic to see if the incoming skin part is one of the parts I have defined. Also notice that if it is a part that I have defined, that I am doing something to it: for the image I am setting its source, for the textArea I am setting its parameter textFlow, and for the closeButton I am setting label and adding a MouseEvent listener to it. The class has public variables for setting those values set in the partAdded function (imgSrc, msgText, and closeButtonLabel respectively).

The Fancy Component
package com.charles.components
{
import flash.events.Event;
import flash.events.MouseEvent;

import flashx.textLayout.elements.TextFlow;

import mx.effects.IEffectInstance;
import mx.events.FlexEvent;

import spark.components.Button;
import spark.components.Image;
import spark.components.SkinnableContainer;
import spark.components.TextArea;
import spark.effects.Fade;

[Event(name="closeMe", type="flash.events.Event")]
public class FancyComponent extends SkinnableContainer
{
[SkinPart(required="true")]
public var image:Image;

[SkinPart(required="true")]
public var textArea:TextArea;

[SkinPart(required="true")]
public var closeButton:Button;

public var imgSrc:Object;
public var msgText:TextFlow;
public var closeButtonText:String;

protected var fadeEffect:Fade;

public function FancyComponent()
{
addEventListener(FlexEvent.CREATION_COMPLETE, addFadeEffect);
super();
}

override public function effectFinished(effectInst:IEffectInstance):void{
if(effectInst.effect == fadeEffect){
closeButton.removeEventListener(MouseEvent.CLICK, removeView);
dispatchEvent(new Event("closeMe"));
}

super.effectFinished(effectInst);
}

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

if(instance == textArea){
textArea.textFlow = msgText;
} else if(instance == image){
image.source = imgSrc;
} else if(instance == closeButton){
closeButton.label = closeButtonText;
closeButton.addEventListener(MouseEvent.CLICK, removeView);
}
}

protected function addFadeEffect(event:FlexEvent):void{
removeEventListener(FlexEvent.CREATION_COMPLETE, addFadeEffect);

fadeEffect = new Fade();
fadeEffect.alphaFrom = 1;
fadeEffect.alphaTo = 0;
fadeEffect.duration = 1000;
}

protected function removeView(event:MouseEvent):void{
removeFancyComponent();
}

protected function removeFancyComponent():void{
fadeEffect.stop();
fadeEffect.play([this]);
}
}
}


Skinning isn't the only thing occurring the FancyComponent. In the constructor, I added a FlexEvent to this object to listen for the creationComplete event. When the event fires, this object will remove the event and set up a fade effect on the object. When the closeButton is clicked, the whole container will begin to fade away. Overriden is the effectFinished function, with special handling to remove the closeButton's event listener and to dispatch an event called closeMe. The event is defined as metadata above the class declaration; this allows implementing objects to explicitly assign functions (like a button's click parameter when defining it in mxml).

With the underlying component defined, skins are now created. Below are two different skins. Notice that there are common features to each of the skins:
  • Each component contains a spark group with id contentGroup.
  • Do you remember the skin part variable names? Within the contentGroup, I have image, textArea, and closeButton as ids for their respective components in the FancyComponent object (s:Image, s:TextArea, s:Button). When the skin is rendered, partAdded in FancyComponent will be called and each of these mxml components will have their appropriate attributes set.
ModernSkin

<?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/mx">
<!-- host component -->
<fx:Metadata>
[HostComponent("spark.components.SkinnableContainer")]
</fx:Metadata>

<!-- states -->
<s:states>
<s:State name="disabled" />
<s:State name="normal" />
</s:states>

<s:Rect left="0" right="0" top="0" bottom="0"
radiusX="10" radiusY="10" width="100%" height="100%">
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry alpha="1"
color="#B0B0B0"
color.disabled="#E0E0E0"/>
<s:GradientEntry
color="#E8E8E8"
color.disabled="#E0E0E0"
alpha=".15"
/>
</s:LinearGradient>
</s:fill>
</s:Rect>
<!-- SkinParts
name=contentGroup, type=spark.components.Group, required=false
-->
<s:Group id="contentGroup">
<s:layout>
<s:HorizontalLayout
paddingBottom="12"
paddingLeft="12"
paddingRight="12"
paddingTop="12"
/>
</s:layout>
<s:Image id="image" width="75" height="75"/>
<s:VGroup width="100%" height="100%">
<s:TextArea id="textArea" height="100%" width="100%"/>
<s:HGroup width="100%">
<s:Spacer width="100%"/>
<s:Button id="closeButton"/>
</s:HGroup>
</s:VGroup>
</s:Group>
</s:Skin>

AlternativeModernSkin
<?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/mx">
<!-- host component -->
<fx:Metadata>
[HostComponent("spark.components.SkinnableContainer")]
</fx:Metadata>

<!-- states -->
<s:states>
<s:State name="disabled" />
<s:State name="normal" />
</s:states>

<!-- SkinParts
name=contentGroup, type=spark.components.Group, required=false
-->

<s:Rect left="0" right="0" top="0" bottom="0"
radiusX="10" radiusY="10" width="100%" height="100%">
<s:fill>
<s:SolidColor color="#C80000"/>
</s:fill>
</s:Rect>
<!-- SkinParts
name=contentGroup, type=spark.components.Group, required=false
-->
<s:Group id="contentGroup">
<s:layout>
<s:HorizontalLayout
paddingBottom="12"
paddingLeft="12"
paddingRight="12"
paddingTop="12"
/>
</s:layout>
<s:VGroup height="100%">
<s:Image id="image" width="75" height="75"/>
<s:Spacer height="100%"/>
<s:Button id="closeButton"/>
</s:VGroup>
<s:TextArea id="textArea" height="100%" width="100%"/>
</s:Group>
</s:Skin>

Notice that the skins are different, and that they are simple and free of any implementation details. The implementation details are handled by the FancyComponent. Therefore, the developer can use this same FancyComponent in different ways by laying out differently the image, textArea, and closeButton components.

The last step is to implement the FancyComponent. Below is mxml that does this. Since we have defined required SkinParts in FancyComponent, the MINIMUM requirement for any skin assigned to the FancyComponent would be those components. Also, the FancyComponent cannot be rendered without a skin that implements those SkinParts.

Implementation
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
  xmlns:s="library://ns.adobe.com/flex/spark"
  xmlns:mx="library://ns.adobe.com/flex/mx"
  xmlns:components="com.charles.components.*"
  backgroundColor="#000000">
<fx:Script>
<![CDATA[
import com.charles.assets.Assets;

import mx.controls.Alert;
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:layout>
<s:VerticalLayout/>
</s:layout>
<components:FancyComponent width="300"
  height="300"
  imgSrc="{Assets.exclaim}"
  closeButtonText="Close Me!"
  skinClass="com.charles.skins.ModernSkin">
<components:msgText>
<s:TextFlow>
<s:p>
This is a <s:span fontWeight="bold">
fancy
</s:span> component. What is rather interesting is that thanks to the underlying
class and skin class, you don't really care about anything but putting in the
important information you desire.
</s:p>
</s:TextFlow>
</components:msgText>
</components:FancyComponent>
<components:FancyComponent width="300"
  height="210"
  imgSrc="{Assets.exclaim}"
  closeButtonText="Close Me!"
  skinClass="com.charles.skins.AlternativeModernSkin"
  closeMe="{Alert.show('Notice this!', 'listening for closeMe');}">
<components:msgText>
<s:TextFlow>
<s:p>
This is a <s:span fontWeight="bold">
 fancy
 </s:span> component. What is rather interesting is that thanks to the underlying
class and skin class, you don't really care about anything but putting in the
important information you desire.
</s:p>
</s:TextFlow>
</components:msgText>
</components:FancyComponent>
</s:WindowedApplication>

This is only a small taste of what clever component development and skinning can do. If you find this appealing, you might want to experiment with SkinParts that aren't required ([SkinPart(required="false")]). This means that a single component can be multi-purposed both visually and functionally (for example, you can have a three buttoned component that optionally implements two of the buttons, therefore fade effects and other features such as text areas need be applied once, and the partAdded method will handle the cases where the non-required skin parts are present).