本次设计参考了《Qt及Qt Quick开发实战精解》一书,并对其进行bug补全修正,添加些许新功能进行二次开发,基本的功能:
添加功能如下:
我这里并没有对其进行多ip组网聊天测试,但是功能正常,清空聊天记录和保存功能也可以正常显示,这里就不再过多的赘述。
此局域网聊天工具既要作为服务器端,又要作为客户端,因此以可将其作为端到端的P2P模式。
界面的话,需要三个界面来实现所有的功能,一个主窗口界面,一个文件发送端,一个文件接收端。构建如下
UDP有个最显著的特点就是有着强大的广播功能,聊天功能就使用UDP协议,构建组网,在同一局域网下通信,这里只需要将程序运行在不同的ip环境下即可。
使用TCP协议实现文件的快速传输,用TCP协议在Qt中的三次握手应用,调用界面,实现传输功能
其他功能就是对字体的操作了,可以更改字体的颜色和大小,粗细,格式等等,通过调用Qt中font类的字体设置。
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
#include
#include
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
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
#include
#include
#include
#include
#include "tcpserver.h"
#include "tcpclient.h"
#include
#include
#include
#include
#include
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 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(event);
if(k->key() == Qt::Key_Return)
{
on_sendButton_clicked(); //替换为需要响应的函数事件,以这里的按钮为例
return true;
}
}
}
return QWidget::eventFilter(target,event);
}