ComboBox 智能过滤,模糊匹配,拼音首字母匹配

在一个项目总ComboBox中有很多项目,用户查找非常麻烦,系统自带的快速定位是匹配首字母,使用起来非常不方便。网上找了很多,都是基于Items.Add()的方式,这种方式不支持数据源的键值对应,只有自己写一个了,看看效果先!

看起来不错,允许从任意位置匹配项目,而且不需要是连续的,最重要的是支持汉语拼音首字母匹配。

实现原理:将数据源备份到变量DataSource2,在TextChanged事件中循环DataSource2,找到匹配后添加到临时表,循环结束后将临时表绑定到DataSource。注意,在绑定中不要修改DataSource,这样速度无法忍受。

该方法使用DataSource2作为原数据源的引用,DataSource只是过滤后的副本,好了,废话少说,看代码

 

 

using System;
using System.Text;

namespace Socg
{
	public class ComboBox:System.Windows.Forms.ComboBox
	{
		#region public bool CleverFilter----智能过滤
		/// <summary>
		/// 智能过滤时使用
		/// </summary>
		public object DataSource2=null;
		/// <summary>
		/// 避免绑定数据源后导致的递归调用
		/// </summary>
		private bool lock_文本改变=false;
		//如果中下拉菜单中选择项目,事件顺序为SelectionChangeCommitted->TextChanged->SelectedValue
		private bool lock_点选列表项=false;

		private bool cleverFilter=false;
		/// <summary>
		/// 使ComboBox有智能过滤功能。在输入文本时会自动根据输入的内容进行匹配。这种匹配不需从首字母开始,且可以“间隔匹配”,还支持拼音首字母匹配。
		/// 例如:“以太网交换机”用“以交换”、“y交换”、“以jh”、“y交h”可以匹配(包括空字符串),用“已交换”,“以换交”不能匹配。
		/// 实现方法是增加变量“DataSource2”来存储原始数据源。输入字符后搜索“DataSource2”,找到匹配后添加到临时DataTable,最后将临时DataTable赋值到DataSource属性达到过滤效果。
		/// 需要注意的是从DataSource的到的数据源已经不是原始的数据源。回收时需要注意数据源副本的回收。为此,代码规定了启用“智能过滤”前必须手动设置DataSource2。停用“DataSource2”时必须先使DataSource2=null,否则会抛出异常。
		/// </summary>
		public bool CleverFilter
		{
			get
			{
				return cleverFilter;
			}
			set
			{
				if(CleverFilter!=value)
				{
					cleverFilter=value;
					if(value==true)
					{
						if(DataSource2==null)
						{
							throw new Exception("启用“智能过滤”时必须主动设置DataSource2=DataSource,设置成功之后DataSource将变为副本,真正原始的数据源在DataSource2");
						}
						this.TextChanged+=new EventHandler(ComboBox_TextChanged);
						this.SelectedValueChanged+=new EventHandler(ComboBox_SelectedValueChanged);
						this.SelectionChangeCommitted+=new EventHandler(ComboBox_SelectionChangeCommitted);

					}
					else
					{
						if(DataSource2!=null)
						{
							throw new Exception("取消“智能过滤”时必须主动设置DataSource2为null,它是真正的数据源,这是应将DataSource=DataSource2");
						}
						this.TextChanged-=new EventHandler(ComboBox_TextChanged);
						this.SelectedValueChanged-=new EventHandler(ComboBox_SelectedValueChanged);
						this.SelectionChangeCommitted-=new EventHandler(ComboBox_SelectionChangeCommitted);
					}
				}
			}
		}
		#endregion
		void ComboBox_SelectionChangeCommitted(object sender,EventArgs e)
		{
			lock_点选列表项=true;
		}

