Flex custom item renderer for the displayed item in the combobox

14,305

Solution 1

By default you cannot do this. However, if you extend ComboBox you can add this functionality easily. Here is a quick example, it is a rough version and probably needs testing / tweaking but it shows how you could accomplish this.

package
{
    import mx.controls.ComboBox;
    import mx.core.UIComponent;

    public class ComboBox2 extends ComboBox
    {
        public function ComboBox2()
        {
            super();
        }

        protected var textInputReplacement:UIComponent;

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

            if ( !textInputReplacement ) {
                if ( itemRenderer != null ) {
                    //remove the default textInput
                    removeChild(textInput);

                    //create a new itemRenderer to use in place of the text input
                    textInputReplacement = itemRenderer.newInstance();
                    addChild(textInputReplacement);
                }
            }
        }

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

            if ( textInputReplacement ) {
                textInputReplacement.width = unscaledWidth;
                textInputReplacement.height = unscaledHeight;
            }
        }
    }
}

Solution 2

I tried the above solution, but found that the selectedItem did not display when the combobox was closed. A extra line of code was required to bind the itemRenderer data property to the selectedItem:

            if ( !textInputReplacement ) {
                    if ( itemRenderer != null ) {
                            //remove the default textInput
                            removeChild(textInput);

                            //create a new itemRenderer to use in place of the text input
                            textInputReplacement = itemRenderer.newInstance();

                            // ADD THIS BINDING:
                            // Bind the data of the textInputReplacement to the selected item
                            BindingUtils.bindProperty(textInputReplacement, "data", this, "selectedItem", true);

                            addChild(textInputReplacement);
                    }
            }

Solution 3

I've extended Dane's code a bit further. In some cases clicking did not open the drop box with my renderer and I noticed that the normal Flex ComboBox skins did not fire. Thus in replaceTextInput() I added some additional event listeners and save a reference to the ComboBox button used to display the skins. Now it behaves just like the normal ComboBox.

