Archive for February, 2007

USA Map for Flex 2.0.1

I’ve ported my original USA Map Project to Flex 2.0.1. You can read about it and download the code on my new (temporary) Flex 2 Blog.

You’ll find a number of differences, most notably that the USA Map SWF is now an ActionScript 3 SWF, built with the Flash 9 preview. This eliminates the need for LocalConnection as the Flex app can work directly with the objects in the Flash movie.

A Flex Weblog

A couple of weeks ago I was writing a blog entry and got a little frustrated with the formatting. I decided to try my hand at writing a blog and blog editor in Flex 2.

You can click here to see the result: Peter Ent’s Flex 2 Weblog The source is available by right-clicking and picking View Source from the menu. The source includes the blog editor.

One of the tricky things was being to embed images and swfs within the articles. I really wanted to be able to insert live SWFs and the Rich Text Editor isn’t capable to doing that.

If you build the blog editor you’ll see that I used Alex Uhlmann‘s Distoration Effects package, CubeRotate, effect. This effect is more, to me, than just cool. It makes good use of screen real estate; you will need to use his SWC to run the application.

Data Binding Tip

Here’s a simple way to enable/disable controls based on selection. For example, suppose you have a DataGrid and a number of buttons which operate on the data. Some buttons can only be used when a row is selected. Other buttons can only be used when there are no selections. Still others can only be used on certain selections.

The Problem

To handle this you may be tempted to code a number of if-statements or to use data binding to set up a variable and then set the variable at various times in your code. Something like this:

[Bindable] private var somethingSelected:Boolean = false;

<mx:Button label="Publish" click="publishItem()" enabled="{somethingSelected}" />

This is a good attempt. All the controls which depend on the selection state of the DataGrid are bound to this one variable. Changing the variable changes the controls’ enabled state. However, you still have to decide when to change the variable. For example:

private function publishItem() : void {
     // get the selected item
     // publish it
     grid.selectedItem = -1; // clear the selection
     somethingSelected = false;
}

To complicate things, suppose another button should be enabled if the selection contains a specific value. You not only have to worry about the somethingSelected variable, you also have to worry about this other test. In other words, in the publishItem() function you have to include setting the other variable. This gets more complex the more conditions you have.

An Easier Way

There is an easier way. Right now the Publish button has a binding with the somethingSelected variable. What is needed is a binding between the somethingSelected variable and the selection state of the DataGrid. You can set that up with the <mx:Binding> tag:

<mx:Binding source="grid.selectedIndex >= 0" destination="somethingSelected" />

Now the value of somethingSelected is tied to the selection state of the DataGrid. Select a row and somethingSelected turns true and all of the buttons with enabled="{somethingSelected}" are enabled and all the buttons with enabled="{!somethingSelected}" are disabled.

The source of the Binding does not have to be a variable. It can be an expression as shown here. The source is simply the condition of the grid’s selectedIndex being greater than or equal to zero.

Here is a slightly more complex example:

<mx:Binding source="grid.selectedItem.code == 1" destination="codeOnePicked" />

Now a record in the DataGrid while a field whose value is 1 will turn the variable codeOnePicked true.

<mx:CheckBox label="Code One?" selected="{codeOnePicked}" />
<mx:Button label="Publish" enabled="{somethingSelected && !codeOnePicked}" />

Here, the CheckBox is selected whenever any record from the DataGrid is selected that as a code field of 1; the Publish button is disabled when that happens.

Conclusion

If you have controls that depend on selections or other conditions in the UI, see if the <mx:Binding> tag can clean up your code and make it easier to read and extend.

Coloring the Background of Cells

The Flex DataGrid is probably the most commonly used control (after Labels and Buttons). One question that keeps popping up is, "How do I color the background of the cells?" I’ll answer that question plus show you how to color the background of columns and rows, too.

Click here to download the source to the samples shown.

Cell Background Color

You need an itemRenderer to change the background color of specific cells. An itemRenderer either applies to all the cells in a DataGrid (when specified in the <mx:DataGrid> tag) or all the cells in a column (when specified in the <mx:DataGridColumn> tag).

Here’s an example of coloring the background of cells in the Year column. Those with a value less than 2000 are blue and those greater than 2000 are green.