		void ComboBox_SelectedValueChanged(object sender,EventArgs e)
		{
			lock_点选列表项=false;
		}
		#region void ComboBox_TextChanged(object sender,EventArgs e)
		void ComboBox_TextChanged(object sender,EventArgs e)
		{
			if(this.CleverFilter==true)
			{
				if(lock_文本改变==true)
				{
					return;
				}
				if(lock_点选列表项==true)
				{
					return;
				}
				if(this.Enabled==false||this.Visible==false||this.Focused==false)
				{
					return;
				}
				try
				{
					lock_文本改变=true;
					System.Data.DataTable tempDataTable=null;
					System.Windows.Forms.Application.DoEvents();//输入中文的一个词会先送进来一个汉字,导致读取的值是第一个汉字,加上这一句可以让汉字一次性全部接收
					string text=this.Text;

					#region 查找匹配项放入临时表
					if(DataSource2 is System.Data.DataView)
					{
						System.Data.DataView v=(System.Data.DataView)DataSource2;
						tempDataTable=v.Table.Clone();
						for(int i=0;i<v.Count;i++)
						{
							if(Socg.Common.CleverMatch(v[i][this.DisplayMember].ToString(),text,false,true,true)==true)
							{
								tempDataTable.Rows.Add(v[i].Row.ItemArray);
							}
						}
					}
					else if(DataSource2 is System.Data.DataTable)
					{
						System.Data.DataTable v=(System.Data.DataTable)DataSource2;
						tempDataTable=v.Clone();
						for(int i=0;i<v.Rows.Count;i++)
						{
							if(Socg.Common.CleverMatch(v.Rows[i][this.DisplayMember].ToString(),text,false,true,true)==true)
							{
								tempDataTable.Rows.Add(v.Rows[i].ItemArray);
							}
						}
					}
					else
					{
						throw new Exception("目前只支持DataTable和DataView");
					}
					#endregion

					this.DroppedDown=false;//关闭下拉列表
					object tempTable=this.DataSource;
					this.DataSource=tempDataTable;

					#region 释放旧数据源
					if(tempTable!=null)
					{
						if(tempTable!=DataSource2)//不要把原始数据源释放了
						{
							if(tempTable is System.Data.DataTable)
							{
								((System.Data.DataTable)tempTable).Dispose();
							}
							else if(tempTable is System.Data.DataView)
							{
								((System.Data.DataView)tempTable).Dispose();
							}
						}
					}
					#endregion
					this.DroppedDown=true;//打开下拉列表
					System.Windows.Forms.Application.DoEvents();//处理其它消息循环,在ComboBox外部实现是可省略此代码。继承方式实现需加这一句
					this.Text=text;
					this.SelectionStart=text.Length;
					//打开下拉列表后系统会自动隐藏鼠标指针,下面两句为了显示指针
					System.Windows.Forms.Cursor.Current=System.Windows.Forms.Cursors.Default;
					System.Windows.Forms.Cursor.Show();

				}
				finally
				{
					lock_文本改变=false;
				}
			}
		}
		#endregion
	}
}


//一下是相关函数

