0%

updating: “ https://github.com/Heydullsam/paste_pic.git

1 全屏截图

  • 设置图标
  • QSystemTrayIcon sysMenu.setIcon() .show()
  • QCursor::pos()定位鼠标位置
  • QRect确定矩形
  • QMenu.addAction添加小组件的功能menu->addAction("截图",this,SLOT(grapScreen()));可以直接使用槽函数
  • Qpainter.drawpixmap绘制,方便调用重写paintEvent

2 选区截图

  • 实现了selectRectWidget类,通过重写鼠标事件实现部分区域的抓取
  • 通过update()和drawRect实现选中框的更新
  • 原窗口逻辑中加入resize,避免框选图片被强制拉长

3 实现截图的拖动功能

  • 复用了之前DragFiler,加入了右键关闭功能
  • 安装DragFilter

4 实现快捷键

  • 引入第三方库QHotkey, 解决了没有主窗口无法触发截图快捷键的问题
    +third_party文件目录,修改cmakelists target_link_libraries(paste_pic PRIVATE QHotkey) add_subdirectory(third_party/QHotkey)
  • 通过QDialog QVBoxLayout QKeySequenceEdit收集快捷键输入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SetHotkeyDialog : public QDialog {
Q_OBJECT
public:
SetHotkeyDialog(QWidget* parent = nullptr) : QDialog(parent) {
setWindowTitle("设置快捷键");
auto layout = new QVBoxLayout(this);
keyEdit = new QKeySequenceEdit(this);
auto btn = new QPushButton("确定", this);
layout->addWidget(keyEdit);
layout->addWidget(btn);
connect(btn, &QPushButton::clicked, this, &QDialog::accept);
}
QKeySequence keySequence() const { return keyEdit->keySequence(); }
private:
QKeySequenceEdit* keyEdit;

};
  • 设置快捷键并调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void SnipasteApp::setHotkey()
{
SetHotkeyDialog dlg;
if (dlg.exec() == QDialog::Accepted) {
QKeySequence seq = dlg.keySequence();
if (!seq.isEmpty()) {
if (m_hotkey) {
m_hotkey->setRegistered(false);
delete m_hotkey;
}
m_hotkey = new QHotkey(seq, true, this); // true 表示立即注册
connect(m_hotkey, &QHotkey::activated, this, &SnipasteApp::grapScreen);
}
}
}

1. 观察者模式 (Observer Pattern)

这是最直接、最经典的解决方案,也是Qt信号/槽机制的理论基础。

核心思想: 定义一种一对多的依赖关系,当一个对象(Subject,主题)的状态发生改变时,所有依赖于它的对象(Observers,观察者)都会得到通知并自动更新。

实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>

// 前向声明
class Observer;

// 主题 (Subject) 接口
class Subject {
public:
virtual ~Subject() = default;
virtual void attach(std::weak_ptr<Observer> observer) = 0;
virtual void detach(std::weak_ptr<Observer> observer) = 0;
virtual void notify() = 0;
};

// 观察者 (Observer) 接口
class Observer {
public:
virtual ~Observer() = default;
virtual void update() = 0;
};

// 具体的主题(例如:后台工作线程)
class ConcreteSubject : public Subject, public std::enable_shared_from_this<ConcreteSubject> {
public:
void doWork() {
std::cout << "Working..." << std::endl;
// ... 一些工作 ...
std::cout << "Work done! Notifying observers." << std::endl;
notify(); // 工作完成,通知所有观察者
}

void attach(std::weak_ptr<Observer> observer) override {
observers_.push_back(observer);
}

void detach(std::weak_ptr<Observer> observer) override {
// 在实际项目中,这里需要更复杂的逻辑来清理失效的 weak_ptr
observers_.erase(
std::remove_if(observers_.begin(), observers_.end(),
[&observer](const std::weak_ptr<Observer>& wp) {
return wp.lock() == observer.lock();
}),
observers_.end());
}

void notify() override {
for (auto it = observers_.begin(); it != observers_.end(); ) {
if (auto obs = it->lock()) {
obs->update();
++it;
} else {
// 观察者对象已被销毁,移除失效的 weak_ptr
it = observers_.erase(it);
}
}
}

private:
std::vector<std::weak_ptr<Observer>> observers_;
};

