在WinForm中增加查询对话框对DataGridView数据进行循环查找

简介:

在开发WinForm窗体程序时,我们希望增加一个对DataGridView数据进行查找的对话框,类似于Visual Studio中的“查找和替换”对话框,但是功能没有这么复杂,需求如下:

  1. 用户可以通过主窗体中的菜单打开数据查找对话框。

  2. DataGridView数据未加载前不显示查找对话框。

  3. 查找对话框中可以进行大小写匹配和全字匹配。

  4. 查找对话框以非模式对话框的形式显示在主窗体的上面。

  5. DataGridView中高亮显示被查找到的关键字所在的行。

  6. 用户可以在查找对话框中DataGridView中的数据进行循环查找,即用户每进行一次查找,DataGridView都将从上一次查找到的位置开始向下进行查找直到最后一行,然后再从第一行开始继续查找。

  7. 可对DataGridView进行逐行逐列查找。

  对DataGridView进行逐行逐列的遍历并匹配关键字然后高亮显示当前行,这个功能实现起来应该没有什么难度,关键在于如何实现循环查找,并且能够很好地与子窗体(查找对话框)进行互动。另外就是需要实现大小写匹配和圈子匹配,这里需要使用到正则表达式。我们先看一下程序的主界面。

Capture   主窗体的实现我在这里不具体介绍了,这不是本文的重点,况且上面这个程序截图中还实现了许多其它的功能。我在这里主要介绍一下子窗体的功能以及如何实现DataGridView数据的循环查找。

 

先来看一下如何打造一个相对美观的查找对话框

  如上图,你可以将用于设置查询参数部分的控件(Match case,Match whole word)放到一个布局控件中,如GroupBox。这样界面看起来会比较专业一些。然后你还需要对子窗体进行一些参数设置,使其看起来更像一个对话框。

  FormBorderStyle: FixedDialog

  Text: Find Record

  Name: FindRecord

  StartPosition: CenterScreen

  AcceptButton: btFindNext (Find Next按钮)

  CancelButton: btCancel (Cancel按钮)

  MaximizeBox: False

  MinimizeBox: False

  ShowIcon: False

  ShowInTaskbar: False

  TopMost: True

 

给对话框增加一些功能

  首先对话框应该是在全局有效的,否则我们就不能记录每一次查找后DataGridView中被命中的记录的Index。所以对话框窗体的实例应该是在主窗体中被初始化,并且只被实例化一次。每次打开对话框时只是调用实例的Show()方法,关闭对话框时只调用窗体的Hide()方法而不是Close()方法,因为Close()方法会将窗体的实例在内存中注销掉。那么我们需要定义btCancel按钮的事件和重写窗体的FormClosing事件并在其中调用窗体的Hide()方法。

  查询参数中的大小写匹配和全字匹配都是复选框控件,这意味着参数会有多种组合方式,不妨将这些组合定义成一个枚举,一共是四种情况:任意匹配(None),大小写匹配(MatchCase),全字匹配(MatchWholeCase),大小写和全字匹配(MatchCaseAndWholeWord)。

  以事件模型来实现数据查找功能在这里再好不过了。首先需要在查询对话框中定义一个EventHandler,然后在主窗体中订阅这个事件,事件的执行代码写到子窗体的btFindNext按钮的事件中,一共传递三个参数:查询内容,DataGridView的当前行号(用于定位下一次查找),以及查询参数枚举变量。下面是子窗体的具体实现代码:

