一、概要設計
登錄對話框(繼承自QDialog類)進行用戶登錄查詢數據庫用戶是否存在,注冊插入數據到用戶表。用戶表字段:
(chatid int primary key, passwd varchar(30), name varchar(30), email varchar(30), history int)
顯示好友列表(繼承自QWidget類),窗體間數據傳遞,顯示登錄用戶頭像及昵稱。輪詢數據庫用戶表顯示好友列表。點擊好友跳出聊天窗口(繼承自MainWindow類),窗體間數據傳遞,顯示好友昵稱,實現多用戶聊天,支持中文通信,設置快捷鍵,保存聊天記錄。
關鍵字:數據庫、窗體傳值、中文通信、udp協議、快捷鍵、文件操作。
二、詳細設計
1.登錄對話框:
數據庫操作:
(1)創建數據庫和數據表
// 設置參數
QString select_table = "select tbl_name name from sqlite_master where type = 'table'";
QString create_sql = "create table user (chatid int primary key, passwd varchar(30), name varchar(30), email varchar(30), history int)";
QString select_max_sql = "select max(chatid) from user";
QString insert_sql = "insert into user values (?, ?, ?, ? ?)";
//QString update_sql = "update user set name = :name where chatid = :chatid";
QString select_sql = "select name from user";
//QString select_all_sql = "select * from user";
//QString delete_sql = "delete from user where chatid = ?";
//QString clear_sql = "delete from user";
QString select_nameInfo = "selcet * from user where name=";
database = QSqlDatabase::addDatabase("QSQLITE");
database.setDatabaseName("database.db");
//打開數據庫
if(!database.open())
{
qDebug()<<database.lastError();
qFatal("failed to connect.") ;
}
else
{
qDebug()<<"open success";
QSqlQuery sql_query; //變量必須在成功打開數據庫后定義才有效
sql_query.prepare(select_table);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
QString tableName;
while(sql_query.next())
{
tableName = sql_query.value(0).toString();
qDebug()<<tableName;
if(tableName.compare("user"))
{
tableFlag=false;
qDebug()<<"table is not exist";
}
else
{
tableFlag=true;
qDebug()<<"table is exist";
}
}
}
// 創建數據表
if(tableFlag==false)
{
sql_query.prepare(create_sql);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
qDebug()<<"table created!";
}
}
//database.close();
}
(2)注冊驗證及插入數據
if(ui->passwd1LineEdit->text()==""||ui->passwd2LineEdit->text()=="")
{
passwdFlag=false;
}
else if(ui->passwd1LineEdit->text()==ui->passwd2LineEdit->text()) //兩次密碼相同
{
//newpasswd=ui->passwd1LineEdit->text();
passwdFlag=true;
}
else
{
QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("密碼輸入不一致!"));
qDebug()<<"passwd err";
passwdFlag=false;
//return;
}
//以下為數據庫的操作
QSqlQuery sql_query;
//查詢最大id
max_id = 0;
sql_query.prepare(select_max_sql);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
while(sql_query.next())
{
max_id = sql_query.value(0).toInt();
qDebug()<<QString("max chatid:%1").arg(max_id);
}
}
//查詢部分數據(name)
if(!sql_query.exec(select_sql))
{
qDebug()<<sql_query.lastError();
}
else
{
while(1)
{
if(sql_query.next()) //name有數據
{
QString name = sql_query.value("name").toString();
qDebug()<<QString("name=%1").arg(name);
if(ui->nameLineEdit->text()==name) //用戶名已經存在
{
QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("用戶名已存在!"));
qDebug()<<"name existed";
nameFlag=false;
break;
}
else
{
//newname=ui->nameLineEdit->text();
nameFlag=true;
}
}
else
{ //name列為空
nameFlag=true;
break;
}
}
}
newchatid=max_id+1;
if(nameFlag==true) newname=ui->nameLineEdit->text();
else return;
if(passwdFlag==true) newpasswd=ui->passwd1LineEdit->text();
else return;
//插入數據
sql_query.prepare(insert_sql);
sql_query.addBindValue(newchatid); //chatid
sql_query.addBindValue(newpasswd); //passwd
sql_query.addBindValue(newname); //name
sql_query.addBindValue(newemail); //email
sql_query.addBindValue(0); //history
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("注冊成功!"));
qDebug()<<"inserted!";
}
登錄界面:
(1)登錄驗證
if(matchFlag==false)
{
//用戶名錯誤
QMessageBox::warning(this, tr("警告"), tr("用戶不存在!"), QMessageBox::Yes);
this->ui->et_username->clear();
this->ui->et_pwd->clear();
this->ui->et_username->setFocus();
}
else
{
if(usr_passwd!=ui->et_pwd->text())
{
//密碼錯誤
QMessageBox::warning(this, tr("警告"), tr("用戶不存在!"), QMessageBox::Yes);
this->ui->et_username->clear();
this->ui->et_pwd->clear();
this->ui->et_username->setFocus();
}
else
{
//用戶名和密碼均正確
// ChatWindow cw(this);
// this->hide();
// cw.show();
// cw.exec();
// this->close();
LoginDialog::NICKNAME = usr_name;
accept();
}
(2)用戶頭像
QSqlQuery sql_query; //變量必須在成功打開數據庫后定義才有效
//查詢部分數據(name)
QString tempstring="select * from user where name='"+name+"'";
qDebug()<<tempstring;
if(!sql_query.exec(tempstring))
{
qDebug()<<sql_query.lastError();
matchFlag=false;
}
else
{
while(sql_query.next())
{
usr_id = sql_query.value(0).toInt();
usr_passwd = sql_query.value(1).toString();
usr_name = sql_query.value(2).toString();
usr_email = sql_query.value(3).toString();
usr_history = sql_query.value(4).toInt();
qDebug()<<QString("chatid=%1 passwd=%2 name=%3 email=%4 history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);
}
if(usr_name==name) matchFlag=true;
else matchFlag=false;
}
qDebug()<<matchFlag;
if(matchFlag==true)
{
QString path=":/images/facex.jpg";
QString diff="face"+QString::number(usr_id);
path.replace("facex",diff);
qDebug()<<path;
QImage img;
img.load(path);
QPixmap pic=QPixmap::fromImage(img.scaled(ui->userPic->width(),ui->userPic->height()));
ui->userPic->setPixmap(pic);
}
else
{
QPixmap pic;
ui->userPic->setPixmap(pic);
}
2.好友列表:
數據庫查詢及列表顯示:
database = QSqlDatabase::addDatabase("QSQLITE");
database.setDatabaseName("database.db");
QSqlQuery sql_query; //改變量必須在成功打開數據庫后定義才有效
//打開數據庫
if(!database.open())
{
qDebug()<<database.lastError();
qFatal("failed to connect.") ;
}
else
{
QSqlQuery query; //改變量必須在成功打開數據庫后定義才有效
QString execstring="select * from user";
ctoolTip = new CToolTip();
// 定義全局的ToolTip,方便使用
g_toolTip = ctoolTip;
// 本行代碼主要針對ListWidgetItem右鍵點擊時才生效的
ui->listWidget->setMouseTracking(true);
if(!query.exec(execstring))
{
qDebug()<<QString("chatid");
qDebug()<<query.lastError();
// matchFlag=false;
}
else
{
// 添加測試數據
while (query.next()) {
usr_id = query.value(0).toInt();
usr_passwd = query.value(1).toString();
usr_name = query.value(2).toString();
usr_email = query.value(3).toString();
usr_history = query.value(4).toInt();
qDebug()<<QString("chatid=%1 passwd=%2 name=%3 email=%4 history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);
ItemWidget *itemWidget = new ItemWidget(ui->listWidget);
itemWidget->setText(QPixmap(QString(":/images/face%1").arg(usr_id)).scaled(80, 80),
QString("%1").arg(usr_name), QString("127.0.0.1:800%1").arg(usr_id));
QListWidgetItem *listItem = new QListWidgetItem(ui->listWidget);
// 此處的size如果不設置,界面被壓縮了看不出ItemWidget的效果,高度一定要設置
listItem->setSizeHint(QSize(200, 70));
ui->listWidget->setItemWidget(listItem, itemWidget);
}
}
}
QObject::connect(ui->listWidget,SIGNAL(itemClicked(QListWidgetItem*)),this,SLOT(conChat(QListWidgetItem*)));
qDebug()<<"LoginWidget:"+LoginDialog::NICKNAME;
ui->nickname->setText(LoginDialog::NICKNAME);
QString tempstring="select * from user where name='"+LoginDialog::NICKNAME+"'";
qDebug()<<tempstring;
if(!sql_query.exec(tempstring))
{
qDebug()<<sql_query.lastError();
matchFlag=false;
}
else
{
while(sql_query.next())
{
usr_id = sql_query.value(0).toInt();
usr_passwd = sql_query.value(1).toString();
usr_name = sql_query.value(2).toString();
usr_email = sql_query.value(3).toString();
usr_history = sql_query.value(4).toInt();
qDebug()<<QString("chatid=%1 passwd=%2 name=%3 email=%4 history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);
}
if(usr_name==LoginDialog::NICKNAME) matchFlag=true;
else matchFlag=false;
登錄用戶信息設置:
QString path=":/images/facex.jpg";
QString diff="face"+QString::number(usr_id);
path.replace("facex",diff);
qDebug()<<path;
QImage img;
img.load(path);
QPixmap pic=QPixmap::fromImage(img.scaled(ui->userPic->width(),ui->userPic->height()));
ui->userPic->setPixmap(pic);
ui->ipaddress->setText(QString("127.0.0.1:800%1").arg(usr_id));
列表項:
labelIcon = new QLabel(this);
labelName = new QLabel(this);
labelName->setStyleSheet("QLabel{color: green; font: 23pt bold;}");
labelInfo = new QLabel(this);
labelInfo->setStyleSheet("QLabel{color: gray;}");
verlayout = new QVBoxLayout();
verlayout->setContentsMargins(0, 0, 0, 0);
verlayout->addWidget(labelName);
verlayout->addWidget(labelInfo);
horLayout = new QHBoxLayout(this);
horLayout->setContentsMargins(2, 2, 2, 2);
horLayout->addWidget(labelIcon, 1, Qt::AlignTop);
horLayout->addLayout(verlayout, 4);
懸浮窗口:
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint);
this->resize(200, 100); ;
this->setObjectName("CToolTip");
this->setStyleSheet("QWidget#CToolTip {border: 2px solid green; background-color: skyblue;}");
groupBox = new QGroupBox(this);
groupBox->setGeometry(10, 10, 180, 80);
groupBox->setTitle("用戶信息");
labelIcon = new QLabel(groupBox);
labelName = new QLabel(groupBox);
labelInfo = new QLabel(groupBox);
verlayout = new QVBoxLayout();
verlayout->setContentsMargins(0, 0, 0, 0);
verlayout->addWidget(labelName);
verlayout->addWidget(labelInfo);
horLayout = new QHBoxLayout(groupBox);
horLayout->setContentsMargins(10, 10, 10, 10);
horLayout->addWidget(labelIcon, 1, Qt::AlignTop);
horLayout->addLayout(verlayout, 4);
選擇列表項:
QObject::connect(ui->listWidget,SIGNAL(itemClicked(QListWidgetItem*)),this,SLOT(conChat(QListWidgetItem*)));
void LoginWidget::conChat(QListWidgetItem*)
{
qDebug()<<QString("onChat:%1").arg(usr_id);
LoginWidget::ID = ui->listWidget->currentRow()+1;
chatWindow=new ChatWindow;
chatWindow->show();
}
3.聊天窗口:
數據庫查詢、信息設置及綁定端口號:
database = QSqlDatabase::addDatabase("QSQLITE");
database.setDatabaseName("database.db");
QSqlQuery chat_query; //變量必須在成功打開數據庫后定義才有效
//打開數據庫
if(!database.open())
{
qDebug()<<database.lastError();
qFatal("failed to connect.") ;
}
else
{
QString tempstring="select * from user where name='"+LoginDialog::NICKNAME+"'";
qDebug()<<tempstring;
if(!chat_query.exec(tempstring))
{
qDebug()<<chat_query.lastError();
matchFlag=false;
}
else
{
while(chat_query.next())
{
usr_id = chat_query.value(0).toInt();
usr_passwd = chat_query.value(1).toString();
usr_name = chat_query.value(2).toString();
usr_email = chat_query.value(3).toString();
usr_history = chat_query.value(4).toInt();
qDebug()<<QString("chatid=%1 passwd=%2 name=%3 email=%4 history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);
port = 8000+usr_id;
}
}
QString receiverId="select * from user where chatid="+QString("%1").arg(LoginWidget::ID)+"";
qDebug()<<receiverId;
if(!chat_query.exec(receiverId))
{
qDebug()<<chat_query.lastError();
matchFlag=false;
}
else
{
while(chat_query.next())
{
usr_id = chat_query.value(0).toInt();
usr_passwd = chat_query.value(1).toString();
usr_name = chat_query.value(2).toString();
usr_email = chat_query.value(3).toString();
usr_history = chat_query.value(4).toInt();
qDebug()<<QString("chatid=%1 passwd=%2 name=%3 email=%4 history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);
ui->name->setText(usr_name);
}
}
UDP接收端:
senderAno = new QUdpSocket(this);
receiver = new QUdpSocket(this);
receiver->bind(port, QUdpSocket::ShareAddress);
connect(receiver, &QUdpSocket::readyRead, this, &ChatWindow::processPendingDatagram);
void ChatWindow::processPendingDatagram()
{
//中文支持
// QTextCodec *codec = QTextCodec::codecForName("GBK");
// 擁有等待的數據報
while(receiver->hasPendingDatagrams())
{
QDateTime time = QDateTime::currentDateTime();//獲取系統現在的時間
QString str = time.toString("yyyy-MM-dd hh:mm:ss ddd"); //設置顯示格式
QByteArray datagram;
// 讓datagram的大小為等待處理的數據報的大小,這樣才能接收到完整的數據
datagram.resize(receiver->pendingDatagramSize());
// 接收數據報,將其存放到datagram中
receiver->readDatagram(datagram.data(), datagram.size());
//ui->label->setText(datagram);
ui->listWidget->addItem(str);
chat += str+"\n";
qDebug()<<datagram<<QString::fromLocal8Bit(datagram);
ui->listWidget->addItem(usr_name+":"+QString::fromLocal8Bit(datagram));
chat += usr_name+":"+datagram+"\n";
}
}
UDP發送端:
//中文支持
// QTextCodec *codec = QTextCodec::codecForName("GBK");
QDateTime time = QDateTime::currentDateTime();//獲取系統現在的時間
QString str = time.toString("yyyy-MM-dd hh:mm:ss ddd"); //設置顯示格式
int port = LoginWidget::ID+8000;
qDebug()<<port;
QByteArray datagram = ui->textEdit->toPlainText().toUtf8();
senderAno->writeDatagram(datagram.data(), datagram.size(),
QHostAddress("127.0.0.1"), port);
ui->listWidget->addItem(str);
chat += str+"\n";
qDebug()<<ui->textEdit->toPlainText()<<QString::fromLocal8Bit(ui->textEdit->toPlainText().toUtf8());
ui->listWidget->addItem("me:"+ui->textEdit->toPlainText());
chat += "me:"+ui->textEdit->toPlainText()+"\n";
this->ui->textEdit->clear();
this->ui->textEdit->setFocus();
快捷鍵設置:
ui->pushButton->setShortcut(tr("ctrl+return"));
ui->pushButton_3->setShortcut(tr("alt+c"));
ui->pushButton_8->setShortcut(tr("ctrl+s"));
文件操作:
/*
* 通過QFile實現數據操作
*/
qDebug()<<tr("Save File");
qDebug()<<chat;
QFile file("/Users/apple/Documents/QT/ChatLog.txt");
// 以只寫方式打開,如果文件不存在,那么會創建該文件
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
qDebug() << file.errorString();
file.write(chat);
file.close();
QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("聊天記錄保存成功!"));
4.窗體傳值:
使用QT中的Signal&Slot機制進行傳值:
QT中的Signal&Slot機制相比於MFC中的消息機制簡單了許多,它保證了任何對象之間均可以通過這種方式進行通信,甚至可以得到消息的sender。這里就拿一個簡單的窗體間傳值作為例子。
首先看一下主窗體MainWindow:
在設計器中拖拽一個Label和一個TextEdit控件到界面上,TextEdit用於顯示傳遞過來的數據。

創建一個右下有兩個按鍵的對話框,放置一個Label和一個LineEdit。

下面就是編碼的操作了,我們需要在Dialog中聲明一個信號,當用戶點擊OK時傳遞LineEdit中的內容到mainWindow中,具體的dialog.h代碼為:
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
signals:
void sendData(QString);
private slots:
void on_buttonBox_accepted();
};
#endif // DIALOG_H
其中的signals:void sendData(QString)便是我們需要的信號函數,同時聲明了一個槽函數
void on_buttonBox_accepted();用於相應確定按鈕的click事件。下面就是需要在該函數中產生一個信號。代碼如下:
void Dialog::on_buttonBox_accepted()
{
emit sendData(ui->lineEdit->text());
}
代碼異乎尋常的簡單,只需要用emit的方式調用sendData函數,將需要的參數傳遞進去即可。而MainWindow中則需要聲明接收的槽函數,注意槽函數參數只能與信號函數少或相等,而不能多於信號函數參數個數。在MainWindow的頭文件中聲明槽函數:
private slots:
void receiveData(QString data);
為了便於測試,我只在MainWindow的構造函數中創建了一個Dialog對象,並連接了信號和槽,具體為:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//信號槽方式下父子窗體傳值的測試
Dialog *dlg = new Dialog;
//關聯信號和槽函數
connect(dlg,SIGNAL(sendData(QString)),this,SLOT(receiveData(QString)));
// dlg->setModal(true); 不論是模態或者非模態都可以正常傳值
dlg->show();
}
這里,我沒有將父窗口的指針傳遞到Dialog中,如new Dialog(this),這種方式下,實際上可以歸結到第三類傳值方式中去。因為此時,可以使用MainWindow中的父窗口的函數進行數據的賦值和操作。
這里,可能還有一個問題就是,父窗口如何給子窗口傳值,一方面,仍然可以使用信號和槽的方式進行,但是,我感覺更便利的方式倒是使用這種public接口的方式進行傳值。這種來的更直接和明顯。當然,可以看出Signal&Signal方式進行此類的處理會更有通用性。
在receiveData(QString)的槽函數中進行接收到數據的處理,這里僅僅進行了簡單的顯示:
void MainWindow::receiveData(QString data)
{
ui->textEdit->setText(data);
}
最后看下結果:

最終的結果,因為信號和槽可以是多對多的,所以,在類似多個窗體廣播信息時,這種方式就很有用,當然也可以使用全局變量的形式。
使用全局變量;
使用public形式的函數接口;
使用QT中的Event機制(這種沒有把握,但是感覺應該是可以的),但是實現起來應該比前幾種復雜,這里不做討論。
5.中文支持:
網上搜索一下,找到的都是這種:
#include < QTextCodec >
int main(int argc, char **argv)
{
....................
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF8"));
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF8"));
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF8"));
..........................
}
Qt5中, 取消了QTextCodec::setCodecForTr()和QTextCodec::setCodecForCString()這兩個函數,而且網上很多都是不推薦這種寫法。
有一下幾種轉換方法:
1、
QTextCodec * BianMa = QTextCodec::codecForName ( "GBK" );
QMessageBox::information(this, "提示", BianMa->toUnicode("中文顯示!"));
2、也可以通過QString定義的靜態函數,先轉換成Unicode類型:
QString::fromLocal8Bit("提示")
3、在Qt5中,提供了一個專門的處理宏,來支持中文常量,那就是QStringLiteral,但它只能處理常量。
QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("中文顯示"));const char* info = "中文顯示";
//不支持
QString strInfo = QStringLiteral(info);
//支持
QString strInfo = QString::fromLocal8Bit(info);
QByteArray QString::toLatin1() const
Latin1是ISO-8859-1的別名,有些環境下寫作Latin-1。
ISO-8859-1編碼是單字節編碼,向下兼容ASCII,其編碼范圍是0x00-0xFF,0x00-0x7F之間完全和ASCII一致,0x80-0x9F之間是控制字符,0xA0-0xFF之間是文字符號。
toLatin1壓縮掉了QString自動給每個英文字符加上的那些00字節.
QString與QByteArray互相轉換的方法
QString轉QByteArray方法
//Qt5.3.2
QString str("hello");
QByteArray bytes = str.toUtf8(); // QString轉QByteArray方法1
QString str("hello");
QByteArray bytes = str.toLatin1(); // QString轉QByteArray方法2
QByteArray轉QString方法
//Qt5.3.2
QByteArray bytes("hello world");
QString string = bytes; // QByteArray轉QString方法1
QByteArray bytes("hello world");
QString string;
string.prepend(bytes);// QByteArray轉QString方法2
qDebug() << string;
QByteArray類同樣不以’\0’為結尾:如
QByteArray bytes; bytes.resize(5); bytes[0] = '1'; bytes[1] = '2'; bytes[2] = '3'; bytes[3] = '\0'; bytes[4] = 'a'; cout << bytes << endl;
三、結果圖
登錄界面:




好友列表:


注冊界面:

聊天窗口:



聊天記錄:

