本文属原创文章,如需转载,请注明出处,谢谢

企业应用中少不了双选下拉列表控件,但几乎都没有独立的控件,Flex在这上面得天独厚,ArrayCollection的过滤功能使得我们只需要一个数据源就可以将数据展示在两个下拉列表中

有呆毛才有真相:

为了实现上图我想要的控件效果,我需要先明确我希望得到的控件该怎么用:

<ui:DDList width="400" height="300" labelField="label" labelFunction="labelFunc" 
  source="{mySource}" values="@{myValues}" valueField="value"/>

这个控件应该能让我像其他List控件一样可以实现自定义label字段或labelFuntion实现,然后,指定一个数据源source:Array,这里没有使用ArrayCollection,我觉得双选组件一般情况下很少会对数据源进行增删操作,主要是左右移动,然后values非常重要,这是一个方便控件使用的属性,values:ArrayCollection表示选中的项是哪些
例如:

[Bindable]
private var source:Array = [
{label:'王大锤', value:0},
{label:'李小虎', value:1},
{label:'舒克', value:2},
{label:'贝塔', value:3},
{label:'金三胖', value:4},
{label:'白小飞', value:5},
{label:'路飞', value:6},
{label:'鸣人', value:7},
{label:'吐槽', value:8}
];

我只要告诉控件,选中项为[2,4,7],那么控件自动将value字段为2,4,7的项目放在右边的下拉列表,同理,操作控件时,values自动更新,这里特别注意value字段必须是String,int等类型,如果是Object等以内存地址做相等判断的,这个就不灵了,不过相信我,你会喜欢这种方式的。

好了,现在开始吧,建立一个Skinnable组件,命名为DDList:

import spark.components.supportClasses.SkinnableComponent;

public class DDList extends SkinnableComponent
{
public function DDList()
{
super();
}
}

然后熟练的建立一个对应的MXML外观,名称DDListSkin,主机主件指定为刚才的DDList:

<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<!-- host component -->
<fx:Metadata>
[HostComponent("DDList")]
</fx:Metadata>
</s:Skin>

现在来考虑界面吧,我需要左右两个List下拉框,以及中间四个按钮,分别表示全选,选择选中项,取消选中项,全部取消四个功能,来吧,MXML写上:

<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<!-- host component -->
<fx:Metadata>
[HostComponent("DDList")]
</fx:Metadata> <s:HGroup width="100%" height="100%" verticalAlign="middle">
<s:List id="listLeft" width="50%" height="100%" allowMultipleSelection="true"/>
<s:VGroup>
<s:Button icon="@Embed(source='arrow-rightAll-16.png')" label="全选"
enabled="{listLeft.dataProvider &amp;&amp; listLeft.dataProvider.length>0}" />
<s:Button icon="@Embed(source='arrow-right-16.png')" label="添加"
enabled="{listLeft.selectedItems.length>0}" />
<s:Button icon="@Embed(source='arrow-left-16.png')" label="移除"
enabled="{listRight.selectedItems.length>0}" />
<s:Button icon="@Embed(source='arrow-leftAll-16.png')" label="清空"
enabled="{listRight.dataProvider &amp;&amp; listRight.dataProvider.length>0}" />
</s:VGroup>
<s:List id="listRight" width="50%" height="100%" allowMultipleSelection="true"/>
</s:HGroup> </s:Skin>

这里的图标就不用说了吧,找了四个同名图标放在相同路径下。
好了,给组件披上外衣吧:

import spark.components.supportClasses.SkinnableComponent;

public class DDList extends SkinnableComponent
{
  public function DDList()
  {
    super();
    setStyle('skinClass', DDListSkin);
  }
}

现在可以测试一下了,看下图:

有点意思了吗?开始实现数据源的展示吧,给组件加上相关属性,labelField,labelFunction,valueField,values以及作为保存选择项列表的selectedItems:

import mx.collections.ArrayCollection;

import spark.components.supportClasses.SkinnableComponent;

