在工做中须要实现一个场景,有一个名单类的数据须要维护,这个维护工做须要有一个复核功能,为了方便复核时对名单变动状况有一个良好的掌握,须要作一个便跟先后名单的对比功能。c#
功能实现后效果以下图:
windows
其中,修改前名单、修改后名单、先后名单对比三个部分都使用了封装后的ListView控件保存数据
框架
封装ListView主要是为了保证对“先后名单对比”部分数据的着色。虽然微软原生的ListView就支持了对数据项进行着色,但由于“先后名单对比”部分使用了分组功能,点击分组标题时,默认选中分组内的所有数据,这回致使分组内的数据颜色都变为黑色。为改变这一状况,咱们须要建立一个继承ListView的类ListViewEnhanced,代码以下:ide
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; namespace NameListComparer { class ListViewEnhanced : ListView { /// <summary> /// call SendMessage using hit test structures /// </summary> [DllImport("User32.dll")] static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref LVHITTESTINFO lParam); #region Windows constants /// <summary> /// WndProc message for the left mouse button down /// </summary> const int WM_LBUTTONUP = 0x0201; /// <summary> /// offset for the first SendMessage for a ListView /// </summary> const int LVM_FIRST = 0x1000; /// <summary> /// ListView SendMessage to check for an item hit test /// </summary> const int LVM_HITTEST = (LVM_FIRST + 18); /// <summary> /// ListView SendMessage to check for a sub-item hit test /// </summary> const int LVM_SUBITEMHITTEST = (LVM_FIRST + 57); #endregion Windows constants /// <summary> /// see http://msdn.microsoft.com/en-us/library/bb774754%28v=VS.85%29.aspx /// </summary> [Flags] internal enum LVHITTESTFLAGS : uint { LVHT_NOWHERE = 0x00000001, LVHT_ONITEMICON = 0x00000002, LVHT_ONITEMLABEL = 0x00000004, LVHT_ONITEMSTATEICON = 0x00000008, LVHT_ONITEM = (LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON), LVHT_ABOVE = 0x00000008, LVHT_BELOW = 0x00000010, LVHT_TORIGHT = 0x00000020, LVHT_TOLEFT = 0x00000040, // Vista/Win7+ only LVHT_EX_GROUP_HEADER = 0x10000000, LVHT_EX_GROUP_FOOTER = 0x20000000, LVHT_EX_GROUP_COLLAPSE = 0x40000000, LVHT_EX_GROUP_BACKGROUND = 0x80000000, LVHT_EX_GROUP_STATEICON = 0x01000000, LVHT_EX_GROUP_SUBSETLINK = 0x02000000, } /// <summary> /// see http://msdn.microsoft.com/en-us/library/bb774754%28v=VS.85%29.aspx /// </summary> [StructLayout(LayoutKind.Sequential)] struct LVHITTESTINFO { public POINT pt; public LVHITTESTFLAGS flags; public int iItem; public int iSubItem; // Vista/Win7+ public int iGroup; } /// <summary> /// see http://msdn.microsoft.com/en-us/library/dd162805%28v=VS.85%29.aspx /// </summary> [StructLayout(LayoutKind.Sequential)] struct POINT { public POINT(int x, int y) { this.x = x; this.y = y; } public int x; public int y; } /// <summary> /// convert the IntPtr LParam to an Point. /// </summary> private static POINT LParamToPoint(IntPtr lparam) { return new POINT(lparam.ToInt32() & 0xFFFF, lparam.ToInt32() >> 16); } protected override void WndProc(ref Message m) { //the link uses WM_LBUTTONDOWN but I found that it doesn't work if (m.Msg == WM_LBUTTONUP) { LVHITTESTINFO info = new LVHITTESTINFO(); //The LParamToPOINT function I adapted to not bother with // converting to System.Drawing.Point, rather I just made // its return type the POINT struct info.pt = LParamToPoint(m.LParam); //if the click is on the group header, exit, otherwise send message if (SendMessage(this.Handle, LVM_SUBITEMHITTEST, -1, ref info) != -1) if ((info.flags & LVHITTESTFLAGS.LVHT_EX_GROUP_HEADER) != 0) return; //* } base.WndProc(ref m); } } }
存放单个名单的对象,可根据业务系统自身状况量身定制,下面代码是一个我实现的MemberInfo类,包含成员编码、成员名称、成员称号三个属性:
函数
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NameListComparer { public class MemberInfo { /// <summary> /// 成员信息 /// </summary> /// <param name="memCode">成员编码</param> /// <param name="memName">成员名称</param> /// <param name="memTitle">成员称号</param> public MemberInfo(string memCode, string memName, string memTitle = "") { this.MemCode = memCode; this.MemName = memName; this.MemTitle = memTitle; } /// <summary> /// 编号 /// </summary> private string _memCode; /// <summary> /// 编号 /// </summary> public string MemCode { get { return _memCode; } set { _memCode = value; } } /// <summary> /// 编号 /// </summary> private string _memName; /// <summary> /// 编号 /// </summary> public string MemName { get { return _memName; } set { _memName = value; } } /// <summary> /// 称号 /// </summary> private string _memTitle; /// <summary> /// 称号 /// </summary> public string MemTitle { get { return _memTitle; } set { _memTitle = value; } } } }
建立一个继承UserControl的自定义控件MemberComparer,以下图所示:
post
三个ListView的View属性,都要设置成System.Windows.Forms.View.Detailsui
MemberComparer控件的代码以下:this
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Collections; namespace NameListComparer { /// <summary> /// 自定义控件:用于比较修改前和修改后的名单 /// </summary> public partial class MemberComparer : UserControl { /// <summary> /// 自定义控件:用于比较修改前和修改后的名单 /// </summary> public MemberComparer() { InitializeComponent(); } /// <summary> /// Load函数 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MemberComparer_Load(object sender, EventArgs e) { //建立最左侧僵尸列(必须) ColumnHeader chPreZombie = new ColumnHeader(); chPreZombie.Name = "zombie"; chPreZombie.Text = ""; chPreZombie.Width = 0; chPreZombie.TextAlign = HorizontalAlignment.Center; lvwPreData.Columns.Add(chPreZombie); //成员编码 ColumnHeader chPreMemCode = new ColumnHeader(); chPreMemCode.Text = "成员编码"; chPreMemCode.Width = 100; chPreMemCode.TextAlign = HorizontalAlignment.Center; lvwPreData.Columns.Add(chPreMemCode); //成员名称 ColumnHeader chPreMemName = new ColumnHeader(); chPreMemName.Text = "成员名称"; chPreMemName.Width = 100; chPreMemName.TextAlign = HorizontalAlignment.Center; lvwPreData.Columns.Add(chPreMemName); ColumnHeader chPreMemTitle = new ColumnHeader(); //成员称号 chPreMemTitle.Text = "成员称号"; chPreMemTitle.Width = 100; chPreMemTitle.TextAlign = HorizontalAlignment.Center; lvwPreData.Columns.Add(chPreMemTitle); //为ListView添加横向滚动条 chPreMemTitle.Width = 110; //你没看错,这个功能就是这么写的 //指定排序规则 lvwPreData.ListViewItemSorter = new ListViewItemComparer(1); //僵尸列不容许拖动 lvwPreData.ColumnWidthChanging += (obj, arg) => { ColumnHeader header = lvwPreData.Columns[arg.ColumnIndex]; if (header.Name == "zombie") { arg.Cancel = true; } arg.NewWidth = lvwPreData.Columns[arg.ColumnIndex].Width; }; //建立最左侧僵尸列(必须) ColumnHeader chPostZombie = new ColumnHeader(); chPostZombie.Name = "zombie"; chPostZombie.Text = ""; chPostZombie.Width = 0; chPostZombie.TextAlign = HorizontalAlignment.Center; lvwPostData.Columns.Add(chPostZombie); //成员编码 ColumnHeader chPostMemCode = new ColumnHeader(); chPostMemCode.Text = "成员编码"; chPostMemCode.Width = 100; chPostMemCode.TextAlign = HorizontalAlignment.Center; lvwPostData.Columns.Add(chPostMemCode); //成员名称 ColumnHeader chPostMemName = new ColumnHeader(); chPostMemName.Text = "成员名称"; chPostMemName.Width = 100; chPostMemName.TextAlign = HorizontalAlignment.Center; lvwPostData.Columns.Add(chPostMemName); ColumnHeader chPostMemTitle = new ColumnHeader(); //成员称号 chPostMemTitle.Text = "成员称号"; chPostMemTitle.Width = 100; chPostMemTitle.TextAlign = HorizontalAlignment.Center; lvwPostData.Columns.Add(chPostMemTitle); //为ListView添加横向滚动条 chPostMemTitle.Width = 110; //你没看错,这个功能就是这么写的 //指定排序规则 lvwPostData.ListViewItemSorter = new ListViewItemComparer(1); //僵尸列不容许拖动 lvwPostData.ColumnWidthChanging += (obj, arg) => { ColumnHeader header = lvwPostData.Columns[arg.ColumnIndex]; if (header.Name == "zombie") { arg.Cancel = true; } arg.NewWidth = lvwPostData.Columns[arg.ColumnIndex].Width; }; //建立最左侧僵尸列(必须) ColumnHeader chCmpZombie = new ColumnHeader(); chCmpZombie.Name = "zombie"; chCmpZombie.Text = ""; chCmpZombie.Width = 0; chCmpZombie.TextAlign = HorizontalAlignment.Center; lvwCmpData.Columns.Add(chCmpZombie); //成员编码 ColumnHeader chCmpMemCode = new ColumnHeader(); chCmpMemCode.Text = "成员编码"; chCmpMemCode.Width = 100; chCmpMemCode.TextAlign = HorizontalAlignment.Center; lvwCmpData.Columns.Add(chCmpMemCode); //成员名称 ColumnHeader chCmpMemName = new ColumnHeader(); chCmpMemName.Text = "成员名称"; chCmpMemName.Width = 100; chCmpMemName.TextAlign = HorizontalAlignment.Center; lvwCmpData.Columns.Add(chCmpMemName); ColumnHeader chCmpMemTitle = new ColumnHeader(); //成员称号 chCmpMemTitle.Text = "成员称号"; chCmpMemTitle.Width = 100; chCmpMemTitle.TextAlign = HorizontalAlignment.Center; lvwCmpData.Columns.Add(chCmpMemTitle); //为ListView添加横向滚动条 chCmpMemTitle.Width = 110; //你没看错,这个功能就是这么写的 //指定排序规则 lvwCmpData.ListViewItemSorter = new ListViewItemComparer(1); //僵尸列不容许拖动 lvwCmpData.ColumnWidthChanging += (obj, arg) => { ColumnHeader header = lvwCmpData.Columns[arg.ColumnIndex]; if (header.Name == "zombie") { arg.Cancel = true; } arg.NewWidth = lvwCmpData.Columns[arg.ColumnIndex].Width; }; } /// <summary> /// ListView比较规则 /// </summary> class ListViewItemComparer : IComparer { /// <summary> /// 按第几列进行比较(首列为第0列) /// </summary> private int col; /// <summary> /// ListView比较规则,默认以第0列比较 /// </summary> public ListViewItemComparer() { col = 0; } /// <summary> /// ListView比较规则,指定以第几列比较 /// </summary> /// <param name="column"></param> public ListViewItemComparer(int column) { col = column; } /// <summary> /// 比较函数 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> public int Compare(object x, object y) { return String.Compare(((ListViewItem)x).SubItems[col].Text, ((ListViewItem)y).SubItems[col].Text); } } /// <summary> /// 清空全部数据 /// </summary> public void EmptyAllData() { lvwPreData.Items.Clear(); lvwPostData.Items.Clear(); lvwCmpData.Items.Clear(); lvwCmpData.Groups.Clear(); } /// <summary> /// 初始化名单数据 /// </summary> /// <param name="preMembers"></param> /// <param name="postMembers"></param> public void SetMemberData(MemberInfo[] preMembers, MemberInfo[] postMembers) { EmptyAllData(); //数据更新,UI暂时挂起 this.lvwPreData.BeginUpdate(); for (int i = 0; i < preMembers.Length; i++) //添加10行数据 { if (preMembers[i] != null) { ListViewItem lvi = new ListViewItem(); lvi.SubItems.Add(preMembers[i].MemCode); lvi.SubItems.Add(preMembers[i].MemName); lvi.SubItems.Add(preMembers[i].MemTitle); this.lvwPreData.Items.Add(lvi); } } //结束数据处理,UI界面一次性绘制 this.lvwPreData.EndUpdate(); //数据更新,UI暂时挂起 this.lvwPostData.BeginUpdate(); for (int i = 0; i < postMembers.Length; i++) //添加10行数据 { if (postMembers[i] != null) { ListViewItem lvi = new ListViewItem(); lvi.SubItems.Add(postMembers[i].MemCode); lvi.SubItems.Add(postMembers[i].MemName); lvi.SubItems.Add(postMembers[i].MemTitle); this.lvwPostData.Items.Add(lvi); } } //结束数据处理,UI界面一次性绘制 this.lvwPostData.EndUpdate(); //数据更新,UI暂时挂起 this.lvwCmpData.BeginUpdate(); //添加分组 ListViewGroup lvgAdd = new ListViewGroup(); lvgAdd.Header = "新加入成员"; lvgAdd.Name = "add"; lvgAdd.HeaderAlignment = HorizontalAlignment.Left; ListViewGroup lvgDelete = new ListViewGroup(); lvgDelete.Header = "已删除成员"; lvgDelete.Name = "delete"; lvgDelete.HeaderAlignment = HorizontalAlignment.Left; ListViewGroup lvgNoChange = new ListViewGroup(); lvgNoChange.Header = "未变更成员"; lvgNoChange.Name = "nochange"; lvgNoChange.HeaderAlignment = HorizontalAlignment.Left; lvwCmpData.Groups.Add(lvgAdd); lvwCmpData.Groups.Add(lvgDelete); lvwCmpData.Groups.Add(lvgNoChange); lvwCmpData.ShowGroups = true; //显示分组 //新增长成员 IEnumerable<MemberInfo> memberAdd = from member1 in postMembers where !(from member2 in preMembers where member1.MemCode == member2.MemCode select member2).Any() select member1; foreach (MemberInfo member in memberAdd) { ListViewItem lvi = new ListViewItem(); lvi.ForeColor = Color.Blue; lvi.SubItems.Add(member.MemCode); lvi.SubItems.Add(member.MemName); lvi.SubItems.Add(member.MemTitle); lvgAdd.Items.Add(lvi); this.lvwCmpData.Items.Add(lvi); } //已删除成员 IEnumerable<MemberInfo> memberDelete = from member1 in preMembers where !(from member2 in postMembers where member1.MemCode == member2.MemCode select member2).Any() select member1; foreach (MemberInfo member in memberDelete) { ListViewItem lvi = new ListViewItem(); lvi.ForeColor = Color.Red; lvi.SubItems.Add(member.MemCode); lvi.SubItems.Add(member.MemName); lvi.SubItems.Add(member.MemTitle); lvgDelete.Items.Add(lvi); this.lvwCmpData.Items.Add(lvi); } //未变更成员 IEnumerable<MemberInfo> memberNoChange = from member1 in preMembers from member2 in postMembers where member1.MemCode == member2.MemCode select member1; foreach (MemberInfo member in memberNoChange) { ListViewItem lvi = new ListViewItem(); lvi.ForeColor = Color.Black; lvi.SubItems.Add(member.MemCode); lvi.SubItems.Add(member.MemName); lvi.SubItems.Add(member.MemTitle); lvgNoChange.Items.Add(lvi); this.lvwCmpData.Items.Add(lvi); } //结束数据处理,UI界面一次性绘制 this.lvwCmpData.EndUpdate(); } } }
本段代码有以下几点须要注意(能够理解为ListView控件的几个坑)编码
一、ListView最左侧的列,是没法设置对齐规则的,所以我将它设置成一个长度为0的列(即上面代码中的“僵尸列”),这一列的长度在ColumnWidthChanging事件中被指定为不能经过鼠标拉伸。对于用户而言,能够认为这一列是没有的。spa
二、将ListView的Scrollable设置成true后,能够作到列被拉伸超过ListView时会下方会自动出现滚动条,但若是窗口被打开时列的总长度就已经超出ListView的显示范围,滚动条则不会默认出现,而是须要拉动一下列才能出现。所以在代码中,须要找一列作一下调整,就像下面这行代码作的这样,这行代码看上去没有什么意义,倒是为了规避ListView的一个缺陷。
//为ListView添加横向滚动条 chCmpMemTitle.Width = 110;
三、对ListView内数据进行分组后,单击分组标题会全选该组下数据,若是对该组下数据进行了着色,则着色会消失,这个问题须要重写ListView的WndProc方法,在第二节已有描述。
四、使用ListView内的BeginUpdate和EndUpdate函数可有效避免因不断刷新控件致使显示器上内容的闪动。
五、ListView的排序规则需本身指定,需实现一个继承自System.Collections.IComparer的类,放到ListView的ListViewItemSorter属性下。
在FormMain中添加一个Dock为Fill的控件MemberComparer,实现FormMain的Load函数,代码以下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace NameListComparer { public partial class FormMain : Form { public FormMain() { InitializeComponent(); } private void FormMain_Load(object sender, EventArgs e) { MemberInfo[] preMemberInfo = new MemberInfo[] { new MemberInfo("10001", "王伦", "白衣秀士"), new MemberInfo("10005", "吴用", "智多星"), new MemberInfo("10003", "宋江", "及时雨"), new MemberInfo("10004", "卢俊义", "玉麒麟"), new MemberInfo("10002", "晁盖", "托塔天王"), new MemberInfo("10007", "花荣", "小李广") }; MemberInfo[] postMemberInfo = new MemberInfo[] { new MemberInfo("10003", "宋江", "及时雨"), new MemberInfo("10004", "卢俊义", "玉麒麟"), new MemberInfo("10008", "张顺", "浪里白条"), new MemberInfo("10009", "周通", "小霸王"), new MemberInfo("10010", "时迁", "鼓上蚤"), new MemberInfo("10005", "吴用", "智多星"), new MemberInfo("10006", "林冲", "豹子头"), new MemberInfo("10007", "花荣", "小李广") }; cmpMembers.SetMemberData(preMemberInfo, postMemberInfo); } } }
这段代码运行的效果,和本文一开始的那张图片是同样的。
一、ListView的使用方法,我参考了这篇博客
http://blog.csdn.net/xiaohan2826/article/details/8603015
二、禁用ListView下分组标题栏的单击全选功能,我参考了下面的资料
三、另外一个资料是为分组标题栏添加单击事件的,资料2的答题者就参考了它
一、我我的感受.NET中的ListView控件并很差驾驭,不是由于使用它的规则有多难,而是由于这个控件的坑比较多
二、本文中程序的一个DEMO能够在这个地址下载到:http://pan.baidu.com/s/1qWRVrac
三、个人Windows版本为Win7旗舰版,VS版本为2012,编译目标框架为.NET Framework 4
END