【问题标题】:Null in Flex ComboBoxFlex ComboBox 中的 Null
【发布时间】:2010-10-31 06:07:06
【问题描述】:

如何制作一个用户可以选择null 的组合框?

如果您只是在 dataprovider 中创建一个带有null 的组合框,则会显示该值但用户无法选择它:

<mx:ComboBox id="cb" dataProvider="{[null, 'foo', 'bar']}" />

有没有办法让那个 null 可选?

一种解决方法是在 dataProvider 中添加一个不为空但“表示”为空的项目;然后每次访问组合框时在 null 和该对象之间进行映射。但这并不是一个优雅的解决方案。在访问“可为空”组合框的所有代码中,您必须始终牢记此映射...

编辑:详细说明我不喜欢该解决方法的原因: 当然可以在子类中完成,但要么我引入新属性(如nullableSelectedItem);但是你必须小心始终使用这些属性。或者我覆盖 ComboBoxes selectedItem;但我担心这会破坏基类:它可能不喜欢改变它对当前选定项目从内部的想法的东西。甚至这个脆弱的 hack 也有效,在 selectedItemdataProvider 之上,这个 nullItem 还需要在 datalistData 中针对渲染器在 labelFunction 中进行特殊处理,然后它可能仍然暴露在ComboBox 发送的事件... 它可能会起作用,但它只是为了解决如果用户单击该项目它没有被激活的问题(对于其余的 ComboBox 处理 null 很好),这是一个相当大的技巧。 (另一种选择是将 ui 组件委托给 ComboBox,但为了避免这个小问题,代码还要多得多)

【问题讨论】:

  • 你可以创建一个 ComboBox 的子类,它封装了这个空值行为。
  • 对 Spark ComboBox 有什么想法吗?

标签: apache-flex combobox null


【解决方案1】:

似乎正确管理空项的唯一方法是将一项实际添加到组合框的数据提供程序中。下面的子类会自动处理这个问题。

为了支持对 dataprovider 的更改,在添加/删除项目和完全重新分配 dataprovider 本身方面,实现有点棘手(只需考虑 arraycollections 绑定到远程服务的响应)。

package {

    import flash.events.Event;
    import flash.events.FocusEvent;
    import mx.collections.ArrayCollection;
    import mx.collections.ICollectionView;
    import mx.collections.IList;
    import mx.containers.FormItem;
    import mx.controls.ComboBox;
    import mx.events.CollectionEvent;
    import mx.events.ListEvent;
    import mx.validators.Validator;

    public class EmptyItemComboBox extends ComboBox {

        protected var _emptyItem:Object = null;
        protected var _originalDataProvider:ICollectionView = null;

        public function EmptyItemComboBox() {
            super();
            addEmptyItem();
            addEventListener(Event.CHANGE, onChange);
        }

        private function onChange(event:Event):void {
            dispatchEvent(new Event("isEmptySelectedChanged"));
        }

        [Bindable]
        public function get emptyItem():Object {
            return _emptyItem;
        }

        public function set emptyItem(value:Object):void {
            if (_emptyItem != value) {
                clearEmptyItem();
                _emptyItem = value;
                addEmptyItem();
            }
        }

        [Bindable(event="isEmptySelectedChanged")]
        public function get isEmptySelected():Boolean {
                return (selectedItem == null || (_emptyItem != null && selectedItem == _emptyItem));
        }

        override public function set selectedItem(value:Object):void {
            if (value == null && emptyItem != null) {
                super.selectedItem = emptyItem;
            } else    if (value != selectedItem) {
                super.selectedItem = value;
            }
            dispatchEvent(new Event("isEmptySelectedChanged"));
        }

        override public function set dataProvider(value:Object):void {
            if (_originalDataProvider != null) {
                _originalDataProvider.removeEventListener(
                        CollectionEvent.COLLECTION_CHANGE,
                        onOriginalCollectionChange);
            }
            super.dataProvider = value;
            _originalDataProvider = (dataProvider as ICollectionView);
            _originalDataProvider.addEventListener(
                    CollectionEvent.COLLECTION_CHANGE,
                    onOriginalCollectionChange)
            addEmptyItem();
        }

        private function clearEmptyItem():void {
            if (emptyItem != null && dataProvider != null 
                    && dataProvider is IList) {
                var dp:IList = dataProvider as IList;
                var idx:int = dp.getItemIndex(_emptyItem);
                if (idx >=0) {
                    dp.removeItemAt(idx);    
                }
            }
            dispatchEvent(new Event("isEmptySelectedChanged"));
        }

        private function addEmptyItem():void {
            if (emptyItem != null) {
                 if (dataProvider != null && dataProvider is IList) {
                    var dp:IList = dataProvider as IList;
                    var idx:int = dp.getItemIndex(_emptyItem);
                    if (idx == -1) {
                        var newDp:ArrayCollection = new ArrayCollection(dp.toArray().concat());
                        newDp.addItemAt(_emptyItem, 0);
                        super.dataProvider = newDp;
                    }
                }
            }
            dispatchEvent(new Event("isEmptySelectedChanged"));
        }

        private function onOriginalCollectionChange(event:CollectionEvent):void {
            if (_emptyItem != null) {
                dataProvider = _originalDataProvider;
                addEmptyItem();
            }
        }
    }
}