// 具体的观察者(例如:UI更新器)
class ConcreteObserver : public Observer, public std::enable_shared_from_this<ConcreteObserver> {
public:
ConcreteObserver(const std::string& name) : name_(name) {}

void update() override {
std::cout << "Observer " << name_ << ": Received update! Updating UI..." << std::endl;
}

// 一个便捷函数,用于将自己注册到主题
void subscribeTo(const std::shared_ptr<Subject>& subject) {
subject_ = subject;
subject_->attach(std::weak_ptr<Observer>(shared_from_this()));
}

void unsubscribe() {
if (auto sub = subject_.lock()) {
sub->detach(std::weak_ptr<Observer>(shared_from_this()));
}
}

private:
std::string name_;
std::weak_ptr<Subject> subject_;
};

// 使用示例
int main() {
auto worker = std::make_shared<ConcreteSubject>();
auto uiUpdater1 = std::make_shared<ConcreteObserver>("UI 1");
auto uiUpdater2 = std::make_shared<ConcreteObserver>("UI 2");

// 建立订阅关系(松耦合:Worker不知道谁订阅了它)
uiUpdater1->subscribeTo(worker);
uiUpdater2->subscribeTo(worker);

// 执行工作,自动通知所有订阅者
worker->doWork();

// 一个观察者取消订阅
uiUpdater1->unsubscribe();
std::cout << "\n--- After unsubscribe ---\n";
worker->doWork();

return 0;
}

优点: 实现了松耦合,Subject不需要知道Observer的具体细节。
缺点: 需要自己管理Observer的注册、注销和生命周期(使用std::weak_ptr防止悬挂指针)。


2. 回调函数 (Callbacks) / 函数对象 (Function Objects / std::function)

这是一种更轻量级的方式,特别适合一对一的通信场景。

实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <functional>
#include <vector>

class Worker {
public:
// 使用 std::function 定义回调类型
using Callback = std::function<void(int result)>;

void setCompletionCallback(const Callback& cb) {
completionCallback_ = cb;
}

void addProgressCallback(const Callback& cb) {
progressCallbacks_.push_back(cb);
}

void doWork() {
for (int i = 0; i <= 100; i += 25) {
std::cout << "Progress: " << i << "%" << std::endl;
// 通知所有进度回调(模拟一对多)
for (const auto& cb : progressCallbacks_) {
if (cb) cb(i);
}
}
// 工作完成,通知完成回调
if (completionCallback_) {
completionCallback_(42); // 假设结果是42
}
}

private:
Callback completionCallback_;
std::vector<Callback> progressCallbacks_;
};

// 一个接收通知的类
class UI {
public:
void updateProgress(int value) {
std::cout << "UI: Updating progress bar to " << value << "%" << std::endl;
}

void handleResult(int result) {
std::cout << "UI: Got result: " << result << std::endl;
}
};

int main() {
Worker worker;
UI ui;

// 设置回调(建立连接)
worker.setCompletionCallback([&ui](int result) { ui.handleResult(result); });
worker.addProgressCallback([&ui](int progress) { ui.updateProgress(progress); });

// 也可以使用lambda直接处理
worker.addProgressCallback([](int progress) {
std::cout << "Logger: Progress is " << progress << "%" << std::endl;
});

worker.doWork();

return 0;
}

优点: 非常灵活,语法简单,C++11及以上标准支持良好。
缺点: 一对多需要自己管理回调列表。要非常小心回调函数和对象的生命周期(上面的例子使用了lambda捕获引用&ui,如果ui先于worker被销毁,就会导致未定义行为)。


3. 基于事件的系统 (Event Bus)

