Friday, August 30, 2013

Flex: A Simple Slider with a Labeled Button

Time to move back to the easier stuff. In the older versions of Flex, you would need to play around a few properties and create a few components in order to create a similar UI component. However, in the newer versions of Flex, some sophisticated stuff back then have now been simplified. However that doesn't mean that the development time can reduce greatly, it simply means that you don't need to waste too much time figuring out these small little stuff.

The source codes of the main application file - SimpleSliderWithValue.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/mx">
 <fx:Style source="css/style.css"/>
 <s:HGroup width="100%" 
     height="100%" 
     verticalAlign="middle"
     horizontalAlign="center">
  <s:VSlider minimum="0"
       maximum="100"
       value="50"  
       showDataTip="false" 
       height="200"
       styleName="customSliderV"/>
   <s:HSlider minimum="0"
       maximum="100"
       value="50" 
       showDataTip="false" 
       width="200" 
       styleName="customSliderH"/>
 </s:HGroup>
</s:Application>

Skin Class for the Horizontal Slider - skins/CustomHSliderWithLabel.mxml
<?xml version="1.0" encoding="utf-8"?>

<!--

    ADOBE SYSTEMS INCORPORATED
    Copyright 2008 Adobe Systems Incorporated
    All Rights Reserved.

    NOTICE: Adobe permits you to use, modify, and distribute this file
    in accordance with the terms of the license agreement accompanying it.
-->

<!--- The default skin class for the Spark HSlider component. The thumb and track skins are defined by the
HSliderThumbSkin and HSliderTrackSkin classes, respectively.  

       @see spark.components.HSlider
       @see spark.skins.spark.HSliderThumbSkin
       @see spark.skins.spark.HSliderTrackSkin
                
      @langversion 3.0
      @playerversion Flash 10
      @playerversion AIR 1.5
      @productversion Flex 4
-->
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:fb="http://ns.adobe.com/flashbuilder/2009" minHeight="11" alpha.disabled="0.5">

    <fx:Metadata>
    <![CDATA[ 
        /** 
         * @copy spark.skins.spark.ApplicationSkin#hostComponent
         */
        [HostComponent("spark.components.HSlider")]
    ]]>
    </fx:Metadata> 
    
    <fx:Script fb:purpose="styling">
        /* Define the skin elements that should not be colorized. 
           For slider, the skin itself is colorized but the individual parts are not. */
        static private const exclusions:Array = ["track", "thumb"];

        /**
         * @private
         */  
        override public function get colorizeExclusions():Array {return exclusions;}
        
        /**
         * @private
         */
        override protected function initializationComplete():void
        {
            useChromeColor = true;
            super.initializationComplete();
        }
    </fx:Script>
    
    <fx:Script>
        /**
         *  @private
         */  
        override protected function measure() : void
        {
            // Temporarily move the thumb to the left of the Slider so measurement
            // doesn't factor in its x position. This allows resizing the
            // HSlider to less than 100px in width. 
            var thumbPos:Number = thumb.getLayoutBoundsX();
            thumb.setLayoutBoundsPosition(0, thumb.getLayoutBoundsY());
            super.measure();
            thumb.setLayoutBoundsPosition(thumbPos, thumb.getLayoutBoundsY());
        }
    </fx:Script>
    
    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>
    
    <fx:Declarations>
        <!--- The tooltip used in the mx.controls.Slider control. 
               To customize the DataTip's appearance, create a custom HSliderSkin class.-->
        <fx:Component id="dataTip">     
           <s:DataRenderer minHeight="24" minWidth="40" y="-34">  
              <s:Rect top="0" left="0" right="0" bottom="0">
                    <s:fill>
                        <s:SolidColor color="0x000000" alpha=".9"/>
                    </s:fill>
                    <s:filters>
                        <s:DropShadowFilter angle="90" color="0x999999" distance="3"/>
                    </s:filters>
                </s:Rect>
                <s:Label id="labelDisplay" text="{data}"
                         horizontalCenter="0" verticalCenter="1"
                         left="5" right="5" top="5" bottom="5"
                         textAlign="center" verticalAlign="middle"
                         fontWeight="normal" color="white" fontSize="11">
                </s:Label>
            </s:DataRenderer>
       </fx:Component>
    </fx:Declarations>
    
    <!--- 
   The default skin class is HSliderTrackSkin. 
   @copy spark.components.supportClasses.TrackBase#track
   @see spark.skins.spark.HSliderTrackSkin 
 
   We changing the size of the track so that it will
   become very thin.
 -->
    <s:Button id="track" left="0" right="0" top="8" bottom="8" minWidth="33" width="100" 
              tabEnabled="false"
              skinClass="spark.skins.spark.HSliderTrackSkin" />
              
    <!--- 
   The default skin class is HSliderThumbSkin.
         @copy spark.components.supportClasses.TrackBase#thumb 
         @see spark.skins.spark.HSliderThumbSkin
 
   We changing the Button Skin Class to 
   spark.skins.spark.ButtonSkin so that we 
   can bind the label to the thumb.
 -->
    <s:Button id="thumb" top="0" bottom="0" width="45" height="20" 
              tabEnabled="false" styleName="customSliderHThumb" 
     label="{hostComponent.value}" />
</s:SparkSkin>

Skin Class for the Vertical Slider - skins/CustomVSliderWithLabel.mxml
<?xml version="1.0" encoding="utf-8"?>

<!--

    ADOBE SYSTEMS INCORPORATED
    Copyright 2008 Adobe Systems Incorporated
    All Rights Reserved.

    NOTICE: Adobe permits you to use, modify, and distribute this file
    in accordance with the terms of the license agreement accompanying it.
-->

<!--- The default skin class for the Spark VSlider component. The thumb and track skins are defined by the
VSliderThumbSkin and VSliderTrackSkin classes, respectively.  

       @see spark.components.VSlider
       @see spark.skins.spark.VSliderThumbSkin
       @see spark.skins.spark.VSliderTrackSkin
        
      @langversion 3.0
      @playerversion Flash 10
      @playerversion AIR 1.5
      @productversion Flex 4
