Avg. Rating 5.0

Problem

You want to implement a 3 state checkbox for use in a checklist tree view.

Solution

The default spark checkbox extends from ToggleButtonBase which has 2 selection states, selected or not selected. But since we are dealing with 3 state checkbox, we need more. So we are going to make a checkbox extended from ButtonBase which is the base class of most button controls.

Detailed explanation

First, we'll start by creating a class extended from ButtonBase.

package kola.staticfunction.controls
{
       import spark.components.supportClasses.ButtonBase;

       public class CheckBox extends ButtonBase
       {
           public function CheckBox()
           {
               super();
           }
       }
}

Just after the import statements, we'll define metadata for skin states and the event when the checkbox changed state.

[Event(name="change", type="flash.events.Event")]

[SkinState("upAndChecked")]

[SkinState("overAndChecked")]

[SkinState("downAndChecked")]

[SkinState("disabledAndChecked")]

[SkinState("upAndIndeterminate")]

[SkinState("overAndIndeterminate")]

[SkinState("downAndIndeterminate")]

[SkinState("disabledAndIndeterminate")]
Before the constructor, we are going to add constants. These are the selection states of our checkbox.
        /**
* Checkbox value if the checkbox is unchecked.
*/
public static const UNCHECKED:String = "unchecked";

/**
* Checkbox value if the checkbox is indeterminate or partially checked.
*/
public static const INDETERMINATE:String = "indeterminate";

/**
* Checkbox value if the checkbox is fully checked.
*/
public static const CHECKED:String = "checked";
 
 
  

After the constructor, we are going to define a getter and setter for a variable that will hold the selection state. This variable will be bindable. So we need to define a [Bindable] metadata tag to our getter and also our setter needs to dispatch the event that was define in the [Bindable] metadata of our getter. We also want to assist developers with their code hinting dependency so I drop in an [Inspectable] metadata tag that defines the default value of our selection state and the possible values we can define in the value property. 

        private var _value:String = UNCHECKED;

[Bindable("change")]
[Inspectable(enumeration="unchecked,indeterminate,checked", defaultValue="unchecked")]
public function get value():String
{
     return _value;
}

public function set value(value:String):void
{
     if(value == UNCHECKED || value == INDETERMINATE ||
        value == CHECKED)
     {
        _value = value; 
     }
      else
          return;

      dispatchEvent(new FlexEvent(Event.CHANGE));

      invalidateSkinState();
}
 
 
  

Don't forget to invalidateSkinState(). You must call this everytime you are going to change the state of the component.

Next we need to set selection state whenever the user clicks the button. Good thing with Spark ButtonBase, we no longer need to add event listener for a mouse click event. Instead we just override the buttonReleased() method.

NOTE: The partial or indeterminate selection state is typically set programmatically, so if the user clicks on the checkbox either on unchecked or indeterminate state, we expect the next state would be checked. If the button is checked then the next state would be unchecked.

        override protected function buttonReleased():void
{
     if(value == UNCHECKED || value == INDETERMINATE)
        value = CHECKED;
     else
        value = UNCHECKED;

     dispatchEvent(new Event(Event.CHANGE));
}

 
 
  

Then lastly we override getCurrentSkinState to mix ButtonBase states with our custom skin states.

        override protected function getCurrentSkinState():String
{
     var skinState:String = super.getCurrentSkinState();

     switch(value)
     {
         case CHECKED:
              skinState+="AndChecked";
              break;

         case INDETERMINATE:
              skinState+="AndIndeterminate";
              break; 
     }

     return skinState;
}
 
 
 
  

But WAIT!!! Our checkbox doesn't have a skin class yet, so if you try to run what we have so far well have runtime errors. So copy this modified CheckBox skin and set it as the default skin class of your component.

        <?xml version="1.0" encoding="utf-8"?>

<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<!-- host component -->
<fx:Metadata>
[HostComponent("kola.controls.CheckBox")]
</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"];

/**
* @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>

<!-- states -->
<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="upAndChecked" stateGroups="checkedStates"/>
<s:State name="overAndChecked" stateGroups="overStates, checkedStates" />
<s:State name="downAndChecked" stateGroups="downStates, checkedStates"/>
<s:State name="disabledAndChecked" stateGroups="disabledStates, checkedStates"/>
<s:State name="upAndIndeterminate" stateGroups="indeterminateStates"/>
<s:State name="overAndIndeterminate" stateGroups="overStates, indeterminateStates"/>
<s:State name="downAndIndeterminate" stateGroups="downStates, indeterminateStates"/>
<s:State name="disabledAndIndeterminate" stateGroups="disabledStates, indeterminateStates"/>
</s:states>

<!-- SkinParts
name=labelDisplay, type=spark.components.supportClasses.TextBase, required=false
-->
<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="checkedStates" 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="0x000000" alpha="0.8" />
</s:fill>
</s:Path>

<!-- blockmark -->
<s:Rect left="2" right="2" top="2" bottom="2" includeIn="indeterminateStates" id="block" itemCreationPolicy="immediate">
<s:fill>
<s:SolidColor id="blockMarkFill" color="0x000000" alpha="0.8"/>
</s:fill>
</s:Rect>
</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" />

</s:SparkSkin>

 
 
  

There are many ways of setting this skin to our custom component, you can try setting it in the constructor like:

public function CheckBox()
{

     super();
     setStyle("skinClass", kola.staticfunction.skins.MyCustomSkin);

}

But my preferred way is to define it on a CSS file. Either works fine but via CSS is cleaner.

/* CSS file */
        @namespace s "library://ns.adobe.com/flex/spark";
        @namespace kola "library://kola.staticfunction.com/2011";


        kola|CheckBox {skinClass: ClassReference("kola.skins.spark.CheckBoxSkin")}

So there you have it, your Spark 3-state checkbox!  See the uploaded zip file.


+
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.

Report abuse

Related recipes