Qt Signal Mapper:一堆按钮,怎么共用一个槽函数

Qt Signal Mapper:一堆按钮,怎么共用一个槽函数

假设界面上有十个"打开文件"按钮,每个按钮对应不同的文件。你当然可以写十个槽函数:

cpp 复制代码
void MainWindow::openFile1();
void MainWindow::openFile2();
void MainWindow::openFile3();

但写到第三个时,你应该已经觉得不对劲了。它们的逻辑大概率一样,区别只是"到底点了哪个"。

QSignalMapper 就是 Qt 早年给这种场景准备的工具:多个对象先把相同的信号发给它,它再带着一个映射值,转发成统一的信号。最终你只写一个槽函数。

cpp 复制代码
按钮 A 的 clicked() ---\
按钮 B 的 clicked() ----> QSignalMapper ---> mappedInt(int) ---> 统一槽函数
按钮 C 的 clicked() ---/

最经典的按钮编号例子

先准备三个按钮,并让每个按钮映射到一个编号:

cpp 复制代码
auto *mapper = new QSignalMapper(this);

connect(ui->buttonOne, &QPushButton::clicked,
        mapper, QOverload<>::of(&QSignalMapper::map));
connect(ui->buttonTwo, &QPushButton::clicked,
        mapper, QOverload<>::of(&QSignalMapper::map));
connect(ui->buttonThree, &QPushButton::clicked,
        mapper, QOverload<>::of(&QSignalMapper::map));

mapper->setMapping(ui->buttonOne, 1);
mapper->setMapping(ui->buttonTwo, 2);
mapper->setMapping(ui->buttonThree, 3);

connect(mapper, &QSignalMapper::mappedInt,
        this, &MainWindow::handleButton);

统一槽函数只接收映射后的值:

cpp 复制代码
void MainWindow::handleButton(int buttonId)
{
    switch (buttonId) {
    case 1:
        openDocument("one.txt");
        break;
    case 2:
        openDocument("two.txt");
        break;
    case 3:
        openDocument("three.txt");
        break;
    }
}

这里有两个步骤,缺一个都不行。

先把每个按钮的 clicked() 连到 mapper->map(),让 mapper 知道"有人触发了"。再用 setMapping() 给这个发送者绑定数据,告诉 mapper "这个人触发时该带上什么值"。最后连接 mappedInt(int),接收者才能拿到编号。

map() 有重载,因此用函数指针连接时写了 QOverload<>::of。它表示选取无参数版本的 map(),正好匹配 QPushButton::clicked(bool) 可以忽略参数的规则。

映射的不一定是整数

除了整数,QSignalMapper 还可以映射字符串和对象指针。

菜单动作很适合映射字符串:

cpp 复制代码
auto *mapper = new QSignalMapper(this);

connect(ui->actionOpen, &QAction::triggered,
        mapper, QOverload<>::of(&QSignalMapper::map));
connect(ui->actionSave, &QAction::triggered,
        mapper, QOverload<>::of(&QSignalMapper::map));

mapper->setMapping(ui->actionOpen, QStringLiteral("open"));
mapper->setMapping(ui->actionSave, QStringLiteral("save"));

connect(mapper, &QSignalMapper::mappedString,
        this, &MainWindow::handleAction);
cpp 复制代码
void MainWindow::handleAction(const QString &action)
{
    if (action == "open") {
        openFile();
    } else if (action == "save") {
        saveFile();
    }
}

当映射对象指针时,接收方可以知道具体是谁触发:

cpp 复制代码
mapper->setMapping(ui->buttonOne, ui->buttonOne);

connect(mapper, &QSignalMapper::mappedObject,
        this, &MainWindow::handleButtonObject);

不过对象指针映射通常不是今天最舒服的写法。它会让业务逻辑重新靠对象身份判断,读起来不如直接把需要的数据捕获进 Lambda。

它解决过的问题,如今有更直接的办法

Qt 4 时代,函数指针式 connect 和 Lambda 还没普及,QSignalMapper 很实用。现在写 Qt 5/Qt 6 时,多个按钮共用逻辑,优先考虑 Lambda:

cpp 复制代码
connect(ui->buttonOne, &QPushButton::clicked, this, [this]() {
    openDocument("one.txt");
});

connect(ui->buttonTwo, &QPushButton::clicked, this, [this]() {
    openDocument("two.txt");
});

若只是把不同编号传给同一个处理函数,写法更紧凑:

cpp 复制代码
for (int index = 0; index < m_buttons.size(); ++index) {
    QPushButton *button = m_buttons.at(index);

    connect(button, &QPushButton::clicked, this, [this, index]() {
        handleButton(index);
    });
}

这段代码少了一个 mapper 对象,也少了"点击信号先绕一圈再发 mapped 信号"的中间层。映射值就在连接旁边,后来读代码的人也更容易看懂。

循环里捕获变量,别捕获引用

上面的 [this, index] 不是随手写的。index 是循环局部变量,必须按值捕获。

下面写法会留下悬空引用:

cpp 复制代码
for (int index = 0; index < m_buttons.size(); ++index) {
    QPushButton *button = m_buttons.at(index);

    connect(button, &QPushButton::clicked, this, [this, &index]() {
        handleButton(index);
    });
}

循环结束后,index 已经不存在;以后再点击任何按钮,Lambda 都会读取无效引用。即使碰巧没崩,拿到的编号也不可信。

同样地,button 如果要在 Lambda 内使用,也应按值捕获:

cpp 复制代码
connect(button, &QPushButton::clicked, this, [this, button]() {
    ui->statusBar->showMessage(button->text());
});

连接的 context 使用 this,窗口销毁时连接自动失效。button 若可能单独提前销毁,则还要看它的生命周期是否由窗口管理;复杂场景可以用 QPointer<QPushButton> 防御。

什么时候还值得用 QSignalMapper

说 QSignalMapper 已经过时,并不等于项目里一见它就该删掉。

  • • 维护 Qt 4 或较早 Qt 5 代码时,它是成熟且正常的写法。

  • • 已有工程大量使用它,改成 Lambda 不能带来实际收益时,保持一致更好。

  • • 你确实需要把多个来源做成一个可复用的映射中转对象时,它依然能完成工作。

但新代码里,如果一条 Lambda 就能把"这个按钮触发后带上哪个参数"说清楚,优先用 Lambda。QSignalMapper 的价值,是理解 Qt 信号槽如何做信号汇聚;它不该再变成每组按钮都必须套上的样板。

还有一个很现实的提醒:别因为多个按钮都要执行不同操作,就急着把它们塞进一长串 switch。操作本身若有清晰名字,直接连接到对应成员函数往往更直白。只有"流程相同,数据不同"时,映射才真正帮你减少重复。

相关推荐
蜗牛互联网1 小时前
Java HttpClient 调用 Gemini 图像理解与金额字段校验
java·开发语言·人工智能·后端·python
蜗牛互联网1 小时前
Python Responses API视觉输入与本地金额校验最小实现
java·开发语言·人工智能·后端·python
Python图像识别1 小时前
35-【2027毕设】YOLO11中草药检测识别系统 - Python完整源码+PyQt5界面+训练模型+数据集
python·qt·课程设计
czq_26867194872 小时前
Python打卡第34天
开发语言·python·机器学习
Lhan.zzZ2 小时前
Qt/QML:业务记录单展示、签名确认与 PDF 导出开发
c++·qt·pdf
小黄人软件2 小时前
粘贴助手C++版 支持 Windows 7 / 10 / 11 不会出现缺少api-ms-win-core-path-11-1-0.dll
开发语言·c++·windows
半亩码田2 小时前
C#转Python第4.7篇:logging:Python 的标准日志库
开发语言·python·c#
程序员老陆2 小时前
从汇编层面理解 C++ 多线程
开发语言·c++·程序设计
谢亮_vipxieliang2 小时前
深入理解 JavaScript 数据类型与类型转换
开发语言·javascript·ecmascript