Changing the background color of a cell is as simple as overriding the updateDisplayList function and drawing a filled rectangle. If all you want to do is color the background of a cell, then you can make an itemRenderer that extends mx.controls.Label. This code is for a simple itemRenderer, based on Label, that colors the background blue if the value of year is less than 2000 and green otherwise:

<mx:Label xmlns:mx="http://www.adobe.com/2006/mxml">
  <mx:Script><![CDATA[
     override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
     {
          super.updateDisplayList(unscaledWidth,unscaledHeight);
          var g:Graphics = graphics;
          g.clear();
          g.beginFill( data.year < 2000 ? 0x0000FF : 0x00FF00 );
          g.drawRect(0,0,unscaledWidth,unscaledHeight);
          g.endFill();
     }
   ]]></mx:Script>
</mx:Label>

Column Background Color

You do not need to make an itemRenderer just to color the background of every cell in a column. The DataGridColumn class uses the backgroundColor style and applies it uniformly to a column. In fact, the background really is a background – it has nothing to do with cells as the color is applied below the cells.

<mx:DataGridColumn headerText="Make" dataField="col1" backgroundColor="red" />

The Make column will have a solid red background. Cell highlight and selection color are applied on top of the background, so those indicators will be seen. But any row colors, such as the DataGrid’s alternating row colors, will not be seen as the column’s backgroundColor is solid and above the row color. Here’s an example of coloring the columns:

Column Background Alpha

Unfortunately, the DataGridColumn does not use the backgroundAlpha style and the color is always solid. But it is pretty easy to change if you need to have some transparency to a column’s background.

The first thing you need to do is create a class that extends DataGrid. You can do this either as an MXML file (with DataGrid as its root tag) or with an ActionScript class extending DataGrid.

In your class, overrride the drawColumnBackground function and adjust the alpha level:

override protected function drawColumnBackground(s:Sprite, columnIndex:int, color:uint,
                                                                            column:DataGridColumn):void
{
      super.drawColumnBackground(s,columnIndex,color,column);

      var background:Shape = Shape(s.getChildByName(columnIndex.toString()));
      if( background ) {
          background.alpha = 0.5;
      }
}

The function calls the super class version to do all the hard work, then sets the background’s alpha to 0.5. Now the color will be semi-transparent, showing the alternating row colors, as seen in this image:

To make the class more reusable, use a public property for the alpha value. Add to your class:

public var columnBackgroundAlpha:Number = 1;

And change the function:

       backgroundAlpha = columnBackgroundAlpha;

The default behavior is to mimic the way the DataGrid works and apply a solid background color. To make the backgrounds more translucent:

<local:ColoredBackgroundDataGrid columnBackgroundAlpha="0.3" … >

Assuming you called your class ColoredBackgroundDataGrid, the above MXML tag sets the alpha to 0.3, making it very transparent. Now you have a reusable class.

Row Background Color

Next to coloring the background of specific cells, coloring the background of an entire row is probably the most popular color-related question for the DataGrid. Here’s an example of coloring the background of some rows based on the data in the row:

Setting the background color of a row is accomplished by overriding the drawRowBackground function:

override protected function drawRowBackground(s:Sprite, rowIndex:int, y:Number, height:Number, color:uint,
                                                                      dataIndex:int):void
{
      color = 0xFF0000;
      super.drawRowBackground(s,rowIndex,y,height,color,dataIndex);
}

In this example the row will have a solid red background, ignoring the color value passed to the function. Of course, our aim is to look at the data for the given row and determine the color. So the function changes to:

override protected function drawRowBackground(s:Sprite, rowIndex:int, y:Number, height:Number, color:uint,
                                                                       dataIndex:int):void
{
      var dp:ArrayCollection = dataProvider as ArrayCollection;
      var item:Object;
      if( dataIndex < dp.length ) item = dp.getItemAt(dataIndex);
      if( item != null && item.year < 20000 ) color = 0xFF8800;
      else if( item != null && item.year>= 2000 ) color = 0xFFFFFF;
      else color = 0x00CC00;
      super.drawRowBackground(s,rowIndex,y,height,color,dataIndex);
      }

Now the color is determined by the value of the year field for each row: less than 2000 is orange, greater than 2000 is white, and if there is no item for the row it is green.

The dataIndex is may be larger than the number of items in the dataProvider. That’s because you may have more rows showing than you have data. For example, if you can see 10 rows in the DataGrid but you have only 6 records in the DataProvider, 4 of those rows have no data, thus the item will be null.