public class DDList extends SkinnableComponent
{
  public function DDList()
  {
    super();
    setStyle('skinClass', DDListSkin);
  }   [Bindable]public var labelField:String = "label";
  [Bindable]public var labelFunction:Function;   [Bindable]internal var leftDataProvider:ArrayCollection;
  [Bindable]internal var rightDataProvider:ArrayCollection;   private var _valueField:String = "value";
  [Bindable]
  public function get valueField():String
  {
    return _valueField;
  }
  public function set valueField(value:String):void
  {
    if(value==_valueField)
    {
      return;
    }
    _valueField = value;
  }   private var _values:ArrayCollection = new ArrayCollection();
  [Bindable]
  public function get values():ArrayCollection
  {
    return _values;
  }
  public function set values(value:ArrayCollection):void
  {
    if(null==value || value==_values)
    {
      return;
    }
    _values = value;
  }   private var _selectedItems:Vector.<Object> = new Vector.<Object>();
  [Bindable("selectedItemsChanged")]
  public function get selectedItems():Vector.<Object>
  {
    return _selectedItems;
  }
}

我们的核心就在于这个_selectedItems,用于保存当前选中项的数组,通过维护这个数组,来将数据展示给用户,也能将values反馈给程序
现在要来实现对这个数组的维护,当source,valueField,values变化时,我们都要根据values来更新_selectedItems数组,使它反映出最新的选中项列表,通过下面的代码来实现对项目的选中设置:

private function setItemState(item:Object, selected:Boolean):void
{
  var index:int = _selectedItems.indexOf(item);
  if(selected)
  {
    if(index<0)
    {
      _selectedItems.push(item);
    }
    if(item.hasOwnProperty(valueField) && !_values.contains(item[valueField]))
    {
      _values.source.push(item[valueField]);
    }
  }
  else
  {
    if(index>=0)
    {
      _selectedItems.splice(index, 1);
    }
    if(item.hasOwnProperty(valueField))
    {
      while(_values.contains(item[valueField]))
      {
        _values.source.splice(_values.getItemIndex(item[valueField]), 1);
      }
    }
  }
}

如果item选中,就调用setItemState(item, true),如果item被移除,就调用setItemState(item, false);里面会为我们维护好selectedItems和values。
有了这个方法,就好办多了,当设定values时,遍历values的每一个value,看是否在source中找到对应的item,找到了就setItemState(item, true),否则就是无效项,从values中删掉吧。

