1.簡介
1)基本功能
本次設計參考了《Qt及Qt Quick開發實戰精解》一書,並對其進行bug補全修正,添加些許新功能進行二次開發,基本的功能:
- 使用UDP進行組網聊天
- 使用TCP進行文件傳輸
- 能夠更改字體,字體大小,粗體,斜體,下劃線和字體顏色
- 能保存聊天記錄,清空聊天記錄等
- 能在同一個局域網下通過不同的ip地址加入組網
2)添加功能
添加功能如下:
- 修改中文亂碼問題
- 自Qt4轉移至Qt5環境
- 添加重繪事件設置窗口背景
- 支持Enter鍵發送 消息
- 對tablewidget進行優化佈局
- 其他功能修正添加
3)效果
我這裏並沒有對其進行多ip組網聊天測試,但是功能正常,清空聊天記錄和保存功能也可以正常顯示,這裏就不再過多的贅述。
2.構建
此局域網聊天工具既要作爲服務器端,又要作爲客戶端,因此以可將其作爲端到端的P2P模式。
3.基本功能框架
- 構建基本界面 – 設計是模式下構建
- 實現聊天功能 – 使用UDP來事項用戶信息的顯示和基本的聊天功能
- 實現文件傳輸功能 – 使用TCP實現快速傳輸
- 完善其他功能 – 類QQ聊i天(更改字體,保存聊天記錄等 )
1)界面構建
界面的話,需要三個界面來實現所有的功能,一個主窗口界面,一個文件發送端,一個文件接收端。構建如下
2)聊天功能實現
UDP有個最顯著的特點就是有着強大的廣播功能,聊天功能就使用UDP協議,構建組網,在同一局域網下通信,這裏只需要將程序運行在不同的ip環境下即可。
3)文件傳輸功能
使用TCP協議實現文件的快速傳輸,用TCP協議在Qt中的三次握手應用,調用界面,實現傳輸功能
4)其他功能
其他功能就是對字體的操作了,可以更改字體的顏色和大小,粗細,格式等等,通過調用Qt中font類的字體設置。
4.相關源碼
wdiget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTextCodec>
#include <QTextCharFormat>
#include <QPaintEvent>
class QUdpSocket;
class TcpServer;
namespace Ui {
class Widget;
}
// 枚舉變量標誌信息的類型,分別爲消息,新用戶加入,用戶退出,文件名,拒絕接受文件
enum MessageType{Message, NewParticipant, ParticipantLeft, FileName, Refuse};
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
void init(); //初始化背景色
protected:
void newParticipant(QString userName,
QString localHostName, QString ipAddress); //處理新用戶加入
void participantLeft(QString userName,
QString localHostName, QString time); //處理用戶離開
void sendMessage(MessageType type, QString serverAddress=""); //使用UDP廣播發送信息s
QString getIP(); //獲取ip地址
QString getUserName(); //獲取用戶名
QString getMessage(); //獲得要發送的消息
void hasPendingFile(QString userName, QString serverAddress,
QString clientAddress, QString fileName); //處理是否接收文件
bool saveFile(const QString& fileName); //保存聊天記錄
void closeEvent(QCloseEvent *); //關閉事件
void paintEvent(QPaintEvent *event); //重繪事件
bool eventFilter(QObject *target, QEvent *event); //事件過濾器
private:
Ui::Widget *ui;
QUdpSocket *udpSocket;
qint16 port; //定義端口
QString fileName;
TcpServer *server; //聲明TCP文件傳輸
QColor color; //定義字體顏色
QTextCodec * unCodec = QTextCodec::codecForName ( "GBK" ); // 用於解碼
private slots:
void processPendingDatagrams();
void on_sendButton_clicked();
void getFileName(QString);
void on_sendToolBtn_clicked();
void on_fontComboBox_currentFontChanged(QFont f);
void on_sizeComboBox_currentIndexChanged(QString );
void on_boldToolBtn_clicked(bool checked);
void on_italicToolBtn_clicked(bool checked);
void on_underlineToolBtn_clicked(bool checked);
void on_colorToolBtn_clicked();
void currentFormatChanged(const QTextCharFormat &format);
void on_saveToolBtn_clicked();
void on_clearToolBtn_clicked();
void on_exitButton_clicked();
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QUdpSocket>
#include <QHostInfo>
#include <QMessageBox>
#include <QScrollBar>
#include <QDateTime>
#include <QNetworkInterface>
#include <QProcess>
#include "tcpserver.h"
#include "tcpclient.h"
#include <QFileDialog>
#include <QColorDialog>
#include <QPainter>
#include <QPixmap>
#include <QKeyEvent>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
udpSocket = new QUdpSocket(this);
port = 45454;
udpSocket->bind(port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
connect(udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));
sendMessage(NewParticipant);
server = new TcpServer(this);
connect(server, SIGNAL(sendFileName(QString)), this, SLOT(getFileName(QString)));
connect(ui->messageTextEdit, SIGNAL(currentCharFormatChanged(QTextCharFormat)),
this, SLOT(currentFormatChanged(const QTextCharFormat)));
init(); //初始化背景
}
Widget::~Widget()
{
delete ui;
}
// 初始化背景
void Widget::init()
{
ui->messageBrowser->setStyleSheet("background-color:transparent;");
ui->messageTextEdit->setStyleSheet("background-color:transparent;");
ui->userTableWidget->setStyleSheet("background-color:transparent;");
ui->userTableWidget->horizontalHeader()->setStyleSheet("QHeaderView::section{background-color:transparent;font:13pt '宋體';color: white;}"); //row
ui->userTableWidget->verticalHeader()->setVisible(false);
//ui->userTableWidget->verticalHeader()->setStyleSheet("QHeaderView::section{background-color:transparent;}"); //col
//ui->userTableWidget->setStyleSheet("QTableCornerButton::section{background-color:transparent;}"); //左上角交會處
//設置鍵盤事件
ui->sendButton->setFocus();
ui->sendButton->setDefault(true);
ui->messageTextEdit->installEventFilter(this);//設置完後自動調用其eventFilter函數
}
// 使用UDP廣播發送信息
void Widget::sendMessage(MessageType type, QString serverAddress)
{
QByteArray data;
QDataStream out(&data, QIODevice::WriteOnly);
QString localHostName = QHostInfo::localHostName();
QString address = getIP();
out << type << getUserName() << localHostName;
switch(type)
{
case Message :
if (ui->messageTextEdit->toPlainText() == "") {
QMessageBox::warning(0,unCodec->toUnicode("警告"),unCodec->toUnicode("輸入內容不能爲空!"),QMessageBox::Ok);
return;
}
out << address << getMessage();
ui->messageBrowser->verticalScrollBar()
->setValue(ui->messageBrowser->verticalScrollBar()->maximum());
break;
case NewParticipant :
out << address;
break;
case ParticipantLeft :
break;
case FileName : {
int row = ui->userTableWidget->currentRow();
QString clientAddress = ui->userTableWidget->item(row, 2)->text();
out << address << clientAddress << fileName;
break;
}
case Refuse :
out << serverAddress;
break;
}
udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast, port);
}
// 接收UDP信息
void Widget::processPendingDatagrams()
{
while(udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
udpSocket->readDatagram(datagram.data(), datagram.size());
QDataStream in(&datagram, QIODevice::ReadOnly);
int messageType;
in >> messageType;
QString userName,localHostName,ipAddress,message;
QString time = QDateTime::currentDateTime()
.toString("yyyy-MM-dd hh:mm:ss");
switch(messageType)
{
case Message:
in >> userName >> localHostName >> ipAddress >> message;
ui->messageBrowser->setTextColor(Qt::blue);
ui->messageBrowser->setCurrentFont(QFont("Times New Roman",12));
ui->messageBrowser->append("[ " +userName+" ] "+ time);
ui->messageBrowser->append(message);
break;
case NewParticipant:
in >>userName >>localHostName >>ipAddress;
newParticipant(userName,localHostName,ipAddress);
break;
case ParticipantLeft:
in >>userName >>localHostName;
participantLeft(userName,localHostName,time);
break;
case FileName: {
in >> userName >> localHostName >> ipAddress;
QString clientAddress, fileName;
in >> clientAddress >> fileName;
hasPendingFile(userName, ipAddress, clientAddress, fileName);
break;
}
case Refuse: {
in >> userName >> localHostName;
QString serverAddress;
in >> serverAddress;
QString ipAddress = getIP();
if(ipAddress == serverAddress)
{
server->refused();
}
break;
}
}
}
}
// 處理新用戶加入
void Widget::newParticipant(QString userName, QString localHostName, QString ipAddress)
{
bool isEmpty = ui->userTableWidget->findItems(localHostName, Qt::MatchExactly).isEmpty();
if (isEmpty) {
QTableWidgetItem *user = new QTableWidgetItem(userName);
QTableWidgetItem *host = new QTableWidgetItem(localHostName);
QTableWidgetItem *ip = new QTableWidgetItem(ipAddress);
ui->userTableWidget->insertRow(0);
ui->userTableWidget->setItem(0,0,user);
ui->userTableWidget->setItem(0,1,host);
ui->userTableWidget->setItem(0,2,ip);
ui->messageBrowser->setTextColor(Qt::gray);
ui->messageBrowser->setCurrentFont(QFont("Times New Roman",10));
ui->messageBrowser->append(unCodec->toUnicode("%1 在線").arg(userName));
ui->userNumLabel->setText(unCodec->toUnicode("在線人數:%1").arg(ui->userTableWidget->rowCount()));
sendMessage(NewParticipant);
}
}
// 處理用戶離開
void Widget::participantLeft(QString userName, QString localHostName, QString time)
{
int rowNum = ui->userTableWidget->findItems(localHostName, Qt::MatchExactly).first()->row();
ui->userTableWidget->removeRow(rowNum);
ui->messageBrowser->setTextColor(Qt::gray);
ui->messageBrowser->setCurrentFont(QFont("Times New Roman", 10));
ui->messageBrowser->append(unCodec->toUnicode("%1 在 %2 離開!").arg(userName).arg(time));
ui->userNumLabel->setText(unCodec->toUnicode("在線人數:%1").arg(ui->userTableWidget->rowCount()));
}
// 獲取ip地址
QString Widget::getIP()
{
QList<QHostAddress> list = QNetworkInterface::allAddresses();
foreach (QHostAddress address, list) {
if(address.protocol() == QAbstractSocket::IPv4Protocol)
return address.toString();
}
return 0;
}
// 獲取用戶名
QString Widget::getUserName()
{
QStringList envVariables;
envVariables << "USERNAME.*" << "USER.*" << "USERDOMAIN.*"
<< "HOSTNAME.*" << "DOMAINNAME.*";
QStringList environment = QProcess::systemEnvironment();
foreach (QString string, envVariables) {
int index = environment.indexOf(QRegExp(string));
if (index != -1) {
QStringList stringList = environment.at(index).split('=');
if (stringList.size() == 2) {
return stringList.at(1);
break;
}
}
}
return "unknown";
}
// 獲得要發送的消息
QString Widget::getMessage()
{
QString msg = ui->messageTextEdit->toHtml();
ui->messageTextEdit->clear();
ui->messageTextEdit->setFocus();
return msg;
}
// 發送消息
void Widget::on_sendButton_clicked()
{
sendMessage(Message);
}
// 獲取要發送的文件名
void Widget::getFileName(QString name)
{
fileName = name;
sendMessage(FileName);
}
// 傳輸文件按鈕
void Widget::on_sendToolBtn_clicked()
{
if(ui->userTableWidget->selectedItems().isEmpty())
{
QMessageBox::warning(0, unCodec->toUnicode("選擇用戶"),
unCodec->toUnicode("請先從用戶列表選擇要傳送的用戶!")/**/, QMessageBox::Ok);
return;
}
server->show();
server->initServer();
}
// 處理是否接收文件
void Widget::hasPendingFile(QString userName, QString serverAddress,
QString clientAddress, QString fileName)
{
QString ipAddress = getIP();
if(ipAddress == clientAddress)
{
int btn = QMessageBox::information(this,unCodec->toUnicode("接受文件"),
unCodec->toUnicode("文件來自於%1(%2):%3,是否接受?")
.arg(userName).arg(serverAddress).arg(fileName),
QMessageBox::Yes,QMessageBox::No);
if (btn == QMessageBox::Yes) {
QString name = QFileDialog::getSaveFileName(0,unCodec->toUnicode("保存文件"),fileName);
if(!name.isEmpty())
{
TcpClient *client = new TcpClient(this);
client->setFileName(name);
client->setHostAddress(QHostAddress(serverAddress));
client->show();
}
} else {
sendMessage(Refuse, serverAddress);
}
}
}
// 更改字體族
void Widget::on_fontComboBox_currentFontChanged(QFont f)
{
ui->messageTextEdit->setCurrentFont(f);
ui->messageTextEdit->setFocus();
}
// 更改字體大小
void Widget::on_sizeComboBox_currentIndexChanged(QString size)
{
ui->messageTextEdit->setFontPointSize(size.toDouble());
ui->messageTextEdit->setFocus();
}
// 加粗
void Widget::on_boldToolBtn_clicked(bool checked)
{
if(checked)
ui->messageTextEdit->setFontWeight(QFont::Bold);
else
ui->messageTextEdit->setFontWeight(QFont::Normal);
ui->messageTextEdit->setFocus();
}
// 傾斜
void Widget::on_italicToolBtn_clicked(bool checked)
{
ui->messageTextEdit->setFontItalic(checked);
ui->messageTextEdit->setFocus();
}
// 下劃線
void Widget::on_underlineToolBtn_clicked(bool checked)
{
ui->messageTextEdit->setFontUnderline(checked);
ui->messageTextEdit->setFocus();
}
// 顏色
void Widget::on_colorToolBtn_clicked()
{
color = QColorDialog::getColor(color, this);
if (color.isValid()) {
ui->messageTextEdit->setTextColor(color);
ui->messageTextEdit->setFocus();
}
}
void Widget::currentFormatChanged(const QTextCharFormat &format)
{
ui->fontComboBox->setCurrentFont(format.font());
// 如果字體大小出錯(因爲我們最小的字體爲12),使用15
if (format.fontPointSize() < 12) {
ui->sizeComboBox->setCurrentIndex(3);
} else {
ui->sizeComboBox->setCurrentIndex( ui->sizeComboBox
->findText(QString::number(format.fontPointSize())));
}
ui->boldToolBtn->setChecked(format.font().bold());
ui->italicToolBtn->setChecked(format.font().italic());
ui->underlineToolBtn->setChecked(format.font().underline());
color = format.foreground().color();
}
// 保存聊天記錄
void Widget::on_saveToolBtn_clicked()
{
if (ui->messageBrowser->document()->isEmpty()) {
QMessageBox::warning(0, unCodec->toUnicode("等待"), unCodec->toUnicode("聊天記錄爲空,無法保存!")/*!*/, QMessageBox::Ok);
} else {
QString fileName = QFileDialog::getSaveFileName(this,
unCodec->toUnicode("保存聊天記錄"), unCodec->toUnicode("聊天記錄"), unCodec->toUnicode("文本(*.txt);;所有文件(*.*)"));
if(!fileName.isEmpty())
saveFile(fileName);
}
}
// 保存聊天記錄
bool Widget::saveFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QFile::WriteOnly | QFile::Text)) {
QMessageBox::warning(this, unCodec->toUnicode("保存文件"),
unCodec->toUnicode("無法保存文件! %1:\n %2").arg(fileName)
.arg(file.errorString()));
return false;
}
QTextStream out(&file);
out << ui->messageBrowser->toPlainText();
return true;
}
// 清空聊天記錄
void Widget::on_clearToolBtn_clicked()
{
ui->messageBrowser->clear();
}
// 退出按鈕
void Widget::on_exitButton_clicked()
{
close();
}
// 關閉事件
void Widget::closeEvent(QCloseEvent *e)
{
sendMessage(ParticipantLeft);
QWidget::closeEvent(e);
}
//重繪事件
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawPixmap(rect(),QPixmap("://images/moutain.jpg"),QRect());
}
bool Widget::eventFilter(QObject *target, QEvent *event)
{
if(target == ui->messageTextEdit) //可替換
{
if(event->type() == QEvent::KeyPress)//回車鍵
{
QKeyEvent *k = static_cast<QKeyEvent *>(event);
if(k->key() == Qt::Key_Return)
{
on_sendButton_clicked(); //替換爲需要響應的函數事件,以這裏的按鈕爲例
return true;
}
}
}
return QWidget::eventFilter(target,event);
}