在開發(fā)WinForm窗體程序時,我們希望增加一個對DataGridView數(shù)據(jù)進行查找的對話框,類似于Visual Studio中的“查找和替換”對話框,但是功能沒有這么復雜,需求如下:
1. 用戶可以通過主窗體中的菜單打開數(shù)據(jù)查找對話框。
2. DataGridView數(shù)據(jù)未加載前不顯示查找對話框。
3. 查找對話框中可以進行大小寫匹配和全字匹配。
4. 查找對話框以非模式對話框的形式顯示在主窗體的上面。
5. DataGridView中高亮顯示被查找到的關(guān)鍵字所在的行。
6. 用戶可以在查找對話框中DataGridView中的數(shù)據(jù)進行循環(huán)查找,即用戶每進行一次查找,DataGridView都將從上一次查找到的位置開始向下進行查找直到最后一行,然后再從第一行開始繼續(xù)查找。
7. 可對DataGridView進行逐行逐列查找。
對DataGridView進行逐行逐列的遍歷并匹配關(guān)鍵字然后高亮顯示當前行,這個功能實現(xiàn)起來應(yīng)該沒有什么難度,關(guān)鍵在于如何實現(xiàn)循環(huán)查找,并且能夠很好地與子窗體(查找對話框)進行互動。另外就是需要實現(xiàn)大小寫匹配和圈子匹配,這里需要使用到正則表達式。我們先看一下程序的主界面。
主窗體的實現(xiàn)我在這里不具體介紹了,這不是本文的重點,況且上面這個程序截圖中還實現(xiàn)了許多其它的功能。我在這里主要介紹一下子窗體的功能以及如何實現(xiàn)DataGridView數(shù)據(jù)的循環(huán)查找。
先來看一下如何打造一個相對美觀的查找對話框
如上圖,你可以將用于設(shè)置查詢參數(shù)部分的控件(Match case,Match whole word)放到一個布局控件中,如GroupBox。這樣界面看起來會比較專業(yè)一些。然后你還需要對子窗體進行一些參數(shù)設(shè)置,使其看起來更像一個對話框。
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
給對話框增加一些功能
首先對話框應(yīng)該是在全局有效的,否則我們就不能記錄每一次查找后DataGridView中被命中的記錄的Index。所以對話框窗體的實例應(yīng)該是在主窗體中被初始化,并且只被實例化一次。每次打開對話框時只是調(diào)用實例的Show()方法,關(guān)閉對話框時只調(diào)用窗體的Hide()方法而不是Close()方法,因為Close()方法會將窗體的實例在內(nèi)存中注銷掉。那么我們需要定義btCancel按鈕的事件和重寫窗體的FormClosing事件并在其中調(diào)用窗體的Hide()方法。
查詢參數(shù)中的大小寫匹配和全字匹配都是復選框控件,這意味著參數(shù)會有多種組合方式,不妨將這些組合定義成一個枚舉,一共是四種情況:任意匹配(None),大小寫匹配(MatchCase),全字匹配(MatchWholeCase),大小寫和全字匹配(MatchCaseAndWholeWord)。
以事件模型來實現(xiàn)數(shù)據(jù)查找功能在這里再好不過了。首先需要在查詢對話框中定義一個EventHandler,然后在主窗體中訂閱這個事件,事件的執(zhí)行代碼寫到子窗體的btFindNext按鈕的事件中,一共傳遞三個參數(shù):查詢內(nèi)容,DataGridView的當前行號(用于定位下一次查找),以及查詢參數(shù)枚舉變量。下面是子窗體的具體實現(xiàn)代碼:
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即子窗體所在的類。下面是具體的數(shù)據(jù)查詢實現(xiàn)及菜單響應(yīng)代碼:
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:\\n{0}", s),
"List Item Editor", MessageBoxButtons.OK, MessageBoxIcon.Information);
65 }
66 }
tlbFind_Click是菜單點擊事件,在顯示子窗體前我們需要通過DataLoaded變量來判斷DataGridView是否已經(jīng)完成數(shù)據(jù)加載了,這是一個布爾變量,在主窗體中定義的私有變量。Form1_KeyDown事件用來響應(yīng)Ctrl + F快捷鍵,如果DataGridView已經(jīng)完成數(shù)據(jù)加載并且用戶使用了鍵盤上的Ctrl + F組合鍵,則執(zhí)行與tblFind_Click事件相同的操作,這是通過tlbFind.PerformClick()這條語句來完成的。
winFind_OnFindClick事件實現(xiàn)了具體的數(shù)據(jù)查詢操作,這個事件是子窗體數(shù)據(jù)查詢EventHandler的具體實現(xiàn)。還記得前面提到過的這個嗎?我們在子窗體的這個EventHandler中定義了三個參數(shù),用來傳遞要查詢的內(nèi)容,以及DataGridView的行號和查詢參數(shù)枚舉值。現(xiàn)在在主窗體的這個事件函數(shù)中可以通過對象e來獲取到這些值。代碼中通過兩個foreach語句來逐行逐列遍歷DataGridView,字符串匹配操作使用了正則表達式,根據(jù)查詢參數(shù)中的枚舉值來使用不同的正則表達式匹配項:
1. 默認情況下正則表達式匹配項被設(shè)置成了大小寫敏感(RegexOptions.IgnoreCase)
2. 如果用戶在子窗體中選擇了大小寫匹配,則將正則表達式匹配項修改成None(RegexOptions.None)
3. 如果用戶在子窗體中選擇了全字匹配,則使用自定義的正則表達式進行匹配。在正則表達式中,'\b'用來判斷單詞邊界,而'\B'用來判斷非單詞邊界,兩者同時使用便可以進行全字匹配(因為內(nèi)容中的有些單詞并非一個真正意思上的單詞,它只是作為一個表達式而已)。有關(guān)如何使用正則表達式進行全字匹配可以參考下這里的一篇文章,或許針對不同的編碼規(guī)則,你需要重新修改一下你的正則表達式來進行更加精確的全字匹配。
子窗體中還有一個公共整型變量CurrentIndex,主窗體在遍歷DataGridView的同時會修改這個值,將DataGridView的當前行號傳遞回子窗體,當用戶下一次進行查詢時,子窗體又會將這個行號傳回到主窗體中。你應(yīng)該已經(jīng)注意到了在內(nèi)層的foreach循環(huán)語句中有一個判斷,如果命中的DataGridView行的行號小于CurrentIndex值,則繼續(xù)向下查找,直到找到下一個匹配的行,且這個行號要大于CurrentIndex值。如果已經(jīng)找到DataGridView的最后一行則彈出一個提示信息。bFind布爾變量用于指示是否已經(jīng)找到匹配的值,如果沒有找到,則在程序的最后會彈出一個提示信息。
好了,程序的所有核心實現(xiàn)都在這里了。其實就是使用了一點小技巧,再就是子窗體通過事件模型去驅(qū)動主窗體的數(shù)據(jù)查詢功能,這比直接在子窗體中定義一個public類型的方法要優(yōu)雅得多,因為這樣做避免了在不同的窗體間傳遞參數(shù)的麻煩,代碼更加簡潔!
標簽:
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:博客園