zoukankan      html  css  js  c++  java
  • 在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类型的方法要优雅得多,因为这样做避免了在不同的窗体间传递参数的麻烦,代码更加简洁!

  • 相关阅读:
    MOSS中的User的Title, LoginName, DisplayName, SID之间的关系
    如何在Network Monitor中高亮间隔时间过长的帧?
    SharePoint服务器如果需要安装杀毒软件, 需要注意什么?
    如何查看SQL Profiler? 如何查看SQL死锁?
    什么是Telnet
    The name or security ID (SID) of the domain specified is inconsistent with the trust information for that domain.
    Windows SharePoint Service 3.0的某个Web Application无搜索结果
    网络连接不上, 有TCP错误, 如果操作系统是Windows Server 2003, 请尝试一下这里
    在WinDBG中查看内存的命令
    The virtual machine could not be started because the hypervisor is not running
  • 原文地址:https://www.cnblogs.com/jaxu/p/2050861.html
Copyright © 2011-2022 走看看