精通Qt4编程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.4 窗口标志及几何布局

QWidget是所有Qt GUI界面类的基类,它接受鼠标、键盘以及其他的窗口事件,并在显示器上绘制自己。

通过传入QWidget构造函数的参数(或者调用QWidget::setWindowFlags()和QWidget::setParent()函数)可以指定一个窗口部件的窗口标志(window flags)和父窗口部件。

窗口部件的窗口标志定义了窗口部件的窗口类型和窗口提示(hint)。窗口类型指定了窗口部件的窗口系统属性(window-system properties),一个窗口部件只有一个窗口类型。窗口提示定义了顶层窗口的外观,一个窗口可以有多个提示(提示能够进行按位或操作)。

图3-28 QWidget类图

3.4.1 窗口标志

没有父窗口部件的Widget对象是一个窗口,窗口通常具有一个窗口边框(frame)和一个标题栏。在Qt4中,QMainWindow和所有的QDialog对话框子类都是经常使用的窗口类型。而子窗口部件通常处在父窗口部件的内部,没有窗口边框和标题栏。

QWidget窗口部件的构造函数QWidget(QWidget* parent = 0, Qt::WindowFlags f = 0)具有两个形参:

参数 parent,指定了窗口部件的父窗口部件,如果parent = 0(默认值),新建的窗口部件将是一个窗口;否则,新建的窗口部件是parent的子窗口部件(是否是一个窗口还需要第二个参数决定),如果新窗口部件不是一个窗口的话,它将会出现在父窗口部件的界面内部。

参数 f,指定了新窗口部件的窗口标志,默认值是0,即Qt::Widget。

QWidget定义的窗口类型(为Qt::WindowFlags枚举类型,它们的可用性依赖于窗口管理器是否支持它们)有:

Qt::Widget,QWidget构造函数的默认值。如果新的窗口部件没有父窗口部件,那么它是一个独立的窗口,否则是一个子窗口部件。

Qt::Window,不管是否具有父窗口部件,新窗口部件都是一个窗口,通常具有一个窗口边框和一个标题栏。

Qt::Dialog,新窗口部件是一个对话框,它是QDialog构造函数的默认值。

Qt::Sheet,新窗口部件是一个Macintosh表单(sheet)。

Qt::Drawer,新窗口部件是一个macintosh抽屉(drawer)。

Qt::Popup,新窗口部件是一个弹出式顶层窗口。

Qt::Tool,新窗口部件是一个工具(tool)窗口,它通常是一个用于显示工具按钮的小窗口;如果一个工具窗口具有父窗口部件,它将显示在父窗口部件的上面,否则的话,相当于使用了Qt::WindowStaysOnTopHint提示。

Qt::ToolTip,新窗口部件是一个提示窗口,没有标题栏和窗口边框。

Qt::SplashScreen,新窗口部件是一个欢迎窗口(splash screen),它是QSplashScreen构造函数的默认值。

图3-29 Qt::ToolTip窗口

Qt::Desktop,新窗口部件是桌面,它是QDesktopWidget构造函数的默认值。

Qt::SubWindow,新窗口部件是一个子窗口,而不管该窗口部件是否具有父窗口部件。

此外,Qt定义了一些控制窗口外观的窗口提示(这些窗口提示只对顶层窗口有效):

Qt::MSWindowsFixedSizeDialogHint,为Windows系统上的窗口装饰一个窄的对话框边框,通常这个提示用于固定大小的对话框。

Qt::MSWindowsOwnDC,为Windows系统上的窗口添加自身的显示上下文(display context)。

Qt::X11BypassWindowManagerHint,完全忽视窗口管理器,它的作用是产生一个根本不被管理的无窗口边框的窗口(此时,用户无法使用键盘进行输入,除非手动调用QWidget::activateWindow()函数)。

Qt::FramelessWindowHint,产生一个无窗口边框的窗口,此时用户无法移动该窗口和改变它的大小。

