天天看點

在WinForm中增加查詢對話框對DataGridView資料進行循環查找

在開發WinForm窗體程式時,我們希望增加一個對DataGridView資料進行查找的對話框,類似于Visual Studio中的“查找和替換”對話框,但是功能沒有這麼複雜,需求如下:

  1. 使用者可以通過主窗體中的菜單打開資料查找對話框。

  2. DataGridView資料未加載前不顯示查找對話框。

  3. 查找對話框中可以進行大小寫比對和全字比對。

  4. 查找對話框以非模式對話框的形式顯示在主窗體的上面。

  5. DataGridView中高亮顯示被查找到的關鍵字所在的行。

  6. 使用者可以在查找對話框中DataGridView中的資料進行循環查找,即使用者每進行一次查找,DataGridView都将從上一次查找到的位置開始向下進行查找直到最後一行,然後再從第一行開始繼續查找。

  7. 可對DataGridView進行逐行逐列查找。

  對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的目前行号(用于定位下一次查找),以及查詢參數枚舉變量。下面是子窗體的具體實作代碼:

<a></a>

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&lt;FindRecordWindowEventArgs&gt; 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 &gt; 0) 

37             { 

38                 FindOptions options = FindOptions.None; 

39                 if (this.chbMatchCase.Checked &amp;&amp; this.chbMatchWholeWord.Checked) 

40                 { 

41                     options = FindOptions.MatchCaseAndWholeWord; 

42                 } 

43                 else if (this.chbMatchCase.Checked &amp;&amp; !this.chbMatchWholeWord.Checked) 

44                 { 

45                     options = FindOptions.MatchCase; 

46                 } 

47                 else if (!this.chbMatchCase.Checked &amp;&amp; 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&lt;FindRecordWindowEventArgs&gt;(this.winFind_OnFindClick);

10     }

11 }

  FindRecord即子窗體所在的類。下面是具體的資料查詢實作及菜單響應代碼:

 1 private void tlbFind_Click(object sender, EventArgs e)

 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 &amp;&amp; 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 &gt; 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 &amp;&amp; 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'用來判斷非單詞邊界。有關如何使用正規表達式進行全字比對可以參考下這裡的一篇文章。

<a href="http://answers.oreilly.com/topic/217-how-to-match-whole-words-with-a-regular-expression/">http://answers.oreilly.com/topic/217-how-to-match-whole-words-with-a-regular-expression/</a>

  子窗體中還有一個公共整型變量CurrentIndex,主窗體在周遊DataGridView的同時會修改這個值,将DataGridView的目前行号傳遞回子窗體,當使用者下一次進行查詢時,子窗體又會将這個行号傳回到主窗體中。你應該已經注意到了在内層的foreach循環語句中有一個判斷,如果命中的DataGridView行的行号小于CurrentIndex值,則繼續向下查找,直到找到下一個比對的行,且這個行号要大于CurrentIndex值。如果已經找到DataGridView的最後一行則彈出一個提示資訊。bFind布爾變量用于訓示是否已經找到比對的值,如果沒有找到,則在程式的最後會彈出一個提示資訊。

  好了,程式的所有核心實作都在這裡了。其實就是使用了一點小技巧,再就是子窗體通過事件模型去驅動主窗體的資料查詢功能,這比直接在子窗體中定義一個public類型的方法要優雅得多,因為這樣做避免了在不同的窗體間傳遞參數的麻煩,代碼更加簡潔!

本文轉自Jaxu部落格園部落格,原文連結:http://www.cnblogs.com/jaxu/archive/2011/05/19/2050861.html,如需轉載請自行聯系原作者