Qt 防抖和节流器的使用方法
1. 防抖器(Debouncer)使用示例
完整实现示例
复制代码
复制代码
复制代码
// debouncer.h
#ifndef DEBOUNCER_H
#define DEBOUNCER_H
#include <QObject>
#include <QTimer>
class Debouncer : public QObject
{
Q_OBJECT
public:
explicit Debouncer(int delayMs = 300, QObject* parent = nullptr);
void trigger(); // 触发防抖
signals:
void triggered(); // 防抖后触发的信号
public slots:
void cancel(); // 取消当前等待
private:
QTimer m_timer;
int m_delayMs;
};
#endif // DEBOUNCER_H
复制代码
复制代码
复制代码
// debouncer.cpp
#include "debouncer.h"
Debouncer::Debouncer(int delayMs, QObject* parent)
: QObject(parent)
, m_delayMs(delayMs)
{
m_timer.setSingleShot(true);
connect(&m_timer, &QTimer::timeout, this, &Debouncer::triggered);
}
void Debouncer::trigger()
{
m_timer.start(m_delayMs);
}
void Debouncer::cancel()
{
m_timer.stop();
}
使用场景1:搜索框实时搜索
复制代码
复制代码
复制代码
// searchwidget.cpp
#include "searchwidget.h"
#include <QLineEdit>
#include <QDebug>
SearchWidget::SearchWidget(QWidget *parent)
: QWidget(parent)
, m_debouncer(new Debouncer(300, this))
{
QLineEdit *searchEdit = new QLineEdit(this);
// 连接防抖信号
connect(m_debouncer, &Debouncer::triggered, this, &SearchWidget::onSearchTriggered);
// 监听文本框变化
connect(searchEdit, &QLineEdit::textChanged, [this, searchEdit](const QString &text){
m_lastSearchText = text;
m_debouncer->trigger(); // 触发防抖
});
// 或者处理键盘事件
searchEdit->installEventFilter(this);
}
void SearchWidget::onSearchTriggered()
{
// 300ms内无新输入才执行搜索
performSearch(m_lastSearchText);
}
bool SearchWidget::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if (!keyEvent->isAutoRepeat()) { // 只处理非重复按键
m_debouncer->trigger();
}
}
return QWidget::eventFilter(watched, event);
}
使用场景2:窗口大小调整
复制代码
复制代码
复制代码
void MainWindow::resizeEvent(QResizeEvent *event)
{
QMainWindow::resizeEvent(event);
static Debouncer resizeDebouncer(200, this);
// 连接布局更新
connect(&resizeDebouncer, &Debouncer::triggered, this, [this](){
updateWidgetLayout();
saveWindowSize();
});
resizeDebouncer.trigger(); // 200ms内无新调整再执行
}
2. 节流器(Throttler)使用示例
完整实现示例
复制代码
复制代码
复制代码
// throttler.h
#ifndef THROTTLER_H
#define THROTTLER_H
#include <QObject>
#include <QElapsedTimer>
class Throttler : public QObject
{
Q_OBJECT
public:
explicit Throttler(int intervalMs = 50, QObject* parent = nullptr);
bool tryTrigger(); // 尝试触发,返回是否成功
void forceTrigger(); // 强制立即触发
void reset(); // 重置计时器
signals:
void triggered(); // 节流后触发的信号
private:
int m_intervalMs;
QElapsedTimer m_timer;
bool m_enabled = true;
};
#endif // THROTTLER_H
复制代码
复制代码
复制代码
// throttler.cpp
#include "throttler.h"
#include <QDebug>
Throttler::Throttler(int intervalMs, QObject* parent)
: QObject(parent)
, m_intervalMs(intervalMs)
{
m_timer.start();
}
bool Throttler::tryTrigger()
{
if (!m_enabled) return false;
if (m_timer.elapsed() >= m_intervalMs) {
m_timer.restart();
emit triggered();
return true;
}
return false;
}
void Throttler::forceTrigger()
{
m_timer.restart();
emit triggered();
}
void Throttler::reset()
{
m_timer.restart();
}
使用场景1:鼠标拖动绘图
复制代码
复制代码
复制代码
// canvaswidget.cpp
#include "canvaswidget.h"
#include <QPainter>
#include <QDebug>
CanvasWidget::CanvasWidget(QWidget *parent)
: QWidget(parent)
, m_throttler(new Throttler(16, this)) // 60fps节流
{
setMouseTracking(true);
setAttribute(Qt::WA_OpaquePaintEvent);
connect(m_throttler, &Throttler::triggered, this, [this](){
update(); // 触发重绘
});
}
void CanvasWidget::mouseMoveEvent(QMouseEvent *event)
{
// 记录最新位置
m_lastMousePos = event->pos();
// 尝试触发节流绘制
if (m_isDrawing) {
m_throttler->tryTrigger();
}
QWidget::mouseMoveEvent(event);
}
void CanvasWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
// 在最新位置绘制
if (m_isDrawing && !m_lastMousePos.isNull()) {
painter.drawEllipse(m_lastMousePos, 5, 5);
// 记录到笔画
m_strokePoints.append(m_lastMousePos);
}
// 绘制已完成的笔画
painter.setPen(QPen(Qt::black, 2));
for (int i = 1; i < m_strokePoints.size(); ++i) {
painter.drawLine(m_strokePoints[i-1], m_strokePoints[i]);
}
}
使用场景2:游戏控制
复制代码
复制代码
复制代码
// gamecontroller.cpp
#include "gamecontroller.h"
GameController::GameController(QObject *parent)
: QObject(parent)
, m_inputThrottler(new Throttler(33, this)) // 30fps处理输入
, m_physicsThrottler(new Throttler(16, this)) // 60fps物理更新
{
// 游戏循环连接
connect(&m_gameTimer, &QTimer::timeout, this, &GameController::gameLoop);
m_gameTimer.start(1); // 1ms高精度循环
// 输入处理连接
connect(m_inputThrottler, &Throttler::triggered, this, &GameController::processInput);
// 物理更新连接
connect(m_physicsThrottler, &Throttler::triggered, this, &GameController::updatePhysics);
}
void GameController::gameLoop()
{
// 输入处理(30fps)
m_inputThrottler->tryTrigger();
// 物理更新(60fps)
m_physicsThrottler->tryTrigger();
// 渲染(尽可能快)
emit updateFrame();
}
void GameController::keyPressEvent(QKeyEvent *event)
{
if (!event->isAutoRepeat()) {
m_keyStates[event->key()] = true;
}
}
void GameController::processInput()
{
// 批量处理按键状态
if (m_keyStates[Qt::Key_W]) playerMove(0, -1);
if (m_keyStates[Qt::Key_S]) playerMove(0, 1);
if (m_keyStates[Qt::Key_A]) playerMove(-1, 0);
if (m_keyStates[Qt::Key_D]) playerMove(1, 0);
}
3. 高级组合使用示例
示例1:带取消的自动保存
复制代码
复制代码
复制代码
class AutoSaveManager : public QObject
{
Q_OBJECT
public:
AutoSaveManager(QObject *parent = nullptr)
: QObject(parent)
, m_saveDebouncer(new Debouncer(2000, this)) // 2秒防抖
{
connect(m_saveDebouncer, &Debouncer::triggered, this, [this](){
if (m_hasUnsavedChanges) {
performAutoSave();
m_hasUnsavedChanges = false;
}
});
}
void documentModified()
{
m_hasUnsavedChanges = true;
m_saveDebouncer->trigger(); // 重启2秒计时器
}
void saveNow() // 用户手动保存
{
m_saveDebouncer->cancel(); // 取消自动保存
performManualSave();
}
private:
Debouncer *m_saveDebouncer;
bool m_hasUnsavedChanges = false;
};
示例2:滚动优化
复制代码
复制代码
复制代码
class OptimizedScrollArea : public QScrollArea
{
public:
OptimizedScrollArea(QWidget *parent = nullptr)
: QScrollArea(parent)
, m_scrollThrottler(new Throttler(16, this)) // 60fps节流
{
// 连接滚动处理
connect(m_scrollThrottler, &Throttler::triggered, this, [this](){
updateVisibleContent();
});
// 安装事件过滤器
horizontalScrollBar()->installEventFilter(this);
verticalScrollBar()->installEventFilter(this);
}
protected:
bool eventFilter(QObject *watched, QEvent *event) override
{
if ((watched == horizontalScrollBar() || watched == verticalScrollBar())
&& event->type() == QEvent::Wheel) {
m_scrollThrottler->tryTrigger();
}
return QScrollArea::eventFilter(watched, event);
}
void scrollContentsBy(int dx, int dy) override
{
QScrollArea::scrollContentsBy(dx, dy);
m_scrollThrottler->tryTrigger();
}
private:
Throttler *m_scrollThrottler;
};
4. 实用技巧和注意事项
1. 选择合适的时间参数
复制代码
复制代码
复制代码
// 推荐的时间间隔设置
const int DEBOUNCE_TIMES[] = {
100, // 快速搜索
300, // 普通输入
500, // 表单验证
1000, // 自动保存
};
const int THROTTLE_INTERVALS[] = {
16, // 60fps 动画/游戏
33, // 30fps 流畅更新
50, // 20fps 可接受响应
100, // 10fps 后台处理
250, // 4fps 最低更新
};
2. 内存管理
复制代码
复制代码
复制代码
// 正确管理生命周期
void MyWidget::setupDebouncers()
{
// 作为成员变量
m_searchDebouncer = new Debouncer(300, this); // parent自动管理
// 局部使用时
{
QScopedPointer<Debouncer> tempDebouncer(new Debouncer(100));
connect(tempDebouncer.data(), &Debouncer::triggered, this, &MyWidget::onTempAction);
// 离开作用域自动删除
}
}