这是一个更高级、更通用的模式,常用于大型应用程序,它本身是一个中央调度器。

简化实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <map>
#include <vector>
#include <functional>
#include <memory>
#include <string>

// 任何需要传递的事件数据都可以继承自此类
struct Event {
virtual ~Event() = default;
};

// 具体的事件类型
struct WorkCompletedEvent : public Event {
int result;
WorkCompletedEvent(int r) : result(r) {}
};

struct ProgressUpdatedEvent : public Event {
int progress;
ProgressUpdatedEvent(int p) : progress(p) {}
};

// 事件总线(通常是单例)
class EventBus {
public:
using Subscriber = std::function<void(const Event&)>;

// 订阅特定类型的事件
template <typename EventType>
void subscribe(Subscriber subscriber) {
subscribers_[typeid(EventType).name()].push_back(subscriber);
}

// 发布一个事件
template <typename EventType>
void publish(const EventType& event) {
auto it = subscribers_.find(typeid(EventType).name());
if (it != subscribers_.end()) {
for (const auto& subscriber : it->second) {
subscriber(event);
}
}
}

private:
std::map<std::string, std::vector<Subscriber>> subscribers_;
};

// 使用示例
int main() {
EventBus bus;

Worker worker(bus); // 假设Worker接收一个EventBus引用
UI ui(bus); // UI也接收一个EventBus引用

// 在UI内部某处进行订阅
bus.subscribe<ProgressUpdatedEvent>([](const Event& e) {
const auto& event = static_cast<const ProgressUpdatedEvent&>(e);
std::cout << "UI Event: Progress " << event.progress << "%" << std::endl;
});

bus.subscribe<WorkCompletedEvent>([](const Event& e) {
const auto& event = static_cast<const WorkCompletedEvent&>(e);
std::cout << "UI Event: Result " << event.result << std::endl;
});

// Worker在执行过程中发布事件
// worker.doWork() 内部会调用 bus.publish(ProgressUpdatedEvent(50));
// 和 bus.publish(WorkCompletedEvent(100));

return 0;
}
// 注:这是一个简化版,完整实现需要考虑线程安全、异常处理等。

优点: 极度松耦合,组件之间通过事件通信,完全不知道对方的存在。非常灵活和强大。
缺点: 实现相对复杂,调试可能更困难(事件流不直观)。

总结:纯C++的解决方案

方法 适用场景 优点 缺点
观察者模式 经典的一对多通知,结构清晰 松耦合,意图明确 需要自己实现和管理,代码量稍大
回调函数 (std::function) 轻量级,一对一或简单一对多 灵活,简单,现代C++标准支持 生命周期管理需谨慎,一对多需自行维护列表
事件总线 (Event Bus) 大型应用,复杂组件间通信 极度松耦合,高度灵活 实现复杂,系统行为不易跟踪,调试难

Qt的信号/槽机制可以看作是这些模式的一个集大成者:
它提供了观察者模式的清晰结构,用起来像回调函数一样简单直观,并且通过元对象系统实现了比手动事件总线更强大、更安全、更易用的功能。

  • QTimer 通过start(int num)函数运行,(以num为间隔)不断发出timeout信号,stop停止
  • QTimer::singleShot(1000,this,SLOT(timeoutSlot()));直接计时后运行
  • connect(对象,信号,发送到对象,调用函数),调用函数一般是自定义槽函数
  • Qimage::load(path)加载图片R(“”)忽略转义,相对路径(加.qrc文件,:加相对路径)
  • QPixmap pix(“D:\CACHE\Qt\objectTimer\pic\1.jpg”);另一种加载图片的方法*
  • Widget::startTimer()返回一个ID,killTimer()停止,不建议使用

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include "widget.h"
#include "./ui_widget.h"
#define TIMEOUT 1000
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
timer=new QTimer;
picID=2;

QImage img;
img.load(R"(D:\CACHE\Qt\Qtimer\pic\1.jpg)");
ui->label->setPixmap(QPixmap::fromImage(img));

