如何使用Qt+C++撸一个待办事项提醒软件?

一、前言

最近大论文已经修改的差不多了,即将提交盲审,所以接下来的一个月时间算是一段比较空闲的日子吧。

也不知道是不是熬夜熬多了,最近记性真不好,前一秒还记着要干什么,后面就忘了。想过用便签记录待办事项,把便签贴到实验室的电脑上,这样每次就能看到了,时刻提醒自己处理事情。但是,咱程序员天生就不会用纸记录事情😎😎(好吧,事实是我没有纸质便签,还得买,有那钱咱买瓶脉动喝不香么🤷‍♂️🤷‍♂️),所以,必须发挥咱代码搬运工的优势,写一个待办事项软件!!!

二、构思

我的想法是这样的,就像便签能够贴在任何地方一样,该软件要能够贴在电脑桌面上,像许多桌面管理软件一样,这个软件不受电脑显示桌面或者Win+D等的控制,始终贴在桌面上,这样无论我如何操作,每次我都能看见我的待办事项,这是最基本的,像是一个”电子便签“,可以记录事情,然后贴到桌面的某个地方。下文统称这个软件为TODO。

当然,作为一个信息记录软件,基本的信息编辑与删除功能是必需的,此外,TODO应该还要有比较好看的界面和一定的UI用户自定义功能,毕竟是个要贴在电脑桌面上的软件,界面难看的话天天看也难免不闹心🤣🤣。

现在一个最基本的待办事项记录软件TODO已经构思的差不多了,很简单,接下来着手实现。

三、设计与实现

TODO软件的实现使用Qt+Cpp+SQLite框架,SQLite负责数据的持久化。

3.1 数据库设计

这部分貌似没啥可说的,TODO目前的功能很简单,数据库主要用来存储待办事项数据,谈不上什么数据库设计。简单提一下,由于TODO中数据主要是文字信息的存储,所以选择使用SQLite数据库,设计的表记录包含5个字段,数据库主键id,待办事项标题,待办事项内容,待办事项的创建时间,标记待办事项是否完成的flag。

id note_title note_context note_time is_fin
int primary key varchar(30) varchar(150) char(20) int
主键 标题 内容 创建时间 完成标记

3.2 TODO功能实现

3.2.1 无边框界面设计实现

界面设计其实没啥可说的,但是实现的时候遇到一个问题还困惑了我好久,在此浅浅提一下,TODO界面采用无边框的样式,同时隐藏了任务栏图标显示,对于这种Tool型的窗口直接使用close()方法是关不掉的(Tool作为工具窗口,一般的关闭事件,系统认为只是隐藏而已,所以不是真正的关闭),如果真正退出应用程序,需要重载QWidgetclose()函数,在其中加入QApplication::quit()

在设置无边框Tool窗口时使用的所有Dialog均需要指定一个parent对象,如果不指定,为nullptr,则作为顶层窗口,一旦这个顶层窗口关闭后(Dialog能够真正的关闭,而不是隐藏),整个程序就会关闭,包括我们设置的无边框Tool窗口。

1
this->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::X11BypassWindowManagerHint);

3.2.2 TODO开机自启动

设置TODO程序开机自启动是通过写注册表的方式实现的。注册表中设置自启动时采用key-value的形式,key为自启动的软件名称,value为自启动的软件路径。通常将value设置为软件可执行文件的路径,但是通过这种方法设置的开机自启动会导致程序启动时读取配置文件或者其它文件失败,因为开机启动命令是CMD命令,是System32进程启动可执行程序,导致可执行程序路径错误。

可行的解决方法是:

  1. 创建快捷方式,开机自启动的value是快捷方式的路径。
  2. 创建bat脚本,开机自启动的value是脚本的路径。
1
2
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

第一个为所有用户登录时启动的项目,第二个是当前用户登录时启动的项目。编辑第一个注册表需要管理员权限,因此使用第二个。

快捷方式设计开机自启动实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
QSettings reg("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);

QString appName = QApplication::applicationName();

if (autoStart)
{
QString strAppPath = QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
strAppPath.replace(".exe", ".lnk");
if(!QFile::exists(strAppPath))
{
QFile::link(QCoreApplication::applicationFilePath(), appName + ".lnk");
}
reg.setValue(appName, strAppPath);
Log::getLogger()->LOG_INFO("Info: Set TODO auto run on computer start.")
}
else
{
reg.remove(appName);
Log::getLogger()->LOG_INFO("Info: Cancel TODO auto run on computer start.")
}

设置开机自启动的bat脚本如下:

1
2
3
@echo off
cd /d %~dp0
start xxxxxx.exe

3.2.3 待办事项列表设计实现

TODO软件待办事项是放在QListWidget容器中的,该容器支持对容器里面的Item进行排序,排序规则可自定义。TODO软件的待办事项需要按照已完成/未完成进行排序,同时已完成/未完成的两组列表中需要按照创建时间的先后进行排序。

实现QListWidget自定义排序可以通过重载QListWidgetItem类的<运算符。实现方式如下:

1
2
3
4
5
6
struct SortWeight
{
int tag;
QString time;
};
Q_DECLARE_METATYPE(SortWeight)
1
2
3
4
5
6
bool compare(const QString &str1, const QString &str2)
{
QDateTime t1 = QDateTime::fromString(str1, "hh:mm:ss yyyy/MM/dd");
QDateTime t2 = QDateTime::fromString(str2, "hh:mm:ss yyyy/MM/dd");
return t1 < t2;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ListWidgetItem : public QListWidgetItem
{
public:
bool operator<(const QListWidgetItem &other) const
{
SortWeight a = this->data(Qt::UserRole).value<SortWeight>();
SortWeight b = other.data(Qt::UserRole).value<SortWeight>();

if(a.tag == b.tag)
{
return !compare(a.time, b.time);
}
return a.tag < b.tag;
}
};

3.2.4 TODO日志系统实现

软件的日志系统是记录分析软件运行错误的重要部分,是必不可少的。

TODO软件的日志系统使用Log4QT搭建。

3.4 TODO界面

TODO主界面如下:

mainwindow.png

软件设置界面如下:

configwindow.png

参考文章

关于Qt隐藏任务栏已及导致QDialog关闭整个程序问题

QT 设置程序开机启动(Windows)

Qt设置应用程序开机自启 (解决设置失败原因、解决不能读取相对路径文件问题)

Author: wnxy
Link: https://wnxy.xyz/2023/04/04/page/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.