一、概要设计
登录对话框(继承自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+" "; qDebug()<<datagram<<QString::fromLocal8Bit(datagram); ui->listWidget->addItem(usr_name+":"+QString::fromLocal8Bit(datagram)); chat += usr_name+":"+datagram+" "; } }
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+" "; qDebug()<<ui->textEdit->toPlainText()<<QString::fromLocal8Bit(ui->textEdit->toPlainText().toUtf8()); ui->listWidget->addItem("me:"+ui->textEdit->toPlainText()); chat += "me:"+ui->textEdit->toPlainText()+" "; 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类同样不以’