4 Replies Latest reply on Feb 11, 2010 6:23 AM by novamatt

    Setting BitmapImage source inside the skin

    novamatt

      Hi all

       

      As a Flex 4 newbie, I've been banging my head for two days trying do to something that could seem pretty trivial to you (and to me as well at first sight) but lefted me completely clueless...

       

      I want to design a fairly simple custom component using the Gumbo architecture. My component falls into 2 parts:

      * a logo picture on the left side

      * a buttonbar next on the right side

      My component must be designed to be quite generic, that is to say the source of the logo image is a property of the component as well as the items of the ButtonBar.

       

      So here is what I did so far:

      I wrote an AS class describing the behavior of my component "MapToolBar.as":

      package nova.style
      {
          
          import spark.components.ButtonBar;
          import spark.components.supportClasses.SkinnableComponent;
          import spark.primitives.BitmapImage;
          
          [SkinState("normal")]
          [SkinState("disabled")]
          public class MapToolBar extends SkinnableComponent
          {
              // Define skin parts
              [SkinPart(required="false")]
              public var clientLogo:BitmapImage;
              [SkinPart(required="true")]
              public var tools:ButtonBar;
              
              // Define component data
              private var _logoSrc:String;
              
              public function set logoSrc(src:String):void {
                  _logoSrc=src;
              }
              [Bindable]
              public function get logoSrc():String {
                  return _logoSrc;
              }
              
              private var _toolsItems:Array;
              
              public function set toolsItems(items:Array):void {
                  _toolsItems=items;
              }
              [Bindable]
              public function get toolsItems():Array {
                  return _toolsItems;
              }
              
              public function MapToolBar()
              {
                  super();
              }
              
              override protected function getCurrentSkinState():String {
                  if (enabled) {
                      return "normal";
                  } else {
                      return "disabled";
                  }
              }
          }
      }
      
      

      And the associated skin mxml class:

      <?xml version="1.0" encoding="utf-8"?>
      <s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" 
                   xmlns:s="library://ns.adobe.com/flex/spark" 
                   xmlns:mx="library://ns.adobe.com/flex/halo">
          <fx:Declarations>
              <!-- Place non-visual elements (e.g., services, value objects) here -->
          </fx:Declarations>
          <fx:Script>
              <![CDATA[
                  import mx.collections.ArrayList;
                  
                  import nova.style.skins.ToolBarSkin;
                  // Initialise les données des skinparts après leur création.
                  private function handleCreationComplete(event:Event):void {
                      clientLogo.source = hostComponent.logoSrc;
                      tools.dataProvider = new ArrayList(hostComponent.toolsItems);
                  }
              ]]>
          </fx:Script>
          <s:states>
              <s:State name="disabled"/>
              <s:State name="normal"/>
          </s:states>
          <!-- host component -->
          <fx:Metadata>
              [HostComponent("nova.style.MapToolBar")]
          </fx:Metadata>
          <s:layout>
              <s:BasicLayout />
          </s:layout>
          <s:HGroup creationComplete="handleCreationComplete(event);">
              <s:BitmapImage id="clientLogo"/>
              <s:ButtonBar id="tools" skinClass="nova.style.skins.ToolBarSkin"/>
          </s:HGroup>
      </s:SparkSkin>
      

       

      As you can notice, my inner ButtonBar has got its own skinClass has well. This is because I'm trying to divide my application in as many reusable bricks as possible. So here's ToolBarSkin:

      <?xml version="1.0" encoding="utf-8"?>
      <s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" alpha.disabled="0.5" xmlns:mx="library://ns.adobe.com/flex/halo">
      
          <!-- host component -->
          <fx:Metadata>
              <![CDATA[ 
              /** 
               * @copy spark.skins.spark.ApplicationSkin#hostComponent
               */
              [HostComponent("spark.components.ButtonBar")]
              ]]>
          </fx:Metadata>
      
          <s:states>
              <s:State name="normal"/>
              <s:State name="disabled"/>
          </s:states>
      
          <fx:Declarations>
              <!---
                   Specifies the skin class for the middle button(s) on the ButtonBar.
                   @default spark.skins.spark.ButtonBarMiddleButtonSkin
              -->
      
              <fx:Component id="middleButton">
                  <s:ButtonBarButton skinClass="nova.style.skins.ToolBarButtonSkin"/>
              </fx:Component>
          </fx:Declarations>
          <s:Rect x="-5" alpha="0.8" left="1" right="1" top="1" bottom="1" radiusX="10" radiusY="10">
              <s:fill>
                  <s:LinearGradient rotation="-90">
                      <s:GradientEntry color="#040303" ratio="0"/>
                      <s:GradientEntry color="#858584" ratio="1"/>
                  </s:LinearGradient>
              </s:fill>
          </s:Rect>
          <!---
               @copy spark.components.SkinnableDataContainer#dataGroup
          -->
          <s:DataGroup id="dataGroup" top="5" left="10" right="5" bottom="5">
              <s:layout>
                  <s:HorizontalLayout gap="12" />
              </s:layout>
          </s:DataGroup>
      </s:SparkSkin>
      

       

      And yes, the ButtonBarButton skin is also customized, 'cause I want to display an icon on them... Here it is :

      <?xml version="1.0" encoding="utf-8"?>
      <s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" 
                   xmlns:s="library://ns.adobe.com/flex/spark" 
                   xmlns:mx="library://ns.adobe.com/flex/halo" xmlns:d="http://ns.adobe.com/fxg/2008/dt" xmlns:ai="http://ns.adobe.com/ai/2008" minWidth="21" minHeight="21" alpha.disabledStates="0.5">
          <!-- 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="upAndSelected" stateGroups="selectedStates, selectedUpStates" />
              <s:State name="overAndSelected" stateGroups="overStates, selectedStates" />
              <s:State name="downAndSelected" stateGroups="downStates, selectedStates" />
              <s:State name="disabledAndSelected" stateGroups="selectedUpStates, disabledStates, selectedStates" />
          </s:states>
      
          <!-- host component -->
          <fx:Metadata>
              <![CDATA[ 
              [HostComponent("spark.components.ButtonBarButton")]
              ]]>
          </fx:Metadata>
          
          <fx:Script>
              /* Define the skin elements that should not be colorized. 
              For toggle button, the graphics are colorized but the label is not. */
              static private const exclusions:Array = ["labelDisplay"];
              
              override public function get colorizeExclusions():Array {return exclusions;}
          </fx:Script>
          <s:Group d:id="2" d:type="layer" d:userLabel="Calque 1">
              <s:Group x="1" y="1" d:id="3">
                  <s:Path alpha="0.7" winding="nonZero" ai:knockout="0" data="M2 30C0.896484 30 0 29.1016 0 28L0 2C0 0.896484 0.896484 0 2 0L28 0C29.1025 0 30 0.896484 30 2L30 28C30 29.1016 29.1025 30 28 30L2 30Z" >
                      <s:fill>
                          <s:LinearGradient x="15.0005" y="30" scaleX="29.9995" rotation="-90">
                              <s:GradientEntry color="#d9d9d9" ratio="0"/>
                              <s:GradientEntry color="#f2f2f2" ratio="1"/>
                          </s:LinearGradient>
                      </s:fill>
                  </s:Path>
              </s:Group>
          </s:Group>
          <!-- Icone embarquée -->
          <s:HGroup horizontalCenter="0" verticalCenter="0"
                    creationComplete="img.source = hostComponent.data;">
              <!-- layer 8: icon -->
              <s:BitmapImage id="img" />
          </s:HGroup>  
      </s:SparkSkin>
      

       

      Finally, in my main application I invoke my component like this :

      <nova:MapToolBar logoSrc="@Embed(source='/assets/img/clients/ge.png')" skinClass="nova.style.MapToolBarSkin">
              <nova:toolsItems>
                  <s:ButtonBarButton id="redBtn"
                                     label="Red"
                                     data="@Embed(source='/assets/img/crown.png')"
                                     />
                  <s:ButtonBarButton id="orangeBtn"
                                     label="Orange"
                                     data="@Embed(source='/assets/img/disk.png')"
                                     />
                  <s:ButtonBarButton id="yelloyBtn"
                                     label="Yellow"
                                     data="@Embed(source='/assets/img/nuclear.png')"
                                     />
              </nova:toolsItems>
          </nova:MapToolBar>
      

       

      Unfortunately,I get the following at runtime:

       

      * the logo image is never displayed - yet the path is correct and the step-by-step debug confirms that the handleCreationComplete() method gets called with correct values being assigned to my parts !

       

      * each buttonbarbutton throws this stackexception, and of course my button icons aren't displayed either, only buttons' backgrounds show up:

      ArgumentError: Error #2015: BitmapData non valide.
           at flash.display::BitmapData()
           at spark.primitives::BitmapImage/set source()[E:\dev\gumbo_beta2\frameworks\projects\spark\src\spark\primitives\BitmapImage.as:269]
           at nova.style.skins::ToolBarButtonSkin/___ToolBarButtonSkin_HGroup1_creationComplete()[C:\EnvDevEclipse\Workspace-Head\novacom-flex-proto-customcomp\src\nova\style\skins\ToolBarButtonSkin.mxml:56]
           at flash.events::EventDispatcher/dispatchEventFunction()
           at flash.events::EventDispatcher/dispatchEvent()
           at mx.core::UIComponent/dispatchEvent()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\core\UIComponent.as:11749]
           at mx.core::UIComponent/set initialized()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\core\UIComponent.as:1525]
           at mx.managers::LayoutManager/doPhasedInstantiation()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\managers\LayoutManager.as:759]
           at mx.managers::LayoutManager/doPhasedInstantiationCallback()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\managers\LayoutManager.as:1069]
      

       

      Any help would be really much appreciated

      Thanks

      Matt

        • 1. Re: Setting BitmapImage source inside the skin
          novamatt Level 1

          Well, I've already solved one of the two issues

           

          In order to display the icons on my buttonbar buttons, I just needed to point at {hostComponent.data.data} instead of {hostComponent.data}. I'm not sure why, but {hostComponent.data} in my skin class is a handle on my instance of ButtonBarButton, not its "data" property. Any explanation is welcomed

           

          I still have got the first issue though. Oddly enough my design works if I put an mx Image instead of the spark BitmapImage for the client logo. That will do the trick for the moment but I'd prefer to deliver a 100% pure spark component in my prototype, and I can see no reason why BitmapImage works inside my ButtonBarButtons and doesn't otherwise...

          • 2. Re: Setting BitmapImage source inside the skin
            CoreyRLucier Adobe Employee

            The only thing I can think of is that BitmapImage only accepts a Class, BitmapData, Bitmap, or DisplayObject as input.

             

            Right now you are setting a String as the source.

             

            Try typing logoSrc in MapToolBar as 'Object'  (same for _logoSrc)...  basically, when you embed ge.png with:

             

            logoSrc="@Embed(source='/assets/img/clients/ge.png')"

             

            The embed is turned into a Class reference by the compiler, you want to make sure it stays a Class up to when it's used as a source for BitmapImage. BitmapImage will then instantiate an instance of the Class resulting in a new Bitmap instance.

             

            Grasping a bit but give it a go.

             

            -C

            • 3. Re: Setting BitmapImage source inside the skin
              CoreyRLucier Adobe Employee

              FWIW, just confirmed, the compiler is coercing the embed class to a string but BitmapImage has no idea what to do with the string (as it only accepts Class, etc.).   Image on other hand has explicit logic to lookup the Class from the String first.

               

              -C

              1 person found this helpful
              • 4. Re: Setting BitmapImage source inside the skin
                novamatt Level 1

                Thanks Corey ! I've set logoSrc as Object and it totally worked.

                What a trick ! I couldn't ever have thought of it by myself. Cheers