connect(timer,&QTimer::timeout,this,&Widget::timeoutSlot);

}

Widget::~Widget()
{
delete ui;
}

void Widget::on_startButton_clicked()
{
timer->start(TIMEOUT);
}

void Widget::timeoutSlot()
{
QString path(R"(D:\CACHE\Qt\Qtimer\pic\)");
path+=QString::number(picID);
path+=".jpg";

QImage img;
img.load(path);
ui->label->setPixmap(QPixmap::fromImage(img));

picID++;
if(picID==5)picID=1;

}

void Widget::on_finishButton_clicked()
{
timer->stop();
}


void Widget::on_nextButton_clicked()
{
QTimer::singleShot(1000,this,SLOT(timeoutSlot()));
}


learned

  • QChar包含了所有的字符()>char), 而switch语句期望获得一个数字,可以用QChar::unicode()进行转换
  • 函数多使用驼峰式命名,与cpp内置函数有所不同
  • 整数处理方式
1
std::abs(result - std::round(result)) < 1e-10
  • 槽的使用

主要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include "calculator.h"
#include <QStack>
#include <QString>
#include <QChar>
#include <cmath>

// 辅助函数:获取运算符优先级
int getPriority(QChar op) {
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/') return 2;
return 0;
}

// 辅助函数:执行运算
double applyOperation(double a, double b, QChar op) {
switch (op.unicode()) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/':
if (b == 0) throw "Division by zero";
return a / b;
default: return 0;
}
}

QString calculate(QString expression)
{
try {
// 移除所有空格
expression = expression.replace(" ", "");

QStack<double> values;
QStack<QChar> operators;

for (int i = 0; i < expression.length(); i++) {
QChar c = expression[i];

// 如果是数字,读取完整数字
if (c.isDigit() || c == '.') {
QString numStr;
while (i < expression.length() &&
(expression[i].isDigit() || expression[i] == '.')) {
numStr += expression[i];
i++;
}
i--; // 回退一个字符

bool ok;
double num = numStr.toDouble(&ok);
if (!ok) throw "Invalid number format";
values.push(num);
}
// 如果是左括号,压入运算符栈
else if (c == '(') {
operators.push(c);
}
// 如果是右括号,计算括号内的表达式
else if (c == ')') {
while (!operators.empty() && operators.top() != '(') {
if (values.size() < 2) throw "Invalid expression";

double b = values.top(); values.pop();
double a = values.top(); values.pop();
QChar op = operators.top(); operators.pop();

values.push(applyOperation(a, b, op));
}

if (operators.empty()) throw "Mismatched parentheses";
operators.pop(); // 弹出左括号
}
// 如果是运算符
else if (c == '+' || c == '-' || c == '*' || c == '/') {
// 处理负号(一元运算符)
if (c == '-' && (i == 0 || expression[i-1] == '(' ||
expression[i-1] == '+' || expression[i-1] == '-' ||
expression[i-1] == '*' || expression[i-1] == '/')) {
// 这是一个负号而不是减号
QString numStr = "-";
i++;
while (i < expression.length() &&
(expression[i].isDigit() || expression[i] == '.')) {
numStr += expression[i];
i++;
}
i--;

bool ok;
double num = numStr.toDouble(&ok);
if (!ok) throw "Invalid number format";
values.push(num);
continue;
}

// 处理普通运算符
while (!operators.empty() &&
getPriority(operators.top()) >= getPriority(c)) {
if (values.size() < 2) throw "Invalid expression";

double b = values.top(); values.pop();
double a = values.top(); values.pop();
QChar op = operators.top(); operators.pop();

values.push(applyOperation(a, b, op));
}
operators.push(c);
}
else {
throw "Invalid character in expression";
}
}

// 处理剩余的运算符
while (!operators.empty()) {
if (values.size() < 2) throw "Invalid expression";

double b = values.top(); values.pop();
double a = values.top(); values.pop();
QChar op = operators.top(); operators.pop();

values.push(applyOperation(a, b, op));
}

// 检查最终结果
if (values.size() != 1 || !operators.empty()) {
throw "Invalid expression";
}

double result = values.top();
// 处理浮点数精度问题,如果结果是整数则显示为整数
if (std::abs(result - std::round(result)) < 1e-10) {
return QString::number(static_cast<long long>(std::round(result)));
} else {
return QString::number(result, 'g', 15); // 保留15位有效数字
}
}
catch (const char* error) {
return QString("Error: ") + error;
}
catch (...) {
return "Error: Unknown error";
}
}

