?

Log in

Flex's answer to GridBagLayout?

« previous entry | next entry »
Jun. 18th, 2006 | 03:22 pm

If you dig layouts (UI), you'll probably like this.

You've heard of the famous GridBagLayout. In the world of Java AWT/Swing development, GridBagLayout is known as one of the most flexible layouts, while also being the most difficult to grasp and often a pain to work with.

The Flex framework does not have an equivalent of GridBagLayout so far (as of version 2.0). And while I've been thinking about layout enhancements for the next version, I've come up with an initial design for a new flexible GridBagLayout-like layout that's hopefully also easy to use and IDE-friendly.

Introducing the CanvasGrid

The CanvasGrid can be thought of as a grid of Canvas'es. Every object in the layout has its own Canvas-like display area, with anchor constraints and percentage-based sizing applied to the object with respect to that area. An object's display area may span across multiple cells.

Let's look at some examples to see how it works.

Note: The examples below won't compile in Flex 2.0 because none of the components implement the ICanvasGridComponent interface. Try playing with test.mxml in the archive instead.

Here's the example from the GridBagLayout documentation, expressed in MXML now using the CanvasGrid:

<mx:Style>
  CanvasGrid {
    horizontalGap: 0;
    verticalGap: 0;
  }
  Button {
    bottom: 0;
    left: 0;
    right: 0;
    top: 0;
  }
</mx:Style>
<CanvasGrid>
  <mx:Button label="Button1" />
  <mx:Button label="Button2" gridX="1" />
  <mx:Button label="Button3" gridX="2" />
  <mx:Button label="Button4" gridX="3" />
  <mx:Button label="Button5" gridY="1" gridWidth="4" />
  <mx:Button label="Button6" gridY="2" gridWidth="3" />
  <mx:Button label="Button7" gridX="3" gridY="2" />
  <mx:Button label="Button8" gridY="3" gridHeight="2" />
  <mx:Button label="Button9" gridX="1" gridY="3" gridWidth="3" />
  <mx:Button label="Button10" gridX="1" gridY="4" gridWidth="3" />
</CanvasGrid>

[screenshot]

Each object's position within the grid is specified explicitly with the gridX and gridY properties. An IDE should be able to adjust the values of these properties as the object is dragged around in its design view.

Similarly, the number of cells an object's display area should span is specified with the gridWidth and gridHeight properties.

The CanvasGrid itself has its own properties that control its sizing and layout: cellHeight, cellWidth, columnCount, and rowCount. If left unspecified, the values for these properties are calculated automatically (as in the above example).

Here's a form-like layout:

<CanvasGrid>
  <mx:Label text="Name" right="0" />
  <mx:Label text="Address" right="0" gridY="1" />
  <mx:Label text="Phone Number" right="0" gridY="3" />
  <mx:TextInput gridX="1" />
  <mx:TextArea gridX="1" gridY="1" height="100%" gridHeight="2" />
  <mx:TextInput gridX="1" gridY="3" />
</CanvasGrid>

[screenshot]

The labels are all right-aligned by setting their right style to 0. The height of the Address input field is set to 100% within its display area, which spans 2 rows.

Here's a common application-level layout, with three small panels in the left "navigation area", laid out one below another, and one large panel occupying the main content area:

<mx:Style>
  Panel {
    bottom: 0;
    left: 0;
    right: 0;
    top: 0;
  }
</mx:Style>
<CanvasGrid>
  <mx:Panel title="Panel1" />
  <mx:Panel title="Panel2" gridY="1" />
  <mx:Panel title="Panel3" gridY="2" />
  <mx:Panel title="Panel4" gridX="1" gridWidth="3" gridHeight="3" />
</CanvasGrid>

[screenshot]


[Update: Monday, June 19th, 2006]

The most frequent question I'm asked about the CanvasGrid is how it's different from putting lots of Canvas'es into a Grid. It's different in more than one way.

Fewer levels of nesting

This is the simple layout we want, a 100x100 grid with a red box on the left and a green and a blue box on the right:

[image: 100x100 grid with a red box on the left and a green and a blue box on the right]

This is the CSS applied to the boxes:

.box {
  backgroundAlpha: 0.6;
  borderColor: #333333;
  borderStyle: solid;
  bottom: 0;
  top: 0;
  left: 0;
  right: 0;
}

Now let's look at the MXML code, using Canvas-inside-Grid approach:

<mx:Grid width="100" height="100">
  <mx:GridRow width="100%" height="100%">
    <mx:GridItem rowSpan="2" width="100%" height="100%">
      <mx:Canvas width="100%" height="100%">
        <mx:HBox styleName="box" backgroundColor="red" />
      </mx:Canvas>
    </mx:GridItem>
    <mx:GridItem width="100%" height="100%">
      <mx:Canvas width="100%" height="100%">
        <mx:HBox styleName="box" backgroundColor="green" />
      </mx:Canvas>
    </mx:GridItem>
  </mx:GridRow>
  <mx:GridRow width="100%" height="100%">
    <mx:GridItem width="100%" height="100%">
      <mx:Canvas width="100%" height="100%">
        <mx:HBox styleName="box" backgroundColor="blue" />
      </mx:Canvas>
    </mx:GridItem>
  </mx:GridRow>
</mx:Grid>

Whoa! And here's the code using the CanvasGrid:

<CanvasGrid width="100" height="100">
  <mx:HBox styleName="box" backgroundColor="red"
    gridHeight="2" />
  <mx:HBox styleName="box" backgroundColor="green"
    gridX="1" />
  <mx:HBox styleName="box" backgroundColor="blue"
    gridX="1" gridY="1" />
</CanvasGrid>

So the first difference is that there's a lot fewer levels of nesting, both in terms of the XML code and in terms of the number of objects on the display list. Less memory-intensive.

Flip side: In the case of CanvasGrid, if one object moves even slightly by a single pixel, the whole grid is laid out all over again. On the other hand, in the Canvas-inside-Grid approach, only the layout of the particular Canvas is redone if an object within it moves. There you go: there's always both pros and cons.

Overlapping objects

You simply can't do this with Canvas-inside-Grid:

[image: Boxes cascading diagonally]

<CanvasGrid width="100" height="100">
  <mx:HBox styleName="box" backgroundColor="red"
    gridWidth="2" gridHeight="2" />
  <mx:HBox styleName="box" backgroundColor="green"
    gridX="1" gridY="1" gridWidth="2" gridHeight="2" />
  <mx:HBox styleName="box" backgroundColor="blue"
    gridX="2" gridY="2" gridWidth="2" gridHeight="2" />
</CanvasGrid>

Easy to move objects around

In the above example, say you want to change the orientation of the objects by moving the red box to the extreme right and the blue box to the extreme left. Assuming the objects have id's redBox, greenBox, and blueBox, here's the code in the button's click event handler:

<mx:Button label="Change Orientation"
  click="redBox.gridX = 2; blueBox.gridX = 0" />

[image: Cascasding boxes, orientation changed on click of the button]

I don't even want to think about how to achieve that with a Grid, with all those GridRow's and GridItem's. Would you like to try?

Link