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(); }
现在编译运行应用程序,测试一下窗口标志和父窗口部件的有无对用户界面的影响。