-->
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:fb="http://ns.adobe.com/flashbuilder/2009" minWidth="11" alpha.disabled="0.5">

    <fx:Metadata>
    <![CDATA[ 
        /** 
         * @copy spark.skins.spark.ApplicationSkin#hostComponent
         */
        [HostComponent("spark.components.VSlider")]
    ]]>
    </fx:Metadata> 
    
    <fx:Script fb:purpose="styling">
        /* Define the skin elements that should not be colorized. 
           For slider, the skin itself is colorized but the individual parts are not. */
        static private const exclusions:Array = ["track", "thumb"];

        /**
         * @private
         */   
        override public function get colorizeExclusions():Array {return exclusions;}
        
        /**
         * @private
         */
        override protected function initializationComplete():void
        {
            useChromeColor = true;
            super.initializationComplete();
        }
    </fx:Script>
 
    <fx:Script>
        /**
         *  @private
         */  
        override protected function measure() : void
        {
            // Temporarily move the thumb to the top of the Slider so measurement
            // doesn't factor in its y position. This allows resizing the
            // VSlider to less than 100px in height. 
            var thumbPos:Number = thumb.getLayoutBoundsY();
            thumb.setLayoutBoundsPosition(thumb.getLayoutBoundsX(), 0);
            super.measure();
            thumb.setLayoutBoundsPosition(thumb.getLayoutBoundsX(), thumbPos);
        }
    </fx:Script>
    
    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>
    
    <fx:Declarations>
        <!--- The tooltip used in the mx.controls.Slider control.
              To customize the DataTip's appearance, create a custom VSliderSkin class. -->
        <fx:Component id="dataTip">
            <s:DataRenderer minHeight="24" minWidth="40" x="20"> 
                <s:Rect top="0" left="0" right="0" bottom="0">
                    <s:fill>
                        <s:SolidColor color="0x000000" alpha=".9"/>
                    </s:fill>
                    <s:filters>
                        <s:DropShadowFilter angle="90" color="0x999999" distance="3"/>
                    </s:filters>
                </s:Rect>
                <s:Label id="labelDisplay" text="{data}"
                         horizontalCenter="0" verticalCenter="1"
                         left="5" right="5" top="5" bottom="5"
                         textAlign="center" verticalAlign="middle"
                         fontWeight="normal" color="white" fontSize="11">
                </s:Label>
            </s:DataRenderer>
        </fx:Component>
    </fx:Declarations>
    
    <!--- 
   The default skin class is VSliderTrackSkin.
   @copy spark.components.supportClasses.TrackBase#track 
   @see spark.skins.spark.VSliderTrackSkin 
 
   We changing the size of the track so that it will
   become very thin. 
 -->
    <s:Button id="track" left="17" right="17" top="0" bottom="0" minHeight="33" height="100"
              tabEnabled="false"
              skinClass="spark.skins.spark.VSliderTrackSkin" />
              
    <!--- 
   The default skin class is VSliderThumbSkin. 
   @copy spark.components.supportClasses.TrackBase#thumb
   @see spark.skins.spark.VSliderThumbSkin
   
   We changing the Button Skin Class to 
   spark.skins.spark.ButtonSkin so that we 
   can bind the label to the thumb.
 -->
    <s:Button id="thumb" left="0" right="0" width="36" height="27"
              tabEnabled="false" styleName="customSliderVThumb" 
     label="{hostComponent.value}" />
</s:SparkSkin>

The main CSS file - css/style.css
/* CSS file */
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

s|VSlider.customSliderV{
 skinClass: ClassReference("skins.CustomVSliderWithLabel");
}
s|HSlider.customSliderH{
 skinClass: ClassReference("skins.CustomHSliderWithLabel");
}
.customSliderHThumb,.customSliderVThumb{
 skinClass: ClassReference("spark.skins.spark.ButtonSkin");
}
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Thursday, August 22, 2013

In another 2 more weeks...

For all you IT savvy geeks out there, aren't you excited about the upcoming IT Show, which is going to happen at Singapore Expo in another 2 more weeks? Have you listed down all the new gadgets that you wanted to buy or play with? Well, if you have not been doing that, you probably want to start now...

Comex 2013 Details:
  • Date: 5 - 8 September 2013
    Venue: Singapore Expo, Hall 5 & 6
    Time: 12noon - 9pm
* Click here to find out more about 'Comex 2013'.
^ Click here for the Price List and Brochures for 'Comex 2013'.
~ Click here for the 'Facebook' Page of 'Comex 2013'.
# Click here to play the official 'Comex 2013' game and you might be rewarded with a
  21.5" iMAC12, 11" MacBook Air or other awesome tech gadgets!

Wednesday, August 14, 2013

Flex: Generating ASDoc

Rather than searching high and low for a particular function, wouldn't it be much more easier if you can just search through a set of HTML files in case you need to reference some codes that you have writen back then? Therefore, I have created a ant file to auto generated the HTML for the Flex 3 and Flex 4 projects that I have shown on this blog.

