一、开发环境
Ubuntu 20.04
QT6.0
二、新建 Qt Wigets Application
这里的基类选择 Wigets,
pro 配置文件添加 sql 模块,需要用到 sqlite,
QT += sql
三、添加数据库连接头文件
// connection.h
#ifndef CONNECTION_H
#define CONNECTION_H
#include <QMessageBox>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
static bool createConnection()
{
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(":memory:");
//db.setDatabaseName("ikun.db");
bool res = db.open();
if (res)
{
qDebug() << "open ikun.db ok !";
}
else
{
qDebug() << "open ikun.db fail: " << db.lastError().text();
}
QSqlQuery query;
if(!query.exec("create table task_history(id INTEGER PRIMARY KEY AUTOINCREMENT,task_name varchar(200) not null, cost_time varchar(20) not null, create_time varchar(25) not null)"))
{
qDebug() << "create table task_history error: " << query.lastError().text();
}
return true;
}
#endif // CONNECTION_H
初始化连接到 sqlite 数据库,这里使用的内存数据库,没有持久化到磁盘,连接数据库之后创建了一个表 task_history ,主键为 id 自增,任务事项 task_name 以及消耗时间 cost_time,
四、布局设计
一个 QLineEdit 用于输入任务事项,一个 QLCDNumber 用于计时,一个 QPlainTextEdit 用于输出日志,两个 QPushButton 用于操作开始计时和终止计时,
除了组件的布局调整,还需要在主窗体新增一个槽函数 on_Widget_customContextMenuRequested ,在添加右键菜单功能时需要用到,
五、修改 widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QDateTime>
#include <QTimer>
#include <QTime>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *);
protected:
QPoint pos;
QTimer * timer;
QTime * timeRecord;
bool isStart;
private slots:
void update_time();
void on_start_btn_clicked();
void get_todo_summary();
void on_end_btn_clicked();
void on_Widget_customContextMenuRequested(const QPoint &pos);
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
头文件中主要定义了计时器的成员属性与成员方法、鼠标事件、绘画事件以及对应的槽方法,
六、修改 widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <qpainter.h>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QMessageBox>
#include <QMenu>
#include <QSqlQueryModel>
#include <QTableView>
#include <QDateTime>
#include <QScreen>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowFlag(Qt::FramelessWindowHint);
this->setAttribute(Qt::WA_TranslucentBackground);
this->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
// init
this->isStart = false;
this->timer = new QTimer;
this->timeRecord = new QTime(0, 0, 0);
// connect timer and SLOT function
connect(this->timer,SIGNAL(timeout()),this,SLOT(update_time()));
// menu
this->setContextMenuPolicy(Qt::CustomContextMenu);
}
Widget::~Widget()
{
delete ui;
}
void Widget::mousePressEvent(QMouseEvent* ev)
{
if(ev->button()==Qt::LeftButton)
{
pos=ev->pos();
}
}
void Widget::mouseMoveEvent(QMouseEvent*ev)
{
if(ev->buttons()==Qt::LeftButton)
{
int x,y;
x=ev->pos().x()-pos.x();
y=ev->pos().y()-pos.y();
this->move(this->x()+x,this->y()+y);
}
}
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pixmap;
pixmap.load(QString("../MyPet/image/pikakun.png"));
painter.drawPixmap(0, 0, 128, 128, pixmap);
}
void Widget::on_start_btn_clicked()
{
if(this->ui->todo_line->text().trimmed().length() == 0 )
{
QMessageBox::warning(this, tr("warning"), tr("please input todo first!"), QMessageBox::Yes);
this->ui->todo_line->setFocus();
return;
}
this->ui->todo_line->setEnabled(false);
this->ui->last_log->setPlainText(QString("Mission " + this->ui->todo_line->text().trimmed() + " is timing..."));
if(!this->isStart)
{
this->timer->start(128);
this->ui->start_btn->setText(QString("Pause"));
}
else
{
this->timer->stop();
this->ui->start_btn->setText(QString("Continue"));
}
this->isStart = !this->isStart;
}
void Widget::update_time()
{
// timer add 1 secs, and then display
*this->timeRecord = this->timeRecord->addMSecs(128);
this->ui->timer->display(this->timeRecord->toString(QString("hh:mm:ss.zzz")));
}
void Widget::on_end_btn_clicked()
{
// stop timer
this->timer->stop();
// show log
this->ui->last_log->setPlainText(
QString(
this->ui->todo_line->text() +
" Cost Time " +
this->timeRecord->toString(QString("hh:mm:ss.zzz"))
)
);
// save db
QSqlDatabase::database().transaction();
QSqlQuery query;
query.prepare(QString("insert into task_history(task_name, cost_time, create_time ) "
"values(:task_name, :cost_time,:create_time)"));
query.bindValue(":task_name", this->ui->todo_line->text());
query.bindValue(":cost_time", this->timeRecord->toString(QString("hh:mm:ss.zzz")));
QDateTime dt =QDateTime::currentDateTime();
// format yyyy//MM/dd hh:mm:s.zzz
QString create_time = dt.toString("yyyy-MM-dd hh:mm:ss.zzz");
query.bindValue(":create_time", create_time);
if(!query.exec())
{
qDebug() << "insert into task_history error: " << query.lastError().text();
}
QSqlDatabase::database().commit();
// reset display
this->timeRecord->setHMS(0,0,0);
this->ui->timer->display(this->timeRecord->toString(QString("hh:mm:ss.zzz")));
this->isStart = false;
this->ui->start_btn->setText(QString("Start"));
// reset todo_line
this->ui->todo_line->clear();
this->ui->todo_line->setEnabled(true);
this->ui->todo_line->setFocus();
}
void Widget::get_todo_summary()
{
QSqlQueryModel *model = new QSqlQueryModel(this);
model->setQuery("select * from task_history");
model->setHeaderData(0, Qt::Horizontal, tr("ID"));
model->setHeaderData(1, Qt::Horizontal, tr("Task"));
model->setHeaderData(2, Qt::Horizontal, tr("Time"));
model->setHeaderData(3, Qt::Horizontal, tr("LogTime"));
QTableView *view = new QTableView;
view->setWindowTitle(QString("summary"));
view->setModel(model);
// must after setModel
view->setColumnWidth(1,250);
view->setColumnWidth(3,150);
QSize *min_size = new QSize;
min_size->setWidth(620);
min_size->setHeight(400);
view->setMinimumSize(*min_size);
QScreen *screen = QApplication::primaryScreen();
QSize screenSize = screen->size();
view->move(( screenSize.width() - min_size->width() ) / 2 , ( screenSize.height() - view->height() ) / 2 );
view->show();
// free mem
delete min_size;
}
void Widget::on_Widget_customContextMenuRequested(const QPoint &pos)
{
QMenu *menu = new QMenu(this);
QAction *summary = new QAction( QIcon(QString("../MyPet/image/pikakun.png")),tr("summary"), this);
menu->addAction(summary);
connect(summary, SIGNAL(triggered()), this, SLOT(get_todo_summary()));
menu->exec(cursor().pos());
}
七、修改 main.cpp
初始化数据库连接,并将窗口移动至屏幕的中间位置,
#include "widget.h"
#include "connection.h"
#include <QApplication>
#include <QLocale>
#include <QTranslator>
#include <QScreen>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
if (!createConnection())
{
return 1;
}
QTranslator translator;
const QStringList uiLanguages = QLocale::system().uiLanguages();
for (const QString &locale : uiLanguages) {
const QString baseName = "MyPet_" + QLocale(locale).name();
if (translator.load(":/i18n/" + baseName)) {
a.installTranslator(&translator);
break;
}
}
Widget w;
QScreen *screen = QApplication::primaryScreen();
QSize screenSize = screen->size();
w.move(( screenSize.width() - w.width() ) / 2 , ( screenSize.height() - w.height() ) / 2 );
w.show();
return a.exec();
}
八、效果演示
1、输入需要计时的事项后,点击“Start”按钮开始计时
2、开始计时后,“Start”按钮会变成“Pause”按钮,点击该按钮会暂停计时
3、暂停后,“Pause”按钮会变成“Continue”按钮,点击该按钮会继续计时
4、点击“End”按钮后,停止计时,并记录事项耗时到 sqlite 数据库中
5、右键有“Summary”菜单,点击后可以看到事项汇总
特别注意,如果需要将数据从内存中持久化到硬盘,则修改 connection.h,
# 保存在内存,注释这行
// db.setDatabaseName(":memory:");
# 保存在 ikun.db,添加这行
db.setDatabaseName("ikun.db");