第九十一讲 综合运用(6)

休个国庆休得累死,好吧,今天没出去,我们来看看我们写的那个网络库怎么用于实际运用中,在上一讲里,我们展示了在控制台中的使用(其实都一样,我理解大家想要写一些窗口程序的心情,一开始学习编程语言的时候都想写出一些实际点的东西出来,但是等到后来大家也就不会再有想要去写窗口程序的冲动了),我们展示了三种方法来说明怎么使用这个网络库,这一讲我们使用第四种方法,这也是一种极为重要的方法,我们让新成员函数成为这个网络库的回调函数。

好吧,接触Qt大概也快一个月的时间了(有空的时候看一看书),所以今天就用Qt来给大家演示一下这个网络库的第四种用法,我们这个程序由于是作为一个聊天服务器来使用,所以界面很简单,基础点说就是一个列表框,用来显示所有在线的用户,一个文本编辑框,用来显示用户的聊天信息,他的样子如下这般:

这个窗口很简单,所以我们就简单的开始吧,首先,我们从QMainindow派生一个我们自己的类,这里就叫他Tester吧,这个类看起来像这样子:第九十一讲 综合运用(6)
//===============================
========class Tester==============

class Tester :public QMainWindow{

Q_OBJECT
public:

Tester(QWidget* parent = 0);



~Tester();


private:

void InitMenu();


void InitLayout();


void SetListIterm();


void SetText();


protected:

void timerEvent(QTimerEvent*);

//计时器

wchar_t* stow(wchar_t**, string&

);

//string转换为wchar_t

char* wtoc(wchar_t*, size_t);

//宽字符转换为多字节字符
private slots:

void SetUser();


void SetItem(QListWidgetItem*);


void SetListItem(std::vector<QListWidgetItem*>&

);


private:

QMenuBar*

p_MenuBar;

//菜单栏

QMenu*

p_Action;

//菜单

QAction*

p_User;

//子菜单

QAction*

p_Close;

//子菜单

QLabel*

p_ListLabel;

//标签贴

QLabel*

p_EditLabel;

//标签贴

ListWidget*

p_List;

//列表框

QTextEdit*

p_Edit;

//编辑框

QHBoxLayout*
p_Hlayout;

//水平布局管理器

QVBoxLayout*
p_Vlayout1;

//垂直布局管理器

QVBoxLayout*
p_Vlayout2;

//垂直布局管理器

QWidget*

p_CentralWidget;

//中心窗口,简单点说就是工作区域

QIcon*

p_usericon;

//用户图标
private:

void ProcMsg(PER_HANDLE_DATA*, std::string&

);

//回调函数

QString

m_ChatString;



int

m_TimerID;



bool

b_setText;



std::vector<QListWidgetItem*>v_ListItem;

//储存所有用户的容器

std::vector<std::wstring> v_listName;



std::vector<std::shared_ptr<PER_HANDLE_DATA>> v_User;



std::shared_ptr<MyNet>
m_Net;



OIFUN

m_fun;


};

===================================

目前我们就只需要这么多,ok,现在我假设大家的C++基础已经足够理解下面我们的实现,所以我可能会跳跃着说一些大家可能不明白的地方,当然关于Qt部分的知识这里就不多说,毕竟想要在一讲两讲内容给大家说清Qt是不可能的,但是我保证给出了界面的一些东西外,我们使用的还是我们最为熟悉的C++知识(当然,Qt也是纯C++写的,只是当前大家可能对Qt还不甚了解)

//==============实现=================
============构造函数=================
Tester::Tester(QWidget* parent) :QMainWindow(parent){

InitMenu();



InitLayout();



this->setWindowTitle("

Chat Service"

);



this->resize(750, 450);



m_path = nullptr;



m_Net = std::make_shared<MyNet>();



m_Net->CreatAndListen();



connect(p_Close, SIGNAL(triggered()), this, SLOT(close()));



this->setWindowIcon(QIcon("

Resources/Classes1_32x32x16.ico"

));



m_TimerID = startTimer(100);



b_setText = false;




//这里注意一点,这是使用我们这个网络库的关键之处,如果大家用的编译器支持C++11,可以选择使用boost的functional库,他提供了相同的功能,而且使用方法也完全一样

//因为成员函数中有个隐藏的参数(this),所以我们想要将成员函数作为我们网络库的回调函数,那就必须将this绑定,也就说让他对网络库的调用来说如同虚设,通过这么bind之后,这个成员函数就是我们想要的类型了,然后通过SetFun来注册一下就可以很好的工作了。

m_fun = std::bind(&

Tester::ProcMsg,this,std::placeholders::_1,std::placeholders::_2);



m_Net->SetFun(m_fun);


//构造列表框图标

p_usericon = new QIcon(tr("

Resources/LogOff.ico"

));



show();


}

