上一篇中,我们讲解的是这个小软件的重构:使用可二进制化的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容器中:

View Code
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

当然,负向前查找和这个使用方式类似,暂略。

全部代码如下:

View Code
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);
                    }
                }

        }

源码下载

点击这里下载源码

posted on 2012-10-31 22:38  程序诗人  阅读(2313)  评论(12编辑  收藏  举报