您的位置:首页 > 理论基础 > 计算机网络

初探基于TCP的服务器/客户端结构的聊天系统(三)之表情聊天的实现

2013-08-15 21:05 585 查看
    前两篇都是讲一些思路上的东西,这次讲点实在一点的东西吧。这次就说说QT中怎么样实现类似QQ那样的动态表情聊天。

    既然是表情聊天首先得有表情。我Windows系统里的QQ版本是2013,找了半天也没找到,应该是腾讯把表情资源都封装起来了。百度了一下,各种说法都有,不过我坚信,总有办法可以找到。我就随便下了两个以前的QQ版本,分别是2003和2008。2003看了发现还是相当简陋的,并且也没有像现在这样的表情,里面仅有的表情图片也是及其简单的,并且不是动态的gif图片。2008里我在Face文件夹下找到了要找的资源,运气不错。顺带我也看了下QQ的目录结构,2003的黑白头像是预先生成好的,这和我做的黑白头像的方法是一样的,并不是动态生成的,这个以后再谈。时间久了就有点忘了具体还有些什么了。2008里的头像就是动态生成的。QQ2008应该还是基于MFC的,因为我再它的目录里面看到了一些mfc开头的DLL文件。感觉有点跑题了。有兴趣的可以把以前的QQ版本下下来,分析一下,还是会有不少收获的。

    得到了表情,下一步就是表情面板的实现了。

    先新建一个ui类,基于QDialog。然后在ui designer里拖几个控件:两个QPushButton,一个QStackedWidget,在两个页面中分别拖入一个QTableWidget。

###facedialog.h
#ifndef FACEDIALOG_H
#define FACEDIALOG_H

#include <QDialog>

namespace Ui {
class FaceDialog;
}

class FaceDialog : public QDialog {
Q_OBJECT
public:
FaceDialog(QWidget *parent = 0);
~FaceDialog();

protected:
void changeEvent(QEvent *e);

private:
Ui::FaceDialog *ui;

private slots:
void on_faceTable_2_cellClicked(int row, int column);
void on_faceTable_1_cellClicked(int row, int column);
void on_backButton_clicked();
void on_foreButton_clicked();

signals:
void catchFace(int row, int column, int page);
};

#endif // FACEDIALOG_H

###facedialog.cpp
#include "facedialog.h"
#include "ui_facedialog.h"
#include <QtGui>

FaceDialog::FaceDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::FaceDialog)
{
ui->setupUi(this);

setWindowFlags(Qt::FramelessWindowHint);

ui->faceTable_1->setColumnCount(14);
ui->faceTable_1->setRowCount(8);

for(int i = 0; i < 8; i++)
{
for(int j = 0; j < 14; j++)
{
QTableWidgetItem *faceItem = new QTableWidgetItem(tr(""));
faceItem->setIcon(QIcon(QPixmap(tr("Face/%1.gif").arg(i * 14 + j))));
ui->faceTable_1->setItem(i, j, faceItem);
}
}

ui->faceTable_2->setColumnCount(14);
ui->faceTable_2->setRowCount(8);
for(int i = 0; i < 8; i++)
{
for(int j = 0; j < 14; j++)
{
QTableWidgetItem *faceItem = new QTableWidgetItem(tr(""));
faceItem->setIcon(QIcon(QPixmap(tr("Face/%1.gif").arg(i * 14 + j + 112))));
ui->faceTable_2->setItem(i, j, faceItem);
}
}

QHBoxLayout * menuLayout = new QHBoxLayout;
menuLayout->addWidget(ui->backButton);
menuLayout->addWidget(ui->foreButton);
menuLayout->addStretch();

QVBoxLayout * firstPageLayout = new QVBoxLayout;
firstPageLayout->addWidget(ui->faceTable_1);
ui->page->setLayout(firstPageLayout);

QVBoxLayout * secondPageLayout = new QVBoxLayout;
secondPageLayout->addWidget(ui->faceTable_2);
ui->page_2->setLayout(secondPageLayout);

QVBoxLayout * mainLayout = new QVBoxLayout;
mainLayout->addLayout(menuLayout);
mainLayout->addWidget(ui->stackedWidget);
mainLayout->setMargin(0);
mainLayout->setSpacing(5);

setLayout(mainLayout);

ui->stackedWidget->setCurrentIndex(0);

}

FaceDialog::~FaceDialog()
{
delete ui;

}

void FaceDialog::changeEvent(QEvent *e)
{
QDialog::changeEvent(e);
switch (e->type()) {
case QEvent::LanguageChange:
ui->retranslateUi(this);
break;
default:
break;
}
}

void FaceDialog::on_foreButton_clicked()
{
ui->stackedWidget->setCurrentIndex((ui->stackedWidget->currentIndex() - 1 + ui->stackedWidget->count()) % ui->stackedWidget->count());
}

void FaceDialog::on_backButton_clicked()
{
ui->stackedWidget->setCurrentIndex((ui->stackedWidget->currentIndex() + 1) % ui->stackedWidget->count());
}

void FaceDialog::on_faceTable_1_cellClicked(int row, int column)
{
emit catchFace(row, column, 0);
}

