简介
基于前面介绍的QT知识,做一个电子相册,总结前文介绍的各类知识,将用到QListWidget,QTreeWidget,双缓冲绘图,信号槽,动画效果,绘图事件,鼠标事件,qss等知识,算是对之前知识的一个总结。
效果如下
MainWindow设计
1 MainWindow.ui的centralWidget中添加水平布局horizontalLayout,在该布局中添加两个垂直布局proLayout和picLayout。
horizontalLayout设置layoutStretch比例为1比4, 同时为MainWindow.ui添加manubar,效果是这个样子的
2 在MainWindow的构造函数中添加菜单项,并为菜单项设置信号连接,截取部分代码
1 | //创建菜单栏 |
3 main函数设置mainwindow最大显示
1 | w.setWindowTitle("Album"); |
4 为MainWindow和菜单栏设置qss
1 | /*mainwindow 样式*/ |
向导类Wizard
1 添加设计师界面类Wizard
Wizard类用来响应创建项目菜单被点击后弹出向导框,其继承于QWizard。
2 添加两个向导页面类ConfirmPage和ProSetPage类,基类选择QWizardPage类,并在Wizard.ui里添加两个wizardpage,将这两个wizardpage升级为ProSetPage和ConfirmPage。ProSetPage类用来设置创建项目的属性,我们先点击其ui文件为其添加网格布局gridLayout,然后将ProSetPage设置为网格布局,设置gridLayout的margin为5,在gridLayout中添加控件,形成如下布局
3 将两个lineEdit注册为wizard的field,保证两个lineEdit是空的时候无法点击下一步,将QLineEdit的textEdited信号和ProSetPage的completeChanged信号连接起来,这样在lineEdit编辑的时候就会发送textEdited信号,进而触发ProSetPage发送completeChanged信号。
setClearButtonEnabled设置为true可以在lineEdit输入数据后显示清除按钮,直接清除已录入的字符。
completeChanged信号是从proSetPage的基类QWizardPage类继承而来的。completeChanged信号发出后会触发QWizardPage类的isComplete函数。
1 | ProSetPage::ProSetPage(QWidget *parent) : |
为了实现特定的判断,我们重写isComplete函数。这样我们就能判断文件夹是否合理以及是否已经有项目路径了。
可以根据不满足的条件设置tips提示用户。
1 | bool ProSetPage::isComplete() const |
4 为浏览按钮添加点击后选择文件夹操作,在prosetpage.ui文件里右键点击browse按钮,选择转到槽,QT会为我们生成槽函数
1 | //添加浏览按钮点击后选择文件夹的操作 |
5 在ProSetPage页面点击下一步会跳转到下一页。ConfirmPage没什么代码,在ui文件里添加提示即可。在完成时我们可以重写QWidzard的done函数。
将页面设置的项目名称和路径传递给ProTree类,ProTree类用来在MainWindow左侧显示树形目录,这个之后介绍。
1 | void Wizard::done(int result) |
项目目录树ProTree类
1 创建Qt设计师界面类,名字为ProTree,基类选择QDialog,ProTree中添加一个垂直布局,布局内添加一个QLabel和一个QTreeWidget,最后将ProTree设置为垂直布局。
2 考虑到QTreeWidget功能有限,我们需要继承QTreeWidget重新实现一个新的类ProTreeWidget,所以在项目中新增C++类ProTreeWidget继承自QTreeWidget。
在构造函数中隐藏头部,并且注册要传递信息的类型
1 | qRegisterMetaType<QVector<int> >("QVector<int>"); |
同时将ProTree布局中的QTreeWidget提升为ProTreeWidget
3 同样的道理为了便于操作定义ProTreeItem继承QTreeWidgetItem,相关的成员变量和函数省略,这里简单介绍下构造函数
1 | ProTreeItem::ProTreeItem(QTreeWidget *view, const QString &name, |
view和type传递给基类,其他参数_path表示项目路径,_name表示项目名称,_root表示根节点,_pre_item表示前一个节点,_next_item表示后一个节点。
还有第二个重载版本的构造函数,可以通过根节点构造新的item节点
1 | ProTreeItem::ProTreeItem(QTreeWidgetItem *parent, const QString &name, |
4 ProTreeWidget添加槽函数AddProToTree
AddProToTree函数里判断路径和名字是否准确,然后创建一个item插入到treewidget里。
1 | void ProTreeWidget::AddProToTree(const QString &name, const QString &path) |
5 在MainWindow中串联创建项目逻辑
因为在MainWindow的构造函数中已经添加了SlotCreatePro和信号的连接
1 | //连接创建项目槽函数 |
所以这里实现点击创建项目后设置向导的逻辑
1 | void MainWindow::SlotCreatePro(bool){ |
我们在qss中设置ProTree样式
1 | ProTree { |
这样在wizard点击完成时触发done函数,进而发送信号触发ProTree的AddProToTree函数了,从而生成一个项目目录的item。
效果如下
文件夹导入功能
我们要在生成的ProTreeWidget的项目root item中点击右键,弹出菜单,然后选择导入文件夹,将文件夹中的目录和文件递归的导入我们创建的项目目录,并且在root下生成item节点。
1 ProTreeWidget构造函数添加信号和槽函数连接,并且创建导入文件的动作,并为该动作连接槽函数。
1 | connect(this, &ProTreeWidget::itemPressed, this, &ProTreeWidget::SlotItemPressed); |
itemPressed信号是从QTreeWidget基类继承而来的,在QTreeWidget中的item被点击时发出。
1 | void ProTreeWidget::SlotItemPressed(QTreeWidgetItem *pressedItem, int column) |
TreeItemPro是我们在const.h中定义的类型,在SlotItemPressed函数中判断是否为右键点击,如果是再根据item的类型判断是root节点,则在菜单中添加动作。
接下来点击导入文件动作之后执行SlotImport函数。
因为导入操作是一个耗时的操作,所以要放到单独的线程中执行,主线程启动一个进度对话框显示导入进度,同时可以控制导入的中止操作等。
在导入时弹出一个文件选择对话框,设置默认路径
1 | void ProTreeWidget::SlotImport() |
文件选择对话框选择要导入的文件夹,返回路径,我们根据这个路径做copy操作,将文件夹内的文件和文件夹都copy到之前设置的项目路径里。
这是个耗时的操作,那我们重新实现一个线程继承自QThread类,简单看一下构造函数
2 自定义线程完成文件复制和树目录创建
1 | ProTreeThread::ProTreeThread(const QString &src_path, const QString &dist_path, |
parent传递给父类构造函数,_src_path表示打开的文件夹路径,_dist_path表示我们创建的项目路径,_file_count表示文件数,用来和进度框交互,_parent_item新创建节点的父节点,_self表示QProTreeWidget对象,_root表示新创建节点隶属于哪个根节点,便于后期做交互。_bstop表示是否停止,如果为true则线程终止。
3 实现copy文件功能和目录树创建
(1) 根据文件类型(文件夹还是文件)执行不同的逻辑,如果是文件则创建item添加到父节点下。如果是文件夹类型,则递归进入创建逻辑,直到所有的文件和文件夹被遍历完成。
(2) 如果_bstop被设置为true,则退出创建逻辑。
(3) 统计文件数,发信号SigUpdateProgress通知进度框更新进度
浅谈一下_bstop的设计逻辑。因为QTread类提供了terminate和quit函数,这些只能从机制上保证线程退出,并不能保证逻辑的准确性,所以我并没有采用这个机制,而是通过_bstop的方式从逻辑上控制退出。
至于为什么有多处判断,因为创建逻辑是递归方式,为了保证退出的效率所以在多处判断,不加锁也是为了提高程序运行的效率。
1 | void ProTreeThread::CreateProTree(const QString &src_path, const QString &dist_path, |
4 重写线程run函数
run函数就是线程启动后执行的函数,如果CreateProTree运行结束,判断_bstop是否为true,如果为true说明取消了创建操作,那么就要把根节点移除,并删除文件夹内的文件。
1 | void ProTreeThread::run() |
5 完善ProTreeWidget的SlotImport函数
创建进队对话框,然后连接线程和对话框的信号和槽
(1) 当对话框被取消时发出QProgressDialog::canceled信号,被ProTreeWidget::SlotCancelProgress捕获。(对话框取消,ProTreeWidget做回收操作并发送SigCancelProgress)
(2) ProTreeWidget发出SigCancelProgress信号,被ProTreeThread::SlotCancelProgress捕获。(对话框取消,线程终止)
(3) 连接ProTreeThread::SigFinishProgress和ProTreeWidget::SlotFinishProgress,进度框响应线程完成操作。
(4) 连接ProTreeThread::SigUpdateProgress和ProTreeWidget::SlotUpdateProgress,更新进度框进度。
1 | int file_count = 0; |
相关槽函数如下
1 | void ProTreeWidget::SlotUpdateProgress(int count) |
运行导入文件效果如下
为了完善共功能,在之前的ProTreeWidget构造函数里添加其他的几个动作,包括设置活动项目,关闭项目,开启轮播等,这里不再赘述。