Main Ant file - build.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<project basedir="." default="dist.asdocs">
 <!-- project root directory -->
 <property name="APP_ROOT" location="${basedir}/.." />
 <property file="build.properties" />

 <target name="dist.asdocs" description="Create asdocs">
  <cleanAsdocsDir3 />
  <createAsdocs3 />
  <cleanAsdocsDir4 />
  <createAsdocs4 />
 </target>

 <macrodef name="createAsdocs3">
  <sequential>
      <tstamp prefix="dateStamp">
          <format property="date" pattern="MMM/dd/yyyy hh:mm:ss aa" unit="hour" />
      </tstamp>
   <java taskname="asdoc" jar="${FLEX_HOME_3}/lib/asdoc.jar" 
    failonerror="true" fork="true">
    <jvmarg value="-Dapplication.home=${FLEX_HOME_3}"/>
    <!-- Need to assign lots for memory for the ant job -->
    <jvmarg value="-Dsun.io.useCanonCaches=false"/>
    <jvmarg value="-Xms512m"/>
    <jvmarg value="-Xmx1024m"/>
    <jvmarg value="-XX:MaxPermSize=256m"/>

    <arg value="-warnings=false" />
    <!-- Root directory of files -->
    <arg value="-source-path" />
    <arg value="${APP_ROOT}/../AccessingSWFLoaderFunctions/src" />
    <arg value="${APP_ROOT}/../BubbleEventTest/src" />
    <arg value="${APP_ROOT}/../ClippingContent/src" />
    <arg value="${APP_ROOT}/../CustomiseToolTip/src" />
    <arg value="${APP_ROOT}/../DateCheckerApplication/src" />
    <arg value="${APP_ROOT}/../drawingShapes/src" />
    <arg value="${APP_ROOT}/../FlexCustomPreloader/src" />
    <arg value="${APP_ROOT}/../htmlEntity/src" />
    <arg value="${APP_ROOT}/../LinkList/src" />
    <arg value="${APP_ROOT}/../ListDragAndDrop/src" />
    <arg value="${APP_ROOT}/../MeasurePosition/src" />
    <arg value="${APP_ROOT}/../MultipleViewWithAlivePDF/src" />
    <arg value="${APP_ROOT}/../PlayingWithAlivePDF/src" />
    <arg value="${APP_ROOT}/../PlayingWithAlivePDF/fonts" />
    <arg value="${APP_ROOT}/../PlayingWithRegExp/src" />
    <arg value="${APP_ROOT}/../PopUpOverlay/src" />
    <arg value="${APP_ROOT}/../ScreenCaptureView/src" />
    <arg value="${APP_ROOT}/../SimpleChartHorizontalAxis/src" />
    <arg value="${APP_ROOT}/../SimpleSingleton/src" />
    <arg value="${APP_ROOT}/../stopBubbleEventTest/src" />
    <!-- List of files to document -->
    <arg value="-doc-sources" />
    <arg value="${APP_ROOT}/../AccessingSWFLoaderFunctions/src" />
    <arg value="${APP_ROOT}/../BubbleEventTest/src" />
    <arg value="${APP_ROOT}/../ClippingContent/src" />
    <arg value="${APP_ROOT}/../CustomiseToolTip/src" />
    <arg value="${APP_ROOT}/../DateCheckerApplication/src" />
    <arg value="${APP_ROOT}/../drawingShapes/src" />
    <arg value="${APP_ROOT}/../FlexCustomPreloader/src" />
    <arg value="${APP_ROOT}/../htmlEntity/src" />
    <arg value="${APP_ROOT}/../LinkList/src" />
    <arg value="${APP_ROOT}/../ListDragAndDrop/src" />
    <arg value="${APP_ROOT}/../MeasurePosition/src" />
    <arg value="${APP_ROOT}/../MultipleViewWithAlivePDF/src" />
    <arg value="${APP_ROOT}/../PlayingWithAlivePDF/src" />
    <arg value="${APP_ROOT}/../PlayingWithRegExp/src" />
    <arg value="${APP_ROOT}/../PopUpOverlay/src" />
    <arg value="${APP_ROOT}/../ScreenCaptureView/src" />
    <arg value="${APP_ROOT}/../SimpleChartHorizontalAxis/src" />
    <arg value="${APP_ROOT}/../SimpleSingleton/src" />
    <arg value="${APP_ROOT}/../stopBubbleEventTest/src" />
    <!-- Adding a external library, if u are using one -->
    <arg value="-external-library-path" />
    <arg value="${APP_ROOT}/../PlayingWithAlivePDF/libs" />
    <!-- Title of the generated asdoc webpage -->
    <arg value="-window-title" />
    <arg value="Flex 3.5.0 Projects Documentation" />
    <!-- Header shown in the generated asdoc webpage -->
    <arg value="-main-title" />
    <arg value="Flex 3.5.0 Projects Documentation" />
    <!-- Footer shown in the generated asdoc webpage -->
    <arg value="-footer" />
    <arg value="Created on ${dateStamp.date}" />
    <!-- Output folder of the generated asdoc webpage -->
    <arg value="-output" />
    <arg value="${OUTPUT_DOCS_DIR_3}" />
    <!-- If your project requires some TrueType, OpenType fonts -->
    <!--<arg value="-managers" />
    <arg value="flash.fonts.AFEFontManager" />-->
   </java>
  </sequential>
 </macrodef>

 <macrodef name="cleanAsdocsDir3">
  <sequential>
   <echo message="Clean Asdocs Directory" />
   <delete dir="${OUTPUT_DOCS_DIR_3}" failonerror="false" />
   <mkdir dir="${OUTPUT_DOCS_DIR_3}" />
   <echo>Cleaned asdocs output directory</echo>
  </sequential>
 </macrodef>

 <macrodef name="createAsdocs4">
  <sequential>
      <tstamp prefix="dateStamp">
          <format property="date" pattern="MMM/dd/yyyy hh:mm:ss aa" unit="hour" />
      </tstamp>
   <java taskname="asdoc" jar="${FLEX_HOME_4}/lib/asdoc.jar" 
    failonerror="true" fork="true">
    <jvmarg value="-Dapplication.home=${FLEX_HOME_4}"/>
    <!-- Need to assign lots for memory for the ant job -->
    <jvmarg value="-Dsun.io.useCanonCaches=false"/>
    <jvmarg value="-Xms512m"/>
    <jvmarg value="-Xmx1024m"/>
    <jvmarg value="-XX:MaxPermSize=256m"/>

    <arg value="-warnings=false" />
    <!-- Root directory of files -->
    <arg value="-source-path" />
    <arg value="${APP_ROOT}/../AutoresizableTextfield/src" />
    <arg value="${APP_ROOT}/../ImageSourceIssue/src" />
    <arg value="${APP_ROOT}/../NotReallyGlobalTooltip/src" />
    <arg value="${APP_ROOT}/../OpenURLOfHTMLText/src" />
    <arg value="${APP_ROOT}/../Simple2WayBinding/src" />
    <arg value="${APP_ROOT}/../Simple3StateCheckBox/src" />
    <arg value="${APP_ROOT}/../SimpleAreaChartToolTip/src" />
    <arg value="${APP_ROOT}/../SimpleButtonBarSelect/src" />
    <arg value="${APP_ROOT}/../SimpleChartAnimation/src" />
    <arg value="${APP_ROOT}/../SimpleChartDataClick/src" />
    <arg value="${APP_ROOT}/../SimpleChartPositionOfAPoint/src" />
    <arg value="${APP_ROOT}/../SimpleChartVisibility/src" />
    <arg value="${APP_ROOT}/../SimpleCopyingOfObjects/src" />
    <arg value="${APP_ROOT}/../SimpleDataTipBorder/src" />
    <arg value="${APP_ROOT}/../SimpleDoubleClicking/src" />
    <arg value="${APP_ROOT}/../SimpleMouseReleaseOutside/src" />
    <arg value="${APP_ROOT}/../SimpleMouseReleaseOutside/src" />
    <arg value="${APP_ROOT}/../SimplePastingDetection/src" />
    <arg value="${APP_ROOT}/../SimpleStackingColumn/src" />
    <arg value="${APP_ROOT}/../SimpleSudokuGrid/src" />
    <arg value="${APP_ROOT}/../SimpleTabSequence/src" />
    <arg value="${APP_ROOT}/../SimpleTwitterPost/src" />
    <!-- List of files to document -->
    <arg value="-doc-sources" />
    <arg value="${APP_ROOT}/../AutoresizableTextfield/src" />
    <arg value="${APP_ROOT}/../ImageSourceIssue/src" />
    <arg value="${APP_ROOT}/../NotReallyGlobalTooltip/src" />
    <arg value="${APP_ROOT}/../OpenURLOfHTMLText/src" />
    <arg value="${APP_ROOT}/../Simple2WayBinding/src" />
    <arg value="${APP_ROOT}/../Simple3StateCheckBox/src" />
    <arg value="${APP_ROOT}/../SimpleAreaChartToolTip/src" />
    <arg value="${APP_ROOT}/../SimpleButtonBarSelect/src" />
    <arg value="${APP_ROOT}/../SimpleChartAnimation/src" />
    <arg value="${APP_ROOT}/../SimpleChartDataClick/src" />
    <arg value="${APP_ROOT}/../SimpleChartPositionOfAPoint/src" />
    <arg value="${APP_ROOT}/../SimpleChartVisibility/src" />
    <arg value="${APP_ROOT}/../SimpleCopyingOfObjects/src" />
    <arg value="${APP_ROOT}/../SimpleDataTipBorder/src" />
    <arg value="${APP_ROOT}/../SimpleDoubleClicking/src" />
    <arg value="${APP_ROOT}/../SimpleMouseReleaseOutside/src" />
    <arg value="${APP_ROOT}/../SimpleMouseReleaseOutside/src" />
    <arg value="${APP_ROOT}/../SimplePastingDetection/src" />
    <arg value="${APP_ROOT}/../SimpleStackingColumn/src" />
    <arg value="${APP_ROOT}/../SimpleSudokuGrid/src" />
    <arg value="${APP_ROOT}/../SimpleTabSequence/src" />
    <arg value="${APP_ROOT}/../SimpleTwitterPost/src" />
    <!-- Title of the generated asdoc webpage -->
    <arg value="-window-title" />
    <arg value="Flex 4.6.0 Projects Documentation" />
    <!-- Header shown in the generated asdoc webpage -->
    <arg value="-main-title" />
    <arg value="Flex 4.6.0 Projects Documentation" />
    <!-- Footer shown in the generated asdoc webpage -->
    <arg value="-footer" />
    <arg value="Created on ${dateStamp.date}" />
    <!-- Output folder of the generated asdoc webpage -->
    <arg value="-output" />
    <arg value="${OUTPUT_DOCS_DIR_4}" />
    <!-- If your project requires some TrueType, OpenType fonts -->
    <!--<arg value="-managers" />
    <arg value="flash.fonts.AFEFontManager" />-->
   </java>
  </sequential>
 </macrodef>

 <macrodef name="cleanAsdocsDir4">
  <sequential>
   <echo message="Clean Asdocs Directory" />
   <delete dir="${OUTPUT_DOCS_DIR_4}" failonerror="false" />
   <mkdir dir="${OUTPUT_DOCS_DIR_4}" />
   <echo>Cleaned asdocs output directory</echo>
  </sequential>
 </macrodef>
</project>

Main Ant Properties file - build.properties
# change this to your Flex SDK directory path
FLEX_HOME_3=C:/Program Files (x86)/Adobe/Adobe Flash Builder 4.5/sdks/3.6.0
FLEX_HOME_4=C:/Program Files (x86)/Adobe/Adobe Flash Builder 4.5/sdks/4.5.0

# output location for the asdocs for flex 3.6
OUTPUT_DOCS_DIR_3 = ${APP_ROOT}/../asdocs_3.6