public class DDList extends SkinnableComponent
{
  public function DDList()
  {
    super();
    setStyle('skinClass', DDListSkin);
  }   [Bindable]public var labelField:String = "label";
  [Bindable]public var labelFunction:Function;   [Bindable]internal var leftDataProvider:ArrayCollection;
  [Bindable]internal var rightDataProvider:ArrayCollection;   private var _valueField:String = "value";
  [Bindable]
  public function get valueField():String
  {
    return _valueField;
  }
  public function set valueField(value:String):void
  {
    if(value==_valueField)
    {
      return;
    }
    _valueField = value;
    validateValues();
  }   private var _values:ArrayCollection = new ArrayCollection();
  [Bindable]
  public function get values():ArrayCollection
  {
    return _values;
  }
  public function set values(value:ArrayCollection):void
  {
    if(null==value || value==_values)
    {
      return;
    }
    _values = value;
    validateValues();
  }   private var _selectedItems:Vector.<Object> = new Vector.<Object>();
  [Bindable("selectedItemsChanged")]
  public function get selectedItems():Vector.<Object>
  {
    return _selectedItems;
  }   private var _source:Array;
  [Bindable]
  public function get source():Array
  {
    return _source;
  }
  public function set source(value:Array):void
  {
    if(value==_source)
    {
      return;
    }
    _source = value;
    validateValues();
  }   private function validateValues(event:CollectionEvent=null):void
  {
    if(source)
    {
      _selectedItems = new Vector.<Object>();       for each(var value:* in _values)
      {
        var b:Boolean = false;
        for each(var item:Object in source)
        {
          if(item && item.hasOwnProperty(valueField) && item[valueField]==value)
          {
            setItemState(item, true);
            b = true;
          }
        }
        if(!b)
        {
          _values.source.splice(_values.getItemIndex(value), 1);
        }
      }
      leftDataProvider.refresh();
      rightDataProvider.refresh();
      dispatchEvent(new FlexEvent("selectedItemsChanged", true));
    }
  }   private function setItemState(item:Object, selected:Boolean):void
  {
    var index:int = _selectedItems.indexOf(item);
    if(selected)
    {
      if(index<0)
      {
        _selectedItems.push(item);
      }
      if(item.hasOwnProperty(valueField) && !_values.contains(item[valueField]))
      {
        _values.source.push(item[valueField]);
      }
    }
    else
    {
      if(index>=0)
      {
        _selectedItems.splice(index, 1);
      }
      if(item.hasOwnProperty(valueField))
      {
        while(_values.contains(item[valueField]))
        {
          _values.source.splice(_values.getItemIndex(item[valueField]), 1);
        }
      }
    }
  }

当source,values,valueField变化时,都需要及时维护好selectedItems和values,所以在他们的setter中,都加上了validateValues();,我们通过对左右两个dataProvider进行refresh来保证数据及时刷新,好了,左右两个dataProvider还没有设置filterFunction呢,来改写一下source的setter方法:

public function set source(value:Array):void
{
  if(value==_source)
  {
    return;
  }
  _source = value;   leftDataProvider = new ArrayCollection(_source);
  leftDataProvider.filterFunction = leftFilterFunction;   rightDataProvider = new ArrayCollection(_source);
  rightDataProvider.filterFunction = rightFilterFunction;   validateValues();
} private function leftFilterFunction(item:Object):Boolean
{
  return !rightFilterFunction(item);
} private function rightFilterFunction(item:Object):Boolean
{
  return _selectedItems.indexOf(item)>=0;
}

看见了吗,leftDataProvider和rightDataProvider都是来自同一数据源source,但设置了不同的过滤函数,右边的只显示在selectedItems中存在的项,左边的正好相反!

嗯,好像有地方不对劲,对了,values一旦在外部发生变化,需要通知控件自动刷新,没问题,values加上事件监听:

public function set values(value:ArrayCollection):void
{
  if(null==value || value==_values)
  {
    return;
  }
  if(_values.hasEventListener(CollectionEvent.COLLECTION_CHANGE))
  {
    _values.removeEventListener(CollectionEvent.COLLECTION_CHANGE, validateValues);
  }
  _values = value;
  _values.addEventListener(CollectionEvent.COLLECTION_CHANGE, validateValues, false, 0 ,true);   validateValues();
}

这样,一旦程序通过编程方式改动了values的内容,立刻会被控件监听到,自动执行validateValues函数,自动维护好selectedItems和values;
看样子就要成功了,还少了什么?嗯,组件的交互,点击四个按钮后也需要对selectedItem和values进行维护,没问题,先定义一个内部事件,用来向组件冒泡:

import flash.events.Event;

internal final class DDListEvent extends Event
{
  public static const ADD_ALL:String = "addAll";
  public static const ADD:String = "addItems";
  public static const REMOVE:String = "removeItems";
  public static const REMOVE_ALL:String = "removeAll";   public function DDListEvent(type:String, items:Vector.<Object>=null, bubbles:Boolean=false, cancelable:Boolean=false)
  {
    _items = items;
    super(type, bubbles, cancelable);
  }   private var _items:Vector.<Object>;
  public function get items():Vector.<Object>
  {
    return _items;
  }   override public function clone():Event
  {
    return new DDListEvent(type, items, bubbles, cancelable);
  }
}

然后给四个按钮加上click事件处理吧:

protected function button1_clickHandler(event:MouseEvent):void
{
  event.stopPropagation();
  dispatchEvent(new DDListEvent(DDListEvent.ADD_ALL, null, true));
} protected function button2_clickHandler(event:MouseEvent):void
{
  event.stopPropagation();
  dispatchEvent(new DDListEvent(DDListEvent.ADD, listLeft.selectedItems, true));
} protected function button3_clickHandler(event:MouseEvent):void
{
  event.stopPropagation();
  dispatchEvent(new DDListEvent(DDListEvent.REMOVE, listRight.selectedItems, true));
} protected function button4_clickHandler(event:MouseEvent):void
{
  event.stopPropagation();
  dispatchEvent(new DDListEvent(DDListEvent.REMOVE_ALL, null, true));
}

好了,给我们的组件添加事件监听,侦听DDListEvent吧:

public function DDList()
{
  super();
  setStyle('skinClass', DDListSkin);   addEventListener(DDListEvent.ADD_ALL, changeHandler);
  addEventListener(DDListEvent.ADD, changeHandler);
  addEventListener(DDListEvent.REMOVE, changeHandler);
  addEventListener(DDListEvent.REMOVE_ALL, changeHandler);
} private function changeHandler(event:DDListEvent):void
{
  event.stopPropagation();   switch(event.type)
  {
    case DDListEvent.ADD_ALL:
      for each(var item:Object in source)
      {
        setItemState(item, true);
      }
      break;
    case DDListEvent.ADD:
      for each(var item1:Object in event.items)
      {
        setItemState(item1, true);
      }
      break;
    case DDListEvent.REMOVE:
      for each(var item2:Object in event.items)
      {
        setItemState(item2, false);
      }
      break;
    case DDListEvent.REMOVE_ALL:
      for each(var item3:Object in source)
      {
        setItemState(item3, false);
      }
      break;
  }
  leftDataProvider.refresh();
  rightDataProvider.refresh();   dispatchEvent(new FlexEvent("selectedItemsChanged", true));
}

最后一步,在皮肤中给两个List绑定数据源:

<s:HGroup width="100%" height="100%" verticalAlign="middle">
<s:List id="listLeft" width="50%" height="100%" allowMultipleSelection="true"
labelField="{hostComponent.labelField}" labelFunction="{hostComponent.labelFunction}"
dataProvider="{hostComponent.leftDataProvider}" />
<s:VGroup>
<s:Button icon="@Embed(source='arrow-rightAll-16.png')" label="全选"
enabled="{listLeft.dataProvider &amp;&amp; listLeft.dataProvider.length>0}"
click="button1_clickHandler(event)"/>
<s:Button icon="@Embed(source='arrow-right-16.png')" label="添加"
enabled="{listLeft.selectedItems.length>0}"
click="button2_clickHandler(event)"/>
<s:Button icon="@Embed(source='arrow-left-16.png')" label="移除"
enabled="{listRight.selectedItems.length>0}"
click="button3_clickHandler(event)"/>
<s:Button icon="@Embed(source='arrow-leftAll-16.png')" label="清空"
enabled="{listRight.dataProvider &amp;&amp; listRight.dataProvider.length>0}"
click="button4_clickHandler(event)"/>
</s:VGroup>
<s:List id="listRight" width="50%" height="100%" allowMultipleSelection="true"
labelField="{hostComponent.labelField}" labelFunction="{hostComponent.labelFunction}"
dataProvider="{hostComponent.rightDataProvider}"/>
</s:HGroup>

大功告成,测试一下吧:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:local="*"> <fx:Style>
global
{
fontFamily:"微软雅黑";
fontSize:12px;
}
</fx:Style> <fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.utils.StringUtil; private var data:Array = [
{label:'王大锤', value:0},
{label:'李小虎', value:1},
{label:'舒克', value:2},
{label:'贝塔', value:3},
{label:'金三胖', value:4},
{label:'白小飞', value:5},
{label:'路飞', value:6},
{label:'鸣人', value:7},
{label:'吐槽', value:8}
]; [Bindable]private var source:Array;
[Bindable]private var values:ArrayCollection; private function labelFunc(item:Object):String
{
return StringUtil.substitute("{0}-{1}", item.value,item.label);
} protected function button1_clickHandler(event:MouseEvent):void
{
source = data.concat();
} protected function button2_clickHandler(event:MouseEvent):void
{
values = new ArrayCollection([3,6,8,22]);
} protected function button3_clickHandler(event:MouseEvent):void
{
Alert.show(values.toArray().join(','));
} protected function button4_clickHandler(event:MouseEvent):void
{
values.addItem(7);
values.addItem(19);
}
]]>
</fx:Script> <s:HGroup horizontalCenter="0" verticalCenter="0">
<s:VGroup horizontalCenter="0" verticalCenter="0">
<s:Button label="加载数据" click="button1_clickHandler(event)" />
<s:Button label="初始化" click="button2_clickHandler(event)" />
<s:Button label="设置values" click="button4_clickHandler(event)" />
<s:Button label="查看values" click="button3_clickHandler(event)"/>
<local:DDList width="400" height="300" labelFunction="labelFunc" source="{source}" values="@{values}" />
</s:VGroup> </s:HGroup> </s:Application>