复制代码
1  using  System; 
2  using  System.Collections.Generic; 
3  using  System.ComponentModel; 
4  using  System.Data; 
5  using  System.Drawing; 
6  using  System.Linq; 
7  using  System.Text; 
8  using  System.Windows.Forms; 
9   
10  namespace  ListItemEditor.UI 
11 
12       public   partial   class  FindRecord : Form 
13      { 
14           public  EventHandler < FindRecordWindowEventArgs >  OnFindClick  =   null
15           public   enum  FindOptions { None, MatchCase, MatchWholeWord, MatchCaseAndWholeWord } 
16           public   int  CurrentIndex  =   - 1
17   
18           public  FindRecord() 
19          { 
20              InitializeComponent(); 
21          } 
22   
23           private   void  btCancel_Click( object  sender, EventArgs e) 
24          { 
25               this .Hide(); 
26          } 
27   
28           private   void  FindRecord_FormClosing( object  sender, FormClosingEventArgs e) 
29          { 
30               this .Hide(); 
31              e.Cancel  =   true
32          } 
33   
34           private   void  btFindNext_Click( object  sender, EventArgs e) 
35          { 
36               if  ( this .tbFindTxt.Text.Trim().Length  >   0
37              { 
38                  FindOptions options  =  FindOptions.None; 
39                   if  ( this .chbMatchCase.Checked  &&   this .chbMatchWholeWord.Checked) 
40                  { 
41                      options  =  FindOptions.MatchCaseAndWholeWord; 
42                  } 
43                   else   if  ( this .chbMatchCase.Checked  &&   ! this .chbMatchWholeWord.Checked) 
44                  { 
45                      options  =  FindOptions.MatchCase; 
46                  } 
47                   else   if  ( ! this .chbMatchCase.Checked  &&   this .chbMatchWholeWord.Checked) 
48                  { 
49                      options  =  FindOptions.MatchWholeWord; 
50                  } 
51                   else  
52                  { 
53                      options  =  FindOptions.None; 
54                  } 
55                  OnFindClick( this new  FindRecordWindowEventArgs( this .tbFindTxt.Text, CurrentIndex, options)); 
56              } 
57          } 
58      } 
59   
60       public   class  FindRecordWindowEventArgs : EventArgs 
61      { 
62           private   string  sFindTxt; 
63           private   int  iIndex  =   0
64           private  FindRecord.FindOptions findOptions; 
65   
66           public   string  FindTxt 
67          { 
68               get  {  return   this .sFindTxt; } 
69          } 
70   
71           public   int  Index 
72          { 
73               get  {  return   this .iIndex; } 
74          } 
75   
76           public  FindRecord.FindOptions FindOptions 
77          { 
78               get  {  return   this .findOptions; } 
79          } 
80   
81           public  FindRecordWindowEventArgs( string  _findTxt,  int  _index, FindRecord.FindOptions _options) 
82          { 
83               this .sFindTxt  =  _findTxt; 
84               this .iIndex  =  _index; 
85               this .findOptions  =  _options; 
86          } 
87      } 
88  }
复制代码

 

主窗体做了什么

  首先我们需要在主窗体中实例化子窗体并定义查询事件,因此下面这几行代码是必须的:

复制代码
 1  public   partial   class  Form1 : Form
 2  {
 3       private  FindRecord winFind  =   new  FindRecord();
 4 
 5      public  Form1()
 6      {
 7          InitializeComponent();
 8 
 9           this .winFind.OnFindClick  +=   new  EventHandler < FindRecordWindowEventArgs > ( this .winFind_OnFindClick);
10      }
11  }
复制代码

  FindRecord即子窗体所在的类。下面是具体的数据查询实现及菜单响应代码:

复制代码
 1  private   void  tlbFind_Click( object  sender, EventArgs e)
 2  {
 3       if  ( ! this .DataLoaded)  return ;
 4      winFind.Show();
 5  }
 6 
 7  private   void  Form1_KeyDown( object  sender, KeyEventArgs e)
 8  {
 9       if  ( ! this .DataLoaded)  return ;
10       if  (e.Modifiers  ==  Keys.Control  &&  e.KeyCode  ==  Keys.F)
11      {
12          tlbFind.PerformClick();
13      }
14  }
15 
16  private   void  winFind_OnFindClick( object  sender, FindRecordWindowEventArgs e)
17  {
18       string  s  =  e.FindTxt;
19       int  index  =  e.Index;
20       bool  bFind  =   false ;
21 
22      RegexOptions regOptions  =  RegexOptions.IgnoreCase;
23       string  pattern  =  Regex.Escape(s);
24 
25       if  (e.FindOptions  ==  FindRecord.FindOptions.MatchCase  ||  e.FindOptions  ==  FindRecord.FindOptions.MatchCaseAndWholeWord)
26      {
27          regOptions  =  RegexOptions.None;
28      }
29 
30       if  (e.FindOptions  ==  FindRecord.FindOptions.MatchWholeWord  ||  e.FindOptions  ==  FindRecord.FindOptions.MatchCaseAndWholeWord)
31      {
32          pattern  =   " \\b "   +  pattern  +  "\\b" ;
33      }
34 
35       foreach  (DataGridViewRow row  in  theGrid.Rows)
36      {
37           this .winFind.CurrentIndex  =  row.Index;
38           foreach  (DataGridViewCell cel  in  row.Cells)
39          {
40               // if (cel.Value.ToString().Contains(s))
41               if  (Regex.IsMatch(cel.Value.ToString(), pattern, regOptions))
42              {
43                  bFind  =   true ;
44                   if  (cel.RowIndex  >  index)
45                  {
46                       this .theGrid.ClearSelection();
47                       this .theGrid.Rows[cel.RowIndex].Selected  =   true ;
48                       return ;
49                  }
50              }
51          }
52      }
53 
54       if  ( this .winFind.CurrentIndex  ==   this .theGrid.Rows.Count  -   1   &&  bFind)
55      {
56           this .winFind.CurrentIndex  =   - 1 ;
57          MessageBox.Show( " Find the last record. " " List Item Editor " , MessageBoxButtons.OK, MessageBoxIcon.Information);
58           return ;
59      }
60 
61       if  ( ! bFind)
62      {
63           this .winFind.CurrentIndex  =   - 1 ;
64          MessageBox.Show( string .Format( " The following specified text was not found:\r\n{0} " , s),  " List Item Editor " , MessageBoxButtons.OK, MessageBoxIcon.Information);
65      }
66  }
复制代码

  tlbFind_Click是菜单点击事件,在显示子窗体前我们需要通过DataLoaded变量来判断DataGridView是否已经完成数据加载了,这是一个布尔变量,在主窗体中定义的私有变量。Form1_KeyDown事件用来响应Ctrl + F快捷键,如果DataGridView已经完成数据加载并且用户使用了键盘上的Ctrl + F组合键,则执行与tblFind_Click事件相同的操作,这是通过tlbFind.PerformClick()这条语句来完成的。

  winFind_OnFindClick事件实现了具体的数据查询操作,这个事件是子窗体数据查询EventHandler的具体实现。还记得前面提到过的这个吗?我们在子窗体的这个EventHandler中定义了三个参数,用来传递要查询的内容,以及DataGridView的行号和查询参数枚举值。现在在主窗体的这个事件函数中可以通过对象e来获取到这些值。代码中通过两个foreach语句来逐行逐列遍历DataGridView,字符串匹配操作使用了正则表达式,根据查询参数中的枚举值来使用不同的正则表达式匹配项:

  1. 默认情况下正则表达式匹配项被设置成了大小写敏感(RegexOptions.IgnoreCase)

  2. 如果用户在子窗体中选择了大小写匹配,则将正则表达式匹配项修改成None(RegexOptions.None)

  3. 如果用户在子窗体中选择了全字匹配,则使用自定义的正则表达式进行匹配。在正则表达式中,'\b'用来判断单词边界,而'\B'用来判断非单词边界。有关如何使用正则表达式进行全字匹配可以参考下这里的一篇文章。

