上一篇中,我們講解的是這個小軟件的重構:使用可二進制化的Model類代替拼接字符串的方式,這樣做的好處是使得代碼可讀性更強,更容易維護,當然,也更符合面向對象的思想:處處皆對象。
效果圖覽
在這一篇中,主要涉及的內容是新增的QQ表情功能。這個功能的設計牽涉到了正則表達式,我們先來看看截圖:
彈出選擇表情面板:
3個用戶的具體聊天內容:
看到GIF圖像在跳動
其中有一個用戶已經下線
下面是設計的准備工作:
首先,我們需要一個能夠支持圖片輸入的TextBox,這里我選擇了這篇文章中介紹的控件:C# 實現IM聊天信息輸入顯示控件(1)-顯示GIF動畫圖片,這個控件通過QQ自帶的ImageOle.dll ActiveX控件實現插入動畫表情,所以說在使用之前,需要先利用regsvr32.exe命令注冊這個dll,具體命令為:regsvr32.exe ImageOle.dll。
當然做完了之后,直接在VS中添加COM引用即可。
其次,彈出選擇表情面板是必不可少的,這里我們利用一個二維的PictureBox數組來存儲GIF表情動畫,並放置到Panel容器中:

private void LoadingEmotion() { PictureBox[,] picList = new PictureBox[5,10]; for (int i = 0; i < 5; i++) { for (int j = 0; j < 10; j++) { int emotionSequenceCount = i * 10 + j; picList[i,j] = new PictureBox(); picList[i, j].Height = picList[i, j].Width = 24; picList[i, j].Image = Image.FromFile(".\\Face2\\" + emotionSequenceCount + ".gif"); picList[i, j].Top = i * 24; picList[i, j].Left = j * 24; picList[i, j].Tag = "#(" + emotionSequenceCount + ")#"; picList[i, j].Parent = panImg; picList[i, j].Click += new EventHandler((sender, e) => { this.rSendContent.AppendText("#(" + emotionSequenceCount + ")#"); emotionFlag = false; this.panImg.Visible = emotionFlag; }); panImg.Controls.Add(picList[i,j]); } } }
上面的GIF動畫位置是通過對象的Top和Left方法來控制的,非常的方便;同時,把每個GIF表情的代碼放到了Tag中進行保存,以方便調用,並且利用了匿名方法來注冊PictureBox的點擊事件。每次點擊圖標,會自動在發送文本框中生成類似#(0)#或者#(1)#等的代碼,這些代碼代表了是哪個表情,比如#(0)#就代表了第一行一列的表情,#(1)#代表了第1行2列的表情,依次類推。
最后就是輸入的時候,如何進行表情匹配了。比如用戶輸入了如下的內容:
Hello Shi#(0)#, How are you today?#(1)##(2)#
其中#(0)#,#(1)#,#(2)#是由我們通過點擊表情輸入進去的,那么發送到對方的機器上的時候,就需要被解析成
Hello Shi, How are you today?
,該如何進行呢?
其實,我的做法就是在這句話的頭部和尾部加上#(S)#和#(E)#標記以區別頭尾,
#(S)#Hello Shi#(0)#, How are you today?#(1)##(2)##(E)#
然后,通過如下的正則來進行分段匹配,其中,?=的作用主要是負向前查找,但是不包含本身。具體內容請參見正則表達式點滴2
Regex regex = new Regex(@"(#\([0-9|S|E]+\)#).*?(?=#\([0-9|S|E]+\)#)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
那么得到的結果就被分成了幾段:
#(S)#Hello Shi
#(0)#, How are you today?
#(1)#
#(2)#
#(S)#
這就是分成的5段,然后觀察這5段就發現,每段開始都是一個圖片的標記(#(S)#和#(E)#除外,那是開始結束標志),然后跟着的是一段文本或者是什么都不跟。
那么這樣的話,我們再繼續對這些段進行區分:
Regex regexImage = new Regex(@"(#\([0-9|S|E]+\)#)", RegexOptions.Singleline | RegexOptions.IgnoreCase); Regex regexPlainText = new Regex(@"(?<=(#\([0-9|S|E]+\)#)).*", RegexOptions.Singleline | RegexOptions.IgnoreCase);
其中
regexImage主要匹配其中的圖片標記,比如#(0)#,
regexPlainText主要匹配其中的文本或者空白字段,拿第二段來說,匹配的結果就是:
#(0)#
, How are you today?
這樣就將表情和文本完全分離出來了,最后直接將表情文字替換為真實圖片,並添加到信息窗體中:
if (!String.IsNullOrEmpty(matchImage.Value)) { if (!matchImage.Value.Contains("#(S)#")) { rAllContent.InsertImageUseImageOle(".\\Face2\\" + matchImage.Value.Replace("#(", string.Empty).Replace(")#", string.Empty) + ".gif"); } rAllContent.AppendText(matchPlainText.Value); }
這里補充一下本新增功能中用到的正則知識:
- 向前查找
從語法上看,一個向前查找模式其實就是一個以?=開頭的子表達式,需要匹配的文本跟在=的后面。
比如我們需要知道一些URL用的是http還是https,則可以利用向前查找:
http://www.cnblogs.com
正則匹配為:.+(?=:)
結果為: http
如果利用.+(:) ,則為 http:
- 向后查找
也就是查找出現在被匹配文本之前的字符(但不消費它),操作符是?<=
文本為:ABC0: $12.56
匹配為: (?<=\$)[0-9.]+
如果不佳?<=,結果為$12.56,反之為12.56
- 向前向后查找集合
例如以下文本:
<head>
<title>Ben Forta’s HomePage</title>
</head>
這里我們如果想得到<title>與</title>標簽內的內容,但是不包含<title>和</title>標簽,如果不利用向前向后查找的話,將顯得異常麻煩。利用向前向后匹配,只需要一個正則表達式就可以搞定:
正則匹配為:(?<=<title>).*?(?=</title>)
剛剛說道的向前向后查找,說准確點應該叫做正向前查找和正向后查找。當然,這里還存在這負向前查找和負向后查找:
操作符 |
說明 |
(?=) |
正向前查找 |
(?!) |
負向前查找 |
(?<=) |
正向后查找 |
(?<!) |
負向后查找 |
- 負向后查找
文本為:I paid $30 for 100 apples.
匹配為:\b(?<!\$)\d+\b
這個的意思是查找不帶有$符號的數字,這里的匹配結果是100
當然,負向前查找和這個使用方式類似,暫略。
全部代碼如下:

public static void AddContent(string text,ChatRichTextBox rAllContent) { //解析發送的內容,實現表情匹配。 string sendText = "#(S)#" + text + "#(E)#"; Regex regex = new Regex(@"(#\([0-9|S|E]+\)#).*?(?=#\([0-9|S|E]+\)#)", RegexOptions.Singleline | RegexOptions.IgnoreCase); Regex regexImage = new Regex(@"(#\([0-9|S|E]+\)#)", RegexOptions.Singleline | RegexOptions.IgnoreCase); Regex regexPlainText = new Regex(@"(?<=(#\([0-9|S|E]+\)#)).*", RegexOptions.Singleline | RegexOptions.IgnoreCase); MatchCollection matches = regex.Matches(sendText); foreach (Match match in matches) { string matchedValue = match.Value; Match matchImage = regexImage.Match(matchedValue); Match matchPlainText = regexPlainText.Match(matchedValue); if (!String.IsNullOrEmpty(matchImage.Value)) { if (!matchImage.Value.Contains("#(S)#")) { rAllContent.InsertImageUseImageOle(".\\Face2\\" + matchImage.Value.Replace("#(", string.Empty).Replace(")#", string.Empty) + ".gif"); } rAllContent.AppendText(matchPlainText.Value); } } }
源碼下載