Qt::CustomizeWindowHint,关闭默认的窗口标题提示。

Qt::WindowTitleHint,为窗口装饰一个标题栏。

Qt::WindowSystemMenuHint,为窗口添加一个窗口系统菜单,并尽可能地添加一个关闭按钮。

Qt::WindowMinimizeButtonHint,为窗口添加一个最小化按钮。

Qt::WindowMaximizeButtonHint,为窗口添加一个最大化按钮。

Qt::WindowMinMaxButtonsHint,为窗口添加一个最小化按钮和一个最大化按钮。

Qt::WindowContextHelpButtonHint,为窗口添加一个上下文帮助按钮。

Qt::WindowStaysOnTopHint,告知窗口系统该窗口应该停留在所有其他窗口的上面。

Qt::WindowType_Mask,一个用于提取窗口标志中的窗口类型部分的掩码。

枚举类型Qt::WindowFlags的低位1个字节用于定义窗口部件的窗口类型,由0x00000000到0x00000012共定义了11个窗口类型。Qt::WindowFlags的高位字节定义了窗口提示,窗口提示能够进行位或操作,例如,

        Qt:: WindowContextHelpButtonHint| Qt::WindowMaximizeButtonHint

当Qt::WindowFlags的窗口提示部分全部为0时,窗口提示不会起作用。当有一个窗口提示被应用时,要想其他的窗口提示起作用,就必须使用位或操作(如果窗口系统支持这些窗口提示的话)。例如,

        Qt::WindowFlags flags = Qt::Window;
        widget->setWindowFlags(flags);

widget窗口部件是一个窗口,它具有一般窗口的外观(具有窗口边框、标题栏、最小化按钮、最大化按钮和关闭按钮等)。此时窗口提示不会起作用。

        flags |= Qt::WindowTitleHint;
        widget->setWindowFlags(flags);

上述代码的执行,将会使得窗口提示发挥作用。在Windows XP系统上,widget窗口部件是一个窗口,它仅仅具有标题栏,没有最小化按钮、最大化按钮和关闭按钮等。而在红旗Linux 5.0工作站和SuSE 10.2系统上,上述代码并不起作用,这说明X11窗口管理器忽略了窗口提示Qt::WindowTitleHint。

在Windows XP系统上,如果要添加一个最小化按钮,必须重新设置窗口部件的窗口标志(在红旗Linux 5.0工作站和SuSE 10.2系统上,下面的窗口提示也被忽略了),

        flags |= Qt::WindowMinimizeButtonHint;
        widget->setWindowFlags(flags);

如果要取消设置的窗口提示,则

        flags &= Qt::WindowType_Mask;
        widget->setWindowFlags(flags);

3.4.2 窗口部件的几何布局

QWidget提供了一些处理窗口部件的几何布局的函数,可以分为两类:

● 包含窗口边框的处理函数,有x()、y()、frameGeometry()、pos()和move()。

● 不包含窗口边框的处理函数,有geometry()、width()、height()、rect()、size()和resize()。

Qt窗口的几何布局如图3-30所示。

图3-30 Qt窗口的几何布局

Linux采用X11窗口系统,它是不同于Windows系统的一种用户界面技术。因此,在Linux系统上使用QWidget的这些函数时,常常出现一些令人迷惑的现象。

在X11上,在窗口管理器(window manager)渲染一个窗口之前,该窗口是没有窗口边框的。窗口边框的出现,是在调用QWidget::show()之后与窗口部件接收到第一个绘制事件(paint event)之前的某个时间点,或者根本就不会发生。

由于客户间通信协定手册(Inter-Client Communication Conventions Manual, ICCCM)未清晰描述,现存的窗口管理器对放置窗口的处理是大相径庭的。Qt采取的策略是,发送提示给窗口管理器。而作为一个独立进程的窗口管理器,可以遵循这些提示,也可以忽略它们。X11也没有提供一个标准的方法获取窗口渲染后的窗口边框的几何布局。Qt的解决方案能够在目前的多数窗口管理器上工作,而如果你发现QWidget::frameGeometry()不能正常工作,也不要大惊小怪。

