如何制作一个ComboBox,用户可以选择null
?
如果您只是在数据提供者中创建一个带null
的组合框,则会显示该值,但用户无法选择它:
<mx:ComboBox id="cb" dataProvider="{[null, 'foo', 'bar']}" />
有没有办法让null可选?
解决方法是将一个项添加到dataProvider中,该项不是null但是'表示'为null;然后每次访问组合框时在null和该对象之间进行映射。但这不是一个优雅的解决方案;在访问'可空'组合框的所有代码中,你总是要记住这个映射......
编辑:扩展为什么我不喜欢这个解决方法:
它当然可以在子类中完成,但我要么引入新属性(如nullableSelectedItem
);但是你必须小心,始终使用这些属性。或者我覆盖ComboBoxes selectedItem
;但是我很害怕打破基类:它可能不喜欢改变它对当前所选项目的内容的想法。即使这个脆弱的黑客工作,在selectedItem
和dataProvider
之上,这个nullItem也需要在data
和listData
中为渲染器处理{{1}然后它可能仍然在ComboBox发送的事件中暴露...
它可能会起作用,但是如果用户点击它没有激活它的项目就会解决这个问题,这是一个很大的问题(其余的ComboBox处理null就好了)。
(另一个替代方案是将一个ui组件委托给一个ComboBox,但是为了避免这个小问题,甚至还有更多的代码)
答案 0 :(得分:5)
似乎正确管理空项目的唯一方法是将项目实际添加到组合框的数据提供者中。以下子类将自动处理。
为了支持对数据提供者的更改,实现有点棘手,包括项目添加/删除以及数据提供者本身的完全重新分配(只需考虑将数据集绑定绑定到远程服务的响应)。
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();
}
}
}
}
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>
答案 1 :(得分:3)
以下解决方案可能是最简单的解决方案:
<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;
}
}
}
答案 2 :(得分:1)
不幸的是,这是不可能的。 但是,一个不会让你“必须记住这个映射”的好解决方案是创建一个继承自ComboBox的类,它具有自己的DataProvider属性。
这个属性setter将处理null值,并在超级ComboBox类上有它的表示。
答案 3 :(得分:0)
一个非常简单但也非常有限的解决方案是添加一个prompt =“”属性..
这将阻止ComboBox自动选择dataProvider中的第一个项目,但只要用户选择一个项目,就不会再显示空行。
答案 4 :(得分:0)
requireSelection =“false”将允许空白值,并且提示允许您输入该空白值的文本用途(如果您愿意)。