简体   繁体   English

Flex组合框中显示项目的自定义项目渲染器

[英]Flex custom item renderer for the displayed item in the combobox

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? 有没有办法让显示的项目与下拉列表中的项目呈现方式相同?

By default you cannot do this. 默认情况下,您无法执行此操作。 However, if you extend ComboBox you can add this functionality easily. 但是,如果扩展ComboBox,则可以轻松添加此功能。 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;
            }
        }
    }
}

I tried the above solution, but found that the selectedItem did not display when the combobox was closed. 我尝试了上面的解决方案,但发现当组合框关闭时,selectedItem没有显示。 A extra line of code was required to bind the itemRenderer data property to the selectedItem: 将itemRenderer数据属性绑定到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);
                    }
            }

I've extended Dane's code a bit further. 我进一步扩展了Dane的代码。 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. 在某些情况下,点击没有打开我的渲染器的下拉框,我注意到正常的Flex ComboBox外观没有触发。 Thus in replaceTextInput() I added some additional event listeners and save a reference to the ComboBox button used to display the skins. 因此,在replaceTextInput()中,我添加了一些额外的事件侦听器,并保存对用于显示外观的ComboBox按钮的引用。 Now it behaves just like the normal ComboBox. 现在它的行为就像普通的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

Thank you maclema and Maurits de Boer. 谢谢maclema和Maurits de Boer。 I added a couple more things to this class to make it fit my needs: 我在这个课程中添加了几个东西,以满足我的需求:

  • I overrode set itemRenderer so that this will work if you set the itemRenderer through AS instead of mxml. 我重写了set itemRenderer,这样如果你通过AS而不是mxml设置itemRenderer就可以了。 I moved the text input replacement code to its own function to avoid duplication. 我将文本输入替换代码移动到它自己的函数以避免重复。

  • I added setters for 'increaseW' and 'increaseH' to resize the combobox if necessary because my renderer was too big for the combobox at first. 我添加了'increaseW'和'increaseH'的setter,以便在必要时调整组合框的大小,因为我的渲染器最初对于组合框来说太大了。

  • I subtracted 25 from the textInputReplacement width so it doesn't ever overlap the dropdown button... may be better to use something more proportional to accommodate different skins and such. 我从textInputReplacement宽度减去25,所以它不会与下拉按钮重叠...可能更好地使用更适合的东西来容纳不同的皮肤等。

Code: 码:

package
{
 import mx.binding.utils.BindingUtils;
 import mx.controls.ComboBox;
 import mx.core.IFactory;
 import mx.core.UIComponent;

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

        protected var textInputReplacement:UIComponent;
        private var _increaseW:Number = 0;
        private var _increaseH:Number = 0;

  public function set increaseW(val:Number):void
  {
   _increaseW = val;
  }

  public function set increaseH(val:Number):void
  {
   _increaseH = val;
  }

  override public function set itemRenderer(value:IFactory):void
  {
   super.itemRenderer = value;
   replaceTextInput();
  }

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

        }

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

          unscaledWidth += _increaseW;
          unscaledHeight += _increaseH;

                super.updateDisplayList(unscaledWidth, unscaledHeight);

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

        protected function replaceTextInput():void
        {
         if ( !textInputReplacement ) {
                        if ( this.itemRenderer != null ) {
                                //remove the default textInput
                                removeChild(textInput);

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

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

                             addChild(textInputReplacement);

                        }
                }
        }
    }
}

I found an easier way of changing the renderer for the selected element. 我找到了一种更简单的方法来更改所选元素的渲染器。 This one only works if your element inherits from the TextInput class, in Flex 4.0 or above. 只有当您的元素继承自Flex 4.0或更高版本的TextInput类时,此方法才有效。

In Flex v4.5, in ComboBase.createChildren at line 1177, you will find that the class definable for the textInput can be passed using the style key textInputClass : 在Flex v4.5中,在第1177行的ComboBase.createChildren中,您会发现可以使用样式键textInputClass传递textInput可定义的类:

// Mechanism to use MXFTETextInput. 
var textInputClass:Class = getStyle("textInputClass");            
if (!textInputClass || FlexVersion.compatibilityVersion < FlexVersion.VERSION_4_0)
{
    textInput = new TextInput();
}
else
{
   textInput = new textInputClass();
}

Just change the value of this key in the constructor of your combo and now you have your own renderer for the selectedItem . 只需在组合的构造函数中更改此键的值,现在您就拥有了自己的selectedItem渲染器。

public function ComboAvailableProfessor()
{
    super();

    itemRenderer = new ClassFactory( ProfessorAvailableListItemRenderer );
    setStyle( 'textInputClass', ProfessorAvailableSelectedListItemRenderer );
}

Finally you must bind the data property to the selectedItem property in your combo in order to get data displayed. 最后,您必须将data属性绑定到组合中的selectedItem属性,以便显示数据。

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

    BindingUtils.bindProperty( textInput, 'data', this, 'selectedItem', true );
}

I was looking for a way to do this using the Spark ComboBox. 我正在寻找一种方法来使用Spark ComboBox。

This thread was very useful to me but so far there have only been answers on how to do it using an mx:ComboBox. 这个帖子对我来说非常有用,但到目前为止,只有答案如何使用mx:ComboBox。 I thought that I should append my answer on how to do it using a spark ComboBox. 我认为我应该使用Spark ComboBox附加我的答案。

  1. Create a new skin of the ComboBox 创建ComboBox的新外观
  2. Hide and disable the textInput 隐藏和禁用textInput
  3. Insert your own component 插入您自己的组件

This is what the skin would look like: 这就是皮肤的样子:

<s:SparkSkin>

    <... Lots of other stuff/>

    <s:BorderContainer height="25">
        <WHATEVER YOU NEED HERE!/>
    </s:BorderContainer>

    <!-- Disable the textInput and hide it -->
    <s:TextInput id="textInput"
        left="0" right="18" top="0" bottom="0" 
        skinClass="spark.skins.spark.ComboBoxTextInputSkin"

        visible="false" enabled="false"/> 


</s:SparkSkin>

With the Spark ComboBox this process is very easy and does not require you to extend ComboBox. 使用Spark ComboBox,这个过程非常简单,不需要您扩展ComboBox。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM