QMessageBox解析

      我在用QMessageBox的时候发现的问题,觉得很有意思,于是就详细的看了一下源码,并打算做一些力所能及的解释。促使我需要理解 QMessageBox是在调用static QMessageBox::question()出现的问题,首先看一下QMessageBox::question()的调用方式:

      StandardButton QMessageBox::question(QWidget *parent,                                              //对话框的父窗口

                                                                        const QString& title, const QString& text,          //对话框标题和内容

                                                                        StandardButtons buttons = Ok,                         //对话框上的按钮列表

                                                                        StandardButton defaultButton = NoButton)       //默认按钮(即默认的焦点所在按钮)

      我在程序中调用QMessageBox::question(this, "Title", "Text");默认显示的是只有一个OK按钮,当我点击对话框上的关闭按钮时,主观上我认为可能返回个NoButton或者Cancel之类的,但事实上我发现返回的仍然是Ok,这使得我无法用默认的按钮设置区分该对话框的返回值。于是我想到用两个按钮来区分,这样调用:

      QMessageBox::question(this, "Title", "Text", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

      结果编译报错为:无法从"int"转化为"QMessageBox::StandardButton"。我的QT版本用的是4.4.1,那么在网上搜到的用法应该与此相同,但的确编译不通过。于是我产生了观看其源码的想法。

      首先StandardButtons是枚举型StandardButton的组合(内部应该是标志位组合),那么在这个地方却不能直接编译通过,当改为:

      QMessageBox::question(this, "Title", "Text", QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);

       此时则正常通过。之所以改成这样也是参考了qmessagebox.cpp的源码,照葫芦画瓢而已。另外还有返回值的问题,在故意设置的情况下,比较纠结,所以不得不分析一个源码。

       这里先介绍QMessageBoxPrivate类(QMessageBoxPrivate是句柄类QMessageBox的实体类)的两个函数,showOldMessageBox()和showNewMessageBox(), 它们都有重载函数,只介绍其中一个即可。

       int QMessageBoxPrivate::showOldMessageBox(QWidget *parent, QMessageBox::Icon icon,
                                          const QString &title, const QString &text,
                                          int button0, int button1, int button2)
      {
             QMessageBox messageBox(icon, title, text, QMessageBox::NoButton, parent);
             messageBox.d_func()->addOldButtons(button0, button1, button2);
             return messageBox.exec();
       }


      static QMessageBox::StandardButton showNewMessageBox(QWidget *parent,
                                                                                        QMessageBox::Icon icon,
                                                                                        const QString& title, const QString& text,
                                                                                        QMessageBox::StandardButtons buttons,
                                                                                        QMessageBox::StandardButton defaultButton)
      {
    // necessary for source compatibility with Qt 4.0 and 4.1
    // handles (Yes, No) and (Yes|Default, No)
             if (defaultButton && !(buttons & defaultButton))
                     return (QMessageBox::StandardButton)QMessageBoxPrivate::showOldMessageBox(parent,

                                                            icon,

                                                            title,  text,
                                                            int(buttons),
                                                            int(defaultButton), 0);

             QMessageBox msgBox(icon, title, text, QMessageBox::NoButton, parent);
             QDialogButtonBox *buttonBox = qFindChild(&msgBox);
             Q_ASSERT(buttonBox != 0);

             uint mask = QMessageBox::FirstButton;
             while (mask <= QMessageBox::LastButton) {
             uint sb = buttons & mask;
              mask <<= 1;
              if (!sb)
                    continue;
               QPushButton *button = msgBox.addButton((QMessageBox::StandardButton)sb);
               // Choose the first accept role as the default
              if (msgBox.defaultButton())
                    continue;
              if ((defaultButton == QMessageBox::NoButton && buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole)
                    || (defaultButton != QMessageBox::NoButton && sb == uint(defaultButton)))
                    msgBox.setDefaultButton(button);
              }
             if (msgBox.exec() == -1)
                   return QMessageBox::Cancel;
            return msgBox.standardButton(msgBox.clickedButton());
       }

       这两个函数等会要详细介绍,下面看一下QMessageBox::question(parent, Information,  title,  text,  buttons,defaultButton)究竟做了什么,查看这个函数的源码发现其函数体里面只有一句话:

       QMessageBox::StandardButton QMessageBox::information(QWidget *parent, const QString &title,
                               const QString& text, StandardButtons buttons,
                               StandardButton defaultButton)
       {
                   return showNewMessageBox(parent, Information, title, text, buttons,defaultButton);
        }

        这说明两个问题,第一,该对话框功能的实现的真实执行者实际上是QMessageBoxPrivate::showNewMessageBox(),所谓 QMessageBox::question()只是个接口而已;第二,所谓 QMessageBox::information(),QMessageBox::warning,QMessageBox::Critical,QMessageBox::question() 调用的都是showNewMessageBox()函数,而它们区分的标志就是QMessageBox::Icon参数,它是QMessageBox的一个枚举成员,标志着对话框的类型。

         下面就是要研究下这个对话框的具体程序流程,即showNewMessageBox()的处理过程:

         它首先检查是否设定了默认按钮。

         >>如果设定了默认按钮,但该默认按钮却不在设定的对话框按钮列表中:它将调用showOldMessageBox()来处理该对话框的显示。应该说这里有个很大的问题,我不知道后续的版本有没有改进这个问题,也就是说,在这种情况下,如果设定的对话框默认列表不止一个,则会因此程序崩溃。为什么呢?因为在调用showOldMessageBox()的时候,该列表被强制转换为int型,然后被传递至showOldMessageBox() 函数中,而showOldMessageBox()函数内部又将该int型参数传递给addOldButton()函数中,addOldButton() 的作用就是把参数中的几个标识按钮的int型作为参数,通过newButton()确定按钮的类型,然后将按钮添加入对话框。问题就出在 newButton()上,下面是newButton()函数源码:

         static QMessageBox::StandardButton newButton(int button)
         {
                 // this is needed for source compatibility with Qt 4.0 and 4.1
                if (button == QMessageBox::NoButton || (button & NewButtonFlag))
                      return QMessageBox::StandardButton(button & QMessageBox::ButtonMask);
 
                #if QT_VERSION < 0x050000
               // this is needed for binary compatibility with Qt 4.0 and 4.1
                switch (button & Old_ButtonMask) {
                case Old_Ok:
                    return QMessageBox::Ok;
                case Old_Cancel:
                    return QMessageBox::Cancel;
                case Old_Yes:
                    return QMessageBox::Yes;
                case Old_No:
                    return QMessageBox::No;
                case Old_Abort:
                    return QMessageBox::Abort;
                case Old_Retry:
                    return QMessageBox::Retry;
                case Old_Ignore:
                    return QMessageBox::Ignore;
                case Old_YesAll:
                    return QMessageBox::YesToAll;
               case Old_NoAll:
                    return QMessageBox::NoToAll;
               default:
                    return QMessageBox::NoButton;
           }
          #endif
       }

      看到这个函数终于知道在这种情况下出错的原因,我们的按钮列表(即StandardButtons类型的buttons被传递进来),作为int型传入到 newButton(int button)函数中试图确认该int型代表的button类型。基本上我们不会调用到#if...#endif之间的程序,因为当前版本的按钮标记为已经分布在高22位中,那么该函数主要关注就是第一句。第一句是什么意思?它检查传递进来的int型是否为0(代表NoButton),如果为0的话好办,直接返回NoButton;如果不为0,它就与NewButtonFlag做并运算,NewButtonFlag是什么意思,它就是说我们新版本的按钮标记用的是那些位!它的值是(0xFFFFFC00),这样,button & NewButtonFlag的结果就认为可以确定该按钮是否为新版本下的标识。这是因为,新版本的按钮标记与老版本不同,新版本的按钮类型以32位 (int型为32位)上的某一位置1,其他位全部置0来相互区分。而老版本则是以{1,2,3,4,5,...}来区分各种按钮类型。这里显然,如果 button是一个按钮类型(即其中只有某一位置1),那么button & QMessageBox::ButtonMask(QMessageBox::ButtonMask = ~QMessageBox::FlagMask = ~0x(00000300))的结果必然是该按钮的类型。但是!回到我们的问题,我们传进来的int型是由一个StandardButtons转为int 型的,当该列表不止一个按钮时,它必然有多个位都是1,那么button & QMessageBox::ButtonMask仍然是多个位为1,也就是说,这个结果根本就不是一个按钮类型的标记。为什么!就因为它不是,它是多个按钮类型的组合!可是你如果一定要传递这个参数给它,它就只能得到一个错误的结果,因为它没有办法去创建一个根本不存在的按钮。 addOldButtons()中调用的addButton(newButton(button0))当然也就不能成功,不但不能成功,而且会出错。

      >>上面说的都是在默认按钮不在按钮列表中的情况。如果defaultButton不存在或者存在但是在所给的按钮列表中:这就不用麻烦 showOldMessageBox()了,showNewMessageBox()完全可以自己处理这些事情。它首先构建一个QMessageBox实例,然后检查buttons中的标志位,从第QMessageBox::FirstButton(0x00000400)到 QMessageBox::LastButton(0x08000000)逐个检查其中的18位,如果检查到有该类型的按钮则添加入对话框中。在将按钮添加入对话框的过程中,如果遇到设定的默认按钮,则将该按钮设置为默认按钮;如果没有指定默认按钮,则遇到某个按钮的角色是 QDialogButtonBox::AcceptRole的时候就将其设置为默认按钮。最后,如果exec()调用失败则返回Cancel按钮类型,否则返回被点击的标准按钮。

      在看完这些源码后,对QMessageBox的几个函数的运行过程终于有所了解,有时候不断的测试已猜测它的函数实现还不如直接看它的源码来得更直接和更快,有时候还可以有其他的收获。

 

你可能感兴趣的:(QT学习笔记)