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

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

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

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

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

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

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

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

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

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

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

 

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

  如上图,你可以将用于设置查询参数部分的控件(Match caseMatch 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|//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. 如果用户在子窗体中选择了大小写匹配,则将正则表达式匹配项修改成NoneRegexOptions.None

  3. 如果用户在子窗体中选择了全字匹配,则使用自定义的正则表达式进行匹配。在正则表达式中,'/b'用来判断单词边界,而'/B'用来判断非单词边界,两者同时使用便可以进行全字匹配(因为内容中的有些单词并非一个真正意思上的单词,它只是作为一个表达式而已)。有关如何使用正则表达式进行全字匹配可以参考下这里的一篇文章,或许针对不同的编码规则,你需要重新修改一下你的正则表达式来进行更加精确的全字匹配。

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

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

 

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

 www.dsb1.info

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值