C++学习之路(十九)C++ 用Qt5实现一个工具箱(用SQLite数据库来管理粘贴板数据)- 示例代码拆分讲解

上篇文章,我们用 Qt5 实现了在小工具箱中添加了《点击按钮以新窗口打开功能面板》功能。今天我们把粘贴板功能用SQLite数据库来管理,用SQLite来实现增删改查。下面我们就来看看如何来规划开发这样的小功能并且添加到我们的工具箱中吧。

老规矩,先上图

在这里插入图片描述

需求功能概述(基于老代码做变更):

我们这次想要使用 SQLite 来存储和管理剪贴板的内容,那么就需要进行一些重构。

首先,需要创建一个 SQLite 数据库并设计一个表来存储剪贴板的内容。通常,表会有一个自增主键 ID,以及存储文本内容的字段。

ClipboardManager 类中,我们需要:

  1. 初始化 SQLite 数据库连接。
  2. 创建一个表来存储剪贴板内容。
  3. 修改 updateList 函数,将文本数据插入到 SQLite 数据库中,而不是直接在 QListWidget 中添加。
  4. 编写一个新的函数来从数据库中检索内容,并在 UI 中显示它们。
  5. 实现删除数据库中内容的功能,以及清空数据库的功能。

这里是一个大致的示例,说明如何使用 SQLite 进行存储和检索:

#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>// 初始化数据库连接
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("clipboard_data.db");
if (!db.open()) {qDebug() << "Error: Failed to open database:" << db.lastError().text();return;
}// 创建表格
QSqlQuery query;
query.exec("CREATE TABLE IF NOT EXISTS clipboard_data (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT)");// 插入数据
void insertData(const QString& data) {QSqlQuery query;query.prepare("INSERT INTO clipboard_data (content) VALUES (:content)");query.bindValue(":content", data);query.exec();
}// 从数据库中获取数据并更新 UI
void updateListFromDatabase(QListWidget* listWidget) {listWidget->clear();QSqlQuery query;query.exec("SELECT * FROM clipboard_data");while (query.next()) {QString data = query.value(1).toString(); // Assumes content is stored in the second columnlistWidget->addItem(data);}
}// 清空数据库
void clearDatabase() {QSqlQuery query;query.exec("DELETE FROM clipboard_data");
}

需要将这些功能结合到 ClipboardManager 类中,并对原有的 updateListclearClipboard 函数进行修改,以便使用这些数据库操作。

此外,为了在用户添加或删除条目时更新数据库,我们还需要重写 QListWidget 上的事件函数。


核心实现代码:

// 新引入的库
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>class ClipboardManager : public QWidget {
Q_OBJECT
public:explicit ClipboardManager(QWidget *parent = nullptr) : QWidget(parent) {// 初始化数据库连接QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");db.setDatabaseName("clipboard_data.db");if (!db.open()) {qDebug() << "Error: Failed to open database:" << db.lastError().text();return;}// 创建表格QSqlQuery query;query.exec("CREATE TABLE IF NOT EXISTS clipboard_data (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT)");auto *newWindow = new QDialog(this);newWindow->setWindowTitle("粘贴板记录");// 创建布局并将组件添加到布局中auto *layout = new QVBoxLayout(newWindow);listWidget = new QListWidget(newWindow);updateListFromDatabase(); // 从数据库更新列表auto *clearButton = new QPushButton("清空记录", newWindow);connect(clearButton, &QPushButton::clicked, this, &ClipboardManager::clearClipboard);layout->addWidget(listWidget);layout->addWidget(clearButton);connect(myApp->clipboard(), &QClipboard::dataChanged, this, &ClipboardManager::addClipboardData);newWindow->setLayout(layout);newWindow->exec();}private slots:// 当剪贴板发生变化时,将新的数据添加到数据库void addClipboardData() {const QClipboard *clipboard = myApp->clipboard();const QMimeData *mimeData = clipboard->mimeData();if (mimeData->hasText()) {const QString clipboardText = mimeData->text();static QString lastClipboardData; // 添加静态变量用于记录最后一次剪贴板内容if (!clipboardText.isEmpty() && clipboardText != lastClipboardData) {insertData(clipboardText);lastClipboardData = clipboardText; // 更新最后一次剪贴板内容}}updateListFromDatabase(); // 更新列表显示最新数据}// 清空剪贴板和数据库void clearClipboard() {myApp->clipboard()->clear();clearDatabase();listWidget->clear();}private:QListWidget *listWidget;static void insertData(const QString& data) {QSqlQuery query;query.prepare("INSERT INTO clipboard_data (content) VALUES (:content)");query.bindValue(":content", data);query.exec();}void updateListFromDatabase() {listWidget->clear();QSqlQuery query;query.exec("SELECT * FROM clipboard_data");while (query.next()) {QString data = query.value(1).toString(); // Assumes content is stored in the second columnlistWidget->addItem(data);}}static void clearDatabase() {QSqlQuery query;query.exec("DELETE FROM clipboard_data");}
};

核心代码逻辑讲解

在本次核心代码中我们使用了 SQLite 数据库来存储(粘贴板)剪贴板内容。这段代码中的 ClipboardManager 类继承自 QWidget,它创建了一个包含列表和清空按钮的对话框,并使用数据库存储剪贴板内容。

它的工作原理如下:

  1. 在构造函数中,初始化了 SQLite 数据库连接,并创建了名为 clipboard_data.db 的数据库文件用于存储剪贴板内容。
  2. 创建了对话框及其布局,并将列表和清空按钮添加到布局中。
  3. 使用 addClipboardData() 槽函数捕获剪贴板内容变化事件,并将新的数据添加到数据库。
  4. 使用 updateListFromDatabase() 函数从数据库中读取剪贴板内容,更新列表显示最新数据。
  5. clearClipboard() 函数清空了剪贴板和数据库,并清空了列表显示。

这个类提供了基本的数据库交互功能,可以用于管理剪贴板内容,并在数据库中存储和显示。

有关模块引入需要提前做的工作

首先我们的 CMakeLists.txt 文件已经包含了 Widgets 模块,但并没有包含 Sql 模块。要使用数据库相关的类和功能,就需要添加 Sql 模块到我们的 CMakeLists.txt 文件中。

尝试添加 Sql 模块到 REQUIRED_LIBS 列表中,然后再次构建我们的项目,这样可以确保数据库相关的类能够被正确链接和使用。我们可以修改 REQUIRED_LIBSREQUIRED_LIBS_QUALIFIED 的设置,如下所示:

set(REQUIRED_LIBS Core Gui Widgets Sql)
set(REQUIRED_LIBS_QUALIFIED Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Sql)

这样修改之后,就可以正常的使用Sql 模块了。


完整 CMakeLists.txt 文件内容
cmake_minimum_required(VERSION 3.17)
project(qt_tools)set(CMAKE_CXX_STANDARD 14)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)set(QT_VERSION 5)
# 本次变更 ---start
set(REQUIRED_LIBS Core Gui Widgets Sql)
set(REQUIRED_LIBS_QUALIFIED Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Sql)
# 本次变更 ---end
# 将 resources.qrc 添加到项目中
set(QT_RESOURCES resources.qrc)add_executable(${PROJECT_NAME} ${QT_RESOURCES} main.cpp)if(NOT CMAKE_PREFIX_PATH)message(WARNING "CMAKE_PREFIX_PATH is not defined, you may need to set it ""(-DCMAKE_PREFIX_PATH=\"path/to/Qt/lib/cmake\" or -DCMAKE_PREFIX_PATH=/usr/include/{host}/qt{version}/ on Ubuntu)")
endif()find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} REQUIRED)
target_link_libraries(${PROJECT_NAME} ${REQUIRED_LIBS_QUALIFIED})
SQLite的数据文件在哪?