windployqt部署

client

learned

  • .pro中+network
  • QTcpsocket类::connetToHost
  • 跳转方式,创建新的指针(指针在堆上,实例在栈上)
  • ba.append(ui->lineEdit->text().toUtf8()); QByteArray加QString字符串的方法

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include"chat.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
socket=new QTcpSocket;
}

Widget::~Widget()
{
delete ui;
}

void Widget::on_cancelButton_clicked()
{
this->close();
}


void Widget::on_connectButton_clicked()
{
//ip port
QString IP=ui->ipLine->text();
QString port=ui->portLine->text();
//connect
socket->connectToHost(QHostAddress(IP),port.toShort());

//whether success
connect(socket,&QTcpSocket::connected,[this](){
QMessageBox::information(this,"连接提示","连接服务器成功");
this->hide();
Chat *C=new Chat(socket);
C->show();
});

connect(socket,&QTcpSocket::disconnected,[this](){
QMessageBox::warning(this,"连接提示","连接异常,网络断开");
});

}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "chat.h"
#include "ui_chat.h"

Chat::Chat(QTcpSocket *s,QWidget *parent)
: QWidget(parent)
, ui(new Ui::Chat)
{
ui->setupUi(this);
socket=s;
}

Chat::~Chat()
{
delete ui;
}

void Chat::on_clearButton_clicked()
{
ui->lineEdit->clear();
}


void Chat::on_sendButton_clicked()
{
QByteArray ba;
ba.append(ui->lineEdit->text().toUtf8());
ui->lineEdit->text();
socket->write(ba);
}

server

  • 线程的创建 继承QThread ->start启动
  • 信号量 emit+signals声明 发送信号

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "mythread.h"

myThread::myThread(QTcpSocket *s)
{
socket=s;
}

void myThread::run()
{
connect(socket,&QTcpSocket::readyRead,this,&myThread::clientInfoSlot);
}

void myThread::clientInfoSlot()
{
// socket->readAll();
QByteArray ba=socket->readAll();
emit sendToWidget(ba);

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
server=new QTcpServer;

server->listen(QHostAddress::AnyIPv4,PORT);

connect(server,&QTcpServer::newConnection,this,&Widget::newClientHandler);
}

Widget::~Widget()
{
delete ui;
}

void Widget::newClientHandler()
{
QTcpSocket *socket=server->nextPendingConnection();
// socket->peerAddress();
// socket->peerPort();

ui->ipLineEdit->setText(socket->peerAddress().toString());
ui->portLineEdit->setText(QString::number(socket->peerPort()));
// connect(socket,&QTcpSocket::readyRead,this,&Widget::clientInfoSlot);

//启动线程
myThread *t=new myThread(socket);
t->start();

connect(t,&myThread::sendToWidget,this,&Widget::threadSlot);

}

// void Widget::clientInfoSlot(){
// QTcpSocket *s = (QTcpSocket*)sender();//获取信号发出者
// ui->mainLineEdit->setText(QString(s->readAll()));

// }

void Widget::threadSlot(QByteArray b)
{
ui->mainLineEdit->setText(QString(b));
}

MediaPlayer+AudioOutput(m_mediaPlayer->setAudioOutput(m_audioOutput);)
获取文件目录
QDirIterator,.hasNext()进行遍历
播放逻辑

