关于Qt的ui自动槽函数

首先,引入ui,你的ui相关控件的初始化就在与你自定义类同名的Ui::Widget中查看(当然这个Ui::Widget是我默认举得例子,具体与你自定义类名相同)。

1
在这里面,你会发现里面定义了你再ui设计师界面中所拖拽的控件,而这些控件的初始化都在setupUi()这个方法中进行。->而这些都是由Qt帮你做的,大多时候你无需关心具体细节。

其次,如果你仔细观察,你会发现今天的主人公 ‘QMetaObject::connectSlotsByName();’

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
这个方法,在我的Qt版本中只能看到定义(有的Qt4版本可以查到其内部实现)。由于它的实现,可以再网上找到其他人的分享(所以我就没浪费不必要的时间去下载其他版本)。
以下内容来自网络分享:
.
.
查看Qt4的一些示例项目的时候,使用设计器打开其UI文件,在文件中竟然找不到signal和slot的连接。但是最终的程序,slot却又能准确的响应信号。打开通过ui文件自动生成的c++文件,其中也找不到connect语句,到底是怎么一回事?
  
  经过逐语句的分析。终于发现连接的原因就在于setUi函数的最后一句
  
  QMetaObject::connectSlotsByName(MainWindow);
  
   找到该静态函数
  
  void QMetaObject::connectSlotsByName(QObject *o)
  {
   if (!o)
   return;
   const QMetaObject *mo = o->metaObject();
   Q_ASSERT(mo);
   const QObjectList list = qFindChildren(o, QString());
   for (int i = 0; i < mo->methodCount(); ++i) {
  
  
   const char *slot = mo->method(i).signature();
   Q_ASSERT(slot);
  
  //以下一行用来判断slot的前三位是否是on_,如果不是,就跳过这个方法。
   if (slot[0] != 'o' || slot[1] != 'n' || slot[2] != '_')
   continue;
   bool foundIt = false;
  
  //遍历子对象。
   for(int j = 0; j < list.count(); ++j) {
   const QObject *co = list.at(j);
  
  //得到子对象名。
   QByteArray objName = co->objectName().toAscii();
   int len = objName.length();
  
  //要求slot跳过前3位(on_)后,接下来的子字符串和子对象名相同,并且接着该子字符串又是一个_
  
  //如果达不到这个要求,continue
   if (!len || qstrncmp(slot + 3, objName.data(), len) || slot[len+3] != '_')
   continue;
   const QMetaObject *smo = co->metaObject();
   int sigIndex = smo->indexOfMethod(slot + len + 4);
   if (sigIndex < 0) { // search for compatible signals
   int slotlen = qstrlen(slot + len + 4) - 1;
  
  //搜索该子对象所能引发的信号
   for (int k = 0; k < co->metaObject()->methodCount(); ++k) {
  //方法类型如果符合要求
  
   if (smo->method(k).methodType() != QMetaMethod::Signal)
   continue;
  
  //如果slot最后的子字符串和信号名相同
  
   if (!qstrncmp(smo->method(k).signature(), slot + len + 4, slotlen)) {
   sigIndex = k;
   break;
   }
   }
   }
   if (sigIndex < 0)
   continue;
  
  //连接操作
   if (QMetaObject::connect(co, sigIndex, o, i)) {
   foundIt = true;
   break;
   }
   }
  
  //连接成功
   if (foundIt) {
   // we found our slot, now skip all overloads
   while (mo->method(i + 1).attributes() & QMetaMethod::Cloned)
   ++i;
   }
  
  //连接失败
  
  else if (!(mo->method(i).attributes() & QMetaMethod::Cloned)) {
   qWarning("QMetaObject::connectSlotsByName: No matching signal for %s", slot);
   }
   }
  }
  
  得出此结论:自动生成的文件中,该函数总会存在setUi函数的最后一句。
  
  该函数的作用就是寻找setUi的唯一指针参数MainWindow所指向对象的成员函数,
  
  该成员函数的名字如果满足以下条件,就做连接操作。
  
  函数名规则:on_子对象名_信号名
  
  函数签名(即返回值与参数要符合slot要求)
  
  所以,我们可以这样做:在qt设计器中添加按纽或者菜单项或者按纽项后,不用在设计器中手动做连接操作。
  
  我们只要在主窗口类中添加符合条件的成员函数即可。
  
  函数名规则:on_子对象名_信号名
  
  函数签名(即返回值与参数要符合slot要求)
  
  例如:
  
  在设计器中添加一个菜单项,其对应的action为actionNew
  
  那么在主窗口类中添加以下的函数
  
  public slots:
  
   void on_actionNew_triggered();
  
  当切换这个菜单时,会自动执行上面的成员函数。

现在我们大致了解了Qt的ui自动槽函数的实现原理,那么我们来验证一下: (在这之前,新建一个带ui的项目,通过ui设计界面放置一个按钮,并转到槽(随便选一个信号,并在槽函数中打印个调试日志),编译运行)。

1
2
3
4
5
6
我们来看看发生了什么
找到项目构建目录所在位置,打开它,在里面寻找到一个名字为 moc_widget.cpp 的文件(注意这个名字与你自定义的有关),打开它,并在随后的操作过程中观察它的变化
第一步:我们仅删除(注释掉即可,方便最后的试验练习).cpp中,槽函数的实现。 编译运行。 现象:报错。(由于moc_widget.cpp 文件中,自动生成了信号槽的关联信息,而你没有实现槽函数,故报错)
第二步:我们进一步删除(注释掉即可,方便最后的试验练习).h文件中,槽函数的定义。 现象:编译通过。(moc_widget.cpp 文件中,已经自动清除了有关信号槽连接的关联信息。)
第三步:在ui设计师界面,放置第二个按钮,并将按钮对象名,命名为 ‘QQQ’。 仿照刚才删除 的槽函数声明与定义的命名格式规范,自己写一个QQQ槽函数声明与定义,编译运行。
现象:编译通过,槽函数可用(虽然我们只定义了槽函数)。(moc_widget.cpp 文件中,已经自动生成了信号槽的关联信息。)

然后,我们可以单通过现象来总结得知

1
自动关联信号和槽,实际上就是 通过 ‘QMetaObject::connectSlotsByName();’方法,抓取代码中的槽函数声明定义等字段中的关键信息 如(on_控件名称_信号种类),来自动为你匹配生成信号与槽的连接,不需要向手动配置那样,自己去定义信号然后建立连接了。  并且槽函数的声明和定义这一步骤,也由Qt通过Ui设计师界面的一个‘转到槽’选项帮助我们快速定义。大大的简化了程序员的工作步骤,提高了开发效率,被动提升了代码的质量。  

最后,做个小实验,练习今天关于Qt的知识点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
试验步骤:
一、注释掉‘QQQ’按钮槽函数的声明和定义
二、解除第一个按钮槽函数的声明和定义,并将此函数命名中对象的的名字改为‘QQQ’
二、编译运行。
提问:现在点击‘QQQ’按钮,执行的是否是之前第一个按钮的功能?
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
答案:是的。 原因是:‘QMetaObject::connectSlotsByName();’方法,抓取代码中的槽函数声明定义等字段中的关键信息。 目前的关键信息属于‘QQQ’按钮对象。