===========析构函数============
//因为使用智能指针的话,所以析构函数很简单,除了m_path这个指针需要注意之外,我的想法是谁使用,谁释放,所以我这里就不管他了(这不是个好设计思想)
Tester::~Tester(){

}

//关于wtoc和stow是两个辅助函数,因为我们的网络库用的是多字节来开发的,但是Qt默认情况是UniCode,所以我们会需要用他们来完成字节的转换,可能大家会想,为什么当初我们不直接使用宽字符来开发呢?因为现在很多的C API依然使用const char*作为参数接口。

============两个辅助函数=======

char* Tester::wtoc(wchar_t* str, size_t count)
{

char* m_path = new char[count];



WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, str, -1,m_path, count, NULL, NULL);


return m_path;


}


wchar_t* Tester::stow(wchar_t** result, string&

str){

int w_len = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);



*result = new wchar_t[w_len + 1];



ZeroMemory(*result, w_len + 1);



MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, *result, w_len);


return *result;


}


===========初始化菜单==========

void Tester::InitMenu(){

p_MenuBar = menuBar();



p_Action = p_MenuBar->addMenu(tr("

Action"

));



p_User = new QAction(QIcon("

Resources/LogOff.ico"

), tr("

User(&

U)"

), this);



p_Close = new QAction(QIcon("

Resources/No3_32x32x256.ico"

), tr("

Exit(&

E)"

),this);



p_Action->addAction(p_User);



p_Action->addSeparator();



p_Action->addAction(p_Close);



p_MenuBar->addMenu(p_Action);


}


===========初始化布局===========

void Tester::InitLayout(){

p_CentralWidget = new QWidget(this);



this->setCentralWidget(p_CentralWidget);



p_List = new ListWidget(p_CentralWidget);



p_Edit = new QTextEdit(p_CentralWidget);



p_ListLabel = new QLabel(tr("

User List"

), p_CentralWidget);



p_EditLabel = new QLabel(tr("

Message Box"

), p_CentralWidget);



p_List->setFixedWidth(150);



p_List->setMinimumHeight(400);



p_Edit->setMinimumWidth(400);



p_Edit->setFont(tr("

Arial"

));



p_Edit->setFontPointSize(12);



connect(p_Edit, SIGNAL(cursorPositionChanged()), this, SLOT(setStatusBar()));



p_ListLabel->setFont(tr("

Arial"

));



p_ListLabel->setFixedSize(150, 20);



p_ListLabel->setStyleSheet("

font:12pt;

"

);



p_EditLabel->setFont(tr("

Arial"

));



p_EditLabel->setMidLineWidth(400);



p_EditLabel->setFixedHeight(20);



p_EditLabel->setStyleSheet("

font:12pt"

);



p_Hlayout = new QHBoxLayout(p_CentralWidget);



p_Vlayout1 = new QVBoxLayout(p_CentralWidget);



p_Vlayout1->addWidget(p_ListLabel);



p_Vlayout1->addWidget(p_List);



p_Vlayout2 = new QVBoxLayout(p_CentralWidget);



p_Vlayout2->addWidget(p_EditLabel);



p_Vlayout2->addWidget(Tester::p_Edit);



p_Hlayout->addLayout(p_Vlayout1);



p_Hlayout->addLayout(p_Vlayout2);



p_CentralWidget->setLayout(p_Hlayout);



connect(p_List, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(SetItem(QListWidgetItem*)));

}

============设置列表框============

void Tester::SetListIterm(){

v_User = m_Net->GetUserVector();



if (!v_User.empty()){

auto it = v_User.begin();


while (it != v_User.end()){

if (std::find_if(v_ListItem.begin(), v_ListItem.end(), [&

](const QListWidgetItem* listitem){

return (listitem->text()).toStdString() == (*it)->GetName();


}) == v_ListItem.end()){

wchar_t* temp;


temp = stow(&

temp, (*it)->GetName());


QListWidgetItem* item = new QListWidgetItem(*p_usericon, QString(QString::fromStdWString(temp)));


v_ListItem.push_back(item);


delete[] temp;


}

it++;


}

}

std::sort(v_ListItem.begin(), v_ListItem.end(), [&

](QListWidgetItem*item1, QListWidgetItem* item2){

return item1->text().toStdString() <item2->text().toStdString();


});



SetListItem(v_ListItem);


}