1
2
3
4
5
6
void MusicPlayer::on_musicListView_doubleClicked(const QModelIndex &index)
{
auto url=index.data(Qt::UserRole+1).toUrl();
m_mediaPlayer->setSource(url);
m_mediaPlayer->play();
}

StandardItemModel在viewList上展示(数据处理setModel,appendRow)

learned

  • QFileDialog用于获取需要打开的文件(点击)
  • QFile::open打开文件,readAll()获取QByteArray类型的数据
  • 键与槽函数Qt::ControlModifier,mousePressEvent,QPoint
1
2
3
4
if(QKetEvent* k->modifiers()==Qt::ControlModifier && k->key()==Qt::Key_S)
{
saveActionSlot();
}
  • 槽函数的连接(与按键事件)
1
connect(ui->newAction,&QAction::triggered,this,&MainWindow::newActionSlot);

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QFileDialog>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);

connect(ui->newAction,&QAction::triggered,this,&MainWindow::newActionSlot);
connect(ui->openAction,&QAction::triggered,this,&MainWindow::openActionSlot);
connect(ui->saveAction,&QAction::triggered,this,&MainWindow::saveActionSlot);
}

MainWindow::~MainWindow()
{
delete ui;
}

void MainWindow::newActionSlot()
{
ui->textEdit->clear();
this->setWindowTitle("新建文本文档.txt");
}

void MainWindow::openActionSlot()
{
QString fileName = QFileDialog::getOpenFileName(this,"打开",
QCoreApplication::applicationFilePath(),"*.cpp");
if(fileName.isEmpty())
{
QMessageBox::warning(this,"警告","请选择一个文件");
}
else
{
// qDebug()<<fileName;
QFile file(fileName);
file.open(QIODevice::ReadOnly);
QByteArray ba=file.readAll();
ui->textEdit->setText(QString(ba));
file.close();
}
}

void MainWindow::saveActionSlot()
{
QString fileName = QFileDialog::getSaveFileName(this,"保存",
QCoreApplication::applicationFilePath());

if(fileName.isEmpty())
{
QMessageBox::warning(this,"警告","请选择一个文件");
}
else
{
// qDebug()<<fileName;
QFile file(fileName);
file.open(QIODevice::WriteOnly);
QByteArray ba;
ba.append(qPrintable(ui->textEdit->toPlainText()));
file.write(ba);
file.close();
}

}

void MainWindow::keyPressEvent(QKeyEvent *k)
{
if(k->modifiers()==Qt::ControlModifier && k->key()==Qt::Key_S)
{
saveActionSlot();
}
else if(k->modifiers()==Qt::ControlModifier&&k->key()==Qt::Key_N)
{
newActionSlot();
}
else if(k->modifiers()==Qt::ControlModifier&&k->key()==Qt::Key_O)
{
openActionSlot();
}
}

void MainWindow::mousePressEvent(QMouseEvent *m)
{
QPoint pt = m->pos();
qDebug()<<pt;

if(m->button()==Qt::LeftButton)
{
qDebug()<<"左键被按下";
}
else if(m->button()==Qt::RightButton)
{
qDebug()<<"右键被按下";
}
}

本地

learned

  • .pro +sql
  • QSqlDatabase::addDatabase(“NAME”)加载数据库
  • QSqlquery::exec执行sql命令,返回true或false
  • QString的拼接 .arg()
1
QString sql=QString("insert into student values (%1,'%2','%3');)").arg(id).arg(name).arg(birthday);

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QSqlQuery>

Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);

db = QSqlDatabase::addDatabase("QMYSQL");//加载MySQL数据库驱动
db.setDatabaseName("mydatabase");
db.setHostName("localhost");
db.setUserName("root");
db.setPassword("ljn20040118@@");

qDebug()<<QSqlDatabase::drivers();
if(db.open())
{
QMessageBox::information(this,"连接提示","连接成功");
}
else{
QMessageBox::warning(this,"连接提示","连接失败");
}
}

Widget::~Widget()
{
delete ui;
}