此外,X11也没有提供最大化一个窗口的方法。Qt的QWidget::showMaximized()只是模拟实现了窗口的最大化。而能否真正的最大化也要依赖于QWidget::frameGeometry()的返回结果以及窗口管理器是否能够正确地放置窗口。

应用程序可以使用Qt提供的几何布局函数QWidget::geometry()保存窗口部件的位置和大小,然后在下一个会话依次调用QWidget::setGeometry()和QWidget::show()恢复显示这个窗口部件。这些函数在Windows系统上工作是没有问题的。然而,在X11窗口系统上使用这些函数恢复显示一个窗口的大小和位置时,并不像在Windows系统上工作的那样理想(具体差异会因窗口系统的不同而变化)。这是因为,在X11上,不可见的窗口是没有窗口边框的。另一个保存、恢复窗口部件的大小和位置的做法是,先保存函数QWidget::size()和QWidget::pos()的返回值,然后再调用QWidget::resize()和QWidget::move()函数恢复窗口的大小和位置,最后调用函数QWidget::show()显示窗口部件。如果还有问题的话,只有首先调用QWidget::show()显示窗口部件,再恢复窗口部件的大小和位置。不过这样做的后果是,窗口先出现在一个错误的位置,再闪到正确的位置。

由于管理器的不同,相同的应用程序在使用QWidget这些几何布局函数的时候,其行为有所差异(不同版本的X11窗口系统,窗口部件的行为也有差异)。因此上面的描述也只提供参考,对于具体的系统平台,应该以读者的测试结果为准。

下面,看一个演示QWidget窗口部件类型和几何布局的例子。这个例子在Windows系统、红旗Linux 5.0工作站和SuSE 10.2系统上测试通过,但因为窗口系统的不同(或者窗口管理器系统的版本不同),应用程序的行为和窗口外观有所差异。

新建KDevelop工程geometry。在设计器中绘制ui界面,如图3-31所示。其窗口部件属性表如表3-3所示。

图3-31 控制窗口ui界面

表3-3 窗口部件属性表(按从左到右、从上至下顺序,包括部分布局管理器)

将ui文件保存为“CtrlForm.ui”,并加入到KDevelop工程中。

添加控制类CCtrlform的定义文件ctrlfrom.h和实现文件ctrlform.cpp到KDevelop工程中。

类CCtrlform的定义文件ctrlform.h内容如下。

        // chapter03/geometry/src/ctrlform.h.
        #ifndef _CTRLFORM_H_
        #define _CTRLFORM_H_

        #include <QWidget>
        #include "ui_ctrlform.h"

        class QPushButton;

        class CCtrlForm :   public QWidget,
                            public Ui_CtrlForm
        {
            Q_OBJECT
        public:
            CCtrlForm(QWidget* = 0);

        private:
            QWidget*    m_pWidget;
            QPoint      m_Pos;
            QSize       m_Sz;

        private slots:
            void doClicked();
            void doSpinBoxChanged(int);
        };
        #endif

类CCtrlForm用来设置另一个窗口部件m_pWidget的窗口标志,控制m_pWidget的显隐以及位置和大小的恢复。

成员变量m_Pos和m_Sz分别保存m_pWidget窗口部件的位置和大小(可以使用QSettings类实现永久性保存,关于QSettings的使用见4.6节)。

槽函数doClicked()响应单选按钮和复选框的单击信号clicked(),完成演示窗口标志和父窗口的重置。

槽函数doSpinBoxChanged()控制窗口部件m_pWidget的移动。