void FaceDialog::on_faceTable_2_cellClicked(int row, int column)
{
emit catchFace(row, column, 1);
}


    记得将ui designer中的变量名改为代码中的名字。
    这个类也很简单就是捕获QTableWidget的点击事件,将信号发送除去。
QTableWidgetItem *faceItem = new QTableWidgetItem(tr(""));
faceItem->setIcon(QIcon(QPixmap(tr("Face/%1.gif").arg(i * 14 + j))));
ui->faceTable_1->setItem(i, j, faceItem);    这3句是从文件加载表情到QTableWidget中。QTableWidget还应该在属性中设置HorizontalHeaderVisible和VerticalHeaderVisible属性为false ,取消表头的显示。
    关于表情面板就说这么多吧,也不难。
    接下来说说表情聊天的实现。
    在聊天窗口中添加一个FaceDialog的实例作为成员变量face。在聊天窗口的初始化函数中添加:
    connect(face, SIGNAL(catchFace(int,int,int)), this, SLOT(on_catchFace(int,int,int)));
    添加on_catchFace(int,int,int)槽函数。

    这样就可以通过on_catchFace(int, int, int)的三个参数获取表情的id了。
    剩下就是怎么样实现表情聊天了。  这个要利用一个特性,就是QTextEdit支持富文本,可以在QTextEdit中添加图片
4000
,并且可以将QTextEdit中的内容通过toHtml()成员函数得到html语句,并将该语句发送到对端。在对端通过同样的html解析就可以得到同样的表情了。简单点说就是我们从QTextEdit中得到文本的时候使用toHtml()函数,在接收端设置文本的时候使用insertHtml()语句就可以了。中间的传输还是一样的。这样就可以在传输的时候保持文本的字体一致,并且支持图片显示。由于聊天窗口的代码太多了,我就不贴出来,从里面挑也不是一件容易的事。void ChatDialog::on_catchFace(int row, int column, int page)
{
QString faceFileName;
faceFileName = tr("Face/%1.gif").arg(row * 14 + column + page * 112);
ui->sendEdit->insertHtml(tr("<img src='") + faceFileName + tr("'/>"));
addAnimation(QUrl(faceFileName), faceFileName);
face->hide();
ui->expressionBoxButton->setChecked(false);

  这是on_catchFace()的槽函数实现,通过这个函数就可以向发送编辑框中添加一个表情。其中"<img src = 'Face/x.gif'/>"这一句是html语句中插入图片的语句。
    当点击发送按钮的时候就通过toHtml()语句得到html文本,然后在接收端insertHtml()就能显示图片了。但是还有一个问题,那就是图片并没有动态效果。

    接下来介绍一下怎么实现动态效果。有人可能已经注意到on_catchFace()中的addAnimation函数了。就是通过这个函数来给图片添加动画的。下面给出这个函数的实现。void ChatDialog::addAnimation(const QUrl &url, const QString &fileName)
{
QFile *file =new QFile(fileName);
if(!file->open(QIODevice::ReadOnly)){
qDebug()<<" open err";
}

if(lstUrl.contains(url)){ //同一个gif 使用同一个movie
return;
}else{
lstUrl.append(url);
}

QMovie* movie = new QMovie(this);
movie->setFileName(fileName);
movie->setCacheMode(QMovie::CacheNone);

lstMovie.append(movie); //获取该movie,以便删除
urls.insert(movie, url);

//换帧时刷新
connect(movie, SIGNAL(frameChanged(int)), this, SLOT(animate(int)));
movie->start();
file->close();
delete file;
}
有几个是ChatDialog的成员变量,定义如下:
    QList<QUrl> lstUrl;

    QList<QMovie *> lstMovie;

    QHash<QMovie*, QUrl> urls;

    在发送端通过这个函数就可以给某个表情添加动画了。那是不是要在开始的时候给所有的表情添加动画呢? 那样也可以,但是效率太低。不要为没用的东西付出代价。发送端就在添加表情后再添加相应的动画。在接收端,则需要解析接收语句中的内容,为响应的图片添加动画,否则,将会出现只有发送过的表情才有动画,接收到的表情没有动画的尴尬情况。接收端解析并添加表情代码如下:
ui->recvEdit->insertHtml(textMSG);

int i = 0;
int j = 0;
while(i < textMSG.length())
{
i = textMSG.indexOf("<img src=", j);
if(i != -1)
{
j = textMSG.indexOf("\"", i + 10);
QString faceFileName = textMSG.mid( i + 10, j - i - 10);
addAnimation(QUrl(faceFileName), faceFileName);
}
else
{
break;
}
}

    其中,textMSG就是接收过来的html语句。这样,就给接收端也添加了相应的动画。

    经过这么多步骤,就可以实现一个表情聊天功能了。
    说的有点乱,基本思路整理如下:
    先实现表情面板当有表情被点击的时候发送信号。
    在QTextEdit中添加相应的表情。并为相应的表情添加动画,实现本地发送内容的动画效果。
    在接收对话框中解析相应的表情,并添加相应的动画效果。

    虽然没有讲文字聊天,但是这个过程中已经附带实现了文字的效果,因为QTextEdit是支持富文本的。感觉自己的表达能力真的有限,也不知道有没有哪位理解能力超强的人能看懂。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Qt 聊天工具 QQ表情