# output location for the asdocs for flex 4.5
OUTPUT_DOCS_DIR_4 = ${APP_ROOT}/../asdocs_4.5
* Click here to find out more about ASDoc.
* Click here for the ASDoc that I have generated for my Flex 3 projects.
* Click here for the ASDoc that I have generated for my Flex 4 projects.
* Click here to download the ant build files that I have shown in this post.

Thursday, August 8, 2013

Flex: Creating a Checkbox with 3 states...

There will be situations that requires a list of checkboxes and you are giving the user the option of selecting and unselecting the individual options. However, the normal checkbox might work for the individual selections but it will not work nicely for the header. Therefore...

Main Application Source Code - Simple3StateCheckBox.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/mx" 
      creationComplete="creationCompleteHandler(event)">
 <fx:Declarations>
  <!-- Place non-visual elements (e.g., services, value objects) here -->
 </fx:Declarations>
 <fx:Style source="css/style.css"/>
 <fx:Script>
  <![CDATA[
   import events.CustomCheckBoxEvents;
   
   import mx.collections.ArrayCollection;
   import mx.events.FlexEvent;
   
   import object.SingletonObject;
   
   /**
    * We are using a singleton object to store the objects/data.
    */
   private var dataObj:SingletonObject = 
    SingletonObject.getInstance();
   /**
    * Upon creationComplete, we need to add all the Listeners
    * and check the appropriate state that we should be
    * displaying for the header.
    */
   protected function creationCompleteHandler(event:FlexEvent):void
   {
    this.addEventListener(
     CustomCheckBoxEvents.SELECT_ALL_ITEMS,
     checkBoxEvent);
    this.addEventListener(
     CustomCheckBoxEvents.UNSELECT_ALL_ITEMS,
     checkBoxEvent);
    this.addEventListener(
     CustomCheckBoxEvents.SELECT_AN_ITEM,
     checkBoxEvent);
    this.addEventListener(
     CustomCheckBoxEvents.UNSELECT_AN_ITEM,
     checkBoxEvent);
    updateHeaderState();
   }
   
   /**
    * All the events are handled over here.
    */
   private function checkBoxEvent(event:CustomCheckBoxEvents):void
   {
    for each(var item:Object in dataObj.expenses)
    {
     if(event.type == CustomCheckBoxEvents.SELECT_ALL_ITEMS)
     {
      item.selected = true;
     }else if(event.type == CustomCheckBoxEvents.UNSELECT_ALL_ITEMS)
     {
      item.selected = false;
     }else if(item.id == event.data.id)
     {
      if(event.type == 
       CustomCheckBoxEvents.SELECT_AN_ITEM)
      {
       item.selected = true;
      }else if(event.type == 
       CustomCheckBoxEvents.UNSELECT_AN_ITEM)
      {
       item.selected = false;
      }
     }
    }
    dataObj.expenses.refresh();
    updateHeaderState();
   }
   
   /**
    * Function to determine that best state that we are
    * showing for the header.
    */
   private function updateHeaderState():void
   {
    var count:Number = 0;
    var maxCount:Number = 0;
    for each(var item:Object in dataObj.expenses)
    {
     maxCount ++;
     if(item.selected)
     {
      count ++;
     }
    }
    if(count == 0)
    {
     dataObj.selectedState = 0;
    }else if(count == maxCount)
    {
     dataObj.selectedState = 2;
    }else{
     dataObj.selectedState = 1;
    }
   }
  ]]>
 </fx:Script>
 <s:VGroup width="100%"
     height="100%" 
     verticalAlign="middle" 
     horizontalAlign="center">
  <s:DataGrid width="50%"
      height="50%" 
     dataProvider="{dataObj.expenses}">
   <s:columns>
    <s:ArrayList>
     <s:GridColumn dataField="selected" 
          width="20"
          headerRenderer="renderers.CustomHeaderCheckBoxRenderer"
          itemRenderer="renderers.CustomCheckBoxRenderer"/>
     <s:GridColumn dataField="amount"/>
    </s:ArrayList>
   </s:columns>
  </s:DataGrid>
 </s:VGroup>
</s:Application>

Event class for the checkbox - events/CustomCheckBoxEvents.as
package events
{
 import flash.events.Event;
 
 public class CustomCheckBoxEvents extends Event
 {
  //All the different event type
  public static const SELECT_ALL_ITEMS:String = 
   "selectAllItems";
  public static const UNSELECT_ALL_ITEMS:String = 
   "unselectAllItems";
  public static const SELECT_AN_ITEM:String = 
   "selectAnItem";
  public static const UNSELECT_AN_ITEM:String = 
   "unselectAnItem";
  
  private var _data:Object = null;

  public function get data():Object
  {
   return _data;
  }

  public function set data(value:Object):void
  {
   _data = value;
  }
  
  public function CustomCheckBoxEvents(type:String, tempData:Object = null, bubbles:Boolean=true, cancelable:Boolean=false)
  {
   super(type, bubbles, cancelable);
   _data = tempData;
  }
  
  override public function clone():Event
  {
   // TODO Auto Generated method stub
   return new CustomCheckBoxEvents(type, data, bubbles, cancelable);
  }
 }
}

Singleton Object class to store the neccessary data - object/SingletonObject.as
package object
{
 import mx.collections.ArrayCollection;

 public class SingletonObject
 {
  private static var _instance:SingletonObject=null;
  
  /**
   * You need the following functions to create a
   * singleton Object. SingletonObject(e:SingletonEnforcer)
   * and getInstance():SingletonObject
   * Rather than using new SingletonObject to create a new
   * object of the class, you need to use
   * SingletonObject.getInstance() to point to the Singleton
   * class.
   */
  public function SingletonObject(e:SingletonEnforcer){
   trace("new singleton object created");
  }

  public static function getInstance():SingletonObject{
   if(_instance==null){
    _instance=new SingletonObject(new SingletonEnforcer);
   }
   return _instance;
  }
  
  /**
   * Here's our data for the DataGrid.
   */
  private var _expenses:ArrayCollection = new ArrayCollection([
   {selected:true, id:0, amount:2000},
   {selected:true, id:1, amount:1000},
   {selected:true, id:2, amount:100},
   {selected:true, id:3, amount:450},
   {selected:true, id:4, amount:100},
   {selected:true, id:5, amount:200}
  ]);
  
  [Bindable]
  public function get expenses():ArrayCollection
  {
   return _expenses;
  }
  
  public function set expenses(value:ArrayCollection):void
  {
   _expenses = value;
  }
  
  private var _selectedState:Number = 0;
  
  [Bindable]
  public function get selectedState():Number
  {
   return _selectedState;
  }

  public function set selectedState(value:Number):void
  {
   _selectedState = value;
  }
 }
}

/**
 * This class is needed in creating a singleton class.
 */
class SingletonEnforcer{
 
}

