找回密码
 立即注册
收起左侧

KS05-05 信号-槽: 消息阻塞-防止额外触发槽函数

1
回复
4345
查看
[复制链接]
累计签到:41 天
连续签到:1 天
来源: 原创 2019-9-2 17:07:04 显示全部楼层 |阅读模式
本帖最后由 baizy77 于 2020-2-22 14:26 编辑

该文章原创于Qter开源社区(www.qter.org
作者: 女儿叫老白
转载请注明出处!
----------------------------------------------------------------


引言
----------------------------------------------------------------
前些天因为工作需要,为工具条增加了样式反显功能。反显功能指的是什么呢?比如,在一个文字编辑器中,当用户选中某段文字时,在工具条的字体控件中可以显示这段文字的字体,而且随着选中文本区域的变换,字体控件可以随之更新并显示当前新选中文字的字体,这就是反显功能。但是,这个功能与选中文本后通过工具条上的字体控件修改其字体的功能相互影响,这是怎么回事呢?

正文
----------------------------------------------------------------
我们分别看一下这两个功能的流程图。

先看通过工具条的字体控件修改选中文本的字体功能:(图05-05-01)
在图05-05-01中,用户通过工具条上的字体控件修改字体后,会导致字体控件发出信号:currentFontChanged(),并触发槽函数slot_fontFamilyChanged()的调用。


再来看用户选中文本后,在工具条的字体控件反显字体功能:(图05-05-02)

在图05-05-02中,当用户选中的文本区域发生变换时,会导致QLineEdit发出selectionChanged()信号;在其对应的槽函数中,我们获取当前选中文本的字体,并将其更新到工具条上的字体控件中,从而实现字体反显。

看上去这两个功能井水不犯河水,好像没啥关系。但是我们忽略了一个问题,就是在第二个流程图的最后一步,将选中文本的字体更新到字体控件时,需要调用:
  1. ui.fontComboBox->setCurrentFont(ft);
复制代码
如果字体控件中的字体与ft不同,将导致字体控件发出信号:currentFontChanged(constQFont&)。而从流程图05-05-01可知,这会导致槽函数slot_fontFamilyChanged()的调用,在槽函数中将更新选中文本的字体,这是一次额外的触发,并不是我们期望的。
这样,整个流程就变成下面的样子:图05-05-03
在图05-05-03对于两个功能的描述中,多了一次对选中对象的设置字号操作。而且对文本设置字体相当于对文件内容的修改,有可能导致关闭文件时提示文件已修改并要求用户保存文件。这会让用户感觉一头雾水。这一切都因为对选中对象多了一次设置字号的操作。这是个比较麻烦的问题,该怎么解决呢?

    我们先来看一下反显接口:
代码清单05-05-01

dialog.cpp
  1. void CDialog::updateFontWidget(){
  2.         if (NULL == m_pCurrentLabel) {
  3.                 return;
  4.         }

  5.         QFont ft = m_pCurrentLabel->font();
  6.         QString str;
  7.         str.sprintf("%d", ft.pointSize());
  8.         ui.cbFontSize->setCurrentText(str);
  9.         ui.fontComboBox->setCurrentFont(ft);
  10. }
复制代码
从代码清单05-05-01可以看出,关键是第10~11行,会导致这两个控件继续发出新的信号(比如currentFontChanged()),从而产生问题。

实际上,Qt为了防止发生此类事件,早就提供了解决方案。方法就是调用blockSignals ()。
  1. class QObject {
  2.     //......
  3.     bool blockSignals(bool b);
  4.     //......
  5. }
复制代码
    该接口由QObject类提供。当b=true时,发给QObject对象的信号将被阻塞,该对象不会收到信号。当b=false时,对象又能收到信号了。我们需要做的就是在反显时,先将信号阻塞,反显操作结束后,将信号回复。
代码清单05-05-02

dialog.cpp
  1. void CDialog::updateFontWidget(){
  2.         if (NULL == m_pCurrentLabel) {
  3.                 return;
  4.         }
  5.         ui.cbFontSize->blockSignals(true);
  6.         ui.fontComboBox->blockSignals(true);

  7.         QFont ft = m_pCurrentLabel->font();
  8.         QString str;
  9.         str.sprintf("%d", ft.pointSize());
  10.         ui.cbFontSize->setCurrentText(str);
  11.         ui.fontComboBox->setCurrentFont(ft);

  12.         ui.cbFontSize->blockSignals(false);
  13.         ui.fontComboBox->blockSignals(false);
  14. }
复制代码
代码清单05-05-02中:
第17~18行对两个控件的信号进行阻塞;
第26~27行,解除了对两个控件的信号阻塞。
请朋友们用附件的源代码做一下测试,在下述代码中,我们添加了qDebug来输出对于文本框的操作记录:
代码清单05-05-03

dialog.cpp
  1. void CDialog::setTextFont(QLineEdit* pLabel, const QFont& newFont)
  2. {
  3.         qDebug() << "setTextFont: " << pLabel;
  4.         if (NULL != pLabel) {
  5.                 pLabel->setFont(newFont);
  6.                 updateFontWidget();
  7.         }
  8. }
复制代码
我们把代码清单05-05-02中的blockSignals()都封掉,看一下输出的调试信息。然后再把blockSignals()解封,看一下输出的调试信息。通过对比,我们可以发现当封掉blockSignals()时,对于文本框会多一次操作,而当使用blockSignals()时,仅有一次对文本框的操作。通过对blockSignals的调用,我们成功防止了信号被额外触发。

结语
----------------------------------------------------------------

     在开发类似的反显功能时,我们需要先阻塞信号,当反显操作完成后再接触阻塞。这样,反显功能就可以正常运转。

----------------------------------------------------------------

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

尚未签到

2019-10-12 12:29:44 显示全部楼层
应该还可以对currentFontChanged事件先取消,再connect
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了