Flex4 双选下拉列表的实现(源代码)

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

企业应用中少不了双选下拉列表控件,但几乎都没有独立的控件,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 双选下拉列表的实现(源代码),布布扣,bubuko.com

时间: 2024-12-28 00:41:10

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

导师制双选系统

综合应用算法训练 一任务说明 设计一个用于软件工程系本科导师制双选的系统. 设计要求: (1) 实现学生信息管理,支持信息更新: (2) 实现教师信息管理,支持信息更新: (3) 实现学生填写导师志愿的过程,并提供当前导师的选择情况: (4) 实现基于规则的志愿调整过程: (5) 实现导师的学生筛选过程: (6) 实现各种信息的多类查询. 二实验环境及实验准备 C#编程语言 三系统分析与设计 此部分包括软件程序的需求分析与软件设计部分,最好图文并茂. (1)需求分析 参照实验要求,实现调用数据库

javascript操作多选下拉列表

闲来无事,把javascript操作多选下拉列表有关的操作知识复习了一遍,代码附上 <%-- Created by IntelliJ IDEA. User: Administrator Date: 2014/8/9 Time: 18:05 To change this template use File | Settings | File Templates.--%><%@ page contentType="text/html;charset=UTF-8" lang

导师双选制系统

导师双选制系统,功能如下: 该系统分为三个用户,一个是老师,一个是学生,一个是管理员,每个角色都有一个不同的界面,对于学生来说可以进行导师信息浏览,申请填报,个人信息修改,导师查询 ,老师登陆包括申请浏览,申请审核,申请筛选,修改个人信息,一个是管理员,可以对学生,老师,申请信息进行浏览以及导出文件 注:当程序第一次开始运行,没有学生,老师,申请的文件,所以就不可以自己初始化,所以第一次运行时就必须利用静态块来实现,Erie必须由管理员登陆进行导出文件,这样下次基于可以直接从文件中获取数据 由于

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

以下为工作中遇到的,备注一下 先需要几个辅助类 1 #region GripBounds 2 3 using System.Drawing; 4 internal struct GripBounds 5 { 6 private const int GripSize = 6; 7 private const int CornerGripSize = GripSize << 1; 8 9 public GripBounds(Rectangle clientRectangle) 10 { 11 th

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

下载插件 插件地址:http://silviomoreto.github.io/bootstrap-select/ 下载好后引用css和js文件 <!-- 因为是jquery插件,所以引用前先引用jquery--> <script type="text/javascript" src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script> <!-- bo

java+selenium+new——操作多选下拉列表——选中、取消——select类

package rjcs; import java.util.List; import org.openqa.selenium.WebElement; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.support.ui.Select; public class ddddd { public s

Datagridview全选,更新数据源代码

private void button1_Click(object sender, EventArgs e) { string sqlcmd = ""; for (int i = 0; i < dataGridView1.Rows.Count; i++) { if (dataGridView1.Rows[i].Cells[0].Value != null) { sqlcmd += "delete from T_danhao where id = " + dat

使用bootstrap-select完成可搜索的多选下拉列表

1.引用bootstrap-select及相关文件 1 <link href='<%= Application["scriptURL"] %>JS/bootstrap.min.css' type="text/css" rel="stylesheet" /> 2 <link href="<%# Application["scriptURL"] %>bootstrap-selec

POJ 3281 牛双选问题

题目大意:有F种食物和D种饮料,每种食物或饮料只能供一头牛享用,且每头牛只享用一种食物和一种饮料.现在有N头牛,每头牛都有自己喜欢的食物种类列表和饮料种类列表,问最多能使几头牛同时享用到自己喜欢的食物和饮料.(1 <= F <= 100, 1 <= D <= 100, 1 <= N <= 100) 题解:赤裸裸的网络流分配问题.把牛放到中间,两边放东西即可.每一种食物控汇1控源1再引流牛就行了.注意我一开始Wa是因为忘记牛要拆点了...牛是要限1的...