初探基于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
###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是支持富文本的。感觉自己的表达能力真的有限,也不知道有没有哪位理解能力超强的人能看懂。
既然是表情聊天首先得有表情。我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是支持富文本的。感觉自己的表达能力真的有限,也不知道有没有哪位理解能力超强的人能看懂。
相关文章推荐
- 初探基于TCP的服务器/客户端结构的聊天系统(四)视频聊天的实现
- 初探基于TCP的服务器/客户端结构的聊天系统(二)之应用层通信协议设计
- 初探基于TCP的服务器/客户端结构的聊天系统
- golang简单实现一个基于TLS/SSL的 TCP服务器和客户端
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- Java 实现TCP网络聊天[服务器-客户端]UI界面
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- Android Socket编程基于TCP实现客户端与服务器简易通信
- C# 网络编程之Tcp实现客户端和服务器聊天
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- 基于Linux的TCP多路复用IO结构网络在线聊天系统
- Windows下TCP聊天服务器与客户端雏形的实现
- 基于C/S结构的高校学生网络行为规范系统的设计与实现(包含服务器与客户端)
- 基于Java的tcp实现文本聊天系统
- winsock实现基于TCP的客户端/服务器通讯