#region public static char GetChineseSpell()----提取汉字的拼音首字母
/// <summary>
/// 将Asc码转换为拼音首字母。
/// 如果 Asc 小于256,则原样返回
/// 如果 转换失败,则返回"*"。如:中文标点符号
/// 速度:25,000,000次/秒
/// </summary>
/// <param name="ChineseAsc">字符的Asc码</param>
/// <returns>拼音首字母</returns>
public static char GetChineseSpell(int ChineseAsc)
{
	if(ChineseAsc < 256)
		return (char)ChineseAsc;
	else if(ChineseAsc >= 45217 && ChineseAsc < 45253)
		return 'A';
	else if(ChineseAsc >= 45253 && ChineseAsc < 45761)
		return 'B';
	else if(ChineseAsc >= 45761 && ChineseAsc < 46318)
		return 'C';
	else if(ChineseAsc >= 46318 && ChineseAsc < 46826)
		return 'D';
	else if(ChineseAsc >= 46826 && ChineseAsc < 47010)
		return 'E';
	else if(ChineseAsc >= 47010 && ChineseAsc < 47297)
		return 'F';
	else if(ChineseAsc >= 47297 && ChineseAsc < 47614)
		return 'G';
	else if(ChineseAsc >= 47614 && ChineseAsc < 48119)
		return 'H';
	//				else if(ChineseAsc>=48119&&ChineseAsc<48119)return 'I';
	else if(ChineseAsc >= 48119 && ChineseAsc < 49062)
		return 'J';
	else if(ChineseAsc >= 49062 && ChineseAsc < 49324)
		return 'K';
	else if(ChineseAsc >= 49324 && ChineseAsc < 49896)
		return 'L';
	else if(ChineseAsc >= 49896 && ChineseAsc < 50371)
		return 'M';
	else if(ChineseAsc >= 50371 && ChineseAsc < 50614)
		return 'N';
	else if(ChineseAsc >= 50614 && ChineseAsc < 50622)
		return 'O';
	else if(ChineseAsc >= 50622 && ChineseAsc < 50906)
		return 'P';
	else if(ChineseAsc >= 50906 && ChineseAsc < 51387)
		return 'Q';
	else if(ChineseAsc >= 51387 && ChineseAsc < 51446)
		return 'R';
	else if(ChineseAsc >= 51446 && ChineseAsc < 52218)
		return 'S';
	else if(ChineseAsc >= 52218 && ChineseAsc < 52698)
		return 'T';
	//				else if(ChineseAsc>=52698&&ChineseAsc<52698)return 'U';
	//				else if(ChineseAsc>=52698&&ChineseAsc<52698)return 'V';
	else if(ChineseAsc >= 52698 && ChineseAsc < 52980)
		return 'W';
	else if(ChineseAsc >= 52980 && ChineseAsc < 53689)
		return 'X';
	else if(ChineseAsc >= 53689 && ChineseAsc < 54481)
		return 'Y';
	else if(ChineseAsc >= 54481)
		return 'Z';
	else
		return '*';
}
/// <summary>
/// 将汉字字符转换为拼音首字母。
/// 如果 Asc 小于256,则原样返回
/// 如果 转换失败,则返回"*"。如:中文标点符号
/// 速度:1,500,000次/秒
/// </summary>
/// <param name="ChineseChar">要转换的字符</param>
/// <returns>拼音首字母</returns>
public static char GetChineseSpell(char ChineseChar)
{
	int ChineseAsc;
	byte[] ChineseByte =System.Text.Encoding.Default.GetBytes(ChineseChar.ToString());

	if(ChineseByte.Length < 2)
	{
		ChineseAsc = ChineseByte[0];
	}
	else
	{
		ChineseAsc = (ChineseByte[0] << 8) + ChineseByte[1];
	}
	return GetChineseSpell(ChineseAsc);
}
/// <summary>
/// 将包含汉字的字符串转换为拼音首字母。
/// 如果 Asc 小于256,则原样返回
/// 如果 转换失败,则返回"*"。如:中文标点符号
/// 速度:650000字/秒
/// </summary>
/// <param name="ChineseString">要转换的字符串</param>
/// <returns>拼音首字母组成的字符串</returns>
public static string GetChineseSpell(string ChineseString)
{
	int count = ChineseString.Length;
	System.Text.StringBuilder returnString = new System.Text.StringBuilder(count);
	for(int i = 0;i < count;i++)
	{
		returnString.Append(GetChineseSpell(ChineseString[i]));
	}
	return returnString.ToString();
}
#endregion

