R – What’s the best way to design a custom button that changes font color/size when selected

actionscript-3button

I want to design a custom component that extends Button that will have two new styles: selectedColor and selectedFontSize. These will allow the button to have a different text label color and font size when the button is selected. (When I say 'selected' I mean selected state, not being selected (mouse down).

The approach I've taken is to extend Button, add two new styles, and then override the selected setter, and then use setStyle on the original color style to change font color to selectedColor (if button is selected) or color (if button is de-selected).

I am using selectedColor for simplicity sake in this example, but I want to be able to do this for selectedFontSize as well.

[Style(name="selectedColor",type="uint", format="Color", inherit="no")]
[Style(name="selectedFontSize",type="Number",inherit="no")]

private var _deselectedColor:uint;
private var _selectedColor:uint;

override public function set selected(value:Boolean):void {
    super.selected = value;

    _selectedColor = getStyle("selectedColor") as uint;
    if (!_deselectedColor)
        _deselectedColor = getStyle("color") as uint;

    if (selected)
        setStyle("color",_selectedColor);
    else
        setStyle("color",_deselectedColor);
}

I'm storing the original color (de-selected color, that is) because it is overwritten when I setStyle("color",_deselectedColor). I need to have some way to retrieve it when the button is de-selected.

There are many problems to this approach. Firstly, the first time the selected setter is called, during object construction, the styles are not yet populated. So I am getting a color of 0x000000 and selectedColor of 0x000000. Once I manually change the button state post-construction the styles are able to be read-in correctly, but by then it's too late, I've already stored the wrong value. Also, I need to be able to get style updates when set via mxml, actionscript setStyle method, or through css, both statically and dynamically. This approach obviously doesn't do that.

I've also tried overriding the setStyle and styleChanged methods. setStyle never gets called during construction when initializing the button via mxml, so that's a no-go. styleChanged gets called, but with nulls as the styleProp, not color like I need.

How do the pros do this?

Best Answer

I think I've got a much more polished (and working!) answer to this now.

I ended up doing the work in updateDisplayList() so the update will be delayed for the official update cycle, rather than forcing it when the selected setter is executed. To make sure I'm not storing the incorrect de-selected color value, I now only store it when the button is selected, and I clear the storage when the button is de-selected.

I also used the CSSStyleDeclaration class, as Amarghosh suggested, to manage reading in the custom styles from css and from mxml.

Lastly, one big thing I discovered was I needed to call super.callLater(invalidateDisplayList), or else the button would only update when I rolled over it.

Complete solution is posted. Hopefully this helps someone else who is just delving into the world of custom components!

import flash.display.Graphics;
import flash.geom.Matrix;

import mx.controls.Button;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;

[Style(name="selectedColor",type="uint", format="Color", inherit="no")]
[Style(name="selectedFontSize",type="Number",inherit="no")]

public class ControlBarButton extends Button
{
    private static var classConstructed:Boolean = constructStyle(); // initialize default style properties

    public function ControlBarButton() {
        super();
    }

    private static function constructStyle():Boolean {
        var style:CSSStyleDeclaration = StyleManager.getStyleDeclaration( "ControlBarButton" );

        // check to see if there's already an existing style declaration for this class
        if (style) {
            // its possible for a style to exist without defining all of the possible styles 
            // in which case we need to check each style explicitly and set a default if needed
            if ( style.getStyle( "selectedColor" ) == undefined ) {
                style.setStyle( "selectedColor", 0xFFFFFF );
            }
            if ( style.getStyle( "selectedFontSize" ) == undefined ) {
                style.setStyle( "selectedFontSize", 12 );
            }
        }
        else { // create a default style declaration
            style = new CSSStyleDeclaration();
            style.defaultFactory = function():void {
                this.selectedColor = 0xFFFFFF;
                this.selectedFontSize = 12;
            }
            StyleManager.setStyleDeclaration( "ControlBarButton", style, true );
        }

        return true;
    }

    private var selectedColorChanged:Boolean = true;
    private var selectedFontSizeChanged:Boolean = true;
    private var selectedChanged:Boolean = true;

    override public function styleChanged(styleProp:String):void {
        super.styleChanged(styleProp);

        // Check to see if style changed. 
        if (styleProp=="selectedColor") {
            selectedColorChanged=true;
            invalidateDisplayList();
            return;
        }
        if (styleProp=="selectedFontSize") {
            selectedFontSizeChanged=true;
            invalidateDisplayList();
            return;
        }
    }

    private var deselectedColorStored:Boolean = false;
    private var deselectedColorVal:uint;
    private var selectedColorVal:uint;
    private var deselectedFontSizeStored:Boolean = false;
    private var deselectedFontSizeVal:Number;
    private var selectedFontSizeVal:Number;

    override public function set selected(value:Boolean):void {
        super.selected = value;

        selectedChanged=true;
        invalidateDisplayList();
    }

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

        // selected color
        if (selectedColorChanged || selectedChanged) {
            if (selected) {
                if (!deselectedColorStored) {
                    deselectedColorVal = getStyle("color") as uint;
                    deselectedColorStored = true;
                }
                selectedColorVal = getStyle("selectedColor") as uint;
                setStyle("color",selectedColorVal);
                super.callLater(invalidateDisplayList);
            }
            else {
                if (deselectedColorStored) {
                    setStyle("color",deselectedColorVal);
                    super.callLater(invalidateDisplayList);
                    deselectedColorStored = false;
                }
            }

            selectedColorChanged=false;
        }

        // selected font size
        if (selectedFontSizeChanged || selectedChanged) {
            if (selected) {
                if (!deselectedFontSizeStored) {
                    deselectedFontSizeVal = getStyle("fontSize") as Number;
                    deselectedFontSizeStored = true;
                }
                selectedFontSizeVal = getStyle("selectedFontSize") as Number;
                setStyle("fontSize",selectedFontSizeVal);
                super.callLater(invalidateDisplayList);
            }
            else {
                if (deselectedFontSizeStored) {
                    setStyle("fontSize",deselectedFontSizeVal);
                    super.callLater(invalidateDisplayList);
                    deselectedFontSizeStored = false;
                }
            }

            selectedFontSizeChanged=false;
        }

        selectedChanged=false;
    }
}
Related Topic