下面是类CCtrlForm实现文件ctrlform.cpp的内容。

        // chapter03/geometry/src/ctrlform.cpp.
        #include <QtGui>
        #include <QDebug>
        #include "ctrlform.h"

        CCtrlForm::CCtrlForm(QWidget* parent)
        :  QWidget(parent)
        {
            setupUi(this);

            m_pWidget = new QWidget(0, Qt::Widget);
            QPushButton* btn = new QPushButton(tr("关闭"));
            connect(btn, SIGNAL(clicked()), m_pWidget, SLOT(close()));
            QTextEdit* edit = new QTextEdit(tr("武汉欢迎您!"));
            QVBoxLayout* layout = new QVBoxLayout;
            layout->addWidget(edit);
            layout->addWidget(btn);
            m_pWidget->setLayout(layout);
            m_pWidget->resize(200, 100);
            m_pWidget->move(400, 500);
            m_pWidget->show();

初始化演示QWidget对象m_pWidget,设置它的大小和初始化位置,并显示。

        widgetRadioBtn->setChecked(true);
        nullRadioBtn->setChecked(true);
        xSpinBox->setRange(-100, 100);
        ySpinBox->setRange(-100, 100);

初始化窗口部件的状态。设置“无父窗口部件”和“widget”单选按钮为选中状态;设置SpinBox按钮的调节范围。

        connect(widgetRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(windowRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(dialogRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(sheetRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(drawerRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(popupRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(toolRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(tooltipRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(splashscreenRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(subwindowRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(fsdialogCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(owndcCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(managerCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(framelessCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(customizeCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(titleCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(menuCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(minCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(maxCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(minmaxCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(helpCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(shadeCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(topCheckBox, SIGNAL(clicked()),
            this, SLOT(doClicked()));

关联与窗口标志有关的所有单选按钮和复选按钮的clicked()信号到槽函数doFlagChanged(),以响应重置窗口标志的操作。

        connect(nullRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));
        connect(thisRadioBtn, SIGNAL(clicked()),
            this, SLOT(doClicked()));

关联设置父窗口的单选按钮“无父窗口”和“本窗口”的clicked()信号到槽函数doClicked(),以响应重置父窗口的操作。

        connect(xSpinBox, SIGNAL(valueChanged(int)),
            this, SLOT(doSpinBoxChanged(int)));
        connect(ySpinBox, SIGNAL(valueChanged(int)),
            this, SLOT(doSpinBoxChanged(int)));

关联SpinBox窗口部件的QSpinBox::valueChanged()信号到槽doSpinBoxChanged(),以响应移动演示窗口部件的操作。

            move(500, 400);
        }

下面是重置演示窗口部件的窗口标志的槽函数。

        void CCtrlForm::doFlagChanged()
        {
            QPoint  position = m_pWidget->pos();
            QSize   sz  = m_pWidget->size();

获取当前演示窗口部件m_pWidget的位置和大小,用于再次显示窗口时恢复它们。

        QRadioButton* btn = qobject_cast<QRadioButton*>(sender());
        if(btn == nullRadioBtn)
            m_pWidget->setParent(0);
        else if(btn == thisRadioBtn)
            m_pWidget->setParent(this);

如果选择了“无父窗口”或者“本窗口”单选按钮,则调用函数QWidget::setParent()重新设置演示窗口部件的父窗口部件。函数QWidget::setParent()的调用将会隐藏演示窗口部件m_pWidget。

        Qt::WindowFlags flags = 0;
        if(widgetRadioBtn->isChecked())
            flags = Qt::Widget;
        else if(windowRadioBtn->isChecked())
            flags = Qt::Window;
        else if(dialogRadioBtn->isChecked())
            flags = Qt::Dialog;
        else if(sheetRadioBtn->isChecked())
            flags = Qt::Sheet;
        else if(drawerRadioBtn->isChecked())
            flags = Qt::Drawer;
        else if(popupRadioBtn->isChecked())
            flags = Qt::Popup;
        else if(toolRadioBtn->isChecked())
            flags = Qt::Tool;
        else if(tooltipRadioBtn->isChecked())
            flags = Qt::ToolTip;
        else if(splashscreenRadioBtn->isChecked())
            flags = Qt::SplashScreen;
        else if(subwindowRadioBtn->isChecked())
            flags = Qt::SubWindow;

声明一个枚举类型Qt::WindowFlags的变量flags,并保存重置的窗口类型。

        if(fsdialogCheckBox->isChecked())
            flags |= Qt::MSWindowsFixedSizeDialogHint;
        if(owndcCheckBox->isChecked())
            flags |= Qt::MSWindowsOwnDC;
        if(managerCheckBox->isChecked())
            flags |= Qt::X11BypassWindowManagerHint;
        if(framelessCheckBox->isChecked())
            flags |= Qt::FramelessWindowHint;
        if(customizeCheckBox->isChecked())
            flags |= Qt::CustomizeWindowHint;
        if(titleCheckBox->isChecked())
            flags |= Qt::WindowTitleHint;
        if(menuCheckBox->isChecked())
            flags |= Qt::WindowSystemMenuHint;
        if(minCheckBox->isChecked())
            flags |= Qt::WindowMinimizeButtonHint;
        if(maxCheckBox->isChecked())
            flags |= Qt::WindowMaximizeButtonHint;
        if(minmaxCheckBox->isChecked())
            flags |= Qt::WindowMinMaxButtonsHint;
        if(helpCheckBox->isChecked())
            flags |= Qt::WindowContextHelpButtonHint;
        if(shadeCheckBox->isChecked())
            flags |= Qt::WindowShadeButtonHint;
        if(topCheckBox->isChecked())
            flags |= Qt::WindowStaysOnTopHint;

保存演示窗口部件的窗口提示到枚举变量flags。

            m_pWidget->setWindowFlags(flags);
            if(widgetRadioBtn->isChecked()
               && thisRadioBtn->isChecked())
            {
               position = QPoint(0, 0);
            }
            m_pWidget->resize(sz);
            m_pWidget->move(position);
            m_pWidget->show();
        }

调用函数QWidget::setWindowFlags()重新设置演示窗口部件的标志。函数QWidget::setWindowFlags()在设置窗口标志的同时也会隐藏窗口部件。

如果演示窗口部件的窗口类型为Qt::Widget,并且父窗口是控制窗口,那么它将会显示在控制窗口部件的内部。为了能够将演示部件显示在可见的位置,在这种情况下,需要将演示窗口部件的位置设为(0, 0)。

调用函数QWidget::move()恢复窗口部件的位置,然后调用函数QWidget::show()重新显示演示窗口部件。

Qt提供了保存和恢复窗口布局和状态的函数。QWidget::saveGeometry()保存窗口的几何布局和最大化/全屏状态;相应地,函数QWidget::restoreGeometry()则恢复窗口的几何布局和最大化/全屏状态,该函数还会判断恢复后的几何布局是否超出了可用的屏幕布局,如果超出它会进行适当的调整。

        void CCtrlForm::doSpinBoxChanged(int value)
        {
            QSpinBox* box = qobject_cast<QSpinBox*>(sender());
            int x = m_pWidget->x();
            int y = m_pWidget->y();

            if(box == xSpinBox)
            x += value;
            else if(box == ySpinBox)
            y += value;

            m_pWidget->move(x, y);
            m_pWidget->show();
        }

函数doSpinBoxChanged()响应移动窗口部件的操作,主要演示在不同的窗口类型、不同的窗口提示和不同的父窗口下演示窗口部件的移动情况。

修改主程序geometry.cpp文件,如下。

        // chapter03/geometry/src/geometry.cpp.
        #include <QtGui>
        #include <QDebug>

        #include "ctrlform.h"

        int main(int argc, char *argv[])
        {
            QApplication app(argc, argv);
            QTextCodec::setCodecForTr(QTextCodec::codecForName("gb2312"));
            CCtrlForm form;
            form.show();
            return app.exec();
        }

现在编译运行应用程序,测试一下窗口标志和父窗口部件的有无对用户界面的影响。