I've done this, with the label vertically above the form item in Flex 3, and it was not pretty. It worked, but it wasn't the most stable and required a serious amount of hacking of the Component Life Cycle methods.
In Flex 4, the new skinning model fixes this, use that if possible, coming in the Hero release (http://blog.flexexamples.com/2010/08/28/creating-a-simple-spark-form-in-flex-hero/).
Here are the relevant bits of the code I used to override in Flex 3 and is one approach to it, YMMV.
NB This component had a lot more in it, I took stuff out so is given as a rough guide to how I approached it, it may not run as is, but with some tweaking it should be okay.
package my.controls
{
import flash.display.DisplayObject;
import mx.containers.FormItem;
import mx.containers.FormItemDirection;
import mx.core.EdgeMetrics;
import mx.core.IDeferredInstance;
import mx.core.UIComponent;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;
/**
* Allows positioning of the FormItem labels to be as inherited from FormItem, or
* allows for top alignment of labels whilst resizing form elements to fill width
* of parent container.
*
* <p>Possible values are: <code>left</code>; and <code>top</code>.</p>
* <p>Default value is: left.</p>
*/
[Style(name="labelAlign", type="String", enumeration="left,top", inherit="no")]
public class MyFormItem extends FormItem
{
private static const labelAlignStyleName:String = "labelAlign";
private static var classConstructed:Boolean = constructCustomStyle();
private var _isFormItemLabelTopAligned:Boolean;
private var _hasFormItemLabel:Boolean;
/**
* This calls the super.commitProperties() method and then sets instance variables
* indicating on whether the labels should be top aligned, and also if there is
* a non zero length string as the label value.
*/
override protected function commitProperties():void
{
super.commitProperties();
if (label != null && label.length > 0)
{
_hasFormItemLabel = true;
}
if (getStyle(labelAlignStyleName) == "top")
{
_isFormItemLabelTopAligned = true;
}
}
/**
* After calling the super.measure() method, a check is made to see if there is a label
* value, and if the label is top aligned. If so, it modifies the measuredHeight and
* measuredMinHeight to include the height of the label, plus the indicatorGap style value
*/
override protected function measure():void
{
super.measure();
//If this is top aligned, then change the height of this component
if (_isFormItemLabelTopAligned)
{
if (_hasFormItemLabel)
{
var addedHeight:Number = itemLabel.getExplicitOrMeasuredHeight() + getStyle(indicatorGapStyleName);
//Add the height of the label
measuredHeight = measuredHeight + addedHeight;
measuredMinHeight = measuredMinHeight + addedHeight;
}
//Ignore any measured width we may get, caclulate the width as being the greater of the label or child
var children:Array = getChildren();
var maxChildWidth:Number = 0;
//Usually there is only 1 child, but code for in case there are more
for each (var child:DisplayObject in children)
{
var uiComp:UIComponent = child as UIComponent;
maxChildWidth = Math.max(maxChildWidth, uiComp.getExplicitOrMeasuredWidth());
}
//Set the measurement
measuredWidth = measuredMinWidth = Math.max(itemLabel.getExplicitOrMeasuredWidth(),
maxChildWidth);
}
}
/**
* <p>Checks for label top alignment and if so, changes the location of label to be
* moved to the top left of the container. It then proceeds to layout the children depending on
* whether the <code>MyFormItem.direction</code> property is horizontal or vertical.</p>
*/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (_isFormItemLabelTopAligned)
{
//Anchor the label to the top left of the container
if (_hasFormItemLabel)
{
var vm:EdgeMetrics = viewMetricsAndPadding;
itemLabel.move(vm.left, vm.top);
}
//Depending on the layout of the MyFormItem items, take appropriate action
if (numChildren > 0)
{
if (direction == FormItemDirection.VERTICAL)
{
moveVerticalLayoutWithTopLabel();
}
else
{
moveHorizontalLayoutWithTopLabel();
}
}
findAndRemoveIndicator();
}
}
/**
* <p>Given that labelAlign is set to be top aligned, this method works out the new
* positioning and size of the vertically aligned children. It uses the <code>verticalGap</code>
* style property to set a width between the children.</p>
*/
protected function moveVerticalLayoutWithTopLabel():void
{
var vm:EdgeMetrics = viewMetricsAndPadding;
var commonX:Number = vm.left;
var child:UIComponent;
var previousChild:UIComponent = null;
var newY:Number = vm.top + (_hasFormItemLabel ? (itemLabel.getExplicitOrMeasuredHeight() + getStyle(indicatorGapStyleName)) : 0);
var availableWidth:Number = this.width - getStyle("paddingLeft") - getStyle("paddingRight");
for (var i:Number = 0; i < numChildren; i++)
{
//Calculate the new Y value of the child including the vertical gap
if (previousChild != null)
{
newY = newY + previousChild.getExplicitOrMeasuredHeight() + getStyle("verticalGap");
}
child = UIComponent(getChildAt(i));
child.move(commonX, newY);
//If these children are percentWidths, then set the size of them as we have more width now
if (!isNaN(child.percentWidth))
{
var childSize:Number = availableWidth * (child.percentWidth / 100);
child.setActualSize(childSize, child.getExplicitOrMeasuredHeight());
}
previousChild = child;
}
}
/**
* <p>Given that labelAlign is set to be top aligned, this method works out the new
* positioning and size of the horizontal aligned children. It uses the <code>horizontalGap</code>
* style property to set a width between the children.</p>
*/
protected function moveHorizontalLayoutWithTopLabel():void
{
var child:UIComponent;
var previousChild:UIComponent = null;
var vm:EdgeMetrics = viewMetricsAndPadding;
var commonY:Number = vm.top + (_hasFormItemLabel ? (itemLabel.getExplicitOrMeasuredHeight() + getStyle(indicatorGapStyleName)) : 0);
var newX:Number = vm.left;
var availableWidth:Number = this.width - getStyle("paddingLeft") - getStyle("paddingRight");
for (var i:int = 0; i < numChildren; i++)
{
//Calculate the new X postion of the child taking into account of the label move
if (previousChild != null)
{
newX = newX + previousChild.width + getStyle("horizontalGap");
}
child = UIComponent(getChildAt(i));
child.move(newX, commonY);
//If these children are percentWidths, then set the size of them as we have more width now
if (!isNaN(child.percentWidth))
{
var childSize:Number = availableWidth * (child.percentWidth / 100);
child.setActualSize(childSize, child.getExplicitOrMeasuredHeight());
}
previousChild = child;
}
}
private function findAndRemoveIndicator():void
{
if (!required)
{
return;
}
var indicatorClass:Class = getStyle("indicatorSkin") as Class;
var rawChildrenLength:int = rawChildren.numChildren;
for (var i:int = 0; i < rawChildrenLength; i++)
{
var displayObject:DisplayObject = rawChildren.getChildAt(i);
if (displayObject is indicatorClass)
{
rawChildren.removeChild(displayObject);
return;
}
}
}
/**
* A convenience method to register a style with the StyleManager and create the necessary
* defaults.
*/
private static function constructCustomStyle():Boolean
{
const className:String = "MyFormItem";
const defaultLabelAlignment:String = "left";
const defaultIndicatorGap:Number = 2;
if (!classConstructed)
{
var style:CSSStyleDeclaration = StyleManager.getStyleDeclaration(className);
if (style)
{
if (style.getStyle(labelAlignStyleName) == undefined)
{
style.setStyle(labelAlignStyleName, defaultLabelAlignment);
}
}
else
{
style = new CSSStyleDeclaration();
style.defaultFactory = function():void
{
this[labelAlignStyleName] = defaultLabelAlignment;
}
StyleManager.setStyleDeclaration(className, style, true);
}
}
return true;
}
/**
* Calls super.styleChanged() method.
*
* If the labelAlign style changes, then the component is invalidated as
* its properties and size recalculated as well as being redrawn.
*/
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
// Check to see if style changed.
if (styleProp == labelAlignStyleName)
{
invalidateProperties();
invalidateSize();
invalidateDisplayList();
}
}
}
}