C# - 使用自定义控件实现名单修改的比较功能

 2023-09-05 阅读 43 评论 0

摘要:一、写在前面 在工作中需要实现一个场景,有一个名单类的数据需要维护,这个维护工作需要有一个复核功能,为了方便复核时对名单变更情况有一个良好的掌握,需要做一个便跟前后名单的对比功能。 功能实现后效果如下图: 其中,修改前名单

一、写在前面

在工作中需要实现一个场景,有一个名单类的数据需要维护,这个维护工作需要有一个复核功能,为了方便复核时对名单变更情况有一个良好的掌握,需要做一个便跟前后名单的对比功能。

功能实现后效果如下图:

194019_h561_1425762.png

其中,修改前名单、修改后名单、前后名单对比三个部分都使用了封装后的ListView控件保存数据

二、步骤一:封装ListView

封装ListView主要是为了保证对“前后名单对比”部分数据的着色。虽然微软原生的ListView就支持了对数据项进行着色,但因为“前后名单对比”部分使用了分组功能,点击分组标题时,默认选中分组内的全部数据,这回导致分组内的数据颜色都变为黑色。为改变这一情况,我们需要创建一个继承ListView的类ListViewEnhanced,代码如下:

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+ onlyLVHT_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 workif (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 structinfo.pt = LParamToPoint(m.LParam);//if the click is on the group header, exit, otherwise send messageif (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,如下图所示:

195946_ntWF_1425762.png

三个ListView的View属性,都要设置成System.Windows.Forms.View.Details

MemberComparer控件的代码如下:

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 postMemberswhere !(from member2 in preMemberswhere member1.MemCode == member2.MemCodeselect 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 preMemberswhere !(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 preMembersfrom member2 in postMemberswhere member1.MemCode == member2.MemCodeselect 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控件的几个坑)

1、ListView最左侧的列,是无法设置对齐规则的,因此我将它设置成一个长度为0的列(即上面代码中的“僵尸列”),这一列的长度在ColumnWidthChanging事件中被指定为不能通过鼠标拉伸。对于用户而言,可以认为这一列是没有的。

2、将ListView的Scrollable设置成true后,可以做到列被拉伸超过ListView时会下方会自动出现滚动条,但如果窗口被打开时列的总长度就已经超出ListView的显示范围,滚动条则不会默认出现,而是需要拉动一下列才能出现。因此在代码中,需要找一列做一下调整,就像下面这行代码做的这样,这行代码看上去没有什么意义,却是为了规避ListView的一个缺陷。

//为ListView添加横向滚动条
chCmpMemTitle.Width = 110;

3、对ListView内数据进行分组后,单击分组标题会全选该组下数据,如果对该组下数据进行了着色,则着色会消失,这个问题需要重写ListView的WndProc方法,在第二节已有描述。

4、使用ListView内的BeginUpdate和EndUpdate函数可有效避免因不断刷新控件导致显示器上内容的闪动。

5、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);}}
}

这段代码运行的效果,和本文一开始的那张图片是一样的。

六、参考资料

1、ListView的使用方法,我参考了这篇博客

http://blog.csdn.net/xiaohan2826/article/details/8603015

2、禁用ListView下分组标题栏的单击全选功能,我参考了下面的资料

http://stackoverflow.com/questions/10532039/listview-group-header-click-disable-select-all-in-windows-7

3、另一个资料是为分组标题栏添加单击事件的,资料2的答题者就参考了它

https://social.msdn.microsoft.com/Forums/windows/en-US/eccdf58a-dd06-4ae3-908d-5f5863c01d64/listviewgroupclicked-event?forum=winforms

七、附言

1、我个人感觉.NET中的ListView控件并不好驾驭,不是因为使用它的规则有多难,而是因为这个控件的坑比较多

2、本文中程序的一个DEMO可以在这个地址下载到:http://pan.baidu.com/s/1qWRVrac

3、我的Windows版本为Win7旗舰版,VS版本为2012,编译目标框架为.NET Framework 4

END

转载于:https://my.oschina.net/Tsybius2014/blog/610051

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/1093.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息