void Tester::SetListItem(std::vector<QListWidgetItem*>&

v){

if (!v.empty()){

int count = v.size();


p_List->setStyleSheet("

font:12pt;

"

);


for (int i = 0;

i <count;

++i){

p_List->insertItem(i, v.at(i));


}

m_Net->SetupdateUserinfo(false);


}
}


===============================

void Tester::SetUser(){

std::vector<std::shared_ptr<PER_HANDLE_DATA>>v_User;



v_User = m_Net->GetUserVector();



std::vector<QListWidgetItem*>v_UserList;



if (!v_User.empty()){

auto it = v_User.begin();


while (it != v_User.end()){

QListWidgetItem* item = new QListWidgetItem(*p_usericon,tr((*it)->GetName().c_str()));


v_UserList.push_back(item);


it++;


}

}

SetListItem(v_UserList);


}

void Tester::SetText(){


p_Edit->setText(m_ChatString);


}

//如果我们用的是MFC,我们完全不需要这个计时器,但是我们现在用的是Qt,所以我们只能用一个计时器来查询信息(也许可能是我接触Qt的时间不多,对他的了解不透彻,觉得在跨线程中MFC做的比Qt好很多,当然在Qt中我们可以用一个计时器来解决窗口部件跨线程的问题


void Tester::timerEvent(QTimerEvent* e){


// 还记得我们网络库里面有一个bool量吗?当时就给大家说他是用在窗口程序中的,就是为Qt准备的,如果是MFC的话就不需要那东西,但是我们现在需要用他来执行通知,使用完成之后,我们会将他重新设置为false


bool b = m_Net->GetupdateUserInfo();



if (b){

this->SetListIterm();


}

if (b_setText){

this->SetText();


b_setText = false;


}
}

//最后我们处理消息的函数就这么简单,因为关于消息的收发都在网络类中自动完成,所以我们只需要取出收到的信息

void Tester::ProcMsg(PER_HANDLE_DATA* pData, std::string&

_str)
{

QDateTime tm = QDateTime::currentDateTime();



QString str = tm.toString("

MM/dd/yyyy hh:mm:ss ddd"

);



m_ChatString += str;



m_ChatString += "

"

;



wchar_t*
temp = nullptr;



stow(&

temp, _str);

//如果没有这步的转换,中文将会显示乱码,可能大家会说怎么不适用翻译呢?用过了,依然没效果,所以最后只好使用显示转码来显示中文的
m_ChatString += QString::fromStdWString(temp);



m_ChatString += "

rn"

;



std::ofstream outFile("

InfoLog.txt"

,std::ios_base::app);



outFile <<m_ChatString.toStdString();



b_setText = true;



delete[] temp;

//使用完成之后记得释放

temp = nullptr;


}

=====================================


最终我们的小程序使用起来如下图所示,每个人的聊天记录都会储存在这里,而且写进日志,驱动程序如下:

//=============main=============

int main(int argc, char *argv[])
{

QApplication a(argc, argv);



Tester w;


return a.exec();


}

==================================

运行效果如下:

//================================

第九十一讲 综合运用(6)


======================================

我将列表框响应右键消息的代码省略了,如果大家有兴趣的,其实很简单,从QListWidget派生一个类出来,重写
void contextMenuEvent(QContextMenuEvent*)
函数,他就能够响应鼠标右键的消息了。

ok,就这么多吧,关于怎么去写一个服务器程序,想必大家都有自己的想法了,我们的这个实例开发就算是结束了,对了,最近翻了一下More Effective C++,重新审视了我对引用计数的使用方法,发现我和他的用法很不一样,这样吧,假设我们要写一个string库,因为我们对效率有相当的苛求,所以希望这个string库是带引用计数的,大家可以回头去看看我给大家介绍的引用计数的知识,然后试着来写一个这样的库,我们希望得到的结果如同下面这样:

//=======Test MyString==========

MyString str("

Hello"

);

MyString str1(str);

MyString str2;

str2 = str;

//到这里,我们可能保证上面三个MyString对象的数据共享,但是如果对任意一个对象使用[]操作符时,他不再共享数据,如下:

str2[2] = 'w';

这时,str2不再和str,str1共享数据。

==================================

下一讲我们来展示两种构造方法,大家也随便比较一下,是我使用的方法更好一些呢还是More Effective C++上面展示的方法更为高效一些。



=====================================

回复D或者d直接查看目录,回复数字查看当前相应章节内容

原文始发于微信公众号(

C/C++的编程教室

):第九十一讲 综合运用(6)

|

发表评论