The colors for this example are hard-coded and is determined by a specific data field, also hard-coded. You can make this class re-usable by giving your class a color function, which is like a labelFunction for a column, except this would return a color value instead of a String. To do this, add another public property:

public var rowColorFunction:Function;

Notice the data type is Function. You can use that in the drawRowBackground function:

override protected function drawRowBackground(s:Sprite, rowIndex:int, y:Number, height:Number, color:uint,
                                                                       dataIndex:int):void
{
      if( rowColorFunction != null ) {
           var dp:ArrayCollection = dataProvider as ArrayCollection;
           var item:Object;
           if( dataIndex < dp.length ) item = dp.getItemAt(dataIndex);
           color = rowColorFunction( item, rowIndex, dataIndex, color );
      }
      super.drawRowBackground(s,rowIndex,y,height,color,dataIndex);
}

Now drawRowBackground looks to see if the rowColorFunction is defined. If it is NOT defined, then the standard color is used. If the rowColorFunction is defined, then it is invoked with some parameters and it is expected to return a color.

Here’s an example with the result shown in the image above:

<local:ColoredBackgroundDataGrid rowColorFunction="determineColor" … >

private function determineColor( item:Object, rowIndex:int, dataIndex:int, oldColor:uint ) : uint
{
      if( item == null ) return 0x00CC00; // green are empty rows
      if( item.year< 2000 ) return 0xFFCC00;
      else if( item.year >= 2000 ) return 0xFFFFFF;
}

The logic used in the hard-coded drawRowFunction is now in a function in the main application. This means you can use the ColoredBackgroundDataGrid anywhere you would normally use the DataGrid.

Advanced Column Background

A function similar to the rowColorFunction can be applied to columns. Here’s one possibility:

Consider this columnBackgroundFunction:

private function columnGradient(column:DataGridColumn, columnIndex:int, columnShape:Shape,
                                               x:Number, y:Number, width:Number, height:Number) : void
{
      var m:Matrix = new Matrix();
      m.createGradientBox(width,height,0,x,0);
      columnShape.graphics.clear();
      columnShape.graphics.beginGradientFill(GradientType.LINEAR,[0xFFFF00,0xFF0000],[1,1],[0,255],m);
      columnShape.graphics.drawRect(x,y,width,height);
      columnShape.graphics.endFill();
}

This function draws a gradient-filled rectangle in the columnShape (see image above). Using a Shape allows lightweight graphics to be drawn as the background to the column; you can do the same for rows.

Change the drawColumnBackground function in your DataGrid class as follows:

override protected function drawColumnBackground(s:Sprite, columnIndex:int, color:uint,
                                                                           column:DataGridColumn):void
{
      super.drawColumnBackground(s,columnIndex,color,column);

      var background:Shape = Shape(s.getChildByName(columnIndex.toString()));
      if( background ) {
          background.alpha = columnBackgroundAlpha;
      }

      if( columnBackgroundFunction != null ) {
          var columnShape:Shape = Shape(s.getChildByName("lines"+columnIndex.toString()));
          if( columnShape == null ) {
              columnShape = new Shape();
              columnShape.name = "lines"+columnIndex;
              s.addChild(columnShape);
          }
          var lastRow:Object = rowInfo[listItems.length - 1];
          var xx:Number = listItems[0][columnIndex].x;
          var yy:Number = rowInfo[0].y;
          var ww:Number = listItems[0][columnIndex].width;
          if (this.headerHeight > 0)
          yy += rowInfo[0].height;
          var hh:Number = Math.min(lastRow.y + lastRow.height,
                                                  listContent.height – yy);
          columnBackgroundFunction( column, columnIndex, columnShape, xx, yy, ww, hh );
      }
}

As you can see, this is more complex as a Shape must be created, added to the Sprite, and location and dimension calculated.

Conclusion

While the DataGrid does provide the ability to color the background of columns, you cannot change the background alpha of the columns. You also do not need to use an itemRenderer to set the background of a row (or a column). Instead, by creating a class that extends DataGrid and overriding a couple of functions, you can easily color the backgrounds of rows or columns (or both) and make it generic enough to use the new class anywhere.

The advanced columnBackgroundFunction should give you some ideas of how advanced you can get when considering the possibilities for the background to your DataGrids.