我是一向都不喜欢WinForm的ListBox控件,Items中的项没有Tag属性,只好直接使用ListBox的DataSource和DisplayMember属性设定数据源和显示,用起来很不爽。可恶的DataSource不会因为源的改变而重新响应到前台UI,每次不得不通过重新设置DataSource实现刷新UI的效果。下面是一个简单列表框数据源设置示例,直接用csc编译即可。
- /* ListBox的数据源
- * 编译:csc.exe /target:winexe ListBoxDataSource.cs
- */
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Drawing;
- using System.Linq;
- using System.Text;
- using System.Windows.Forms;
- using System.Diagnostics;
- namespace WindowsFormsApplication1
- {
- public partial class Form1 : Form
- {
- /// <summary>
- /// 声明将来作为ListBox数据的类型
- /// </summary>
- struct MyData
- {
- public Guid Id { get; set; } //数据必须采用公开属性
- public string Name { get; set; }
- }
- List<MyData> m_SourceList; //源ListBox的数据源对象
- List<MyData> m_DestinationList; //目的ListBox的数据源对象
- static readonly int m_Capacity = 10; //列表容量
- //构造
- public Form1()
- {
- InitializeComponent();
- m_SourceList = new List<MyData>(m_Capacity);
- m_DestinationList = new List<MyData>(m_Capacity);
- KeyPreview = true;
- }
- /// <summary>
- /// 初始化数据
- /// </summary>
- private void InitData()
- {
- m_SourceList.Clear();
- m_DestinationList.Clear();
- m_SourceList.Capacity = m_DestinationList.Capacity = m_Capacity;
- for (int i = 0; i < m_Capacity; i++)
- {
- var data = new MyData()
- {
- Id = Guid.NewGuid(),
- Name = string.Format("the {0} of hearts", i == 0 ? "ace" : (i + 1).ToString())
- };
- m_SourceList.Add(data);
- }
- }
- /// <summary>
- /// 初始化UI
- /// </summary>
- private void InitUI()
- {
- listBox1.DataSource = null;
- listBox1.DisplayMember = null;
- listBox2.DataSource = null;
- listBox2.DisplayMember = null;
- InitData();
- RefreshUI();
- }
- /// <summary>
- /// 刷新UI
- /// </summary>
- private void RefreshUI()
- {
- //重新设置数据源
- listBox1.DataSource = m_SourceList;
- listBox1.DisplayMember = "Name";
- listBox2.DataSource = m_DestinationList;
- listBox2.DisplayMember = "Name";
- }
- /// <summary>
- /// 重写窗口OnLoad方法
- /// </summary>
- /// <param name="e"></param>
- protected override void OnLoad(EventArgs e)
- {
- InitUI();
- base.OnLoad(e);
- }
- //“>”
- private void button1_Click(object sender, EventArgs e)
- {
- var selecteditems = listBox1.SelectedItems;
- if (selecteditems.Count == 0) return;
- var tmpList = new List<MyData>(selecteditems.Count);
- foreach (MyData item in selecteditems) tmpList.Add(item);
- m_SourceList = m_SourceList.Except(tmpList).ToList();
- m_DestinationList = m_DestinationList.Union(tmpList).ToList();
- RefreshUI();
- }
- //“<”
- private void button2_Click(object sender, EventArgs e)
- {
- var selecteditems = listBox2.SelectedItems;
- if (selecteditems.Count == 0) return;
- var tmpList = new List<MyData>(selecteditems.Count);
- foreach (MyData item in selecteditems) tmpList.Add(item);
- m_SourceList = m_SourceList.Union(tmpList).ToList();
- m_DestinationList = m_DestinationList.Except(tmpList).ToList();
- RefreshUI();
- }
- //“>>”
- private void button3_Click(object sender, EventArgs e)
- {
- var items = listBox1.Items;
- if (items.Count == 0) return;
- var tmpList = new List<MyData>(items.Count);
- foreach (MyData item in items) tmpList.Add(item);
- m_SourceList = m_SourceList.Except(tmpList).ToList();
- m_DestinationList = m_DestinationList.Union(tmpList).ToList();
- RefreshUI();
- }
- //“<<”
- private void button4_Click(object sender, EventArgs e)
- {
- var items = listBox2.Items;
- if (items.Count == 0) return;
- var tmpList = new List<MyData>(items.Count);
- foreach (MyData item in items) tmpList.Add(item);
- m_SourceList = m_SourceList.Union(tmpList).ToList();
- m_DestinationList = m_DestinationList.Except(tmpList).ToList();
- RefreshUI();
- }
- (略……)
- }
- #region 入口点
- static class Program
- {
- /// <summary>
- /// 应用程序的主入口点。
- /// </summary>
- [STAThread]
- static void Main()
- {
- Application.Run(new Form1());
- }
- }
- #endregion
- }
需要注意的是,使用DataSource“刷新”数据的时候,如果重新指定DataSource的引用与原来的引用是同一个,那么控件是不会重新读取数据进行刷新的。这一点很重要,因此只能重新设定DataSource时才有效,判断是否重新设定了DataSource,可以在其后读取DisplayMember或者ValueMember属性,它们会被置为空串,这意味着你需要再重新设定DisplayMember属性和ValueMember属性。如代码中62行的InitUI方法,它总是先设置DataSource=null再调用RefreshUI方法重新指定DataSource。代码75行的RefreshUI方法实际上是个败笔,因为仅仅是它并不能总会正确地实现“刷新UI”的效果,先将DataSource指向null是必要的。但为什么这段程序能够正确执行?95行到140行的代码中,所有对数据源m_SourceList或者m_DestinationList的操作采用的集合运算Except方法和Union方法,最终会产生一组新的集合,也就是说m_SourceList和m_DestinationList的引用已经是新的,所以RefreshUI方法中DataSource会生效,数据被正确绘制出来。
上面代码中有一个非常致命的错误。我用List<T>来作数据源,所以ListBox并不能够自动根据源的改变而正确响应。作为“列表”的数据源绑定,.NET 2.0中有一个。用csc编译下面的程序:
- using System;
- using System.Windows.Forms;
- using System.Drawing;
- using System.ComponentModel;
- namespace ListBoxDataSource2
- {
- static class Program
- {
- [STAThread]
- static void Main()
- {
- var blist = new BindingList<mydata>(); //声明一个用于绑定的BindingList<T>泛型对象
- var f = new Form() //主窗口
- {
- Text = "列表框的数据绑定2 F1 - 博客",
- Size = new Size(200, 300),
- KeyPreview = true
- };
- var listbox = new ListBox() //列表框
- {
- Location = new Point(10, 10),
- Size = new Size(f.Width - 30, f.Height - 100),
- DataSource = blist, //设置列表框的数据源为blist
- DisplayMember = "Text", //设置显示成员为blist.Text
- Parent = f
- };
- var buttonAdd = new Button() //添加按钮
- {
- Text = "Add",
- Location = new Point(10, f.Height - 75),
- Parent = f
- };
- var buttonRemove = new Button() //删除按钮
- {
- Text = "Remove",
- Location = new Point(110, f.Height - 75),
- Parent = f
- };
- buttonAdd.Click += delegate //添加按钮的单击事件
- {
- blist.Add(new mydata()
- {
- Text = System.IO.Path.GetRandomFileName(),
- Time = DateTime.Now
- });
- };
- buttonRemove.Click += delegate //删除按钮的单击事件
- {
- if (listbox.SelectedItem != null)
- {
- var selected = (mydata)listbox.SelectedItem;
- blist.Remove(selected);
- }
- };
- f.KeyUp += delegate(object sernder, KeyEventArgs e)
- {
- if (Keys.F1 == e.KeyCode)
- {
- System.Diagnostics.Process.Start("http://hi.baidu.com/wingingbob/blog/item/7c2ffb343f026dbfd0a2d391.html");
- }
- };
- Application.Run(f);
- }
- struct mydata //我的数据类型
- {
- public string Text { get; set; }
- public DateTime Time { get; set; }
- }
- }
- }