void Widget::on_insertButton_clicked()
{
QString id = ui->numberEdit->text();
QString name = ui->numberEdit->text();
QString birthday = ui->birthdayEdit->text();

QString sql=QString("insert into student values (%1,'%2','%3');)").arg(id).arg(name).arg(birthday);

QSqlQuery query;
if(query.exec(sql))
{
QMessageBox::information(this,"插入提示","插入成功");
}
else
{
QMessageBox::warning(this,"插入提示","插入失败");
}

}


void Widget::on_findButton_clicked()
{
QSqlQuery query;
query.exec("select * from student");
while(query.next())
{
qDebug()<<query.value(0);


}
}


网络

learned

QSqlTableModel::->select方法+QTableView组件->setModel 方便显示

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>

Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QSqlDatabase db=QSqlDatabase::addDatabase("QMYSQL");

db.setHostName("192.168.217.129");
db.setDatabaseName("mydatabase");
db.setUserName("test0");
db.setPassword("ljn20040118LJN@@");

if(db.open())
{
QMessageBox::information(this,"连接提示","连接成功");

m=new QSqlTableModel();
m->setTable("student");
ui->tableView->setModel(m);
}
else
{
QMessageBox::warning(this,"连接提示","连接失败");
}
}

Widget::~Widget()
{
delete ui;
}

void Widget::on_pushButton_clicked()
{
m->select();
}


learned

  • 重载QPaintEvent::paintEvent, 使之能够加载图片
  • snprintf基于printf处理字符串,QString中.arg()可以模拟,注意参数是%num;
  • connect调用的函数必须加&
  • Qpixmap::scale()用于调整大小
  • Widget::update直接更新窗口
  • 透明窗口、透明背景、与移动
1
2
3
this->setWindowFlag(Qt::FramelessWindowHint);     // 去除窗口边框
this->setAttribute(Qt::WA_TranslucentBackground); // 背景透明
this->installEventFilter(new DragFilter);
  • 最小化系统图标的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
QSystemTrayIcon sysTray(QIcon(":/pic/image/icon.png"),&w);
QMenu menu;
auto showAct=new QAction("show",&sysTray);
auto exitAct=new QAction("exit",&sysTray);
QObject::connect(showAct,&QAction::triggered,&w,[&](){
w.setVisible(true);
});
QObject::connect(exitAct,&QAction::triggered,&a,[&](){
QApplication::quit();
});
menu.addAction(showAct);
menu.addAction(exitAct);

sysTray.setContextMenu(&menu);

sysTray.show();
  • widget::setVisible暂时关闭窗口
  • QUrl更安全的跨平台链接
  • Q_ENUM_NS(RoleAct)//注册枚举

new_learn

拖动窗口的内置函数实现

1
2
3
4
5
if (w->windowHandle())
{
w->windowHandle()->startSystemMove();
return true;
}

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include "widget.h"
#include <QPixmap>
#include <QDebug>
#include <Qcursor>
#include <QMetaEnum>
Widget::Widget(QWidget *parent)
: QWidget(parent),
timer(new QTimer(this)),//自动析构
menu(new QMenu(this))
{
this->setWindowFlag(Qt::FramelessWindowHint);//去除窗口边框
this->setAttribute(Qt::WA_TranslucentBackground);//背景透明
this->installEventFilter(new DragFilter);

connect(timer,&QTimer::timeout,this,[this](){
static int index=0;//记录显示动作的图片索引
auto paths=this->action_map.value(this->cur_role_act);
this->cur_role_pix=paths[index++%paths.size()];
//paintEvent()会递归调用
this->update();

});
loadRoleActRes();

showActAnimation(RoleAct::SayHello);
initMennu();
}

Widget::~Widget() {}

void Widget::showActAnimation(RoleAct k)
{
timer->stop();
this->cur_role_act=k;
timer->start(500);

}

void Widget::onMenuTriggered(QAction *action)
{
QMetaEnum me=QMetaEnum::fromType<RoleAct>();
bool ok;
int k=me.keyToValue(action->text().toStdString().c_str(),&ok);
if(!ok)return;
showActAnimation(static_cast<RoleAct>(k));
}