#region public static bool CleverMatch(string source,string part,bool ignoreCase,bool chineseSpell,bool continuous)----智能匹配
/// <summary>
/// 智能匹配
/// 比较一个字符串是否是另一个字符串的“子串”,支持智能模式:例如 “以太网交换机”用“以交换”、“y交换”、“以jh”、“y交h”可以匹配(包括空字符串),用“已交换”,“以换交”不能匹配
/// </summary>
/// <param name="source">源字符串</param>
/// <param name="part">source的子串</param>
/// <param name="ignoreCase">是否区分大小写</param>
/// <param name="chineseSpell">是否启用拼音匹配。为true时忽略 ignoreCase 参数</param>
/// <param name="continuous">是否必须连续匹配</param>
/// <returns>如果part属于source的一部分,则返回true;否则返回false</returns>
public static bool CleverMatch(string source,string part,bool ignoreCase,bool chineseSpell,bool continuous)
{
	//*********************算法***************************
	//1、循环 partString 中的字符,查找这些字符在 sourceString 中是否存在,只要有一个不存在则认为不匹配。
	//2、在检查字符在 sourceString 中是否存在时不能搜索整个区域,只搜索上次匹配之后的部分。这样是为了满足逐一匹配。例如:sf与assf匹配,而fs则不匹配。
	//3、如果启用拼音首字母选项。则在比较时需判断partString中的字符是否是因为,如果是英文,则与sourceString的拼音形式进行比较。注意这是忽略大小写的。
	//4、如果启用连续匹配,则需要判断本次匹配的位置是否与上次匹配的位置之间无间隔。如果有间隔,则需要重新循环 partString,从sourceString中合适的位置之后开始比较。具体做法:
	//		记录 partString 中第一个匹配的位置,如果发现不连续,则以这个位置作起点 重新循环partString做比较。
	//		记录上次配置的位置,本次匹配后判断与上次是否连续。第一个字符匹配时不需要做比较,因为没有“上一次”
	//****************************************************

	int k=-1;//存储找到单字母匹配的位置
	int j=-1;//存储partString中第一个字符匹配的位置,如果后面的字符不匹配了,再中这个字符之后搜索。
	int temp=-1;//存储上次匹配的位置,与k做比较,用于判断是否连续匹配。如果搜索的是第一个字符,则不需要比较是否连续
	string sourceString2=null;
	for(int i=0;i<part.Length;i++)
	{
		temp=k;
		if(chineseSpell==true)
		{
			if((int)part[i]>255)//中文使用中文来匹配
			{
				k=source.IndexOf(part[i],k+1);
				if(k<0)//有一个字符没匹配直接返回
				{
					return false;
				}
				else//找到匹配
				{
					#region 检查本次匹配是否与上次匹配的位置连续,如果不连续,则从第一个匹配字符的下一个字符开始搜索
					if(continuous==true)
					{
						if(i==0)//第一个字符已经匹配,记录这个位置
						{
							j=k;
						}
						else
						{
							if(k-temp!=1)//不是连续匹配
							{
								i=-1;//回到第一个字符开始搜索
								k=j;//从这个位置后开始搜索(搜索使用的是k+1)
							}
						}
					}
					#endregion
				}
			}
			else//发现字母时,将“源”变换为拼音首字母再来匹配
			{
				if(sourceString2==null)
				{
					sourceString2=Socg.Common.GetChineseSpell(source);
				}
				k=sourceString2.IndexOf(part[i].ToString(),k+1,System.StringComparison.CurrentCultureIgnoreCase);
				if(k<0)
				{
					return false;
				}
				else
				{
					#region 检查本次匹配是否与上次匹配的位置连续,如果不连续,则从第一个匹配字符的下一个字符开始搜索
					if(continuous==true)
					{
						if(i==0)//第一个字符已经匹配,记录这个位置
						{
							j=k;
						}
						else
						{
							if(k-temp!=1)//不是连续匹配
							{
								i=-1;//回到第一个字符开始搜索
								k=j;//从这个位置后开始搜索(搜索使用的是k+1)
							}
						}
					}
					#endregion
				}
			}
		}
		else
		{
			if(ignoreCase==true)
			{
				k=source.IndexOf(part[i].ToString(),k+1,System.StringComparison.CurrentCultureIgnoreCase);
				if(k<0)
				{
					return false;
				}
				else
				{
					#region 检查本次匹配是否与上次匹配的位置连续,如果不连续,则从第一个匹配字符的下一个字符开始搜索
					if(continuous==true)
					{
						if(i==0)//第一个字符已经匹配,记录这个位置
						{
							j=k;
						}
						else
						{
							if(k-temp!=1)//不是连续匹配
							{
								i=-1;//回到第一个字符开始搜索
								k=j;//从这个位置后开始搜索(搜索使用的是k+1)
							}
						}
					}
					#endregion
				}
			}
			else
			{
				k=source.IndexOf(part[i].ToString(),k+1);
				if(k<0)
				{
					return false;
				}
				else
				{
					#region 检查本次匹配是否与上次匹配的位置连续,如果不连续,则从第一个匹配字符的下一个字符开始搜索
					if(continuous==true)
					{
						if(i==0)//第一个字符已经匹配,记录这个位置
						{
							j=k;
						}
						else
						{
							if(k-temp!=1)//不是连续匹配
							{
								i=-1;//回到第一个字符开始搜索
								k=j;//从这个位置后开始搜索(搜索使用的是k+1)
							}
						}
					}
					#endregion
				}
			}
		}
	}
	return true;
}
/// <summary>
/// 智能匹配=CleverMatch(source,part,true,true,true)
/// </summary>
/// <param name="source"></param>
/// <param name="part"></param>
/// <returns></returns>
public static bool CleverMatch(string source,string part)
{
	return CleverMatch(source,part,true,true,true);
}
#endregion


 我的资源中有例程下载

 

 

 

  • 3
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

socg

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值