记一次开发:Qt简单电话本程序
前言
断断续续学习C++一年了,现在要做课设,觉得控制台界面实在太难看,于是用Qt做一个图形化的程序出来。
学习Qt也没有多久,只是了解了个大概,这次开发基本上是啃了2天的官方帮助文档,然后利用各种Qt提供的轮子实现的。有些地方做的确实还很不完善,不过似乎没有什么致命的bug。
代码质量底下,谨慎模仿。
Qt真的是一个很好的C++扩展库,学习完C++觉得没有用武之地的可以去学习一下Qt来开发几个图形化软件来练练手。
项目介绍
实现一个简单的电话本程序,能够实现添加、查找、修改、删除、保存到文件等基本功能
开发环境
IDE: QtCreator(我个人认为非常好用,这也是我现在在linux下编写C++的主力IDE,因为是官方的IDE所以契合度非常高)
系统:Windows(本来是想在linux下开发的,但是考虑到最后做的程序要在windows平台上展示,不想进行二次编译所以选择了windows平台,Qt是跨平台的因此这个不用过多考虑)
编程思路
其实第一反应肯定是用数据库来实现这个功能了。但是既然是C++的课设,而且考虑到数据量应该不会太大,所以就直接使用文本文档来储存和读取数据(这是一个偷懒的做法orz)
主界面,看了一下,本来是想用一个表格来展示的,调教了一天之后发现设置起来非常麻烦,于是就改用了列表控件。(其实是我上网找到了一个类似的教程我会乱说?)查询,删除,添加都可以使用列表控件自带的方法,至于保存,直接是使用文本文档输出的方式即可。
功能开发
程序主体
首先来编写程序的主窗口界面,建立一个新的Qt项目,基类选择为QMainWindow,然后编辑界面文件如下(没有美化,轻喷):
各个部件的命名如图。
接下来开始编写各个功能
添加
添加功能需要一个弹出的窗口,输入姓名和电话,然后插入到主程序的listWidget中去。因此需要再编写一个对话窗口类,向工程中添加一个设计师界面类,基类选择QDialog,命名为editDialog(这个窗口待会也可以用在修改功能中),模板选择Dialog with Buttons under(就是下面有一组按钮的对话框),然后把界面设置成这样:
两个输入框都是PlainTextEdit控件,下面那组按钮已经分别关联了这个对话框的accept()和reject()槽
然后在editDialog中添加以下几个公用函数:
QString name() const;//获取姓名 QString number() const;//获取号码 void setName(const QString &);//设置姓名 void setNumber(const QString &);//设置号码
上面两个函数用来获取编辑框中的内容,下面两个函数则用于设置编辑框中的初始内容(会在下面的修改功能中用到)
实现是这样的:
QString EditDialog::name() const { return ui->nameEdit->toPlainText().trimmed(); } QString EditDialog::number() const { return ui->numEdit->toPlainText().trimmed(); } void EditDialog::setName(const QString & name) { ui->nameEdit->setPlainText(name); } void EditDialog::setNumber(const QString & num) { ui->numEdit->setPlainText(num); }
然后我们来修改主窗口中对应按钮的槽(修改槽只要在控件上右键选择“转到槽”就可以快速切换到槽函数的实现了) 注意实例化EditDialog是要包含头文件的,之后新添加的类也是一样
void MainWindow::on_addButton_clicked() { EditDialog editDialog(this); if(editDialog.exec()==1) { QString line = editDialog.name()+"---"+editDialog.number(); ui->listWidget->addItem(line); this->statusBar()->showMessage("1 item added",2000);//设置状态栏的提示信息 } }
我们在这个槽里实例化一个editDialog对象,并把它的父对象设置为主窗口,利用exec()来实现模态窗口效果,通过editDialog对象内的name和number方法获取用户输入的内容并把他们组装在一起,用addItem()方法插入ListWidget就可以了。
修改
修改功能和添加功能基本是差不多的,我们直接转到对应按钮的槽里实现就可以了
void MainWindow::on_editButton_clicked() { if(!ui->listWidget->currentItem()) return; QStringList parts = ui->listWidget->currentItem()->text().split("---"); EditDialog editDialog(this); editDialog.setName(parts[0]); editDialog.setNumber(parts[1]); if(editDialog.exec()==1) { ui->listWidget->currentItem()->setText(editDialog.name()+"---"+editDialog.number()); this->statusBar()->showMessage("1 item updated",2000); } }
这里用到了QStringList类和QString的split()方法,其实并不难,看一下这个实例就很容易理解,官方的文档也写的很详细
删除
删除功能其实直接利用了QListWidget提供的返回当前选择行的currentItem()函数和delete方法,但是为了防止误操作,需要一个模态的确认对话框,再向工程中添加一个设计师界面类,仍然选择是有两个按钮的对话框,对话框的界面只需要放一个label写上“确定删除?”即可。
这个对话框类我命名为ConfirmDialog,主窗口对应按钮的槽函数如下:
void MainWindow::on_delButton_clicked() { if(!ui->listWidget->currentItem()) return; ConfirmDialog * confirmDialog = new ConfirmDialog(this); if(confirmDialog->exec()==1) { delete ui->listWidget->currentItem(); this->statusBar()->showMessage("1 item deleted",2000); } }
查找
这个是最难写的一个功能,还是先做查询窗口,这次我选择了没有按钮的对话框类,命名为QueryDialog,界面如下:
下面的两个按钮:“查询”设置为queryButton,这个槽稍后将会和主窗口里的槽关联,“退出”则直接和对话框的reject()槽关联了。
另外,为了能够把queryButton跟主窗口的槽关联起来,就必须把他设置为公有的,于是我把头文件中,ui改成了public的:
public: Ui::QueryDialog *ui;
这其实不是一个很好的办法,破坏了面向对象的封装性,但是我懒啊。。于是就这么干了。
查找功能的思路是,用户在查找对话框中输入关键字,然后通过查询按钮发送的信号把关键字传送给主窗口里的槽,这个槽函数调用listWidget的findItem()方法,然后把结果(一个基类性为 QListWidgetItem *
的Qlist)发送给查询窗口来显示。
QueryDialog里增加一个函数来返回编辑框中的内容
QString QueryDialog::getTarget() { return ui->QueryEdit->toPlainText(); }
在MainWindow类里添加一个私有的QueryDialog对象qdlg(这么做是为了实现两个窗口的通信,以及槽的关联)
主窗口“查找”按钮的槽直接写成以模态窗口显示qdlg即可:
void MainWindow::on_queryButton_clicked() { qdlg.exec(); }
主窗口里的槽函数,写在public slots下:
void MainWindow::SendQueryResult() { QString target = qdlg.getTarget(); QList <QListWidgetItem * > resList = ui->listWidget->findItems(target,Qt::MatchContains);//这个函数第二个参数是匹配方法,我这里设置成包含字段就匹配 qdlg.ShowQueryResult(resList);//这个是QueryDialog里面用于处理显示结果数据的,接下来会写实现 qdlg.exec(); }
接着,在MainWindow类的构造函数中,把这个槽和它的成员对象qdlg的查询按钮的信号关联起来:
connect(qdlg.ui->queryButton,SIGNAL(clicked()),this,SLOT(SendQueryResult()));
然后是QueryDialog里面,用于接收到数据并显示的函数ShowQueryResult
:
void QueryDialog::ShowQueryResult(const QList <QListWidgetItem * > & resList) { if(resList.size()==0) { ui->resLabel->setText("No results!"); return; } else { QString resNumber = QString::number(resList.size());//注意这里的字符串处理 ui->resLabel->setText(resNumber+" item(s) founded:"); for(int i=0;i<resList.size();i++) { ui->listWidget->addItem(resList[i]->text()); } } }
在这里说明一下,我本来希望能够直接利用addItem函数重载的QListWidgetItem * 参数来直接读取List里的记录,但是这样做却发现不能显示内容(原因我至今不明,望高人指点),于是我用了QListWidgetItem的text()方法来获取字符串添加进去。
这样就基本实现了查询功能
保存
其实我一开始是想做成动态保存的结果的(对listWidget的操作直接影响保存文件),但是查询略麻烦,所以改成了每点击一下保存按钮就重新写入一次文档(还是偷懒23333)。
要实现保存,就需要一个外部的txt文档(我这里命名为data.txt)
在主窗口类里添加一个私有的QFile对象file,用它来读写data.txt
接下来实现,打开程序时自动加载之前保存内容。其实很简单,直接在构造函数里添加读取txt并写入listWidget就可以
file.setFileName("data.txt"); //从文件读取 if(file.open(QIODevice::ReadOnly|QIODevice::Text))//注意这里的参数 { QTextStream readin(&file); while(!readin.atEnd()) { QString line = readin.readLine();//一行一行读取 ui->listWidget->addItem(line); } file.close(); }
接下来实现保存按钮的槽
void MainWindow::on_saveButton_clicked() { //功能:把当前程序中的所有记录保存到data.txt if(file.open(QIODevice::WriteOnly)) { QTextStream out(&file); for(int i=0;i<ui->listWidget->count();i++) { out<<ui->listWidget->item(i)->text()<<endl; } file.close(); this->statusBar()->showMessage("File saved",2000); } else { this->statusBar()->showMessage("Save failed!",2000); return; } }
看一下帮助文档中QFile和QTextStream的用法,上面的程序应该不难理解。
至此我们的程序就写完了!快快发布release吧~
源码
请查看我的github:
github.com/MarkLux/Qt-PhoneBook