void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QPixmap pix;

// qDebug()<<"pix path:"<<this->cur_role_pix.toLocalFile();
pix.load(this->cur_role_pix.toLocalFile());
int targetWidth = 200; // 你想要的宽
int targetHeight = 150; // 你想要的高
pix = pix.scaled(targetWidth, targetHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
painter.drawPixmap(0,0,pix);

}

void Widget::contextMenuEvent(QContextMenuEvent *event)
{
this->menu->popup(QCursor::pos());
}

void Widget::loadRoleActRes()
{
auto addRes=[this](RoleAct k,QString path,int count)
{
QList<QUrl>paths;
char buf[260];//path最长255
for(int i=0;i<count;++i)
{
memset(buf,0,sizeof(buf));
sprintf_s(buf,path.toStdString().c_str(),i);
paths.append(QUrl::fromLocalFile(buf));
}
action_map.insert(k,paths);
// qDebug()<<paths[1];

};
addRes(RoleAct::SayHello,":/pic/image/sayHello/sayHello%d.png",4);
addRes(RoleAct::Sleep,":/%d.png",32);
addRes(RoleAct::Swing,":/%d.png",11);


}

void Widget::initMennu()
{
menu->addAction("SayHello");
menu->addAction("Sleep");
menu->addAction("Swing");

QAction* act=new QAction("Hide");
connect(act,&QAction::triggered,this,[this](){
this->setVisible(false);
});

menu->addAction(act);
connect(this->menu,&QMenu::triggered,this,Widget::onMenuTriggered);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QEvent>
#include <QMouseEvent>
#include <QContextMenuEvent>
#include <QMenu>

#include<QMap>
#include<QList>
#include<QUrl>
#include<QTimer>

namespace Act{
Q_NAMESPACE
enum RoleAct{
SayHello,
Swing,
Sleep,
};
Q_ENUM_NS(RoleAct)//注册枚举
}

using namespace Act;
class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = nullptr);
~Widget();

public:
void showActAnimation(RoleAct k);

public slots:
void onMenuTriggered(QAction *action);


protected:
void paintEvent(QPaintEvent *event)override;
void contextMenuEvent(QContextMenuEvent*event)override;

private:
void loadRoleActRes();
void initMennu();

private:
QMap<RoleAct,QList<QUrl>>action_map;
QTimer* timer;
RoleAct cur_role_act;
QUrl cur_role_pix;
QMenu* menu;
};

class DragFilter:public QObject{
public:
bool eventFilter(QObject*obj,QEvent*event)
{
auto w=dynamic_cast<QWidget*>(obj);
if(!w)
return false;
if(event->type()==QEvent::MouseButtonPress)
{
auto e =dynamic_cast<QMouseEvent*>(event);
if(e)
{
pos=e->pos();
}
}
else if(event->type()==QEvent::MouseMove)
{
auto e =dynamic_cast<QMouseEvent*>(event);
if(e)
{
if(e->buttons()&Qt::MouseButton::LeftButton)
{
w->move(e->globalPosition().toPoint()-pos);
}
}
}
return QObject::eventFilter(obj,event);

}
private:
QPoint pos;
};


#endif // WIDGET_H

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "widget.h"

#include <QApplication>
#include <QSystemTrayIcon>//系统菜单托盘

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;

QSystemTrayIcon sysTray(QIcon(":/pic/image/icon.png"),&w);
QMenu menu;
auto showAct=new QAction("show",&sysTray);
auto exitAct=new QAction("exit",&sysTray);
QObject::connect(showAct,&QAction::triggered,[&](){
w.setVisible(true);
});
QObject::connect(exitAct,&QAction::triggered,[&](){
QApplication::quit();
});

menu.addAction(showAct);
menu.addAction(exitAct);

sysTray.setContextMenu(&menu);

sysTray.show();

w.show();
return a.exec();
}