此即本文最开始的Demo(呆毛)。

Flex4 双选下拉列表的实现(源代码)的更多相关文章

  1. 一款不错的多选下拉列表利器—— Ext.ux.form.SuperBoxSelect

    在B/S系统中,下拉列表(select/dropdownlist/combobox)的应用随处可见,为了增强用户体验,开发人员也常常会做一些带联想功能的下拉列表, 特别是数据项比较多的时候,用户筛选起 ...

  2. bootstrap下的双选时间插件使用方法

    bootstrap画的页面很漂亮,能自动适应网页端,移动端.实现一个双选时间控件: 要得jar包自己去下 一.页面 二.JS var $createTime=$('#createTime');$cre ...

  3. javascript操作多选下拉列表

    闲来无事,把javascript操作多选下拉列表有关的操作知识复习了一遍,代码附上 <%-- Created by IntelliJ IDEA. User: Administrator Date ...

  4. kendoUI 多选下拉列表 kendoMultiSelect

    问题1:被重复渲染 点击新增按钮----弹出模态框 多选下拉列表在多选框中只是初始化过一次.但是每次点击新增后  发现 多选下拉列表 被重复渲染了 解决方案 在 新增时  先将其父元素div中  的s ...

  5. Jquery 多选下拉列表插件jquery multiselect

    有一个多选的需求,在网上找到了这个插件:multiselect https://github.com/ehynds/jquery-ui-multiselect-widget csdn博客上有这个插件的 ...

  6. c# 自定义多选下拉列表2

    以下为工作中遇到的,备注一下 先需要几个辅助类 #region GripBounds using System.Drawing; internal struct GripBounds { ; ; pu ...

  7. bootstrap-select多选下拉列表插件使用小记

    下载插件 插件地址:http://silviomoreto.github.io/bootstrap-select/ 下载好后引用css和js文件 <!-- 因为是jquery插件,所以引用前先引 ...

  8. jquery书写左右两个多选下拉列表交换移除功能

    使用jquery做一个多选列表左右互换的功能,代码如下 <!DOCTYPE HTML> <html lang="en-US"> <head> & ...

  9. iview 多选下拉列表选项回显问题

    如,简单的多选Select, <Select v-model="model" filterable clearable transfer multiple > < ...

