15 Replies Latest reply on Apr 9, 2007 11:18 AM by compubrook

    Custom Component Help

    compubrook
      Hi,

      I'm trying to pass a variable to a custom component I made. Below is my import list for the AS script:

      import mx.controls.Image;
      import mx.controls.Label;
      import mx.containers.HBox;
      import mx.containers.VBox;
      import mx.containers.Panel;
      import mx.containers.Canvas;
      import mx.events.FlexEvent;
      import flash.events.Event;

      The script basically make a picture of a thermometer, and then I pass it a value and it raises the "Mercury" level. Below is my .mxml script that works just fine:

      <mx:Canvas label="Testing" width="140" height="268" y="10">

      <custom:Thermometer id="thermo" y="10" x="10" temperature="27"
      highValue="50" lowValue="0" title="Office" width="116" height="245"/>

      </mx:Canvas>

      That makes a picture of the thermometer exactly as it should be. My problem is that when I pass the component a varaible like {pSrv.lastResult.data.current.RH_OFFICE_CURR} it no longer works! I have no idea why and I'm hoping someone has had this problem before. Why can't I pass this data to my component?

        • 1. Re: Custom Component Help
          dimival Level 1
          I am guessing the property you want to set is temperature, is it Bindable? I mean, in your custom component did you declare it with the Bindable metadata?

          • 2. Re: Custom Component Help
            compubrook Level 1
            It wasn't before, so I updated it and it still doesn't accept variables, below is the code. I would include the entire thing, but it's quite long.

            public class Thermometer extends Panel
            {

            private var top:Image;
            private var bottom:Image;
            private var bg:Image;
            private var hbox:HBox;
            private var therm_canvas:VBox;

            private var text_canvas:Canvas;

            public var value:int;
            public var highValue:int;
            public var lowValue:int;

            [Bindable]
            private var _temperature:Number = 0;
            public function set temperature( val:Number ):void
            {
            _temperature = val;
            this.value = val;
            dispatchEvent(new Event("changeValue"));
            }
            • 3. Re: Custom Component Help
              compubrook Level 1
              One thing I should note is that all of the exampes I have seen online have a dataProvider function, but none of the libraries I can import have that.
              • 4. Re: Custom Component Help
                dimival Level 1
                Remove the Bindable metadata from the property and add it to the getter method, example:

                [Bindable]
                public function get temperature() : Number
                {
                return _temperature;
                }

                Tell me if this works
                • 5. Re: Custom Component Help
                  compubrook Level 1
                  Actuall it didnèt have a get method, so I added one as follows:


                  [Bindable]
                  public function get temperature() : Number
                  {
                  return _temperature;
                  }

                  and it still doesnt display properly
                  • 6. Re: Custom Component Help
                    ntsiii Level 3
                    What does the code that sets the temperature look like?

                    is the set temperature() setter getting called?

                    In the setter, if you trace "val.toString()" does it display correctly?

                    Tracy
                    • 7. Re: Custom Component Help
                      compubrook Level 1
                      Hi, Thanks for your help. I have included both the get and set functions below. I'm not too sure what you mean by "trace "val.toString()" ". Can you explain that a bit further?


                      public var _temperature:Number = 0;

                      public function set temperature( val:Number ):void
                      {
                      _temperature = val;
                      this.value = val;
                      dispatchEvent(new Event("changeValue"));
                      }

                      [Bindable]
                      public function get temperature() : Number
                      {
                      return _temperature;
                      }
                      • 8. Re: Custom Component Help
                        compubrook Level 1
                        Just to add:

                        I am getting a caution that states my setter is unreachable.

                        Maybe that could help...
                        • 9. Re: Custom Component Help
                          compubrook Level 1
                          quote:

                          Originally posted by: ntsiii
                          What does the code that sets the temperature look like?

                          is the set temperature() setter getting called?

                          In the setter, if you trace "val.toString()" does it display correctly?

                          Tracy


                          I added the trace to my script. Still no luck getting it to display properly.

                          • 10. Re: Custom Component Help
                            peterent Level 2
                            You don't need [Bindable] nor the dispatchEvent in your set/get functions for temperature. To see if your value is actually being set, set a break point in the function set temperature and see what value it is getting.

                            I assume you are drawing the thermometer in updateDisplayList based on the value of temperature. If that's the case you need to trigger updateDisplayList when temperate is set. It is possible that in your first example, temperature="27", the value has been set when updateDisplayList is called. But when you are using it in a data binding it might be set "later". So try this:

                            public function set temperature( val:Number ):void
                            {
                            _temperature = val;
                            invalidateDisplayList();
                            }

                            This will cause updateDisplayList to be called when the framework is ready to process it (never call updateDisplayList directly).
                            • 11. Re: Custom Component Help
                              compubrook Level 1
                              I'm not using updateDisplayList. I've included my code below.

                              // ActionScript file
                              package components
                              {

                              import mx.controls.Image;
                              import mx.controls.Label;
                              import mx.containers.HBox;
                              import mx.containers.VBox;
                              import mx.containers.Panel;
                              import mx.containers.Canvas;
                              import mx.events.FlexEvent;
                              import flash.events.Event;


                              public class Thermometer extends Panel
                              {

                              private var top:Image;
                              private var bottom:Image;
                              private var bg:Image;
                              private var hbox:HBox;
                              private var therm_canvas:VBox;

                              private var text_canvas:Canvas;

                              public var value:int;
                              public var highValue:int;
                              public var lowValue:int;


                              public function Thermometer()
                              {
                              super();
                              //setStyle("paddingLeft", 10);
                              setStyle("verticalGap", 0);
                              addEventListener(FlexEvent.INITIALIZE, initializeHandler);
                              }

                              // Gets called when the component has been initialized
                              private function initializeHandler(event:FlexEvent):void
                              {
                              // Display the component
                              paint();
                              }

                              private function paint():void
                              {
                              hbox = new HBox();
                              hbox.setStyle("horizontalGap",0);

                              addChild( hbox );
                              text_canvas = new Canvas();
                              hbox.addChild( text_canvas );

                              therm_canvas = new VBox();
                              therm_canvas.setStyle( "verticalGap", 0 );

                              hbox.addChild( therm_canvas );

                              // Create the pieces of the thermometer
                              // Assume total height is 200 pixels.
                              // A temperature is passed in from the mxml file.
                              // First thing is to convert that temperature to the
                              // appropriate display height.
                              var pix_height:int = 200;

                              if ( lowValue == highValue )
                              {
                              lowValue = 0;
                              highValue = 50;
                              }
                              var temp_span:int;
                              temp_span = highValue - lowValue;

                              // Determine which percentage of the range is to be filled by colour.
                              var pctg:Number = ( _temperature / temp_span );
                              var bottom_height:int = ( pctg * pix_height );
                              var top_height:int = ( pix_height - bottom_height );

                              //Images are actually located in a vbox...

                              top = new Image();
                              top.source = "img/blue.png";
                              top.scaleY = top_height;

                              bottom = new Image();
                              bottom.source = "img/red.png";
                              bottom.scaleY = bottom_height;

                              therm_canvas.addChild( top );
                              therm_canvas.addChild( bottom );

                              var start_x:uint = 0;
                              var start_y:uint = 0;
                              var curr_scale_val:uint = 50;

                              //Figure out the temperature number spacing based on the height
                              //of the control.
                              var label_spacing:uint = ( top_height + bottom_height + ( 6 * 4 ) ) / 6;

                              // By default there are 5 divisions for the temperature
                              // in 10 degree increments.
                              for( var i:uint = 1; i <= 6; i++ )
                              {
                              var label:Label = new Label();
                              label.text = curr_scale_val.toString() + " -";
                              label.setStyle( "textAlign", "right" );
                              curr_scale_val -= 10;
                              label.x = start_x;
                              label.y = start_y;
                              start_y += label_spacing;
                              text_canvas.addChild( label );
                              }

                              }

                              private var _temperature:Number = 0;

                              public function get temperature() : Number
                              {
                              return _temperature;
                              }

                              public function set temperature( val:Number ):void
                              {
                              _temperature = val;
                              invalidateDisplayList();

                              }
                              }
                              }
                              • 12. Re: Custom Component Help
                                peterent Level 2
                                You should go with the flow and allow the Flex framework to do its thing and call the proper functions. When you get the initialize event the UI is often not even ready. Overriding updateDisplayList is the correct place to draw your control. You can try and circumvent things, but you'll run into all sorts of event timing difficulties. Better to have your set functions call invalidateDisplayList and then call your paint function from updateDisplayList.

                                Further, you should create the child controls (your Canvas, HBox, etc) by overriding createChildren.

                                The framework will call createChildren for you and you can create the child controls (you can set their properties and styles then, but that's all). Once createChildren has been call and ALL of your set functions have been called, the framework will call commitProperties. If you override that you can make any modifications to your controls' styles or properties.

                                Later the framework will call your measure method and you should set measuredWidth and measuredHeight to some values (either hard-coded or calculated from your children). Not having those set may cause problems when using Design View in Flex Builder.

                                Assuming invalidateDisplayList has been called, updateDisplayList will be called (it will be called once anyway).

                                You can all invalidateDisplayList as many times as you want but updateDisplayList will only get called once during a frame-cycle. Same is true for invalidateProperties (to get your commitProperties to be called) and invalidateSize (to get your measure to be called).

                                updateDisplayList should then determine what it needs to do and size the children, position them, and draw any graphics.
                                • 13. Re: Custom Component Help
                                  compubrook Level 1
                                  Thanks for the help Peter. I'm a bit new at this, so forgive m if I have a bit of troubles with your advice. I've been looking through the help files on ADobe Flex and been learning as I go. Is there anywhere else I can get a more comprehensive look at Flex?

                                  I've included my new code below (which is currently FUBAR) to see if anyone out there sees any problems with it.

                                  Thanks for the help.


                                  package components
                                  {

                                  import flash.display.DisplayObject;
                                  import mx.containers.HBox;
                                  import mx.containers.VBox;
                                  import mx.containers.Panel;
                                  import mx.containers.Canvas;
                                  import mx.core.IFlexDisplayObject;
                                  import mx.core.UIComponent;
                                  import mx.skins.Border;
                                  import mx.skins.ProgrammaticSkin;
                                  import mx.skins.RectangularBorder;
                                  import mx.styles.CSSStyleDeclaration;
                                  import mx.styles.ISimpleStyleClient;
                                  import mx.styles.StyleManager;
                                  import flash.events.MouseEvent;
                                  import flash.filters.DropShadowFilter;
                                  import flash.events.Event;
                                  import mx.controls.Image;
                                  import mx.controls.Label;
                                  import mx.events.FlexEvent;

                                  public class Thermometer extends UIComponent
                                  {
                                  public var top:Image;
                                  public var bottom:Image;
                                  public var bg:Image;
                                  public var hbox:HBox;
                                  public var therm_canvas:VBox;

                                  public var text_canvas:Canvas;

                                  public var value:int;
                                  public var highValue:int;
                                  public var lowValue:int;

                                  public function Thermometer()
                                  {
                                  super();
                                  }

                                  override protected function createChildren():void
                                  {
                                  super.createChildren();

                                  hbox = new HBox();
                                  hbox.setStyle("horizontalGap",0);
                                  text_canvas = new Canvas();
                                  therm_canvas = new VBox();
                                  therm_canvas.setStyle( "verticalGap", 0 );

                                  }

                                  override protected function measure():void
                                  {
                                  super.measure();

                                  measuredWidth = measuredMinWidth = 300;
                                  measuredHeight = measuredMinHeight = 300;
                                  }


                                  override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
                                  {
                                  super.updateDisplayList(unscaledWidth,unscaledHeight);
                                  paint();

                                  }

                                  private function paint():void
                                  {
                                  addChild( hbox );

                                  hbox.addChild( text_canvas );

                                  therm_canvas = new VBox();

                                  hbox.addChild( therm_canvas );

                                  // Create the pieces of the thermometer
                                  // Assume total height is 200 pixels.
                                  // A temperature is passed in from the mxml file.
                                  // First thing is to convert that temperature to the
                                  // appropriate display height.
                                  var pix_height:int = 200;

                                  if ( lowValue == highValue )
                                  {
                                  lowValue = 0;
                                  highValue = 50;
                                  }
                                  var temp_span:int;
                                  temp_span = highValue - lowValue;

                                  // Determine which percentage of the range is to be filled by colour.
                                  var pctg:Number = ( _temperature / temp_span );
                                  var bottom_height:int = ( pctg * pix_height );
                                  var top_height:int = ( pix_height - bottom_height );

                                  //Images are actually located in a vbox...

                                  top = new Image();
                                  top.source = "img/blue.png";
                                  top.scaleY = top_height;

                                  bottom = new Image();
                                  bottom.source = "img/red.png";
                                  bottom.scaleY = bottom_height;

                                  therm_canvas.addChild( top );
                                  therm_canvas.addChild( bottom );

                                  var start_x:uint = 0;
                                  var start_y:uint = 0;
                                  var curr_scale_val:uint = 50;

                                  //Figure out the temperature number spacing based on the height
                                  //of the control.
                                  var label_spacing:uint = ( top_height + bottom_height + ( 6 * 4 ) ) / 6;

                                  // By default there are 5 divisions for the temperature
                                  // in 10 degree increments.
                                  for( var i:uint = 1; i <= 6; i++ )
                                  {
                                  var label:Label = new Label();
                                  label.text = curr_scale_val.toString() + " -";
                                  label.setStyle( "textAlign", "right" );
                                  curr_scale_val -= 10;
                                  label.x = start_x;
                                  label.y = start_y;
                                  start_y += label_spacing;
                                  text_canvas.addChild( label );
                                  }
                                  }


                                  private var _temperature:Number = 0;

                                  public function get temperature() : Number
                                  {
                                  return _temperature;
                                  }

                                  public function set temperature( val:Number ):void
                                  {
                                  _temperature = val;
                                  invalidateDisplayList();

                                  }

                                  }


                                  }

                                  • 14. Re: Custom Component Help
                                    peterent Level 2
                                    You should do the addChild() s in createChildren:
                                    addChild( hbox );
                                    hbox.addChild( text_canvas );
                                    therm_canvas = new VBox();
                                    hbox.addChild( therm_canvas );

                                    I don't see why you can't create them_canvas in createChildren, too. By having addChild in updateDisplayList you are just causing the component to attempt to add the same children over and over again.

                                    The same goes for the images too. Create those images in createChildren and add them to them_canvas there, too.

                                    Ditto for the Labels. Use updateDisplayList to position them, not to create them.

                                    In paint(), use unscaledHeight instead of 200 in your calculations. For your control, depending on what it really looks like, you might need to tweek that a bit, but unscaledHeight is the size the control really is.

                                    • 15. Re: Custom Component Help
                                      compubrook Level 1
                                      The following is the solution discussed:

                                      package components
                                      {
                                      import mx.containers.Canvas;
                                      import mx.containers.VBox;
                                      import mx.controls.Image;

                                      public class Thermometer extends Canvas
                                      {
                                      /* Variables for the Child components */
                                      protected var canvas:VBox;
                                      protected var top_img:Image;
                                      protected var bottom_img:Image;

                                      /* Flex calls the createChildren method when
                                      * the components are added. This is the correct place
                                      * to setup the control. Any final sizing, etc.,
                                      * occurs in updateDisplayList */
                                      override protected function createChildren():void
                                      {
                                      super.createChildren();
                                      if( !canvas )
                                      {
                                      canvas = new VBox();
                                      canvas.setStyle( "verticalGap", 0 );
                                      addChild( canvas );
                                      }

                                      // Create and initialize the top image.
                                      if (!top_img)
                                      {
                                      top_img = new Image();
                                      canvas.addChild(top_img);
                                      }

                                      if (!bottom_img)
                                      {
                                      bottom_img = new Image();
                                      canvas.addChild(bottom_img);
                                      }
                                      }

                                      /* Set the styles of the images */
                                      override protected function commitProperties():void
                                      {
                                      super.commitProperties();
                                      top_img.source = "img/blue.png";
                                      bottom_img.source = "img/red.png";
                                      }

                                      /* updateDisplayList positions, sizes, and draws anything it needs to */
                                      override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
                                      {
                                      super.updateDisplayList(unscaledWidth, unscaledHeight);

                                      //TODO: use the unscaledHeight property to figure out the
                                      //proper scaling for the control.
                                      top_img.scaleY = 20;
                                      bottom_img.scaleY = _temp;
                                      }

                                      override protected function measure():void
                                      {
                                      super.measure();
                                      measuredWidth = measuredMinWidth = 75;
                                      measuredHeight = measuredMinHeight = 200;
                                      }

                                      public function Thermometer()
                                      {
                                      super();
                                      }

                                      [Inspectable(defaultValue=1)]
                                      private var _temp:uint = 1;

                                      [Bindable(event="tempChange")]
                                      public function get temp():uint
                                      {
                                      return _temp;
                                      }

                                      public function set temp( value:uint ):void
                                      {
                                      _temp = value;
                                      invalidateDisplayList();
                                      }
                                      }
                                      }