主页 | 所有的类 | 主要的类 | 注释的类 | 分组的类 | 函数

写你自己的布局管理器

这里我们提供了一个详细的例子。CardLayout类是从同名的Java布局管理器得到的灵感。它把项目(窗口部件或者嵌套的布局)布置到彼此的顶端,每个项目通过QLayout::spacing()偏移的。

要写自己的布局类,你必须像下面这样定义:

在绝大多数情况下,你也需要实现minimumSize()。

card.h

#ifndef CARD_H
#define CARD_H

#include <qlayout.h>
#include <qptrlist.h>

class CardLayout : public QLayout
{
public:
    CardLayout( QWidget *parent, int dist )
        : QLayout( parent, 0, dist ) { }
    CardLayout( QLayout* parent, int dist)
        : QLayout( parent, dist ) { }
    CardLayout( int dist )
        : QLayout( dist ) { }
    ~CardLayout();

    void addItem(QLayoutItem *item);
    QSize sizeHint() const;
    QSize minimumSize() const;
    QLayoutIterator iterator();
    void setGeometry(const QRect &rect);

private:
    QPtrList<QLayoutItem> list;
};

#endif

card.cpp

#include "card.h"

首先我们为布局定义一个迭代。布局迭代被布局系统用作内部处理图形窗口部件删除操作的。它们也可以被提供给应用程序员。

这里有两个两个不同的相关类:QLayoutIterator是一个可以提供给应用程序员可见的类,它是明确地被共享。QLayoutIterator包括一个可以做任何事情地QGLayoutIterator。我们必须生成一个知道如何迭代我们地布局类的QGLayoutIterator的子类。

在这种情况下,我们选择一个简单的实现:我们把一个整数索引和指针存储到一个列表中。每一个QGLayoutIterator的子类都必须实现current()、next()和takeCurrent(),还有一个构造函数。在我们的例子中我们不需要析构函数。

class CardLayoutIterator : public QGLayoutIterator
{
public:
    CardLayoutIterator( QPtrList<QLayoutItem> *l )
        : idx( 0 ), list( l ) { }

    QLayoutItem *current()
    { return idx < int(list->count()) ? list->at(idx) : 0;  }

    QLayoutItem *next()
    { idx++; return current(); }

    QLayoutItem *takeCurrent()
    { return list->take( idx ); }

private:
    int idx;
    QPtrList<QLayoutItem> *list;
};

我们必须实现QLayout:iterator()返回一个这个布局的QLayoutIterator

QLayoutIterator CardLayout::iterator()
{       
    return QLayoutIterator( new CardLayoutIterator(&list) );
}

addItem()实现了布局项目的默认布置策略。它必须被实现。它被QLayout::add()使用,被把一个布局作为父布局的QLayout的构造函数使用,并且它被用来实现自动添加这一特性。如果你的布局有需要参数的高级布置选项,你将必须提供像QGridLayout::addMultiCell()一样的额外的访问函数。

void CardLayout::addItem( QLayoutItem *item )
{
    list.append( item );
}

布局对项目的增加负有责任。因为QLayoutItem不继承QObject,我们必须人工地删除这些项目。QLayout::deleteAllItems()函数使用我们前面定义的迭代来删除布局中的所有项目。

CardLayout::~CardLayout()
{
    deleteAllItems();
}

setGeometry()函数实际上执行了这个布局。作为参数提供的矩形不包括margin()。如果相关的话,项目之间的距离请使用spacing()。

void CardLayout::setGeometry( const QRect &rect )
{
    QLayout::setGeometry( rect );

    QPtrListIterator<QLayoutItem> it( list );
    if (it.count() == 0)
        return;

    QLayoutItem *o;

    int i = 0;

    int w = rect.width() - ( list.count() - 1 ) * spacing();
    int h = rect.height() - ( list.count() - 1 ) * spacing();

    while ( (o = it.current()) != 0 ) {
        ++it;
        QRect geom( rect.x() + i * spacing(), rect.y() + i * spacing(),
                    w, h );
        o->setGeometry( geom );
        ++i;
    }
}

sizeHint()和minimumSize()通常情况下在实现中非常相似。这两个函数返回的大小应该包括spacing(),但不包括margin()。

QSize CardLayout::sizeHint() const
{
    QSize s( 0, 0 );
    int n = list.count();
    if ( n > 0 )
        s = QSize( 100, 70 ); // start with a nice default size
    QPtrListIterator<QLayoutItem> it( list );
    QLayoutItem *o;
    while ( (o = it.current()) != 0 ) {
        ++it;
        s = s.expandedTo( o->minimumSize() );
    }
    return s + n * QSize( spacing(), spacing() );
}

QSize CardLayout::minimumSize() const
{
    QSize s( 0, 0 );
    int n = list.count();
    QPtrListIterator<QLayoutItem> it( list );
    QLayoutItem *o;
    while ( (o = it.current()) != 0 ) {
        ++it;
        s = s.expandedTo( o->minimumSize() );
    }
    return s + n * QSize( spacing(), spacing() );
}

更多的注释

这个布局没有实现heightForWidth()。

我们忽略了QLayoutItem::isEmpty(),这也就是说这个布局将会把隐藏的窗口部件显示出来。

对于复杂的布局,通过存储计算结果可以使速度得到很大的提高。在这种情况下,实现QLayoutItem::invalidate()来把被存储的数据弄脏。

调用QLayoutItem::sizeHint(),其它的也许更浪费时间,如果你在同一个函数中再一次稍晚的情况下需要这个值,你应该把它存储成局部变量。

你不应该在同一个函数中对同一项目调用QLayoutItem::setGeometry()两次。如果这个项目有几个子窗口部件的话,它会很浪费时间,因为它将不得不每次都执行一个完整的布局。相反,计算几何位置并且设置它。(这不仅仅是应用于布局,如果你实现了你自己的resizeEvent()你应该做同样的事情。)


Copyright © 2002 Trolltech Trademarks 译者:Cavendish
Qt 3.0.5版