Renderers class for the header that comes with a custom checkbox with 3 different states - renderers/CustomHeaderCheckBoxRenderer.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:DefaultGridHeaderRenderer 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:component="renderers.component.*" 
        width="100%" click="clickHandler(event)">
 <fx:Declarations>
  <!-- Place non-visual elements (e.g., services, value objects) here -->
 </fx:Declarations>
 <fx:Script>
  <![CDATA[
   import object.SingletonObject;
   /**
    * We are using a singleton object to store the objects/data.
    */
   private var dataObj:SingletonObject = 
    SingletonObject.getInstance();
   
   protected function clickHandler(event:MouseEvent):void
   {
    event.stopImmediatePropagation();
   }
   
  ]]>
 </fx:Script>
 <s:states>
  <s:State name="normal" />
  <s:State name="hovered" />
  <s:State name="down" />
 </s:states>     
 
 <!-- layer 1: shadow -->
 <!--- @private -->
 <s:Rect id="shadow" left="-1" right="-1" top="-1" bottom="-1" radiusX="2">
  <s:fill>
   <s:LinearGradient rotation="90">
    <s:GradientEntry color="0x000000" 
         color.down="0xFFFFFF"
         alpha="0.01"
         alpha.down="0" />
    <s:GradientEntry color="0x000000" 
         color.down="0xFFFFFF" 
         alpha="0.07"
         alpha.down="0.5" />
   </s:LinearGradient>
  </s:fill>
 </s:Rect>
 
 <!-- layer 2: fill -->
 <!--- @private -->
 <s:Rect id="fill" left="0" right="0" top="0" bottom="0">
  <s:fill>
   <s:LinearGradient rotation="90">
    <s:GradientEntry color="0xFFFFFF" 
         color.hovered="0xBBBDBD" 
         color.down="0xAAAAAA" 
         alpha="0.85" />
    <s:GradientEntry color="0xD8D8D8" 
         color.hovered="0x9FA0A1" 
         color.down="0x929496" 
         alpha="0.85" />
   </s:LinearGradient>
  </s:fill>
 </s:Rect>
 
 <!-- layer 3: fill lowlight -->
 <!--- @private -->
 <s:Rect id="lowlight" left="0" right="0" top="0" bottom="0">
  <s:fill>
   <s:LinearGradient rotation="270">
    <s:GradientEntry color="0x000000" ratio="0.0" alpha="0.0627" />
    <s:GradientEntry color="0x000000" ratio="0.48" alpha="0.0099" />
    <s:GradientEntry color="0x000000" ratio="0.48001" alpha="0" />
   </s:LinearGradient>
  </s:fill>
 </s:Rect>
 
 <!-- layer 4: fill highlight -->
 <!--- @private -->
 <s:Rect id="highlight" left="0" right="0" top="0" bottom="0">
  <s:fill>
   <s:LinearGradient rotation="90">
    <s:GradientEntry color="0xFFFFFF"
         ratio="0.0"
         alpha="0.33" 
         alpha.hovered="0.22" 
         alpha.down="0.12"/>
    <s:GradientEntry color="0xFFFFFF"
         ratio="0.48"
         alpha="0.33"
         alpha.hovered="0.22"
         alpha.down="0.12" />
    <s:GradientEntry color="0xFFFFFF"
         ratio="0.48001"
         alpha="0" />
   </s:LinearGradient>
  </s:fill>
 </s:Rect>  
 
 <!-- layer 5: highlight stroke (all states except down) -->
 <!--- @private -->
 <s:Rect id="highlightStroke" left="0" right="0" top="0" bottom="0" excludeFrom="down">
  <s:stroke>
   <s:LinearGradientStroke rotation="90" weight="1">
    <s:GradientEntry color="0xFFFFFF" alpha.hovered="0.22" />
    <s:GradientEntry color="0xD8D8D8" alpha.hovered="0.22" />
   </s:LinearGradientStroke>
  </s:stroke>
 </s:Rect>
 
 <!-- layer 6: highlight stroke (down state only) -->
 <!--- @private -->
 <s:Rect id="hldownstroke1" left="0" right="0" top="0" bottom="0" includeIn="down">
  <s:stroke>
   <s:LinearGradientStroke rotation="90" weight="1">
    <s:GradientEntry color="0x000000" alpha="0.25" ratio="0.0" />
    <s:GradientEntry color="0x000000" alpha="0.25" ratio="0.001" />
    <s:GradientEntry color="0x000000" alpha="0.07" ratio="0.0011" />
    <s:GradientEntry color="0x000000" alpha="0.07" ratio="0.965" />
    <s:GradientEntry color="0x000000" alpha="0.00" ratio="0.9651" />
   </s:LinearGradientStroke>
  </s:stroke>
 </s:Rect>
 <!--- @private -->
 <s:Rect id="hldownstroke2" left="1" right="1" top="1" bottom="1" includeIn="down">
  <s:stroke>
   <s:LinearGradientStroke rotation="90" weight="1">
    <s:GradientEntry color="0x000000" alpha="0.09" ratio="0.0" />
    <s:GradientEntry color="0x000000" alpha="0.00" ratio="0.0001" />
   </s:LinearGradientStroke>
  </s:stroke>
 </s:Rect>
 <s:VGroup verticalAlign="middle" 
     horizontalAlign="center" 
     width="100%"
     height="100%">
  <component:CustomCheckBox styleName="customCheckBox"
          selectedValue="{dataObj.selectedState}"/>
 </s:VGroup>
</s:DefaultGridHeaderRenderer>


Custom checkbox that supports 3 different states - renderers/component/CustomCheckBox.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:CheckBox xmlns:fx="http://ns.adobe.com/mxml/2009" 
   xmlns:s="library://ns.adobe.com/flex/spark" 
   xmlns:mx="library://ns.adobe.com/flex/mx" 
   click="clickHandler(event)" 
   mouseOver="mouseOverHandler(event)" 
   mouseDown="mouseDownHandler(event)" 
   mouseOut="mouseOutHandler(event)">
 <!-- Declaring all the different types of state in this component -->
 <fx:Metadata>
  [SkinState("up")]
  [SkinState("over")]
  [SkinState("down")]
  [SkinState("disabled")]
  [SkinState("upAndSelected")]
  [SkinState("overAndSelected")]
  [SkinState("downAndSelected")]
  [SkinState("disabledAndSelected")]
  [SkinState("upAndSemiSelected")]
  [SkinState("overAndSemiSelected")]
  [SkinState("downAndSemiSelected")]
  [SkinState("disabledAndSemiSelected")]
 </fx:Metadata>
 <fx:Script>
  <![CDATA[
   import events.CustomCheckBoxEvents;
   /**
    * Rather than select and unselected state,
    * we need 3 different states now.
    * 
    * 0 - not selected
    * 1 - semi selected
    * 2 - selected
    * 
    * @default 0
    */
   private var _selectedValue:Number = 0;

   public function get selectedValue():Number
   {
    return _selectedValue;
   }

   public function set selectedValue(value:Number):void
   {
    _selectedValue = value;
    updateState();
   }
   
   /**
    * For storing the component state
    * 
    * 0 - up
    * 1 - over
    * 2 - down
    * 
    * @default 0
    */
   private var currentComponentState:Number = 0;
   
   /**
    * For the skin file to find out the state that it
    * should be displaying.
    */
   override protected function getCurrentSkinState():String
   {
    return this.currentState;
   }
   
   /**
    * Upon clicking on this component, we need to raise
    * a few events.
    */
   override protected function clickHandler(event:MouseEvent):void
   {
    event.stopImmediatePropagation();
    switch (_selectedValue)
    {
     //when unselected
     case 0:
      _selectedValue = 2;
      this.dispatchEvent(
       new CustomCheckBoxEvents(
        CustomCheckBoxEvents.SELECT_ALL_ITEMS));
      break;
     //when semi selected
     case 1:
      _selectedValue = 2;
      this.dispatchEvent(
       new CustomCheckBoxEvents(
        CustomCheckBoxEvents.SELECT_ALL_ITEMS));
      break;
     //when selected
     case 2:
      _selectedValue = 0;
      this.dispatchEvent(
       new CustomCheckBoxEvents(
        CustomCheckBoxEvents.UNSELECT_ALL_ITEMS));
      break;
    }
    updateState();
   }
   
   /**
    * Upon mouse over on this component, we need to update
    * the state.
    */
   protected function mouseOverHandler(event:MouseEvent):void
   {
    event.stopImmediatePropagation();
    currentComponentState = 1;
    updateState();
   }
   
   /**
    * Upon mouse down on this component, we need to update
    * the state.
    */
   protected function mouseDownHandler(event:MouseEvent):void
   {
    event.stopImmediatePropagation();
    currentComponentState = 2;
    updateState();
   }
   
   /**
    * Upon mouse out on this component, we need to update
    * the state.
    */
   protected function mouseOutHandler(event:MouseEvent):void
   {
    event.stopImmediatePropagation();
    currentComponentState = 0;
    updateState();
   }
   
   private function updateState():void
   {
    var tempStr:String = "";
    switch(_selectedValue)
    {
     //when unselected
     case 0:
      tempStr = "";
      break;
     //when semi selected
     case 1:
      tempStr = "AndSemiSelected";
      break;
     //when selected
     case 2:
      tempStr = "AndSelected";
      break;
    }
    if(this.enabled)
    {
     switch(currentComponentState)
     {
      //when mouse out
      case 0:
       tempStr = "up" + tempStr;
       break;
      //when mouse over
      case 1:
       tempStr = "over" + tempStr;
       break;
      //when mouse down
      case 2:
       tempStr = "down" + tempStr;
       break;
     }
    }else{
     tempStr = "disabled" + tempStr;
    }
    this.currentState = tempStr;
   }
  ]]>
 </fx:Script>
 <!-- 
  Upon swaping to these states, we need to call the function
  invalidateSkinState() to update the skin immediately.
 -->
 <s:states>
  <s:State name="up" enterState="invalidateSkinState()"/>
  <s:State name="over" enterState="invalidateSkinState()"/>
  <s:State name="down" enterState="invalidateSkinState()"/>
  <s:State name="disabled" enterState="invalidateSkinState()"/>
  <s:State name="upAndSelected" enterState="invalidateSkinState()"/>
  <s:State name="overAndSelected" enterState="invalidateSkinState()"/>
  <s:State name="downAndSelected" enterState="invalidateSkinState()"/>
  <s:State name="disabledAndSelected" enterState="invalidateSkinState()"/>
  <s:State name="upAndSemiSelected" enterState="invalidateSkinState()"/>
  <s:State name="overAndSemiSelected" enterState="invalidateSkinState()"/>
  <s:State name="downAndSemiSelected" enterState="invalidateSkinState()"/>
  <s:State name="disabledAndSemiSelected" enterState="invalidateSkinState()"/>
 </s:states>
