之前从来没用过Qt中有关信号映射的API,因为我玩得还是比较浅的,最近两天正好在系统学习Qt,看到了QSignalMapper的使用。
一般情况下,在使用connect
连接信号与槽时,如果连接的槽函数有特定的参数,或者你想要把与发射这个信号相关联的某个对象作为参数传入槽函数中,你完全可以使用lambda
表达式配合目标槽函数使用。不过Qt提供了一种更加方便的机制,也就是信号映射,直接把你想要作为参数传入的对象和信号本身构成映射。然后你只需要将信号与相应的槽函数绑定即可。
上面讲得还是比较抽象,可以看个具体的例子。比如现在正在做一个简单的多文档编辑器,然后为了更好地管理多文档,我们会把存在的子窗口地标题都列在“窗口(W)”菜单栏中。并且希望点到对应的标题,这个Action就会checked(true),并且对应的子窗口也会被激活(也就是获得焦点)。
比如我现在点击“未命名文件2.txt”,那么这个标题对应的那个子窗口就会获得焦点,我们的cursor也会浮动在改子窗口中。下图就是点击了“2 未命名文件2.txt”这个Action后的效果。
我们已经事先写好一个函数void MultiWindowEditor::setActiveSubWindow(QWidget* window)
了,这个函数可以让传入的非空QWidget
控件获得焦点。为了实现之前的功能,我们首先需要遍历子窗口列表,为每一个子窗口对象创建一个Action,然后将这个Action与void setActiveSubWindow(QWidget* window)
绑定,使得我们点击它们时会触发获取激活相应子窗口的效果。
这个地方就会有一定的疑惑了,因为void setActiveSubWindow(QWidget* window)
的参数是指向控件的指针,但是我们能够为我们所用的,发射信号的·对象是一个Action,而不是我们想要激活的子窗口本身,这个时候,你当然可以使用lambda表达式来解决这一点。除此之外,使用Qt的信号映射机制或许会显得更加优雅。
首先,引入对应的库。(工作环境为VS)
#include "qfiledialog.h"
然后在主窗口对象的构造函数中加入:
// 创建信号映射器
windowMapper = new QSignalMapper(this);
// 映射器重新发射信号,根据信号设置活动窗口
connect(windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*)));
第一句话创建了一个信号映射器,第二句话则是将将这个映射器发射的信号mapped
与主窗口对象的void setActiveSubWindow(QWidget* window)
函数绑定,之前我们也说了,这个函数已经实现了。
此处的映射器只是一个桥梁,我们需要借助它将Action和它对应的子窗口控件形成关联。接下来,我们需要为子窗口列表中的每一个对象构建Action,并将这个Action和激活窗口的槽函数绑定起来,然后使用这个函数再去使得我们的子窗口显示。代码如下:(重点在最后两句话,前面的都可以不看)
for (int i = 0; i < windows.size(); ++ i)
{
MdiChild* child = qobject_cast<MdiChild*>(windows.at(i)->widget());
QString text;
// 如果窗口数小于9,则设置编号为快捷键
if (i < 9)
{
text = tr("&%1 %2").arg(i + 1).arg(child->userFriendlyCurrentFile());
}
else
{
text = tr("%1 %2").arg(i + 1).arg(child->userFriendlyCurrentFile());
}
// 添加动作到菜单
QAction* action = ui.menu_W->addAction(text);
action->setCheckable(true);
// 设置当前活动窗口动作为选中状态
action->setChecked(child == activeMdiChild());
// 关联动作的触发信号到信号映射器的map()槽
connect(action, SIGNAL(triggered()), windowMapper, SLOT(map()));
// 将action与子窗口对象构成映射
windowMapper->setMapping(action, windows.at(i));
}
倒数第二句话,我们将action与映射器的map()
槽函数绑定在了一起,其中这个map()
函数会发射映射器的mapped()
信号,也就是我们之前和主窗口的激活窗口函数绑定的那个信号。因此只要我们点击了这个action,触发这个action的triggered信号,便会使得映射器的map()
方法被调用,然后map()
的调用会使得映射器释放一个mapped()
信号,我们之前将mapped()
和setActiveSubWindow()
绑在了一起,所以setActiveSubWindow
会被触发。这样一套流程下来,我们实现了点击Action触发setActiveSubWindow
的效果。不过现在还有一个小问题:setActiveSubWindow
需要一个指向控件的指针作为参数,我们怎么将与之前的acion相关联的子窗口的内存地址作为参数在上述的流程中被传递呢?很简单,我们只需要使用映射器的setMapping
方法,将action和它对应的子窗口控件连起来就行。
这样一来,我们就完成了将Action与对应的控件关联起来,并且将这个关联体与槽函数关联起来这样一个相对复杂的过程了。