SQLite 数据库文件通常保存在我们指定的位置或者程序运行的当前目录。在上面的代码中,数据库文件名被设置为 clipboard_data.db

db.setDatabaseName("clipboard_data.db");

如果没有指定路径,数据库文件会默认存储在程序运行的当前工作目录中。通常情况下,这个目录是我们执行可执行文件的位置,比如在项目的构建目录中。

我们可以在代码中指定具体的路径,将数据库文件保存在你想要的位置,例如:

QString path = QDir::homePath() + "/MyAppData"; // 将数据库保存在用户的主目录下的 MyAppData 文件夹中
QDir().mkpath(path); // 创建目录(如果不存在的话)
db.setDatabaseName(path + "/clipboard_data.db");

这将在用户的主目录下创建一个名为 MyAppData 的文件夹,并将数据库文件保存在其中。我们也可以根据需要修改文件路径,将数据库保存在你希望的位置。

本次我们就不做指定路径存放了,就保持默认放在运行的目录中就行。
在这里插入图片描述


核心代码讲解完毕,下面是完整版代码,复制到本地跑一跑吧~


#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include <QMessageBox>
#include <QListWidget>
#include <QClipboard>
#include <QMimeData>
#include <QTextEdit>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDateTime>
#include <QLabel>
#include <QComboBox>
#include <QLineEdit>
#include <QXmlStreamReader>
#include <QFile>
#include <QFileDialog>
#include <QScreen>
#include <QCursor>
#include <QPoint>
#include <QGuiApplication>
#include <QPixmap>
#include <QThread>
#include <QMouseEvent>
#include <QTimer>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QDebug>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>#define myApp (dynamic_cast<QApplication *>(QCoreApplication::instance()))class ClickableLabel : public QLabel {
Q_OBJECTpublic:explicit ClickableLabel(QWidget *parent = nullptr) : QLabel(parent) {}signals:void clicked();protected:void mousePressEvent(QMouseEvent *event) override {if (event->button() == Qt::LeftButton) {emit clicked();}}
};class PlaceholderTextEdit : public QWidget {
Q_OBJECT
public:explicit PlaceholderTextEdit(const QString &placeholderText, QWidget *parent = nullptr) : QWidget(parent) {auto *layout = new QVBoxLayout(this);placeholderLabel = new QLabel(placeholderText, this);layout->addWidget(placeholderLabel);textEdit = new QTextEdit(this);layout->addWidget(textEdit);connect(textEdit, &QTextEdit::textChanged, this, &PlaceholderTextEdit::checkPlaceholder);checkPlaceholder(); // 初始检查setLayout(layout);}QString getText() const {return textEdit->toPlainText();}private slots:void checkPlaceholder() {placeholderLabel->setVisible(textEdit->toPlainText().isEmpty());}private:QLabel *placeholderLabel;QTextEdit *textEdit;
};class ColorConverter : public QWidget {
Q_OBJECT
public:explicit ColorConverter(QWidget *parent = nullptr) : QWidget(parent) {// 在构造函数中初始化定时器colorTimer = new QTimer(this);colorTimer->setInterval(100); // 设置定时器间隔为100毫秒connect(colorTimer, &QTimer::timeout, this, &ColorConverter::extractScreenColor);auto *layout = new QVBoxLayout(this);// 创建颜色值转换部分的输入框和按钮colorInput = new QLineEdit(this);layout->addWidget(colorInput);convertButton = new QPushButton("16进制转RGB", this);connect(convertButton, &QPushButton::clicked, this, &ColorConverter::convertToRgbColor);layout->addWidget(convertButton);convertButton = new QPushButton("RGB转16进制", this);connect(convertButton, &QPushButton::clicked, this, &ColorConverter::convertToHexColor);layout->addWidget(convertButton);convertedColorOutput = new QTextEdit(this);convertedColorOutput->setReadOnly(true);layout->addWidget(convertedColorOutput);// 创建屏幕颜色提取器部分的按钮和显示框extractColorButton = new QPushButton("开始提取屏幕颜色", this);connect(extractColorButton, &QPushButton::clicked, this, &ColorConverter::startColorCapture);layout->addWidget(extractColorButton);// 创建用于存放颜色块和颜色输出的网格布局auto *colorBlockLayout = new QGridLayout();// 创建颜色输出screenColorOutput = new ClickableLabel(this);screenColorOutput->setFixedSize(200, 90);screenColorOutput->setAlignment(Qt::AlignCenter);screenColorOutput->setStyleSheet("border: 1px solid #ddd");// 这里是触发点击事件后的复制到剪切板的函数调用connect(screenColorOutput, &ClickableLabel::clicked, this, &ColorConverter::copyColorToClipboard);colorBlockLayout->addWidget(screenColorOutput, 0, 0); // 将颜色输出放在网格布局的左上角// 创建颜色块screenColorBlock = new QLabel(this);screenColorBlock->setFixedSize(30, 30); // 设置颜色块大小screenColorBlock->setStyleSheet("border: 1px solid #ddd");colorBlockLayout->addWidget(screenColorBlock, 0, 0, Qt::AlignTop | Qt::AlignRight); // 将颜色块放在颜色输出的右上角layout->addLayout(colorBlockLayout);setLayout(layout);}protected slots:void convertToHexColor() {// 从输入框获取颜色值QString colorText = colorInput->text();// 按逗号分隔RGB文本QStringList rgbValues = colorText.split(',');if (rgbValues.size() == 3) {// 获取R、G和B的值int red = rgbValues[0].toInt();int green = rgbValues[1].toInt();int blue = rgbValues[2].toInt();// 将RGB值转换为16进制字符串QString hexRed = QString("%1").arg(red, 2, 16, QChar('0')).toUpper();QString hexGreen = QString("%1").arg(green, 2, 16, QChar('0')).toUpper();QString hexBlue = QString("%1").arg(blue, 2, 16, QChar('0')).toUpper();// 构建16进制颜色代码文本QString hexText = QString("#%1%2%3").arg(hexRed).arg(hexGreen).arg(hexBlue);convertedColorOutput->setText(hexText);} else {// 处理无效颜色输入的情况convertedColorOutput->setText("Invalid color input");}}void convertToRgbColor() {// 从输入框获取颜色值QString colorText = colorInput->text();QColor color(colorText);if (color.isValid()) {// 获取16进制颜色码的红、绿、蓝通道值int red = color.red();int green = color.green();int blue = color.blue();// 构建RGB颜色代码文本QString outputText = QString("RGB(%1, %2, %3)").arg(red).arg(green).arg(blue);convertedColorOutput->setText(outputText);} else {// 处理无效颜色输入的情况convertedColorOutput->setText("Invalid color input");}}// 开始捕获颜色的槽函数void startColorCapture() {extractColorButton->setText("请开始移动鼠标 按Tab键终止取色");colorTimer->start();}void extractScreenColor() {QPoint cursorPos = QCursor::pos();QScreen *screen = QGuiApplication::screenAt(cursorPos);if (screen) {QPixmap screenshot = screen->grabWindow(0, cursorPos.x(), cursorPos.y(), 1, 1);if (!screenshot.isNull()) {QColor color = screenshot.toImage().pixel(0, 0);QString colorText = QString("#%1").arg(color.name().mid(1));screenColorOutput->setText(colorText);QString styleSheet = QString("background-color: %1; border: 1px solid #ddd;").arg(color.name());screenColorBlock->setStyleSheet(styleSheet);} else {screenColorOutput->setText("Failed to grab screen color");}} else {screenColorOutput->setText("No screen found at the cursor position");}}void copyColorToClipboard() {// 从screenColorOutput获取颜色信息QString colorText = screenColorOutput->text();// 复制颜色值到剪切板QApplication::clipboard()->setText(colorText);// 弹出提示框显示复制的颜色值QMessageBox::information(this, "颜色已复制", QString("已复制颜色:") + colorText);}public:// 停止捕获颜色的槽函数void stopColorCapture() {colorTimer->stop();extractColorButton->setText("开始提取屏幕颜色");}
private:QLineEdit *colorInput;QPushButton *convertButton;QTextEdit *convertedColorOutput;QPushButton *extractColorButton;ClickableLabel *screenColorOutput;QLabel *screenColorBlock;QTimer *colorTimer;
};class Base64ImageConverter : public QWidget {
Q_OBJECT
public:explicit Base64ImageConverter(QWidget *parent = nullptr) : QWidget(parent) {auto *newWindow = new QDialog(this);newWindow->setWindowTitle("Base64图片预览");auto *layout = new QVBoxLayout(this);selectImageButton = new QPushButton("选择图片", this);connect(selectImageButton, &QPushButton::clicked, this, &Base64ImageConverter::selectImage);layout->addWidget(selectImageButton);imagePathTextEdit = new QTextEdit(this);imagePathTextEdit->setReadOnly(true);layout->addWidget(imagePathTextEdit);convertToBase64Button = new QPushButton("转换为Base64编码", this);connect(convertToBase64Button, &QPushButton::clicked, this, &Base64ImageConverter::convertToBase64);layout->addWidget(convertToBase64Button);base64OutputTextEdit = new QTextEdit(this);base64OutputTextEdit->setAcceptRichText(false);layout->addWidget(base64OutputTextEdit);convertToImagePreviewButton = new QPushButton("Base64编码转图片预览", this);connect(convertToImagePreviewButton, &QPushButton::clicked, this, &Base64ImageConverter::convertToImagePreview);layout->addWidget(convertToImagePreviewButton);imagePreviewLabel = new QLabel(this);imagePreviewLabel->setFixedHeight(200); // 设置图片高度为 200layout->addWidget(imagePreviewLabel);newWindow->setLayout(layout);newWindow->exec();}private slots:void selectImage() {// 创建文件对话框用于选择图片文件QString imagePath = QFileDialog::getOpenFileName(this, tr("选择图片"), "", tr("Images (*.png *.jpg *.bmp)"));// 如果用户选择了图片文件,则显示文件路径if (!imagePath.isEmpty()) {imagePathTextEdit->setText(imagePath);selectedImagePath = imagePath;}}void convertToBase64() {if (selectedImagePath.isEmpty()) {QMessageBox::warning(this, "警告", "请先选择图片");return;}// 读取选定图片文件的内容QFile file(selectedImagePath);if (file.open(QIODevice::ReadOnly)) {QByteArray imageData = file.readAll();QString base64Data = imageData.toBase64();base64OutputTextEdit->setText(base64Data);} else {QMessageBox::critical(this, "错误", "无法读取图片文件");}}void convertToImagePreview() {QString base64Data = base64OutputTextEdit->toPlainText().toUtf8();if (base64Data.isEmpty()) {QMessageBox::warning(this, "警告", "请先转换为Base64编码");return;}// 从Base64数据创建QImage并显示在标签中QByteArray byteArray = QByteArray::fromBase64(base64Data.toUtf8());QImage image;image.loadFromData(byteArray);if (!image.isNull()) {// 水平居中对齐imagePreviewLabel->setAlignment(Qt::AlignHCenter);// 缩放图片到 200x200QPixmap pixmap = QPixmap::fromImage(image.scaled(200, 200, Qt::KeepAspectRatio));imagePreviewLabel->setPixmap(pixmap);} else {QMessageBox::critical(this, "错误", "无法加载图片");}}private:QPushButton *selectImageButton;QTextEdit *imagePathTextEdit;QPushButton *convertToBase64Button;QTextEdit *base64OutputTextEdit;QPushButton *convertToImagePreviewButton;QLabel *imagePreviewLabel;QString selectedImagePath;
};class Base64Converter : public QWidget {
Q_OBJECT
public:explicit Base64Converter(QWidget *parent = nullptr) : QWidget(parent) {auto *newWindow = new QDialog(this);newWindow->setWindowTitle("Base64加解密");auto *layout = new QVBoxLayout(this);inputTextEdit = new QTextEdit(this);layout->addWidget(inputTextEdit);encryptButton = new QPushButton("加密", this);connect(encryptButton, &QPushButton::clicked, this, &Base64Converter::encryptText);layout->addWidget(encryptButton);decryptButton = new QPushButton("解密", this);connect(decryptButton, &QPushButton::clicked, this, &Base64Converter::decryptText);layout->addWidget(decryptButton);outputTextEdit = new QTextEdit(this);outputTextEdit->setReadOnly(true);layout->addWidget(outputTextEdit);newWindow->setLayout(layout);newWindow->exec();}private slots:void encryptText() {QString inputText = inputTextEdit->toPlainText().toUtf8();QByteArray byteArray = inputText.toUtf8().toBase64();outputTextEdit->setText(byteArray);}void decryptText() {QString inputText = inputTextEdit->toPlainText();QByteArray byteArray = QByteArray::fromBase64(inputText.toUtf8());outputTextEdit->setText(byteArray);}private:QTextEdit *inputTextEdit;QTextEdit *outputTextEdit;QPushButton *encryptButton;QPushButton *decryptButton;
};class XmlFormatter : public QWidget {
Q_OBJECT
public:explicit XmlFormatter(QWidget *parent = nullptr) : QWidget(parent) {auto *newWindow = new QDialog(this);newWindow->setWindowTitle("XML格式化");auto *layout = new QVBoxLayout(this);inputTextEdit = new QTextEdit(this);layout->addWidget(inputTextEdit);formatButton = new QPushButton("格式化 XML", this);connect(formatButton, &QPushButton::clicked, this, &XmlFormatter::formatXml);layout->addWidget(formatButton);outputTextEdit = new QTextEdit(this);outputTextEdit->setReadOnly(true);layout->addWidget(outputTextEdit);newWindow->setLayout(layout);newWindow->exec();}private slots:void formatXml() {// 获取输入的XML文本QString inputText = inputTextEdit->toPlainText();if (!inputText.isEmpty()) {// 使用 QXmlStreamReader 读取输入的 XML 文本QXmlStreamReader reader(inputText);QString formattedXml;int indentLevel = 0;while (!reader.atEnd() && !reader.hasError()) {if (reader.isStartElement()) {formattedXml += getIndent(indentLevel) + "<" + reader.name().toString() + ">\n";++indentLevel;} else if (reader.isEndElement()) {--indentLevel;formattedXml += getIndent(indentLevel) + "</" + reader.name().toString() + ">\n";} else if (reader.isCharacters() && !reader.isWhitespace()) {formattedXml += getIndent(indentLevel) + reader.text().toString() + "\n";}reader.readNext();}if (reader.hasError()) {outputTextEdit->setText("XML 解析错误:" + reader.errorString());} else {outputTextEdit->setText(formattedXml);}} else {// 如果输入为空,则清空输出区域outputTextEdit->clear();}}static QString getIndent(int level) {return QString(level * 4, ' '); // 4空格作为缩进}private:QTextEdit *inputTextEdit;QPushButton *formatButton;QTextEdit *outputTextEdit;
};class NumberBaseConverter : public QWidget {
Q_OBJECT
public:explicit NumberBaseConverter(QWidget *parent = nullptr) : QWidget(parent) {auto *newWindow = new QDialog(this);newWindow->setWindowTitle("进制转换");auto *layout = new QVBoxLayout(this);// 横向排列的输入框和选择框auto *horizontalLayout = new QHBoxLayout();// 创建输入框并添加到水平布局inputLineEdit = new QLineEdit(this);horizontalLayout->addWidget(inputLineEdit);// 连接输入框的文本变化信号到槽函数connect(inputLineEdit, &QLineEdit::textChanged, this, &NumberBaseConverter::convertNumber);// 创建下拉选择框并添加到水平布局baseComboBox = new QComboBox(this);baseComboBox->addItem("二进制");baseComboBox->addItem("八进制");baseComboBox->addItem("十进制");baseComboBox->addItem("十六进制");horizontalLayout->addWidget(baseComboBox);// 将水平布局添加到垂直布局layout->addLayout(horizontalLayout);// 创建四个只读的输出框并添加到垂直布局binaryOutput = new QLineEdit(this);binaryOutput->setReadOnly(true);layout->addWidget(binaryOutput);octalOutput = new QLineEdit(this);octalOutput->setReadOnly(true);layout->addWidget(octalOutput);decimalOutput = new QLineEdit(this);decimalOutput->setReadOnly(true);layout->addWidget(decimalOutput);hexOutput = new QLineEdit(this);hexOutput->setReadOnly(true);layout->addWidget(hexOutput);// 连接下拉选择框的选择变化信号到槽函数,并进行初始转换connect(baseComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,&NumberBaseConverter::convertNumber);convertNumber(); // 初始转换newWindow->setLayout(layout);newWindow->exec();}private slots:void convertNumber() {QString inputText = inputLineEdit->text();bool ok;int base = baseComboBox->currentIndex();if (!inputText.isEmpty()) {// 根据所选进制进行转换if (base == 0) {int number = inputText.toInt(&ok, 2); // 二进制转十进制if (ok) {binaryOutput->setText(inputText);octalOutput->setText(QString::number(number, 8));decimalOutput->setText(QString::number(number));hexOutput->setText(QString::number(number, 16).toUpper());}} else if (base == 1) {// 八进制转换int number = inputText.toInt(&ok, 8);if (ok) {binaryOutput->setText(QString::number(number, 2));octalOutput->setText(inputText);decimalOutput->setText(QString::number(number));hexOutput->setText(QString::number(number, 16).toUpper());}} else if (base == 2) {// 十进制转换int number = inputText.toInt(&ok, 10);if (ok) {binaryOutput->setText(QString::number(number, 2));octalOutput->setText(QString::number(number, 8));decimalOutput->setText(inputText);hexOutput->setText(QString::number(number, 16).toUpper());}} else if (base == 3) {// 十六进制转换int number = inputText.toInt(&ok, 16);if (ok) {binaryOutput->setText(QString::number(number, 2));octalOutput->setText(QString::number(number, 8));decimalOutput->setText(QString::number(number));hexOutput->setText(inputText.toUpper());}}} else {// 如果输入为空,则清空输出binaryOutput->clear();octalOutput->clear();decimalOutput->clear();hexOutput->clear();}}private:QLineEdit *inputLineEdit;QComboBox *baseComboBox;QLineEdit *binaryOutput;QLineEdit *octalOutput;QLineEdit *decimalOutput;QLineEdit *hexOutput;
};class DateTimeTimestampConverter : public QWidget {
Q_OBJECT
public:explicit DateTimeTimestampConverter(QWidget *parent = nullptr) : QWidget(parent) {auto *newWindow = new QDialog(this);newWindow->setWindowTitle("时间戳转换");auto *layout = new QVBoxLayout(this);inputTextEdit = new PlaceholderTextEdit("在此输入日期时间或时间戳", this);layout->addWidget(inputTextEdit);convertToTimestampButton = new QPushButton("日期时间转时间戳", this);connect(convertToTimestampButton, &QPushButton::clicked, this, &DateTimeTimestampConverter::convertToTimestamp);layout->addWidget(convertToTimestampButton);convertToDateTimeButton = new QPushButton("时间戳转日期时间", this);connect(convertToDateTimeButton, &QPushButton::clicked, this, &DateTimeTimestampConverter::convertToDateTime);layout->addWidget(convertToDateTimeButton);outputTextEdit = new QTextEdit(this);outputTextEdit->setReadOnly(true);layout->addWidget(outputTextEdit);newWindow->setLayout(layout);newWindow->exec();}private slots:void convertToTimestamp() {QString inputText = inputTextEdit->getText();QDateTime dateTime = QDateTime::fromString(inputText, "yyyy-MM-dd HH:mm:ss");if (dateTime.isValid()) {qint64 timestamp = dateTime.toSecsSinceEpoch();outputTextEdit->setText(QString::number(timestamp));} else {outputTextEdit->setText("无效的日期时间格式!");}}void convertToDateTime() {QString inputText = inputTextEdit->getText();bool ok;qint64 timestamp = inputText.toLongLong(&ok);if (ok) {QDateTime dateTime;dateTime.setSecsSinceEpoch(timestamp);outputTextEdit->setText("时间戳 " + inputText + " 对应的日期时间是:" + dateTime.toString("yyyy-MM-dd HH:mm:ss"));} else {outputTextEdit->setText("无效的时间戳格式!");}}private:QPushButton *convertToTimestampButton;QPushButton *convertToDateTimeButton;QTextEdit *outputTextEdit;PlaceholderTextEdit *inputTextEdit;
};class JsonFormatter : public QWidget {
Q_OBJECT
public:explicit JsonFormatter(QWidget *parent = nullptr) : QWidget(parent) {auto *newWindow = new QDialog(this);newWindow->setWindowTitle("JSON格式化");auto *layout = new QVBoxLayout(this);inputTextEdit = new QTextEdit(this);layout->addWidget(inputTextEdit);formatButton = new QPushButton("格式化", this);connect(formatButton, &QPushButton::clicked, this, &JsonFormatter::formatJson);layout->addWidget(formatButton);outputTextEdit = new QTextEdit(this);outputTextEdit->setReadOnly(true);layout->addWidget(outputTextEdit);newWindow->setLayout(layout);newWindow->exec();}private slots:void formatJson() {QString inputText = inputTextEdit->toPlainText();QJsonParseError error{};QJsonDocument jsonDoc = QJsonDocument::fromJson(inputText.toUtf8(), &error);if (error.error != QJsonParseError::NoError) {outputTextEdit->setText("JSON 解析错误:" + error.errorString());return;}QJsonObject jsonObj = jsonDoc.object();QJsonDocument formattedJson(jsonObj);outputTextEdit->setText(formattedJson.toJson());}private:QTextEdit *inputTextEdit;QPushButton *formatButton;QTextEdit *outputTextEdit;
};class ClipboardManager : public QWidget {
Q_OBJECT
public:explicit ClipboardManager(QWidget *parent = nullptr) : QWidget(parent) {// 初始化数据库连接QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");db.setDatabaseName("clipboard_data.db");if (!db.open()) {qDebug() << "Error: Failed to open database:" << db.lastError().text();return;}// 创建表格QSqlQuery query;query.exec("CREATE TABLE IF NOT EXISTS clipboard_data (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT)");auto *newWindow = new QDialog(this);newWindow->setWindowTitle("粘贴板记录");// 创建布局并将组件添加到布局中auto *layout = new QVBoxLayout(newWindow);listWidget = new QListWidget(newWindow);updateListFromDatabase(); // 从数据库更新列表auto *clearButton = new QPushButton("清空记录", newWindow);connect(clearButton, &QPushButton::clicked, this, &ClipboardManager::clearClipboard);layout->addWidget(listWidget);layout->addWidget(clearButton);connect(myApp->clipboard(), &QClipboard::dataChanged, this, &ClipboardManager::addClipboardData);newWindow->setLayout(layout);newWindow->exec();}private slots:// 当剪贴板发生变化时,将新的数据添加到数据库void addClipboardData() {const QClipboard *clipboard = myApp->clipboard();const QMimeData *mimeData = clipboard->mimeData();if (mimeData->hasText()) {const QString clipboardText = mimeData->text();static QString lastClipboardData; // 添加静态变量用于记录最后一次剪贴板内容if (!clipboardText.isEmpty() && clipboardText != lastClipboardData) {insertData(clipboardText);lastClipboardData = clipboardText; // 更新最后一次剪贴板内容}}updateListFromDatabase(); // 更新列表显示最新数据}// 清空剪贴板和数据库void clearClipboard() {myApp->clipboard()->clear();clearDatabase();listWidget->clear();}private:QListWidget *listWidget;static void insertData(const QString& data) {QSqlQuery query;query.prepare("INSERT INTO clipboard_data (content) VALUES (:content)");query.bindValue(":content", data);query.exec();}void updateListFromDatabase() {listWidget->clear();QSqlQuery query;query.exec("SELECT * FROM clipboard_data");while (query.next()) {QString data = query.value(1).toString(); // Assumes content is stored in the second columnlistWidget->addItem(data);}}static void clearDatabase() {QSqlQuery query;query.exec("DELETE FROM clipboard_data");}
};class MyMainWindow : public QWidget {
Q_OBJECT
public:explicit MyMainWindow(QWidget *parent = nullptr) : QWidget(parent) {setWindowTitle("天河工具箱");auto *layout = new QVBoxLayout(this);auto *clipboardButton = new QPushButton("粘贴板记录");clipboardButton->setObjectName("clipboardButton");connect(clipboardButton, &QPushButton::clicked, this, &MyMainWindow::showClipboardManager);layout->addWidget(clipboardButton);auto *jsonFormatButton = new QPushButton("JSON格式化");jsonFormatButton->setObjectName("jsonFormatButton");connect(jsonFormatButton, &QPushButton::clicked, this, &MyMainWindow::showJsonFormatter);layout->addWidget(jsonFormatButton);auto *timestampConverterButton = new QPushButton("时间戳转换");timestampConverterButton->setObjectName("timestampConverterButton");connect(timestampConverterButton, &QPushButton::clicked, this, &MyMainWindow::showDateTimeTimestampConverter);layout->addWidget(timestampConverterButton);auto *numberBaseConverterButton = new QPushButton("进制转换");numberBaseConverterButton->setObjectName("numberBaseConverterButton");connect(numberBaseConverterButton, &QPushButton::clicked, this, &MyMainWindow::showNumberBaseConverter);layout->addWidget(numberBaseConverterButton);auto *xmlFormatterButton = new QPushButton("XML格式化");xmlFormatterButton->setObjectName("xmlFormatterButton");connect(xmlFormatterButton, &QPushButton::clicked, this, &MyMainWindow::showXmlFormatter);layout->addWidget(xmlFormatterButton);auto *base64ConverterButton = new QPushButton("Base64加解密");base64ConverterButton->setObjectName("base64ConverterButton");connect(base64ConverterButton, &QPushButton::clicked, this, &MyMainWindow::showBase64Converter);layout->addWidget(base64ConverterButton);auto *base64ImageConverterButton = new QPushButton("Base64图片预览");base64ImageConverterButton->setObjectName("base64ImageConverterButton");connect(base64ImageConverterButton, &QPushButton::clicked, this, &MyMainWindow::showBase64ImageConverter);layout->addWidget(base64ImageConverterButton);auto *colorConverterButton = new QPushButton("色值提取转换");colorConverterButton->setObjectName("colorConverterButton");connect(colorConverterButton, &QPushButton::clicked, this, &MyMainWindow::showColorConverter);colorConverter = new ColorConverter(this);colorConverter->hide();layout->addWidget(colorConverter);layout->addWidget(colorConverterButton);setLayout(layout);}protected:ColorConverter *colorConverter{};// 重写关闭事件处理函数void closeEvent(QCloseEvent *event) override {if (this->isVisible()) {// 隐藏主窗口,而不是退出程序this->hide();event->ignore(); // 忽略关闭事件,防止程序退出}}void keyPressEvent(QKeyEvent *event) override {qDebug() << event->modifiers();if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_C) {colorConverter->stopColorCapture();// 在这里执行按下 Control + C 键的操作qDebug() << "Control + C pressed";} else {// 如果有其他键盘事件,交给基类处理QWidget::keyPressEvent(event);}}
public slots:void showClipboardManager() {new ClipboardManager(this);}void showJsonFormatter() {new JsonFormatter(this);}void showDateTimeTimestampConverter() {new DateTimeTimestampConverter(this);}void showNumberBaseConverter() {new NumberBaseConverter(this);}void showXmlFormatter() {new XmlFormatter(this);}void showBase64Converter() {new Base64Converter(this);}void showBase64ImageConverter() {new Base64ImageConverter(this);}void showColorConverter() {auto *curButton = findChild<QPushButton *>("colorConverterButton");if (colorConverter->isHidden()) {if (curButton) {curButton->setText("隐藏色值提取转换功能");}colorConverter->show();} else {if (curButton) {curButton->setText("显示色值提取转换功能");}colorConverter->hide();}}void showPanel() {this->show(); // 显示主窗口this->raise(); // 将窗口置于其他窗口上方}
};int main(int argc, char *argv[]) {QApplication a(argc, argv);MyMainWindow mainWindow;// 创建托盘图标auto* trayIcon = new QSystemTrayIcon(QIcon(":/res/icon/logo.png"), &mainWindow);trayIcon->setToolTip("天河工具箱");// 创建托盘菜单auto* trayMenu = new QMenu(&mainWindow);auto* showPanelAction = new QAction("显示面板", &mainWindow);QObject::connect(showPanelAction, &QAction::triggered, &mainWindow, &MyMainWindow::showPanel);trayMenu->addAction(showPanelAction);auto* quitAction = new QAction("退出", &mainWindow);QObject::connect(quitAction, &QAction::triggered, &a, &QApplication::quit);trayMenu->addAction(quitAction);trayIcon->setContextMenu(trayMenu);trayIcon->show();mainWindow.show();return QApplication::exec();
}#include "main.moc"

好了,这次的完整代码就这些了,抓紧动手开始吧~ 💪🏻

想要了解这个小工具是如何发展到现在这个地步的话,就看看往期文章吧~,记得要多跑一跑代码。如果大家有什么想要的功能或者想问的问题的话,就在评论区留言吧。Thanks♪(・ω・)ノ

往期文章一览

C++学习之路(一)什么是C++?如何循序渐进的学习C++?【纯干货】

C++学习之路(二)C++如何实现一个超简单的学生信息管理系统?C++示例和小项目实例

C++学习之路(三)解析讲解超简单学生信息管理系统代码知识点 - 《根据实例学知识》

C++学习之路(四)C++ 实现简单的待办事项列表命令行应用 - 示例代码拆分讲解

C++学习之路(五)C++ 实现简单的文件管理系统命令行应用 - 示例代码拆分讲解

C++学习之路(六)C++ 实现简单的工具箱系统命令行应用 - 示例代码拆分讲解

C++学习之路(七)C++ 实现简单的Qt界面(消息弹框、按钮点击事件监听)- 示例代码拆分讲解

C++学习之路(八)C++ 用Qt5实现一个工具箱(增加一个粘贴板记录管理功能)- 示例代码拆分讲解

C++学习之路(九)C++ 用Qt5实现一个工具箱(增加一个JSON数据格式化功能)- 示例代码拆分讲解

C++学习之路(十)C++ 用Qt5实现一个工具箱(增加一个时间戳转换功能)- 示例代码拆分讲解

C++学习之路(十一)C++ 用Qt5实现一个工具箱(增加一个进制转换器功能)- 示例代码拆分讲解

C++学习之路(十二)C++ 用Qt5实现一个工具箱(增加一个XML文本格式化功能)- 示例代码拆分讲解

C++学习之路(十三)C++ 用Qt5实现一个工具箱(增加一个Base64加解密功能)- 示例代码拆分讲解

C++学习之路(十四)C++ 用Qt5实现一个工具箱(增加一个Base64图片编码预览功能)- 示例代码拆分讲解

C++学习之路(十五)C++ 用Qt5实现一个工具箱(增加16进制颜色码转换和屏幕颜色提取功能)- 示例代码拆分讲解

C++学习之路(十六)C++ 用Qt5实现一个工具箱(为屏幕颜色提取功能增加一个点击复制的功能)- 示例代码拆分讲解

C++学习之路(十七)C++ 用Qt5实现一个工具箱(增加托盘图标并且增加显示和退出菜单)- 示例代码拆分讲解

C++学习之路(十八)C++ 用Qt5实现一个工具箱(点击按钮以新窗口打开功能面板)- 示例代码拆分讲解


好了~ 本文就到这里了,感谢您的阅读,每天还有更多的实例学习文章等着你 🎆。别忘了点赞、收藏~ Thanks♪(・ω・)ノ 🍇。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/200218.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于51单片机多功能时钟闹钟系统

**单片机设计介绍&#xff0c;基于51单片机多功能时钟闹钟系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于51单片机的多功能时钟闹钟系统是一种基于单片机的电子设备&#xff0c;能够显示时间、设置闹钟、进行计时以及提…

随机链表的复制[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你一个长度为n的链表&#xff0c;每个节点包含一个额外增加的随机指针random&#xff0c;该指针可以指向链表中的任何节点或空节点。构造这个链表的深拷贝。深拷贝应该正好由n个全新节点组成&#xff0c;其中每个新节点的值都设为…

Hadoop学习笔记(HDP)-Part.18 安装Flink

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

sort by modulus of a complex number

描述 复数E包含实部x和虚部y, Exyi;E的模为: 输入n(<1000)和n对(x,y); 按模数升序对复合体进行排序&#xff0c;如果模数相等&#xff0c;则按输入顺序排序。 排序后输出n行of (x_i,y_i,mod_i)&#xff0c;保留2个十进制小数。 输入 输入n和n对(x,y); 输出 输出排序后的n行(…

P=NP?

背景&#xff1a;   2000年5月24日&#xff0c;新罕布什尔州的克莱数学研究所列出了数学和计算机科学中七个未解决的问题。然而&#xff0c;直到今天&#xff0c;这些问题中只有一个被解决了&#xff0c;那就是庞加莱猜想&#xff08;Poincar Conjecture&#xff09;——被俄…

手机如何设置防骚扰电话?

很多人都曾接到过烦人的推销电话&#xff0c;这些电话不仅让人感到烦恼&#xff0c;而且有时候还会接二连三地打来&#xff0c;让人不胜其烦。我们的手机号码似乎已经被泄露&#xff0c;很难避免这些骚扰。 有时&#xff0c;我们因无法忍受骚扰电话而选择立即将其拉黑&#xff…

考研数据结构

851专业课 线性表线性表的定义线性表的顺序表示顺序表代码 线性表的链式存储表示单链表代码 顺序表和链表的比较 栈和队列栈顺序栈链栈 队列顺序队列链队列 串和数组kmp数组广义表 树和二叉树二叉树二叉树代码 线索二叉树线索二叉树代码 树和森林树的存储结构 哈夫曼树 图图的存…

【原创】提升MybatisPlus分页便捷性,制作一个属于自己的分页插件,让代码更加优雅

前言 MybatisPlus的分页插件有一点非常不好&#xff0c;就是要传入一个IPage&#xff0c;别看这个IPage没什么大不了的&#xff0c;最多多写一两行代码&#xff0c;可这带来一个问题&#xff0c;即使用xml的查询没法直接取对象里面变量的值了&#xff0c;得Param指定xml中的变…

探索Selenium的规避检测策略

Selenium之规避检测 背景 ​ 目前很多大网站有对selenium采取了监测机制。在正常情况下我们用浏览器访问相关网站的window.navigator.webdriver的值为 undefined或者为false。而使用selenium访问则该值为true。我们如何伪装&#xff0c;防止被检测出来呢&#xff1f; ​ 这是…

POJ 3233 Matrix Power Series 动态规划(矩阵的幂)

一、题目大意 给出一个矩阵A&#xff0c; 输出矩阵B的每一项对M取余数的值。 二、解题思路 以二维矩阵为例&#xff0c;首先计算K2的情况&#xff0c;我们设结果矩阵为B 有如下表达式 那么不难看出&#xff0c;需要的矩阵其实就是以下的两个矩阵相乘后的左上角的N*N个 然后…

初识Linux——基本指令(详解)1

呀哈喽&#xff0c;我是结衣。 在学习数据结构的同时&#xff0c;也不要忘了Linux的学习啊。今天我们开始Linux的教学&#xff0c;在学习之前我们肯定要会搭建Linux的学习环境&#xff0c;在我们的以前的博客里是有讲解的&#xff0c;所以所以这里我们就不在多说&#xff0c;我…

UDP数据报套接字

文章目录 DatagramSocket APIDatagramPacket API示例一: 请求响应UDP服务端UDP客户端 DatagramSocket API Socket是操作系统中的一个概念&#xff0c;本质上是一种特殊的文件&#xff0c;Socket就属于把“网卡”这个设备给抽象成了文件。往 Socket 文件中写数据&#xff0c;就…

深入探讨Guava的缓存机制

第1章&#xff1a;引言 大家好&#xff0c;我是小黑&#xff0c;今天咱们聊聊Google Guava的缓存机制。缓存在现代编程中的作用非常大&#xff0c;它能提高应用性能&#xff0c;减少数据库压力&#xff0c;简直就是性能优化的利器。而Guava提供的缓存功能&#xff0c;不仅强大…

iptables入门

今天我的工作遇到了巡检网络配置的任务&#xff0c;这次巡检的主机都是运行十多年的机器&#xff0c;并不是新的firewalld&#xff0c;基本都是iptables&#xff0c;上学的时候以为这些都没人用&#xff0c;所以没有认真学习&#xff0c;现在需要用到了&#xff0c;所以写一篇文…

PostGIS学习教程九:空间连接

PostGIS学习教程九&#xff1a;空间连接 空间连接&#xff08;spatial joins&#xff09;是空间数据库的主要组成部分&#xff0c;它们允许你使用空间关系作为连接键&#xff08;join key&#xff09;来连接来自不同数据表的信息。我们认为“标准GIS分析”的大部分内容可以表示…

17.认识下Docker之docker的核心原理(2)

1.容器-我的小世界 不知道大家看没看过小说《完美时间》&#xff0c;里面石昊经常进入一个小世界在里面与世隔绝的修炼或者战斗&#xff0c;总之就是在一个完全封闭的空间里做他想做的事情而与外界隔离&#xff0c;不受侵扰。通过前面的分析我们知道&#xff0c;Namepace让应用…

SringBoot的启动原理,保姆级带你认识,让面试管对你刮目相看

SringBoot的启动原理&#xff0c;保姆级带你认识&#xff0c;让面试管对你刮目相看 一&#xff0c;介绍 graph TD; A[启动类] --> B[SpringApplication.run()] B --> C[创建SpringApplication实例] C --> D[初始化应用上下文] D --> E[加载应用配置] E --> F[…

树_二叉搜索树累加求和

//给出二叉 搜索 树的根节点&#xff0c;该树的节点值各不相同&#xff0c;请你将其转换为累加树&#xff08;Greater Sum Tree&#xff09;&#xff0c;使每个节点 node 的新值等于原树中大于或等于 // node.val 的值之和。 // // 提醒一下&#xff0c;二叉搜索树满足下列约束…

【Vulnhub 靶场】【hacksudo: FOG】【简单 - 中等】【20210514】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/hacksudo-fog,697/ 靶场下载&#xff1a;https://download.vulnhub.com/hacksudo/hacksudo-FOG.zip 靶场难度&#xff1a;简单 - 中等 发布日期&#xff1a;2021年05月14日 文件大小&#xff1a;1.3 GB 靶场作…

解决 vite 中 import.meta.globEager is not function 的问题

本人正在重构两年前搭建到一半的博客网站&#xff0c;相关依赖都很陈旧&#xff0c;用到了 npm-check-updates 检测项目可升级依赖&#xff1a; 升级完成后解决完依赖发现控制台报错 import.meta.globEager is not function解决方案&#xff1a; vite版本降至4.3.0 亲测有效&…