</s:CheckBox>

Skin class for the custom checkbox - skins/CustomCheckBoxSkin.mxml
<?xml version="1.0" encoding="utf-8"?>

<!--

    ADOBE SYSTEMS INCORPORATED
    Copyright 2008 Adobe Systems Incorporated
    All Rights Reserved.

    NOTICE: Adobe permits you to use, modify, and distribute this file
    in accordance with the terms of the license agreement accompanying it.

-->

<!--- The default skin class for the Spark CheckBox component.  

      @see spark.components.CheckBox
        
      @langversion 3.0
      @playerversion Flash 10
      @playerversion AIR 1.5
      @productversion Flex 4
-->
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
    alpha.disabledStates="0.5">
 <fx:Metadata>[HostComponent("renderers.component.CustomCheckBox")]</fx:Metadata>

    <fx:Script fb:purpose="styling">
        /* Define the skin elements that should not be colorized. 
           For button, the graphics are colorized but the label is not. */
        static private const exclusions:Array = ["labelDisplay", "check"];

       /** 
        * @private 
        */     
        override public function get colorizeExclusions():Array {return exclusions;}
        
        /* Define the symbol fill items that should be colored by the "symbolColor" style. */
        static private const symbols:Array = ["checkMarkFill", "semiCheckMarkFill"];

       /**
        * @private 
        */
        override public function get symbolItems():Array {return symbols};
        
        /**
         * @private
         */
        override protected function initializationComplete():void
        {
            useChromeColor = true;
            super.initializationComplete();
        }
    </fx:Script>
    
    <fx:Script>
        <![CDATA[
            /** 
             * @private 
             */     
            private static const focusExclusions:Array = ["labelDisplay"];

            /**
             * @private
             */
            override public function get focusSkinExclusions():Array { return focusExclusions;};
        ]]>
    </fx:Script>
    
 <!--
  All the states of this component.
 -->
    <s:states>
        <s:State name="up" />
        <s:State name="over" stateGroups="overStates" />
        <s:State name="down" stateGroups="downStates" />
        <s:State name="disabled" stateGroups="disabledStates" />
        <s:State name="upAndSelected" stateGroups="selectedStates" />
        <s:State name="overAndSelected" stateGroups="overStates, selectedStates" />
        <s:State name="downAndSelected" stateGroups="downStates, selectedStates" />
        <s:State name="disabledAndSelected" stateGroups="disabledStates, selectedStates" />
  <s:State name="upAndSemiSelected" stateGroups="semiSelectedStates" />
  <s:State name="overAndSemiSelected" stateGroups="overStates, semiSelectedStates" />
  <s:State name="downAndSemiSelected" stateGroups="downStates, semiSelectedStates" />
  <s:State name="disabledAndSemiSelected" stateGroups="disabledStates, semiSelectedStates" />  
    </s:states>
    
    <s:Group verticalCenter="0" width="13" height="13" layoutDirection="ltr">
        <!-- drop shadow -->
        <s:Rect left="-1" top="-1" right="-1" bottom="-1">
            <s:stroke>
                <s:LinearGradientStroke rotation="90" weight="1">
                    <s:GradientEntry color="0x000000" 
                                   color.downStates="0xFFFFFF"
                                   alpha="0.011"
                                   alpha.downStates="0" />
                    <s:GradientEntry color="0x000000" 
                                   color.downStates="0xFFFFFF" 
                                   alpha="0.121"
                                   alpha.downStates="0.57" />
                </s:LinearGradientStroke>
            </s:stroke>
        </s:Rect>
        
        <!-- fill -->
        <s:Rect left="1" top="1" right="1" bottom="1">
            <s:fill>
                <s:LinearGradient rotation="90">
                    <s:GradientEntry color="0xFFFFFF" 
                                   color.overStates="0xBBBDBD" 
                                   color.downStates="0xAAAAAA" 
                                   alpha="0.85" />
                    <s:GradientEntry color="0xD8D8D8" 
                                   color.overStates="0x9FA0A1" 
                                   color.downStates="0x929496" 
                                   alpha="0.85" />
                </s:LinearGradient>
            </s:fill>
        </s:Rect>
        
        <!-- fill highlight -->
        <s:Rect left="1" right="1" top="1" height="5">
            <s:fill>
                <s:SolidColor color="0xFFFFFF" alpha="0.33" alpha.downStates="0" />
            </s:fill>
        </s:Rect>
        
        <!-- layer 6: highlight stroke (all states except down) -->
        <s:Rect left="1" right="1" top="1" bottom="1" excludeFrom="downStates">
            <s:stroke>
                <s:LinearGradientStroke rotation="90" weight="1">
                    <s:GradientEntry color="0xFFFFFF" alpha.overStates="0.33" />
                    <s:GradientEntry color="0xFFFFFF" alpha="0.12" alpha.overStates="0.0396" />
                </s:LinearGradientStroke>
            </s:stroke>
        </s:Rect>
        
        <!-- layer 6: highlight stroke (down state only) -->
        <s:Rect left="1" top="1" bottom="1" width="1" includeIn="downStates">
            <s:fill>
                <s:SolidColor color="0x000000" alpha="0.07" />
            </s:fill>
        </s:Rect>
        <s:Rect right="1" top="1" bottom="1" width="1" includeIn="downStates">
            <s:fill>
                <s:SolidColor color="0x000000" alpha="0.07" />
            </s:fill>
        </s:Rect>
        <s:Rect left="1" top="1" right="1" height="1" includeIn="downStates">
            <s:fill>
                <s:SolidColor color="0x000000" alpha="0.25" />
            </s:fill>
        </s:Rect>
        <s:Rect left="1" top="2" right="1" height="1" includeIn="downStates">
            <s:fill>
                <s:SolidColor color="0x000000" alpha="0.09" />
            </s:fill>
        </s:Rect>
        
        <!-- border - put on top of the fill so it doesn't disappear when scale is less than 1 -->
        <s:Rect left="0" top="0" right="0" bottom="0">
            <s:stroke>
                <s:LinearGradientStroke rotation="90" weight="1">
                    <s:GradientEntry color="0x000000" 
                                   alpha="0.5625"
                                   alpha.downStates="0.6375" />
                    <s:GradientEntry color="0x000000" 
                                   alpha="0.75"
                                   alpha.downStates="0.85" />
                </s:LinearGradientStroke>
            </s:stroke>
        </s:Rect>
    
        <!-- checkmark -->
        <!--- The checkmark in the box for this skin. To create a custom check mark, create a custom skin class. -->
        <s:Path left="2" top="0" includeIn="selectedStates" id="check" itemCreationPolicy="immediate"
                data="M 9.2 0.1 L 4.05 6.55 L 3.15 5.0 L 0.05 5.0 L 4.6 9.7 L 12.05 0.1 L 9.2 0.1">        
            <s:fill>
                <!--- 
                    @private 
                    The solid color fill for the CheckBox's checkmark. The default alpha is .9, and the default fill color is 0x000000. 
                -->
                <s:SolidColor id="checkMarkFill" color="0" alpha="0.8" />
            </s:fill>
        </s:Path>
  
  <!-- semi selected checkmark -->
  <!--- The checkmark in the box for this skin. To create a custom check mark, create a custom skin class. -->
  <s:Path left="2" top="2" includeIn="semiSelectedStates" id="semiCheck" itemCreationPolicy="immediate"
    data="M 1 1 L 10 1 L 10 10 L 1 10 L 1 1">        
   <s:fill>
    <!--- 
    @private 
    The solid color fill for the CheckBox's checkmark. The default alpha is .9, and the default fill color is 0x000000. 
    -->
    <s:SolidColor id="semiCheckMarkFill" color="0" alpha="0.8" />
   </s:fill>
  </s:Path>
    </s:Group>

    <!-- Label -->
    <!--- @copy spark.components.supportClasses.ButtonBase#labelDisplay -->
    <s:Label id="labelDisplay"
             textAlign="start"
             verticalAlign="middle"
             maxDisplayedLines="1"
             left="18" right="0" top="3" bottom="3" 
    verticalCenter="2" visible="false"/>