http://answers.oreilly.com/topic/217-how-to-match-whole-words-with-a-regular-expression/

  正则表达式30分钟入门教程也有关于如何使用\b和\B的介绍,并且描述简单明了。

  子窗体中还有一个公共整型变量CurrentIndex,主窗体在遍历DataGridView的同时会修改这个值,将DataGridView的当前行号传递回子窗体,当用户下一次进行查询时,子窗体又会将这个行号传回到主窗体中。你应该已经注意到了在内层的foreach循环语句中有一个判断,如果命中的DataGridView行的行号小于CurrentIndex值,则继续向下查找,直到找到下一个匹配的行,且这个行号要大于CurrentIndex值。如果已经找到DataGridView的最后一行则弹出一个提示信息。bFind布尔变量用于指示是否已经找到匹配的值,如果没有找到,则在程序的最后会弹出一个提示信息。

 

  好了,程序的所有核心实现都在这里了。其实就是使用了一点小技巧,再就是子窗体通过事件模型去驱动主窗体的数据查询功能,这比直接在子窗体中定义一个public类型的方法要优雅得多,因为这样做避免了在不同的窗体间传递参数的麻烦,代码更加简洁!


本文转自Jaxu博客园博客,原文链接:http://www.cnblogs.com/jaxu/archive/2011/05/19/2050861.html,如需转载请自行联系原作者


相关文章
|
人工智能 C#
c#中在datagridview的表格动态增加一个按钮方法
c#中在datagridview的表格动态增加一个按钮方法,如果想要这一套教程的可以移步去这里 《期末作业C#实现学生宿舍管理系统》,对了最近我们有一个人工智能交流群,如果大家对代码有问题,想交流的可以进群,私聊我就可以了! 效果图片 : 在Load事件中写入代码 那ui有了功能怎么办呢?别急我们在 dataGridView1_CellContentClick事件中添加方法 这样的话 我们就可以点击对应行的修改来获取到id的值这里有一个bug就是第三行没数据需要隐藏,现在还没有解决,欢迎大家指出!.....
546 0
c#中在datagridview的表格动态增加一个按钮方法
c#Winform修改datatable的列的操作和一些combox绑定实体类,dataGridview的注意点 弹出确认框 弹出的winform出现的位置 load
ds是DataSet 是Datatable的集合 ds.Tables[0]是得到第一张表 然后就是对dt的操作 将Fill_ID列名修改为 “序号” 依次修改列名 combox绑定list 显示combox上的值是用cmb_name 但是 在窗体加载的时候 cmb_name是 它本身的类型名字 而不是空 只有当它上面绑定有真正的值后才会显示。
1341 0