随机推荐

  1. 【Struts2学习笔记-6--】Struts2之拦截器

    简单拦截器的使用 拦截器最基本的使用: 拦截方法的拦截器 拦截器的执行顺序 拦截结果的监听器-相当于 后拦截器 执行顺序: 覆盖拦截器栈里特定拦截器的参数 使用拦截器完成-权限控制 主要完成两个功能: ...

  2. MySQL使用Union创建视图报错

    mysql> select * from test_main; +----+-------+ | id | value | +----+-------+ |  1 | ONE   | |  2  ...

  3. 6.25$post('',function(){});无法触发问题

    试了很久,发现把这个方法放错位置了

  4. 2015年8月TIOBE编程语言排行榜

    名副其实的月经贴.

  5. xorm使用pgsql的例子

    测试表 /* Navicat Premium Data Transfer Source Server : localhost Source Server Type : PostgreSQL Sourc ...

  6. HDMI的CEC是如何控制外围互联设备的

    1. HDMI CEC算是一个相当庞大的系统,想了解还要从HDMI接口信号啊.物理地址啊.逻辑地址啊等等HDMI基础的东西说起. 2. 不过可以简单的这么理解,在HDMI CEC最小系统里,所有通过H ...

  7. 黄聪:如何使用WebKitBrowser调用元素点击事件(C#)

    string s = "var _elm = document.getElementById('loginBtn');var _evt = document.createEvent('Mou ...

  8. JAVA 创建类,使用类

    一.创建类: Test.java //定义类 public class Test{ //属性 String name; String gender; int age; //方法,无参无返回 publi ...

  9. nvelocity模板引擎

    using NVelocity.App;using NVelocity.Runtime;using NVelocity; VelocityEngine vltEngine = new Velocity ...

  10. Mybatis where 1=1 和 <where>标签

    <select id="selSampleListByIDX4" resultMap="BaseResultMap" parameterType=&quo ...