Here's the code:

    package
    {
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;

    import mx.binding.utils.BindingUtils;
    import mx.controls.Button;
    import mx.controls.ComboBox;
    import mx.core.IFactory;
    import mx.core.UIComponent;
    import mx.events.DropdownEvent;

    /**
     * Extension of the standard ComboBox that will use the assigned 'itemRenderer'
     * for both the list items and the selected item.
     * 
     * Based on code from:
     * http://stackoverflow.com/questions/269773/flex-custom-item-renderer-for-the-displayed-item-in-the-combobox
     */
    public class ComboBoxFullRenderer extends ComboBox
    {
    protected var textInputReplacement:UIComponent;
    private var _increaseW:Number = 0;
    private var _increaseH:Number = 0;


    /**
     * Keeps track of the current open/close state of the drop down list. 
     */
    protected var _isOpen:Boolean = false;

    /**
     * Stores a reference to the 'Button' which overlays the ComboBox.  Allows
     * us to pass events to it so skins are properly triggered. 
     */
    protected var _buttonRef:Button = null;


    /**
     * Constructor. 
     */
    public function ComboBoxFullRenderer() {
        super();
    }


    /**
     * Sets a value to increase the width of our ComboBox to adjust sizing. 
     * 
     * @param val Number of pixels to increase the width of the ComboBox.
     */
    public function set increaseW(val:Number):void {
        _increaseW = val;
    }

    /**
     * Sets a value to increase the height of our ComboBox to adjust sizing. 
     * 
     * @param val Number of pixels to increase the height of the ComboBox.
     */
    public function set increaseH(val:Number):void {
        _increaseH = val;
    }


    /**
     * Override the 'itemRenderer' setter so we can also replace the selected
     * item renderer.
     *  
     * @param value The renderer to be used to display the drop down list items
     *   and the selected item.
     */
    override public function set itemRenderer(value:IFactory):void {
        super.itemRenderer = value;
        replaceTextInput();
    }


    /**
     * Override base 'createChildren()' routine to call our 'replaceTextInput()'
     * method to replace the standard selected item renderer.
     *  
     * @see #replaceTextInput();
     */
    override protected function createChildren():void {
        super.createChildren();
        replaceTextInput();
    }


    /**
     * Routine to replace the ComboBox 'textInput' child with our own child
     * that will render the selected data element.  Will create an instance of
     * the 'itemRenderer' set for this ComboBox. 
     */
    protected function replaceTextInput():void {
        if ( !textInputReplacement ) {
            if ( this.itemRenderer != null && textInput != null ) {
                //remove the default textInput
                removeChild(textInput);

                //create a new itemRenderer instance to use in place of the text input
                textInputReplacement = this.itemRenderer.newInstance();
                // Listen for clicks so we can open/close the drop down when
                // renderer components are clicked.  
                textInputReplacement.addEventListener(MouseEvent.CLICK, _onClick);
                // Listen to the mouse events on our renderer so we can feed them to
                // the ComboBox overlay button.  This will make sure the button skins
                // are activated.  See ComboBox::commitProperties() code.
                textInputReplacement.addEventListener(MouseEvent.MOUSE_DOWN, _onMouseEvent);
                textInputReplacement.addEventListener(MouseEvent.MOUSE_UP, _onMouseEvent);
                textInputReplacement.addEventListener(MouseEvent.ROLL_OVER, _onMouseEvent);
                textInputReplacement.addEventListener(MouseEvent.ROLL_OUT, _onMouseEvent);
                textInputReplacement.addEventListener(KeyboardEvent.KEY_DOWN, _onMouseEvent);

                // Bind the data of the textInputReplacement to the selected item
                BindingUtils.bindProperty(textInputReplacement, "data", this, "selectedItem", true);

                // Add our renderer as a child.
                addChild(textInputReplacement);

                // Listen for open close so we can maintain state.  The
                // 'isShowingDropdown' property is mx_internal so we don't
                // have access to it. 
                this.addEventListener(DropdownEvent.OPEN, _onOpen);
                this.addEventListener(DropdownEvent.CLOSE, _onClose);

                // Save a reference to the mx_internal button for the combo box.
                //  We will need this so we can call its dispatchEvent() method.
                for (var i:int = 0; i < this.numChildren; i++) {
                    var temp:Object = this.getChildAt(i);
                    if (temp is Button) {
                        _buttonRef = temp as Button;
                        break;
                    } 
                }
            }
        }
    }


    /**
     * Detect open events on the drop down list to keep track of the current
     * drop down state so we can react properly to a click on our selected
     * item renderer.
     *  
     * @param event The DropdownEvent.OPEN event for the combo box.
     */
    protected function _onOpen(event:DropdownEvent) : void {
        _isOpen = true;
    }


    /**
     * Detect close events on the drop down list to keep track of the current
     * drop down state so we can react properly to a click on our selected
     * item renderer.
     *  
     * @param event The DropdownEvent.CLOSE event for the combo box.
     */
    protected function _onClose(event:DropdownEvent) : void {
        _isOpen = false;
    }


    /**
     * When we detect a click on our renderer open or close the drop down list
     * based on whether the drop down is currently open/closed.
     *  
     * @param event The CLICK event from our selected item renderer.
     */
    protected function _onClick(event:MouseEvent) : void {
        if (_isOpen) {
            this.close(event);
        } else {
            this.open();
        }
    }


    /**
     * React to certain mouse/keyboard events on our selected item renderer and
     * pass the events to the ComboBox 'button' so that the skins are properly
     * applied.
     *  
     * @param event A mouse or keyboard event to send to the ComboBox button.
     * 
     */
    protected function _onMouseEvent(event:Event) : void {
        if (_buttonRef != null) {
            _buttonRef.dispatchEvent(event);
        }
    }
    } // end class
    } // end package
Share:
14,305
pbreault
Author by

pbreault

Updated on June 11, 2022

Comments

  • pbreault
    pbreault about 2 years

    I am using a custom item renderer in a combobox to display a custom drawing instead of the default text label.

    This works fine for the dropdown list but the displayed item ( when the list is closed) is still the textual representation of my object.

    Is there a way to have the displayed item rendered the same way as the one in the dropdown?

  • Kumaresan K
    Kumaresan K almost 8 years
    can you help me: //remove the default textInput removeChild(textInput); Implicit coercion of a value of type mx.core:ITextInput to an unrelated type flash.display:DisplayObject.