漫谈QWidget及其派生类(二)

原文地址: https://blog.csdn.net/dbzhang800/article/details/6741344

上一部分漫谈QWidget及其派生类(一) 介绍了QWidget及其派生类,分:窗口、普通控件两种类型(其实有个Qt::SubWindow没有提,不过本系列中也没有介绍它的打算,因为我不熟)。

本文接下来试图看看 QLayout 与窗口的几何尺寸控制。

注意:本文只是试图解释,QLayout其实没有任何神秘的东西,它所有的功能离开它你也都可以做。但这并不是鼓励大家不使用QLayout。

始终记住一点:要改变一个Widget的大小,只有move()、resize()、setGeometry()这3个东西可用,当然,对于带装饰器的顶级窗口,你还可以通过鼠标等改变大小或移动窗口位置(但这个不在本文讨论范围内)。

几何尺寸


一个QWidget 或其派生类

放置到什么位置?

  • 需要占多大的地盘?
  • 对于一个窗口(Window)来说,还要区分:

带窗口装饰器后的位置和大小

  • 不带装饰器(客户区域?绘图区域?)的位置和大小
  • 看起来还蛮复杂的哈,列个表看看。

对于窗口,包含窗口装饰器

frameGeometry() 几何尺寸(位置+大小)
x();y();pos() 只包含位置信息(左上角坐标)
move() 只移动位置

不包含窗口装饰器

geometry() 几何尺寸(位置+大小)
width();height();rect():size() 只包含大小信息
setGeometry() 改变 位置+大小
resize() 只改变大小

关键记住一点:要程序内改变一个Widget的大小,只有move、resize、setGeometry这3个东西可用。不要被QLayout干扰,它一点都不神秘,它也只能老老实实去调用这类函数。

例子


用个例子看看吧,如果

  • 在一个 400X400 的Widget上,放置很多其他Widget(比如64个 45X45 的按钮)
#include 
#include 

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget widget;
    widget.setGeometry(100, 100, 400, 400);
    for (int i=0; i<8; ++i) {
        for (int j=0; j<8; ++j) {
            QPushButton * btn = new QPushButton(QString("(%1,%2)").arg(i).arg(j), &widget);
            btn->setGeometry(i*50, j*50, 45, 45);
        }
    }
    widget.show();
    return a.exec();
}

只需要挨个设置一下几何尺寸,似乎也不复杂嘛。是吧?

困难是什么呢?


  • 如果我们用鼠标拖动来改变窗口Widget的大小(有装饰器,可以拖动),它上面的这些按钮却不会动(我们前面的很黑体字提到的哈)。界面将很难看。

其实这也不是大问题,我们只需要在子类化QWidget,覆盖(override)它的resizeEvent()函数

void Widget::resizeEvent(QResizeEvent *)
{
}

在这儿重新设置它上面的按钮的位置和大小就行了。

  • 考虑一个问题,我们如何知道一个widget用多大的大小合适呢?

这是个大问题,单一的widget还好解决,比如一个按钮,你可以根据文字、按钮样式等等计算一个大小。可是对于复合的widget:比如我们例子中的widget中有64个按钮,如果再将这样的64个widget放于另外一个widget中,会怎么样?

没有什么好办法,仍然是需要我们一个一个进行计算。其实不是太难,但是操作特别繁杂。

  • 再考虑一个问题,如果我们改变一个按钮上的文字,按钮的最佳尺寸要变化,如何处理?

只能是按钮通知其parent(通过LayoutRequest事件),而后parent重新排布子控件,以获得最佳显示效果。

接下来,我们看看 QLayout 是如何解决这三个问题的。

QLayout


layout 做哪些事情呢?

初始放置 将子widget一个一个放置到父widget上 layout 计算各个子widget大小,并调用setGeometry() 来设置
响应父widget变化 父widget大小变化时,子widget相应变化 layout 通过监听父widget的QResizeEvent事件来实现
响应子widget变化 子widget的最佳大小变化时(比如给按钮设置新的Text) 让父对象的layout 重新计算几何尺寸

QResizeEvent


当一个widget的大小变化后,会生成QResizeEvent事件(这时widget所关联Layout就开始重新计算喽...)。

我们知道,事件都是通过QWidget::event()派发的:

bool QWidget::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::MouseMove:
        mouseMoveEvent((QMouseEvent*)event);
        break;
