Main interface:
- Implement a QQ Friends List, the list of people have been identified
- Click on a friend and pop up the corresponding chat window
Chat window interface design:
- Upper left is a Text Browser widget that displays user chat information
- At the bottom is a Text Edit widget to enter chat information.
- The one-line tool control between the two is used to set the font, font size and format of the chat text, and is equipped with buttons such as "Change font color", "Transfer file", "Save chat record" and "Clear chat record".
- On the right is a Table Widget widget that displays the list of users logged in.
Development steps:
- Interface design: the design of the above two interfaces
- (2) Implementing basic chat and conversation functions: using UDP broadcast to chat messages in groups
- (3) File Transfer: Using TCP to implement, the server and client are needed respectively.
- (4) Additional functions: setting chat font format, saving and clearing chat records, etc.
I. Project Creation
- New Qt Gui application, project name is "QQChat", base class is QWidget, class name takes default value
- Add the following lines of code to the. h and. cpp files that are used in Chinese to avoid chaos caused by Chinese.
#if _MSC_VER >=1600 #pragma execution_character_set("utf-8") #endif
2. Design of Friends List Interface
- The Friends List Interface is implemented with the QToolBox class, and the buttons with pictures in the interface are implemented with the QToolButton class.
Development steps:
- Step 1:Right-click on the project to add“ C++Class, the name of the class is“ Drawer",The base class is named“ QToolBox"
- Step 2:Change header files and constructors (because we need to inherit this window from QToolBox),So you need to change the class name that it inherits.
#ifndef DRAWER_H #define DRAWER_H #include <QToolBox> #include <QToolButton> #include "widget.h" class Drawer : public QToolBox { Q_OBJECT public: Drawer(QWidget *parent=0,Qt::WindowFlags f=0); }; #endif // DRAWER_H
Drawer::Drawer(QWidget *parent,Qt::WindowFlags f) :QToolBox(parent,f) { }
- Step 3: Write header files and define nine QToolButton controls as users
#include <QToolBox> #include <QToolButton> class Drawer : public QToolBox { public: Drawer(); private: QToolButton *toolBtn1; QToolButton *toolBtn2; QToolButton *toolBtn3; QToolButton *toolBtn4; QToolButton *toolBtn5; QToolButton *toolBtn6; QToolButton *toolBtn7; QToolButton *toolBtn8; QToolButton *toolBtn9; };
- Step 4: Create a "images" catalog under the project catalog, and put pictures in it, which can be used as user's avatar and some column of tool pictures. Then right-click the project and import the images as resource files
- Step 5: Write the constructor and create nine users
#include "drawer.h" #include <QGroupBox> #include <QVBoxLayout> Drawer::Drawer(QWidget *parent,Qt::WindowFlags f) :QToolBox(parent,f) { setWindowTitle(tr("Myself QQ 2017")); //Setting the title of the main form setWindowIcon(QPixmap(":/pic/images/qq.png")); //Setting the title bar icon of the main form toolBtn1 =new QToolButton; toolBtn1->setText(tr("Water-drifting Oyster")); //Set the text of the button toolBtn1->setIcon(QPixmap(":/pic/images/spqy.png")); //Set the icon of the button toolBtn1->setIconSize(QPixmap(":/pic/images/spqy.png").size()); //Set the button to the same size as the icon toolBtn1->setAutoRaise(true); //When the mouse leaves, the button automatically restores to a pop-up state. toolBtn1->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); //The text of the setup button is displayed next to the icon. Each button is set in the same way. toolBtn2 =new QToolButton; toolBtn2->setText(tr("Dreams are full of memories")); toolBtn2->setIcon(QPixmap(":/pic/images/ymrl.png")); toolBtn2->setIconSize(QPixmap(":/pic/images/ymrl.png").size()); toolBtn2->setAutoRaise(true); toolBtn2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBtn3 =new QToolButton; toolBtn3->setText(tr("Beijing Publisher")); toolBtn3->setIcon(QPixmap(":/pic/images/qq.png")); toolBtn3->setIconSize(QPixmap(":/pic/images/qq.png").size()); toolBtn3->setAutoRaise(true); toolBtn3->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBtn4 =new QToolButton; toolBtn4->setText(tr("Cherry")); toolBtn4->setIcon(QPixmap(":/pic/images/Cherry.png")); toolBtn4->setIconSize(QPixmap(":/pic/images/Cherry.png").size()); toolBtn4->setAutoRaise(true); toolBtn4->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBtn5 =new QToolButton; toolBtn5->setText(tr("Indifferent")); toolBtn5->setIcon(QPixmap(":/pic/images/dr.png")); toolBtn5->setIconSize(QPixmap(":/pic/images/dr.png").size()); toolBtn5->setAutoRaise(true); toolBtn5->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBtn6 =new QToolButton; toolBtn6->setText(tr("Jiao Jiao girl")); toolBtn6->setIcon(QPixmap(":/pic/images/jj.png")); toolBtn6->setIconSize(QPixmap(":/pic/images/jj.png").size()); toolBtn6->setAutoRaise(true); toolBtn6->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBtn7 =new QToolButton; toolBtn7->setText(tr("Falling water without trace")); toolBtn7->setIcon(QPixmap(":/pic/images/lswh.png")); toolBtn7->setIconSize(QPixmap(":/pic/images/lswh.png").size()); toolBtn7->setAutoRaise(true); toolBtn7->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBtn8 =new QToolButton; toolBtn8->setText(tr("Green ink is warm and warm")); toolBtn8->setIcon(QPixmap(":/pic/images/qmnn.png")); toolBtn8->setIconSize(QPixmap(":/pic/images/qmnn.png").size()); toolBtn8->setAutoRaise(true); toolBtn8->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBtn9 =new QToolButton; toolBtn9->setText(tr("Speechless")); toolBtn9->setIcon(QPixmap(":/pic/images/wy.png")); toolBtn9->setIconSize(QPixmap(":/pic/images/wy.png").size()); toolBtn9->setAutoRaise(true); toolBtn9->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); QGroupBox *groupBox=new QGroupBox; //Create an instance of the QGroupBpx class QVBoxLayout *layout=new QVBoxLayout(groupBox); //Create an instance of QVBoxLayout class to set the layout of buttons layout->setMargin(20);//Display Spacing of Forms in Layout layout->setAlignment(Qt::AlignLeft);//Display position of forms in layout layout->addWidget(toolBtn1); //Add buttons to the layout layout->addWidget(toolBtn2); layout->addWidget(toolBtn3); layout->addWidget(toolBtn4); layout->addWidget(toolBtn5); layout->addWidget(toolBtn6); layout->addWidget(toolBtn7); layout->addWidget(toolBtn8); layout->addWidget(toolBtn9); layout->addStretch();//Insert a placeholder this->addItem((QWidget*)groupBox,tr("Group members")); }
3. Design of Chat Window
Step 1:
- Enter the "widget.ui" design mode and set the width and height of the interface to 730 and 450 respectively.
Step 2:
- Design interface
- The control type and name of the relevant serial number are as follows:
Step 3:
Set the properties of seven Tool Button components:
- (1) The autoRaise attribute for each component is selected, and iconSize is set to 26*26
- (2) Setting icons (resource pictures imported above)
- (3) Selection of checkable attributes for the first three Tool Button s
- (4) ToolTips of seven Tool Button s are set as "bold", "tilt", "underscore", "change font color", "transfer file", "save chat record" and "empty chat record" respectively.
Step 4:
- Set the font size drop-down list, set the range of "8-22" (same as Tencent QQ), double-click component settings can be
- Set its current Index property to 4, the font size of 12
Step 5:
- Set the user list Table Widget control, set its selectionMode property to SingleSelection, and set selectBehavior to SelectRows
- Cancel showGrid
- Double-click on the component to add "User Name" and "IP Address" columns
4. Connect Friends List with Chat Window
- When you click on a picture button in the friends list, you can pop up a user's chat window.
Step 1:
- In " drawer.h"Add the following to the header file
//The previous code is omitted for brevity #include "widget.h" class Drawer : public QToolBox { private: //// Chat windows corresponding to nine users Widget *chatWidget1; Widget *chatWidget2; Widget *chatWidget3; Widget *chatWidget4; Widget *chatWidget5; Widget *chatWidget6; Widget *chatWidget7; Widget *chatWidget8; Widget *chatWidget9; private slots: //Display 9 user chat window functions void showChatWidget1(); void showChatWidget2(); void showChatWidget3(); void showChatWidget4(); void showChatWidget5(); void showChatWidget6(); void showChatWidget7(); void showChatWidget8(); void showChatWidget9(); };
Step 2:
- In " drawer.cpp"9 showChatWidget()Functions, codes are similar
void Drawer::showChatWidget1() { chatWidget1 = new Widget(0,toolBtn1->text()); //Create an instance of a Widget class with the text of toolBtn1 as the user name, corresponding to a chat window chatWidget1->setWindowTitle(toolBtn1->text()); //Setting the title of the window chatWidget1->setWindowIcon(toolBtn1->icon()); //Setting window icon chatWidget1->show(); //Display window } void Drawer::showChatWidget2() { chatWidget2 = new Widget(0,toolBtn2->text()); chatWidget2->setWindowTitle(toolBtn2->text()); chatWidget2->setWindowIcon(toolBtn2->icon()); chatWidget2->show(); } void Drawer::showChatWidget3() { chatWidget3 = new Widget(0,toolBtn3->text()); chatWidget3->setWindowTitle(toolBtn3->text()); chatWidget3->setWindowIcon(toolBtn3->icon()); chatWidget3->show(); } void Drawer::showChatWidget4() { chatWidget4 = new Widget(0,toolBtn4->text()); chatWidget4->setWindowTitle(toolBtn4->text()); chatWidget4->setWindowIcon(toolBtn4->icon()); chatWidget4->show(); } void Drawer::showChatWidget5() { chatWidget5 = new Widget(0,toolBtn5->text()); chatWidget5->setWindowTitle(toolBtn5->text()); chatWidget5->setWindowIcon(toolBtn5->icon()); chatWidget5->show(); } void Drawer::showChatWidget6() { chatWidget6 = new Widget(0,toolBtn6->text()); chatWidget6->setWindowTitle(toolBtn6->text()); chatWidget6->setWindowIcon(toolBtn6->icon()); chatWidget6->show(); } void Drawer::showChatWidget7() { chatWidget7 = new Widget(0,toolBtn7->text()); chatWidget7->setWindowTitle(toolBtn7->text()); chatWidget7->setWindowIcon(toolBtn7->icon()); chatWidget7->show(); } void Drawer::showChatWidget8() { chatWidget8 = new Widget(0,toolBtn8->text()); chatWidget8->setWindowTitle(toolBtn8->text()); chatWidget8->setWindowIcon(toolBtn8->icon()); chatWidget8->show(); } void Drawer::showChatWidget9() { chatWidget9 = new Widget(0,toolBtn9->text()); chatWidget9->setWindowTitle(toolBtn9->text()); chatWidget9->setWindowIcon(toolBtn9->icon()); chatWidget9->show(); }
Step 3:
- Establish a signal and slot for each user's Avatar button. When clicked, execute showChatWidget()Function to pop up the chat window
connect(toolBtn1,SIGNAL(clicked()),this,SLOT(showChatWidget1())); connect(toolBtn2,SIGNAL(clicked()),this,SLOT(showChatWidget2())); connect(toolBtn3,SIGNAL(clicked()),this,SLOT(showChatWidget3())); connect(toolBtn4,SIGNAL(clicked()),this,SLOT(showChatWidget4())); connect(toolBtn5,SIGNAL(clicked()),this,SLOT(showChatWidget5())); connect(toolBtn6,SIGNAL(clicked()),this,SLOT(showChatWidget6())); connect(toolBtn7,SIGNAL(clicked()),this,SLOT(showChatWidget7())); connect(toolBtn8,SIGNAL(clicked()),this,SLOT(showChatWidget8())); connect(toolBtn9,SIGNAL(clicked()),this,SLOT(showChatWidget9()));
Step 4:
- Because the default constructor of the Widget class has only one parameter, but when we constructed the Widget above, the constructor used two parameters, so we need to change the constructor of the Widget class.
class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent,QString username); ~Widget(); private: Ui::Widget *ui; };
Widget::Widget(QWidget *parent,QString username) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); }
Step 5:
- Write "main.cpp" content, as follows
- When running the program, it will pop up the Friends List interface, and then click on a Friends Head to pop up the chat window.
#include "drawer.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); /*Widget w; w.show();*/ Drawer drawer; drawer.resize(250,700); drawer.show(); return a.exec(); }
5. Realization of Chat Conversation Function
- In chatting, each user is equal and an endpoint. Users log in to broadcast, and users exit and send messages using UDP broadcast to inform all users
- When transferring files, the sender is the server and the receiver is the client.
- The server sends its file name by UDP before sending the file. If the client user refuses to receive the file, it sends back a UDP response. If the client receives the same file, the server sends the file to the client by TCP connection.
Step 1:
- Define an enumeration variable in the "widget.h" header file to distinguish different UDP broadcast message types
enum MsgType{Msg,UsrEnter,UsrLeft,FileName,Refuse};
Step 2:
- Definition Widget Relevant members and functions of classes
#include <QUdpSocket> #include <QHostInfo> #include <QMessageBox> #include <QScrollBar> #include <QDateTime> #include <QNetworkInterface> #include <QProcess> class Widget : public QWidget { protected: void usrEnter(QString usrname,QString usrname); //Processing new users to join void usrLeft(QString usrname,QString time); //Handling user departure void sndMsg(MsgType type,QString srvaddr=""); //Send UDP broadcast message QString getIP(); //Get IP address QString getUsr(); //Get the username QString getMsg(); //Get chat information private: QUdpSocket *udpSocket; qint16 port; QString uName; private slots: void processPendingDatagrams(); //Accept UDP messages };
Step 3:
- Implementing constructors
Widget::Widget(QWidget *parent,QString username) : QWidget(parent), ui(new Ui::Widget) { uName=username; udpSocket=new QUdpSocket(this); port=23232; udpSocket->bind(port,QUdpSocket::ShareAddress|QUdpSocket::ReuseAddressHint); connect(udpSocket,SIGNAL(readyRead()),this,SLOT(processPendingDatagrams())); sndMsg(UsrEnter); /Send one to the broadcast UsrEnter Message indicating that you have joined the broadcasting group }
Step 4:
- Implementing the sending of information sndMsg function
void Widget::sndMsg(MsgType type, QString srvaddr) { QByteArray data; QDataStream out(&data, QIODevice::WriteOnly); QString address = getIP(); /*Write information type, user name to the data to be sent. * Among them, type is used to partition information types at the receiving end, so that different types of information can be processed differently.*/ out << type << getUsr(); switch(type) { case Msg : //If it's a normal chat message Msg if (ui->msgTxtEdit->toPlainText() == "") {//First, determine whether the message sent is empty QMessageBox::warning(0,tr("warning"),tr("Sending content cannot be empty"),QMessageBox::Ok); return; } out << address << getMsg();//Write native IP and chat message text to the sent data ui->msgBrowser->verticalScrollBar()->setValue(ui->msgBrowser->verticalScrollBar()->maximum()); break; case UsrEnter : //If new users join out << address; //Simple Write IP Address break; case UsrLeft : //Users leave without doing anything break; case FileName : { //If you send a file, you don't process it here, and then add the code. break; } case Refuse : //Refuse to accept files, do not process them here, and add code later. break; } //After the information is processed, UDP broadcasting is performed using the writeDatagram function. udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast, port); }
Step 5:
- Achieving the receipt of messages processPendingDatagrams function
void Widget::processPendingDatagrams() { while(udpSocket->hasPendingDatagrams()) //If there is readable data { QByteArray datagram; datagram.resize(udpSocket->pendingDatagramSize()); udpSocket->readDatagram(datagram.data(), datagram.size());//Read data QDataStream in(&datagram, QIODevice::ReadOnly); int msgType; in >> msgType; //Get the type of message QString usrName,ipAddr,msg; //Get the time of the current system QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); switch(msgType) //Determine the type of message { case Msg: //If it's a chat message, get its username, IP, and message data in >> usrName >> ipAddr >> msg; ui->msgBrowser->setTextColor(Qt::blue); ui->msgBrowser->setCurrentFont(QFont("Times New Roman",12)); ui->msgBrowser->append("[ " +usrName+" ] "+ time); ui->msgBrowser->append(msg); break; case UsrEnter: //If a new user joins, get his username and IP address in >>usrName >>ipAddr; usrEnter(usrName,ipAddr); //Call the usrEnter function for processing break; case UsrLeft: //If the user exits in >>usrName; usrLeft(usrName,time); //Calling the usrLeft function to handle user rollout break; case FileName: {//Do not deal with it first, then add the code. break; } case Refuse: {//Do not deal with it first, then add the code. break; } } } }
Step 6:
- Implementing functions to handle user joins and exits
//User Accession void Widget::usrEnter(QString usrname, QString ipaddr) { bool isEmpty = ui->usrTblWidget->findItems(usrname, Qt::MatchExactly).isEmpty(); //Determine whether the user has previously been added to the user list, if not if (isEmpty) { //Adding relevant information to relevant components QTableWidgetItem *usr = new QTableWidgetItem(usrname); QTableWidgetItem *ip = new QTableWidgetItem(ipaddr); ui->usrTblWidget->insertRow(0); ui->usrTblWidget->setItem(0,0,usr); ui->usrTblWidget->setItem(0,1,ip); ui->msgBrowser->setTextColor(Qt::gray); ui->msgBrowser->setCurrentFont(QFont("Times New Roman",10)); ui->msgBrowser->append(tr("%1 On-line!").arg(usrname)); ui->usrNumLbl->setText(tr("Number of people online:%1").arg(ui->usrTblWidget->rowCount())); //This function is called again to inform the new user of the user information at the previous online endpoints, otherwise the new user will not be able to know the previous online user. sndMsg(UsrEnter); } }
//User exit void Widget::usrLeft(QString usrname, QString time) { //Delete the user from the user list and set the relevant display information int rowNum = ui->usrTblWidget->findItems(usrname, Qt::MatchExactly).first()->row(); ui->usrTblWidget->removeRow(rowNum); ui->msgBrowser->setTextColor(Qt::gray); ui->msgBrowser->setCurrentFont(QFont("Times New Roman", 10)); ui->msgBrowser->append(tr("%1 to %2 Leave!").arg(usrname).arg(time)); ui->usrNumLbl->setText(tr("Number of people online:%1").arg(ui->usrTblWidget->rowCount())); }
Step 7:
- Implementing a series of information acquisition functions
// Get IP address QString Widget::getIP() { QList<QHostAddress> list = QNetworkInterface::allAddresses(); foreach (QHostAddress addr, list) { if(addr.protocol() == QAbstractSocket::IPv4Protocol) return addr.toString(); } return 0; }
//Get the current user name QString Widget::getUsr() { return uName; }
//Get the message entered by the user and make some settings QString Widget::getMsg() { QString msg = ui->msgTxtEdit->toHtml(); ui->msgTxtEdit->clear(); ui->msgTxtEdit->setFocus(); return msg; }
Step 8:
- Trigger function of sending button
void Widget::on_sendBtn_clicked() { sndMsg(Msg); }
VI. Process and Principle of File Transfer
- File transfer is implemented by TCP, but before file transfer we use UDP broadcast to tell each other whether we need to receive files.
Step 1:
- Selecting a user in the user list and then selecting the Send File button opens a Send File dialog box.
Step 2:
- In the dialog box, select the file to be sent, and then click the "Send button", then the program will establish a TCP server and monitor, then use UDP broadcast to send the file name to the receiver, the receiver pops up a prompt box to ask whether to receive the specified file. If the client refuses to receive the file, it cancels the file transfer and closes the TCP server; if it agrees, it carries out normal TCP data transmission.
Step 3:
- If it agrees to receive, a TCP client is created at the receiving end, and then both sides establish a TCP connection for file transfer; if it refuses to receive, the client will use UDP broadcasting to return the rejected message to the sender and cancel the file transfer once the sender receives the message.
7. Establishment of File Transfer Server
Step 1:
- Add Qt Designer Interface Class, named "server"
Step 2:
- Design "server.ui" interface
- Change font attribute of 1 to 12; font attribute of_to 10; default value of 4 to 0
Step 3:
- stay server.h Add variables and function declarations to the header file (only the customized part is given)
#include <QTime> #include <QFile> #include <QTcpServer> #include <QTcpSocket> #include <QMessageBox> #include <QFileDialog> #include <QDebug> class QFile; class QTcpServer; class QTcpSocket; class server : public QDialog { public: void initSrv(); //Some member variables in the initialization class //This function is called in the main interface Widget class when receiving UDP messages that the client refuses to receive files. void refused(); //Close the server protected: void closeEvent(QCloseEvent *); private: qint16 tPort; QTcpServer *tSrv; //Server-side object QString fileName; //File name (including path) QString theFileName;//File name (excluding path) QFile *locFile; //Documents to be sent qint64 totalBytes; //Total number of bytes to be sent qint64 bytesWritten; //Number of bytes sent qint64 bytesTobeWrite; //Number of bytes to be sent qint64 payloadSize; //Initialized as a constant QByteArray outBlock; //Cache once-sent data QTcpSocket *clntConn; //Sockets for Client Connection QTime time; //A timer that counts the time used for transmission. private slots: void sndMsg(); //send data void updClntProgress(qint64 numBytes); //Update progress bar signals: void sndFileName(QString fileName); };
Step 4:
- Constructor and initSrv()function
server::server(QWidget *parent) : QDialog(parent), ui(new Ui::server) { setFixedSize(400,207); //Fixed dialog box size 400*207 tPort = 5555; tSrv = new QTcpServer(this); //Creating TCP Server Objects //If there is a client connection, execute the sndMsg function connect(tSrv, SIGNAL(newConnection()), this, SLOT(sndMsg())); initSrv(); //Initialization of TCP Server }
void server::initSrv() { payloadSize = 64*1024; totalBytes = 0; bytesWritten = 0; bytesTobeWrite = 0; ui->sStatusLbl->setText(tr("Please select the file to be transmitted")); ui->progressBar->reset(); ui->sOpenBtn->setEnabled(true); ui->sSendBtn->setEnabled(false); tSrv->close(); }
Step 5:
- Initialize data transmission sndMsg()function
void server::sndMsg() { ui->sSendBtn->setEnabled(false); clntConn = tSrv->nextPendingConnection(); //Waiting for client connection (blocking here, waiting for client connection) connect(clntConn,SIGNAL(bytesWritten(qint64)),this,SLOT(updClntProgress(qint64))); ui->sStatusLbl->setText(tr("Start transferring files %1 !").arg(theFileName)); locFile = new QFile(fileName); if(!locFile->open((QFile::ReadOnly))){ //Open the file read-only QMessageBox::warning(this, tr("application program"), tr("Unable to read files %1:\n%2").arg(fileName).arg(locFile->errorString())); return; } totalBytes = locFile->size(); //Get the size of the file to be sent QDataStream sendOut(&outBlock, QIODevice::WriteOnly);//Write and read an outBlock in a variable of type QDataStream sendOut.setVersion(QDataStream::Qt_5_8); time.start(); //Start Timing //Remove the path part of the file by using the right() function, leaving only the file name. QString curFile = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1); //Construct a temporary header and append the value to the total Bytes field to complete the actual number of bytes to be sent sendOut << qint64(0) << qint64(0) << curFile; totalBytes += outBlock.size(); //Point the read-write operation to the head sendOut.device()->seek(0); //Fill in the actual total length and file length sendOut << totalBytes << qint64((outBlock.size() - sizeof(qint64)*2)); //Send the header file and modify bytes to be sent bytesTobeWrite = totalBytes - clntConn->write(outBlock); outBlock.resize(0);//Clear the buffer for next use }
Step 6:
- Initialize update progress bar function updClntProgress
void server::updClntProgress(qint64 numBytes) { qApp->processEvents(); //Used to transfer large files so that the interface does not freeze bytesWritten += (int)numBytes; if (bytesTobeWrite > 0) { outBlock = locFile->read(qMin(bytesTobeWrite, payloadSize)); bytesTobeWrite -= (int)clntConn->write(outBlock); outBlock.resize(0); } else { locFile->close(); } ui->progressBar->setMaximum(totalBytes); ui->progressBar->setValue(bytesWritten); float useTime = time.elapsed(); //Get the time it takes for the timer to start counting up to now double speed = bytesWritten / useTime; ui->sStatusLbl->setText(tr("has been sent %1MB (%2MB/s) \n common%3MB Time used:%4 second\n Estimate the remaining time:%5 second") .arg(bytesWritten / (1024*1024)) .arg(speed*1000 / (1024*1024), 0, 'f', 2) .arg(totalBytes / (1024 * 1024)) .arg(useTime/1000, 0, 'f', 0) .arg(totalBytes/speed/1000 - useTime/1000, 0, 'f', 0)); if(bytesWritten == totalBytes) { locFile->close(); tSrv->close(); ui->sStatusLbl->setText(tr("transfer file %1 Success").arg(theFileName)); } }
Step 7:
Response function of a series of buttons
- "The Response Function of "Open Button"
void server::on_sOpenBtn_clicked() { //Pop up a file dialog box, select the file to send and update the text label and button status fileName = QFileDialog::getOpenFileName(this); if(!fileName.isEmpty()) { theFileName = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1); ui->sStatusLbl->setText(tr("The files to be transmitted are:%1 ").arg(theFileName)); ui->sSendBtn->setEnabled(true); ui->sOpenBtn->setEnabled(false); } }
- Response function of Close Button
void server::on_sCloseBtn_clicked() { //Close the server and then close the dialog box if(tSrv->isListening()) { tSrv->close(); if (locFile->isOpen()) locFile->close(); clntConn->abort(); } close(); }
- The Response Function of "Send Button"
void server::on_sSendBtn_clicked() { //Set the server to listen, then send sndFileName signal, associate the signal in the main interface, and send the file name to the receiver using UDP broadcasting if(!tSrv->listen(QHostAddress::Any,tPort))//Start listening { qDebug() << tSrv->errorString(); close(); return; } ui->sStatusLbl->setText(tr("Waiting for reception... ...")); emit sndFileName(theFileName); }
Step 8:
- Handler for window closure events
void server::closeEvent(QCloseEvent *) { on_sCloseBtn_clicked(); }
Step 9:
- Implementation of refused function
// This function is called in the main interface Widget class when receiving UDP messages that the client refuses to receive files. void server::refused() { // Close the server if the client refuses to receive files tSrv->close(); UI - > sStatusLbl - > setText (tr ("the other party refuses to accept! ()) }
8. Establishment of File Transfer Client
Step 1:
- Add Qt Designer Interface Class, named "client"
Step 2:
- Design "client.ui" interface
Step 3:
- client.h Initialization
#include <QHostAddress> #include <QFile> #include <QTime> #include <QTcpSocket> #include <QDebug> #include <QMessageBox> class QTcpSocket; class client : public QDialog { public: void setHostAddr(QHostAddress addr); //Get the IP address of the sender void setFileName(QString name); //Get the file save path protected: void closeEvent(QCloseEvent *); private: QTcpSocket *tClnt; //Client socket class quint16 blockSize; QHostAddress hostAddr; //Service Address qint16 tPort; //Ports on the server side qint64 totalBytes; //Total number of bytes to be received qint64 bytesReceived; //Number of bytes received qint64 fileNameSize; QString fileName; QFile *locFile; //Files to be received QByteArray inBlock; //Cache once received data QTime time; private slots: void newConn(); //Connect to the server void readMsg(); //Read file data void displayErr(QAbstractSocket::SocketError sockErr); //Display error message };
Step 4:
- Constructor Initializers
client::client(QWidget *parent) : QDialog(parent), ui(new Ui::client) { ui->setupUi(this); setFixedSize(400,190); totalBytes = 0; bytesReceived = 0; fileNameSize = 0; tClnt = new QTcpSocket(this); //Create client socket objects tPort = 5555; //Correlation signal and slot connect(tClnt, SIGNAL(readyRead()), this, SLOT(readMsg())); //If there are errors, the displayErr slot function is executed connect(tClnt, SIGNAL(error(QAbstractSocket::SocketError)), this,SLOT(displayErr(QAbstractSocket::SocketError))); }
- Print error message displayErr function
void client::displayErr(QAbstractSocket::SocketError sockErr) { switch(sockErr) { case QAbstractSocket::RemoteHostClosedError : break; default : qDebug() << tClnt->errorString(); } }
- Get the IP address of the sender (this function provides the IP address of the sender in the main interface of the Widget class)
void client::setHostAddr(QHostAddress addr) { hostAddr = addr; newConn(); }
- Get the file save path (pop-up file dialog box in the Widget class main interface to select the file save path, to provide this function in the client to get the path)
void client::setFileName(QString name) { locFile = new QFile(name); }
Step 5:
- Connecting server-side functions
void client::newConn() { blockSize = 0; tClnt->abort(); tClnt->connectToHost(hostAddr, tPort); time.start(); // start timing }
- Receiving server-side file data function
void client::readMsg() { QDataStream in(tClnt); in.setVersion(QDataStream::Qt_5_8); float useTime = time.elapsed(); if (bytesReceived <= sizeof(qint64)*2) { if ((tClnt->bytesAvailable() >= sizeof(qint64)*2) && (fileNameSize == 0)) { in>>totalBytes>>fileNameSize; bytesReceived += sizeof(qint64)*2; } if((tClnt->bytesAvailable() >= fileNameSize) && (fileNameSize != 0)){ in>>fileName; bytesReceived +=fileNameSize; if(!locFile->open(QFile::WriteOnly)){ QMessageBox::warning(this,tr("application program"),tr("Unable to read files %1:\n%2.").arg(fileName).arg(locFile->errorString())); return; } } else { return; } } if (bytesReceived < totalBytes) { bytesReceived += tClnt->bytesAvailable(); inBlock = tClnt->readAll(); locFile->write(inBlock); inBlock.resize(0); } ui->progressBar->setMaximum(totalBytes); ui->progressBar->setValue(bytesReceived); double speed = bytesReceived / useTime; ui->cStatusLbl->setText(tr("Received %1MB (%2MB/s) \n common%3MB When used:%4 second\n Estimate the remaining time:%5 second") .arg(bytesReceived / (1024*1024)) .arg(speed*1000/(1024*1024),0,'f',2) .arg(totalBytes / (1024 * 1024)) .arg(useTime/1000,0,'f',0) .arg(totalBytes/speed/1000 - useTime/1000,0,'f',0)); if(bytesReceived == totalBytes) { locFile->close(); tClnt->close(); ui->cStatusLbl->setText(tr("receive files %1 Complete").arg(fileName)); } }
Step 6:
Response function of a series of buttons
- Response function of Cancel Button
void client::on_cCancleBtn_clicked() { tClnt->abort(); if (locFile->isOpen()) locFile->close(); }
- "Close button response function"
void client::on_cCloseBtn_clicked() { tClnt->abort(); if (locFile->isOpen()) locFile->close(); close(); }
- Window Close Event Function Rewrite
void client::closeEvent(QCloseEvent *) { on_cCloseBtn_clicked(); }
9. Main Interface Control
- After the above server and client are designed, you can start sending and receiving files.
Step 1:
- In " widget.h"Declare variables and functions in
class server; #include "server.h" #include "client.h" #include <QFileDialog> class Widget : public QWidget { protected: //Used to determine whether to receive a file name UDP message when it receives it void hasPendingFile(QString usrname, QString srvaddr,QString clntaddr, QString filename); private: QString fileName; server *srv; //Server object private slots: void getFileName(QString name); //Get the file name sent by the server sndFileName() signal };
Step 2:
- Create server objects in the widget.cpp constructor
Widget::Widget(QWidget *parent,QString username) : QWidget(parent), ui(new Ui::Widget) { srv=new server(this); connect(srv, SIGNAL(sndFileName(QString)), this, SLOT(getFileName(QString))); }
- Getting File Name Function
void Widget::getFileName(QString name) { fileName = name; sndMsg(FileName);//Send UDP broadcast of FileName type }
Step 3:
- Response function of transfer file button in main interface
void Widget::on_sendTBtn_clicked() { // Determine whether the user is selected and then pop up the Send dialog box if(ui->usrTblWidget->selectedItems().isEmpty()) { QMessageBox::warning(0, tr("Select User"), tr("Select Target User First! "), QMessageBox:: Ok; return; } srv->show(); srv->initSrv(); }
Step 4:
- Change the original sndMsg()In function FileName nucleus Refuse Code at
void Widget::sndMsg(MsgType type, QString srvaddr) { switch(type) { case FileName: { int row = ui->usrTblWidget->currentRow(); QString clntaddr = ui->usrTblWidget->item(row, 1)->text(); out << address << clntaddr << fileName; break; } case Refuse : out << srvaddr; break; } }
- Change the code at FileName and Refuse in the processPendingDatagrams() function
void Widget::processPendingDatagrams() { while(udpSocket->hasPendingDatagrams()) //If there is readable data { switch(msgType) { case FileName: { in >> usrName >> ipAddr; QString clntAddr, fileName; in >> clntAddr >> fileName; //Determine whether to receive the file hasPendingFile(usrName, ipAddr, clntAddr, fileName); break; } case Refuse: { in >> usrName; QString srvAddr; in >> srvAddr; QString ipAddr = getIP(); //Determine whether the program is the sender and, if so, execute the refused function of the server if(ipAddr == srvAddr) { srv->refused(); } break; } } } }
Step 5:
- hasPendingFile Used to receive file names UDP Determine whether to receive the file when the message is received
void Widget::hasPendingFile(QString usrname, QString srvaddr,QString clntaddr, QString filename) { QString ipAddr = getIP(); if(ipAddr == clntaddr) { //A dialog box pops up to let the user decide whether to receive the file or not. int btn = QMessageBox::information(this,tr("document of acceptance"),tr("Come from%1(%2)Documents:%3,Is it accepted?").arg(usrname).arg(srvaddr).arg(filename),QMessageBox::Yes,QMessageBox::No); if (btn == QMessageBox::Yes) {//If received, client objects are created to transfer files QString name = QFileDialog::getSaveFileName(0,tr("Save files"),filename); if(!name.isEmpty()) { client*clnt = new client(this); clnt->setFileName(name); clnt->setHostAddr(QHostAddress(srvaddr)); clnt->show(); } } else {//UDP broadcasting that refuses to receive or send a rejection message sndMsg(Refuse, srvaddr); } } }
So far, the basic functions have been completed.
Change fonts, fonts and colors
Change fonts
- Add current FontChanged (QFont) signal function of FontComboBox component in "widget.ui"
void Widget::on_fontCbx_currentFontChanged(const QFont &f) { // First get the currently selected font, and then use the changed font in the message editor ui->msgTxtEdit->setCurrentFont(f); ui->msgTxtEdit->setFocus(); }
Change font size
- Add the current IndexChanged (QString) signal function of the Combo Box component in "widget.ui"
void Widget::on_sizeCbx_currentIndexChanged(const QString &arg1) { ui->msgTxtEdit->setFontPointSize(arg1.toDouble()); ui->msgTxtEdit->setFocus(); }
Set the response function of the buttons such as bold font, tilt, underscore, etc.
- Bold button response function (clicked(bool))
void Widget::on_boldTBtn_clicked(bool checked) { if(checked) ui->msgTxtEdit->setFontWeight(QFont::Bold); else ui->msgTxtEdit->setFontWeight(QFont::Normal); ui->msgTxtEdit->setFocus(); }
- Tilt button response function (clicked(bool))
void Widget::on_italicTBtn_clicked(bool checked) { ui->msgTxtEdit->setFontItalic(checked); ui->msgTxtEdit->setFocus(); }
- Underline Button Response Function (clicked(bool))
void Widget::on_underlineTBtn_clicked(bool checked) { ui->msgTxtEdit->setFontUnderline(checked); ui->msgTxtEdit->setFocus(); }
Setting Text Colors
- Add a response function to change the color component in "widget.ui"
void Widget::on_colorTBtn_clicked() { color = QColorDialog::getColor(color,this); if(color.isValid()){ ui->msgTxtEdit->setTextColor(color); ui->msgTxtEdit->setFocus(); } }
- But you need to add the following code in the "widget.h" header file
#include <QColorDialog> class Widget : public QWidget { private: QColor color; };
Font switching
If different font formats are used in several sections of text in a text editor, you also need to add functions to enable the editor to automatically switch to the corresponding format when the cursor clicks on different text.
- Step 1: Increase header files and private slot functions
#include <QTextCharFormat> class Widget : public QWidget { private slots: void curFmtChanged(const QTextCharFormat &fmt); };
- Step 2: In the constructor, associate the signal with the slot function
Widget::Widget(QWidget *parent,QString username) : QWidget(parent), ui(new Ui::Widget) { connect(ui->msgTxtEdit, SIGNAL(currentCharFormatChanged(QTextCharFormat)),this, SLOT(curFmtChanged(const QTextCharFormat))); }
- Step 3: Implementing slot function
void Widget::curFmtChanged(const QTextCharFormat &fmt) { ui->fontCbx->setCurrentFont(fmt.font()); //If the font size is wrong (because the smallest font is 8), use 12. if (fmt.fontPointSize() < 8) { ui->sizeCbx->setCurrentIndex(4); } else { ui->sizeCbx->setCurrentIndex(ui->sizeCbx->findText(QString::number(fmt.fontPointSize()))); } ui->boldTBtn->setChecked(fmt.font().bold()); ui->italicTBtn->setChecked(fmt.font().italic()); ui->underlineTBtn->setChecked(fmt.font().underline()); color = fmt.foreground().color(); }
11. Keeping and Clearing Chat Records
Keep chat records
- Step 1: Add the protected function declaration in "widget.h"
class Widget : public QWidget { bool saveFile(const QString& filename); }
- Step 2: Implement the response function of saveBtn component in UI interface
void Widget::on_saveTBtn_clicked() { if (ui->msgBrowser->document()->isEmpty()) { QMessageBox::warning(0, tr("warning"), tr("The chat record is empty and cannot be saved!"), QMessageBox::Ok); } else { QString fname = QFileDialog::getSaveFileName(this,tr("Keep chat records"), tr("Chat Record"), tr("text(*.txt);;All documents(*.*)")); if(!fname.isEmpty()) saveFile(fname); } }
- Step 3: Implement the saveFile() function
bool Widget::saveFile(const QString &filename) { QFile file(filename); if (!file.open(QFile::WriteOnly | QFile::Text)) { QMessageBox::warning(this, tr("Save files"),tr("Unable to save files %1:\n %2").arg(filename).arg(file.errorString())); return false; } QTextStream out(&file); out << ui->msgBrowser->toPlainText(); return true; }
clickSave the file button and a file save dialog box will pop up to save the chat record as a. txt file.
Clear chat records
- Step 1: Implement the response function of clearTBtn button in UI interface
void Widget::on_clearTBtn_clicked() { ui->msgBrowser->clear(); }
- Step 2: Implement the response function of the exit button
void Widget::on_exitBtn_clicked() { close(); }
- Step 3: Write Close Events
class Widget : public QWidget { protected: void closeEvent(QCloseEvent *); }
void Widget::closeEvent(QCloseEvent *e) { sndMsg(UsrLeft); //Send an outgoing broadcast message so that other endpoints delete the user from their user list QWidget::closeEvent(e); }