</s:SparkSkin>

Renderers class for the individual items that comes with a normal checkbox - CustomCheckBoxRenderer.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:GridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" 
     xmlns:s="library://ns.adobe.com/flex/spark" 
     xmlns:mx="library://ns.adobe.com/flex/mx" 
     implements="spark.components.gridClasses.IGridItemRenderer"
     width="100%">
 
 <fx:Script>
  <![CDATA[
   import events.CustomCheckBoxEvents;
   /** 
    * Upon setting the data into the renderer, we need to
    * change the state of the checkbox.
    */
   override public function set data(value:Object):void
   {
    super.data = value;
    chk.selected = data[this.column.dataField]
   } 
   
   /**
    * Upon cliking on the check box, we need to dispatch
    * some events.
    */   
   protected function chk_clickHandler(event:MouseEvent):void
   {
    event.stopImmediatePropagation();
    if(chk.selected)
    {
     this.dispatchEvent(
      new CustomCheckBoxEvents(
       CustomCheckBoxEvents.SELECT_AN_ITEM,
      this.data));
    }else{
     
     this.dispatchEvent(
      new CustomCheckBoxEvents(
       CustomCheckBoxEvents.UNSELECT_AN_ITEM,
      this.data));
    }
   }
   
  ]]>
 </fx:Script>
 <s:VGroup verticalAlign="middle" 
     horizontalAlign="center"
     width="100%" 
     height="100%">
  <s:CheckBox id="chk"
     click="chk_clickHandler(event)"/>
 </s:VGroup>
</s:GridItemRenderer>

CSS file for the application - ../css/style.css
/* CSS file */
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

.customCheckBox{
 skinClass: ClassReference("skins.CustomCheckBoxSkin");
}
* Click here for the demo shown in this post.
  (Try changing some of the checkbox to reflect the change of state in the topmost
  checkbox.)
^ Click here for the source files for the demo.

Friday, August 2, 2013

Flex: Creating the Sudoku Base Grid

I have been trying out all sorts of method in creating a Sudoku Grid and I finally managed to do it. The solution was far more easier than I thought ...