...
    case QEvent::Move:
        moveEvent((QMoveEvent*)event);
        break;
    case QEvent::Resize:
        resizeEvent((QResizeEvent*)event);
        break;
...

但是,Qt对QLayout有特殊照顾,在事件到达接收者的event()函数之前,先送到了接收者对应的layout中:

bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
{
...
    if (receiver->isWidgetType()) {
        QWidget *widget = static_cast(receiver);
        if (QLayout *layout=widget->d_func()->layout) {
            layout->widgetEvent(e);
        }
    }
    bool consumed = receiver->event(e);
...

而后layout开始工作

void QLayout::widgetEvent(QEvent *e)
{
    switch (e->type()) {
    case QEvent::Resize:
        if (d->activated) {
            QResizeEvent *r = (QResizeEvent *)e;
            d->doResize(r->size());
        } else {
            activate();
        }
        break;
...

恩,注意看上面代码:如果reciever是widget而且有layout,该事件先送到layout的widgetEvent()中。然后才会通过event()派发到达大家熟悉的resizeEvent()等函数。

QEvent::LayoutRequest


前面的resize比较容易理解,如果子widget的大小想变化,如何通知layout呢?

通过void QWidget::updateGeometry ()函数。

void QWidgetPrivate::updateGeometry_helper(bool forceUpdate)
{
...
        if (!q->isWindow() && !q->isHidden() && (parent = q->parentWidget())) {
            if (parent->d_func()->layout)
                parent->d_func()->layout->invalidate();
            else if (parent->isVisible())
                QApplication::postEvent(parent, new QEvent(QEvent::LayoutRequest));
        }

看看这段代码,如果parent有layout布局,直接让布局无效(强制Layout重新计算大小)。

而如果parent没有布局呢?恩,思想也比较简单:它给父widget发送 LayoutRequest 事件。注意:如果我们不使用布局的话,面对这种情况,我们就要自己处理这个事件喽。

sizeHint等


这个其实似乎是最有趣的,QLayout如果知道它负责控制的各个widget该有多大的大小呢?

这3个东西为layout提供一些大小信息

对于自定义widget,子类化时你可能需要提供这些信息

QWidget::sizePolicy()
QWidget::sizeHint()
QWidget::minimumSizeHint()

熟悉这3个东西,以及各个QLayout派生类的使用,不然,你可能会抱怨——QLayout太难用了

“混用”会如何?


比如:前面的例子,我们64个按钮,如果32个使用QGridLayout进行管理,32个不用layout进行管理。结果会怎么样?

其实不会怎么样。QGridLayout 负责对它管理的widget调用setGeometry,而你负责对自己管理的调用setGeometry。想怎么放就怎么放。(但是你要注意:最好别让它们重合,不然...)

QMainWindow


QMainWindow 上面放置很多的Widget:

这些全是QWidget的派生类

菜单栏 QMenuBar
工具栏 QToolBar
状态栏 QStatusBar
停靠窗口 QDockWidget
中心窗体 ...

其实没有什么神秘的,一堆widget放置到了QMainWindow中,而且还会自动随着QMainWindow变化。你很容易想到它默认就已经设置了一个QLayout!

class QMainWindowLayout : public QLayout
{
    Q_OBJECT
...

这是一个私有类,你不必关心细节,但是可以考虑:平时如何使用QLayout的?是不是要将你的widget加入到layout中??

在QMainWindow中,QMenuBar、QToolBar等等都已经加入到了它的layout中,而且layout中为你留了一个位置,就是中心窗体。

在QMainWindow,QMainWindowLayout管理的这些子widget布满了几乎整个窗体。所以:有人抱怨

  • 为什么在 MainWindow::paintEvent() 中画的东西总是不成功? 不是不成功,是被上面的菜单栏、中心窗体等挡住了。

  • 为什么MainWindow::mousePressEvent()中收不到鼠标事件?? 同上...

  • 为什么new QPushButton(this)创建的按钮总是在左上方? 既没有加入到layout中,又没有手动调用setGeometry()或move(),当然如此了

...

  • 当然还有更隐蔽的,信号槽不起作用? 见http://hi.baidu.com/cyclone

参考


  • http://doc.qt.nokia.com/4.7/application-windows.html#window-geometry

你可能感兴趣的:(漫谈QWidget及其派生类(二))