关于课程的一些注意事项:

  • 它会自动在列表中插入空对象。这对我的场景来说是一个很强的要求:数据提供者是由远程服务返回的,它们不能仅仅为了支持 Flex UI 而包含其他元素,也我可以手动查看每个集合,以便在每次集合刷新时创建空项目。

  • 由于它必须使用集合的内容,它会在内部创建一个具有相同项目实例和空实例的原始集合的副本,因此不会触及原始集合的实例完全可以,并且可以在其他情况下重复使用。

  • 它将监听原始数据提供者的变化,允许对其进行处理,甚至分配一个完全新的:将自动重新创建空项目。

  • 您可以控制提供实际实例以用作具有 emptyItem 属性的“空项”:这允许与集合的其余部分保持一致的数据类型(如果您使用类型化对象),或者在其上定义自定义标签。

一些使用它的示例代码...

    <mx:Script>
    <![CDATA[
        import mx.controls.Alert;
    ]]>
    </mx:Script>

    <mx:ArrayCollection id="myDP">
    <mx:source>
        <mx:Array>
            <mx:Object value="1" label="First"/>  
            <mx:Object value="2" label="Second"/>
            <mx:Object value="3" label="Third"/>
        </mx:Array>
    </mx:source>
    </mx:ArrayCollection>

    <local:EmptyItemComboBox id="comboBox" dataProvider="{myDP}" labelField="label">
    <local:emptyItem>
        <mx:Object value="{null}" label="(select an item)"/>  
    </local:emptyItem>
    </local:EmptyItemComboBox>

    <mx:Button label="Show selected item" click="Alert.show(comboBox.selectedItem.value)"/>

    <mx:Button label="Clear DP" click="myDP.removeAll()"/>
    <mx:Button label="Add item to DP" click="myDP.addItem({value: '4', label: 'Fourth'})"/>
    <mx:Button label="Reset DP" click="myDP = new ArrayCollection([{value: '1', label: 'First'}])"/>

    <mx:Label text="{'comboBox.isEmptySelected = ' + comboBox.isEmptySelected}"/>

</mx:Application>

【讨论】:

  • 您的自动添加代码并查看原始集合的更改看起来不错。但正如我在问题中解释的那样,我真的不喜欢 emptyItem '代表' null;并且不再需要 Hrundik 提出的 NullList。
  • 如果您控制数据提供者集合的创建(因此您有机会添加空集合),Hrundik 建议的解决方案是可行的方法(这里是 KISS 原则的忠实粉丝: )我的子类不是关于拥有一个真实的对象实例与一个空引用(它可以使用空项目/ dropDownFactory 方法重写)。当您的收藏可以随时刷新并且您无法控制时,它更专注于管理这些事情,这是一种非常常见的情况。如果有人正在处理同样的问题,我希望它会很有用。
  • 我喜欢你的解决方案。谢谢。我之前的想法接近这个,但是实现有问题。现在我只是稍微简化了你的:我使用emptyLabel(字符串)而不是emptyItem。
  • 我刚刚重构了方法,将任何源 IList 包装在自定义 FixedElementsList(实现 IList)中,这将保留一组固定元素添加到原始源之上,请参阅我的 github 上的源github.com/cosma/Apache-Flex-Personal-Whiteboard/blob/master/…
【解决方案2】:

以下解决方案可能是最简单的解决方案:

<mx:ComboBox id="cb" dataProvider="{[{label:"", data:null}, {label:'foo', data:'foo'}, {label:'bar', data:'bar'}]}" />

并使用cb.selectedItem.data访问数据

但是,正如 Wouter 所说,这种简单的解决方案对绑定并不友好。

所以这里是一个允许选择空对象的更棘手的解决方案

<mx:ComboBox id="cb" dataProvider="{[null, 'foo', 'bar']}" dropdownFactory="{new ClassFactory(NullList)}" />

其中 NullList 是以下类:

package test
{
import mx.controls.List;

public class NullList extends List
{
    public function NullList()
    {
        super();
    }

    override public function isItemSelectable(data:Object):Boolean
    {
        if (!selectable)
            return false;
        return true;
    }

}
}

【讨论】:

  • 这是解决方法的简化版本。但这不允许您将 selectedItem 绑定到模型。换句话说,你不能用这个来做像 这样的事情。
  • 是的,您是对的,该解决方案的绑定将更加困难。 (但它仍然是可能的 - 您可以绑定到 selectedIndex 并使用一个简单的函数来获取项目索引)。我想出了解决您确切问题的解决方案。希望有帮助
  • 不错!正是我想要的。它确实有一个外观问题:将鼠标悬停在 null 项目上时它不会亮起。
  • 是的,我会尝试在几天内找到一些时间来修复它。它甚至更复杂,因为它发生在 List(甚至是 ListBase)组件的深处。如果您想自己解决问题,请随时发布您自己的答案。
【解决方案3】:

不幸的是,这是不可能的。 然而,一个不会让您“必须牢记此映射”的好解决方案是创建一个继承自 ComboBox 并具有其自己的 DataProvider 属性的类。

此属性设置器将处理空值,并在超级 ComboBox 类中对其进行表示。

【讨论】:

  • 我不太喜欢。但只要没有人想出让“真正的”空值可选的东西,我想你是对的:这是不可能的,我们能做的最好的就是尽可能地将解决方法封装在一个子类中。
【解决方案4】:

一个非常简单但也非常有限的解决方案是添加一个 prompt="" 属性..

这将阻止 ComboBox 自动选择 dataProvider 中的第一项,但是一旦用户选择了一项,将不再显示空行。

【讨论】:

  • 能够仍然选择空行是非常重要的,这就是整个问题的意义所在。
  • 支持这种场景需要一些复杂的编码。请看我的另一个答案。
【解决方案5】:

requireSelection="false" 将允许一个空白值,如果您愿意,提示允许您输入用于该空白值的文本。

【讨论】:

    猜你喜欢
    • 2011-04-13
    • 1970-01-01
    • 2011-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-09
    • 2011-01-17
    • 2011-02-02
    相关资源
    最近更新 更多