Let's take a look at the source code - SimpleSudokuGrid.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/mx" 
      creationComplete="creationCompleteEvent(event)">
 <fx:Declarations>
  <!-- Place non-visual elements (e.g., services, value objects) here -->
 </fx:Declarations>
 <fx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;
   import mx.events.FlexEvent;
   
   import spark.components.Button;
   import spark.events.IndexChangeEvent;
   
   /**
    * Holds the data to form a Grid
    */
   private var baseGrid:ArrayCollection;
   
   /**
    * Boolean to determine if there's a need to recreate the grid
    */
   private var needToReset:Boolean = true;
   
   /**
    * Number to count the number of failure in the current
    * grid data forming.
    */
   private var tries:Number = 0;
   
   /**
    * Maximum number of failure before recreate.
    */
   private static const MAX_TRIES:Number = 9;
   
   /**
    * A set of data that will be used to populate the ButtonBar
    */
   [Bindable]
   private var dummyData:ArrayCollection = new ArrayCollection(
    [{label: "1", value:1},
     {label: "2", value:2},
     {label: "3", value:3},
     {label: "4", value:4},
     {label: "5", value:5},
     {label: "6", value:6},
     {label: "7", value:7},
     {label: "8", value:8},
     {label: "9", value:9}]);
   
   /**
    * Upon Create Complete, we will create a Sudoku(9x9) Grid
    */
   protected function creationCompleteEvent(e:FlexEvent):void
   {
    //Will keep recreating the grid if 'needToReset' is
    //true
    while(needToReset)
    {
     //Set 'needToReset' to false
     needToReset = false;
     createGrid();
     createNumbers();
    }
    
    createGridUI();
    btnBar.dispatchEvent(
     new IndexChangeEvent(IndexChangeEvent.CHANGE));
   }
   
   /**
    * Create new (9*9) DataCollection and fill it up with 0.
    */
   private function createGrid():void
   {
    baseGrid = new ArrayCollection();
    
    var tempCol:ArrayCollection;
    for(var i:int = 0; i < 9; i ++)
    {
     tempCol = new ArrayCollection();
     for(var j:int = 0; j < 9; j++)
     {
      tempCol.addItem(0);
     }
     baseGrid.addItem(tempCol);
    }
   }
   
   /**
    * Start populating the numbers from 1 - 9.
    * If 'needToReset' is true, move out of this function.
    */ 
   private function createNumbers():void
   {
    for(var i:int = 1; i < 10; i ++)
    {
     insertNumber(i);
     
     if(needToReset)
     {
      break; 
     }
    }
    
    if(needToReset)
    {
     return; 
    }
    
    printNumbers();
   }
   
   /**
    * Find the empty boxes that you can slot the value
    * @param value a value that you are going to slot in
    */   
   private function insertNumber(value:Number):void
   {
    //Used to store the rows avaliable in a column
    var numbersFree:Array;
    //Is the 1st segment taken?
    var fromSegment1:Boolean = false;
    //Rows that we will need to remove if 1st segment
    //is taken
    var seg1Array:Array = [0,1,2];
    //Is the 2nd segment taken?
    var fromSegment2:Boolean = false;
    //Rows that we will need to remove if 2nd segment
    //is taken
    var seg2Array:Array = [3,4,5];
    //Is the 3rd segment taken?
    var fromSegment3:Boolean = false;
    //Rows that we will need to remove if 3rd segment
    //is taken
    var seg3Array:Array = [6,7,8];
    //Stores the current selected row of a column
    var rand:Number;
    //Stores all the boxes that has the value 
    var tempArray:Array = new Array();
    for(var i:int = 0; i < 9; i ++)
    {
     //In sudoku, it's divided into 9 (3*3) boxes.
     //Each each of this box, the same number can 
     //appear once.
     //Therefore every 3 column, we need to reset
     //these 3 values.
     if(i % 3 == 0)
     {
      fromSegment1 = false;
      fromSegment2 = false;
      fromSegment3 = false;
     }
     //Find the empty slots of the column first
     numbersFree = findEmptySlot(i);
     //Remove the rows based on the previous selections
     if(fromSegment1)
     {
      numbersFree = removeSlot(numbersFree, seg1Array);
     }
     if(fromSegment2)
     {
      numbersFree = removeSlot(numbersFree, seg2Array);
     }
     if(fromSegment3)
     {
      numbersFree = removeSlot(numbersFree, seg3Array);
     }
     //Remove the rows that have been selected already.
     if(tempArray.length > 0)
     {
      numbersFree = removeSlot(numbersFree, tempArray);
     }
     
     //If there are rows left
     if(numbersFree.length > 0)
     {
      //Select one of them
      rand = Math.floor(Math.random() 
       * numbersFree.length);
      rand = numbersFree[rand];
      //Check which Segment the row number is in
      if(rand < 3)
      {
       fromSegment1 = true;
      }else if(rand < 6)
      {
       fromSegment2 = true;
      }else{
       fromSegment3 = true;
      }
      //Add it to the list of boxes with the 
      //same value
      tempArray.push(rand);
      
      //Update the value in the main DataCollection
      insertSlot(i, rand, value);
     }else{
      //else if '0' row is avaliable
      //increase the number of failures/tries
      tries ++;
      //Revert back all the slots
      revertSlots(tempArray);
      tempArray = new Array();
      i = -1;
      //Check if it has reach the Maximum tries/failures
      if(tries == MAX_TRIES)
      {
       //Need to recreate the grid again.
       tries = 0;
       needToReset = true;
       return;
      }
     }
    }
   }
   
   /**
    * Find all the empty rows of a selected Column
    * @param col the selected column index
    * @return Array of rows that currently has a value of '0'
    */
   private function findEmptySlot(col:Number):Array
   {
    var tempArray:Array = new Array();
    var tempArrayCol:ArrayCollection;
    tempArrayCol = ArrayCollection(baseGrid.getItemAt(col));
    for(var i:int = 0; i < tempArrayCol.length; i ++)
    {
     if(Number(tempArrayCol.getItemAt(i)) == 0)
     {
      tempArray.push(i);
     }
    }
    return tempArray;
   }
   
   /**
    * Compare 2 Arrays.
    * @param tempArray1 Array of values
    * @param tempArray2 Array of values
    * @return the Array of values tempArray1 that are different from 
    * tempArray2
    */ 
   private function removeSlot(tempArray1:Array, tempArray2:Array):Array
   {
    var tempArray:Array = new Array();
    for(var i:int = 0; i < tempArray1.length; i ++)
    {
     tempArray.push(tempArray1[i]);
    }
    for(i = 0; i < tempArray.length; i ++)
    {
     for(var j:int = 0; j < tempArray2.length; j ++)
     {
      if(tempArray[i] == tempArray2[j])
      {
       tempArray.splice(i, 1);
       i --;
       break;
      }
     }
    }
    return tempArray;
   }
   
   /**
    * Replace a particular slot with a new value.
    * @param col selected column
    * @param row selected row
    * @param value the new value
    */
   private function insertSlot(col:Number, row:Number, value:Number):void
   {
    var tempArrayCol:ArrayCollection;
    tempArrayCol = ArrayCollection(baseGrid.getItemAt(col));
    tempArrayCol.setItemAt(value, row);
   }
   
   /**
    * This function will change the newly numbers of the same group 
    * back to 0. For example, halfway through the number 7, a problem 
    * has occur, therefore need to replace all the number 7 from the 
    * first box again. Hence, need to reset all the existing number 
    * 7 box to '0' first.
    * @param tempArray1 Boxes that requires the values to be reset to '0'
    */
   private function revertSlots(tempArray1:Array):void
   {
    var tempArrayCol:ArrayCollection;
    for(var i:int = 0; i < tempArray1.length; i ++)
    {
     tempArrayCol = ArrayCollection(baseGrid.getItemAt(i));
     tempArrayCol.setItemAt(0,tempArray1[i])
    }
   }
   
   /**
    * This is merely for debugging purposes. You can use
    * this function to track the valus in the (9x9) DataCollection.
    */
   private function printNumbers():void
   {
    var tempArrayCol:ArrayCollection;
    var tempArray:Array = new Array();
    for(var i:int = 0; i < 9; i ++)
    {
     tempArray.push("");     
    }
    for(var col:int = 0; col < baseGrid.length; col ++)
    {
     tempArrayCol = 
      ArrayCollection(baseGrid.getItemAt(col));
     for(var row:int = 0; row < tempArrayCol.length; row ++)
     {
      tempArray[row] += 
       Number(tempArrayCol.getItemAt(row));
     }
    }
    for(i = 0; i < tempArray.length; i ++)
    {
     trace(tempArray[i]);
    }
   }
   
   /**
    * Create all the buttons base on the values in the ArrayCollection
    */ 
   private function createGridUI():void
   {
    var btn:Button;
    var tempArrayCol:ArrayCollection;
    
    for(var row:int = 0; row < 9; row ++)
    {
     for(var col:int = 0; col < baseGrid.length; col ++)
     {
      tempArrayCol = 
       ArrayCollection(baseGrid.getItemAt(col));
      btn = new Button();
      btn.width = 30;
      btn.height = 30;
      btn.label = 
       Number(tempArrayCol.getItemAt(row)).toString();
      btn.name = "btn_" + row + "_" + col; 
      grpGrid.addElement(btn);
     }
    }
   }
   
   /**
    * Upon clicking on one of the buttons of the ButtonBar, we will
    * show buttons with the same value with a alpha value of 1 and
    * those that are different will have a alpha value of 0.3.
    */
   protected function changeEvent(event:IndexChangeEvent):void
   {
    var tempBar:ButtonBar = ButtonBar(event.target);
    var tempObj:* = tempBar.selectedItem;
    var selectedValue:Number = -1;
    if(tempObj)
    {
     selectedValue = tempObj.value;
    }
    
    var btn:Button;
    for (var i:int = 0; i < grpGrid.numElements; i ++)
    {
     btn = Button(grpGrid.getElementAt(i));
     if(Number(btn.label) == selectedValue)
     {
      btn.alpha = 1;
     }else{
      btn.alpha = 0.3;
     }
    }
   }
  ]]>
 </fx:Script>
 <s:VGroup verticalCenter="0" 
     horizontalCenter="0" 
     horizontalAlign="center">
  <s:Group id="grpGrid" >
   <s:layout>
    <s:TileLayout requestedRowCount="9" 
         requestedColumnCount="9"
         horizontalGap="3" 
         verticalGap="3"/>
   </s:layout>
  </s:Group>
  <s:Label text="Click on the following to show the buttons with the same value."/>
  <s:ButtonBar id="btnBar" 
      dataProvider="{dummyData}"  
      selectedIndex="-1"
      labelField="label"
      change="changeEvent(event)"/>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.