QQ Project: 11 - Imitate Tencent QQ Chat Software

Keywords: Qt Attribute socket Windows

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

 

Posted by bbmak on Thu, 05 Sep 2019 23:23:51 -0700