QT(9)自定义layout[2] - Flow Layout

83 篇文章 18 订阅
75 篇文章 8 订阅

在上一次学习 QT(8)变动布局Dynamic Layout中,我们在此总结一下:对于修改布局,可以通过removeWidget后在根据新的位置重新加载。为了创建新的合适的布局,我们需要重新resize布局的大小。我们需要注意到在修订时,要考虑组建之间的空间,即spacing()。对如删和增都需要考虑QSize(spacing(),spacing())。

在本次,我们延续QT(7)的学习,再次对layout的继承进行学习。参考http://doc.qt.nokia.com/latest/layouts-flowlayout.html/。在此之前,我们对QT编译中碰到的一些问题进行记录:

问题1:编译中出现make : g++没有找到

对于ubuntu可以使用apt-get install g++,但是在采用yum的系统,例如MeeGo,没有g++的包,yum那里采用了另外的名字yum install gcc-c++。

问题2:编译中出现undefined reference to `vtable for xxxx(某个类名)'

出去这种情况,需要检查*.pro文件,看看是否将所需的*.h和*.cpp加入,或者加入一些空文件。

记录1:制定moc生成文件存放的目录

moc命令将含Q_OBJECT的头文件转换成标准.h文件,在我们定义Q_OBJECT后,很可能会生成moc_xxxxx.cpp的文件。方式:MOC_DIR = build。

言归正卷,我们这次建立一个自定的layout,上面的widget,根据我们addWidget的先后顺序,从左向右排序,如果超过范围,就从下一排开始,也是从左向右,很像现代文字的书写方式。如图所示:

    

搭建程序框架

qtmain.cpp为主程序,mywindow.h和mywindow.cpp为窗口类,flowlayout.h和flowlayout.cpp是我们用于构造我们布局QLayout的子类。mywindow.cpp如下:

MyWindow :: MyWindow()
{
    FlowLayout * layout = new FlowLayout();
    layout->addWidget (new QPushButton(tr("Short")));
    layout->addWidget (new QPushButton(tr("Longer")));
    layout->addWidget (new QPushButton(tr("Different Text")));
    layout->addWidget (new QPushButton(tr("More Text")));
    layout->addWidget (new QPushButton(tr("This is a long text button!")));
    setLayout(layout);
    setWindowTitle("FlowLayout Test!");
}

构造自定义的布局QLayout子类:存放QLayoutItem

我们在QT(7)中学习过,这里我们使用一个QList<QLayoutItem *> itemList来存放我们的item,并且进行了addItem,count,itemAt(int index),takeAt(int index)这几个virtual方法,同时在释放方法~FlowLayout()中清空itemList,并释放空间。这里,将并在详细说明。可以参见参考中给出的源代码

完成构建函数

在MyWindow类中,我们并不需要有特别的构造函数。在Layout中,计算margin,也就是各widget之间的空隙是一个很麻烦的事情。在例子中,我们提供可定制margin(缺省值为11,由于缺省的边框为1,所以11大抵重视觉角度看就是10px),这是Layout之间的留边位置,同时我们也设定了组件之间的间隔大小(m_hSpace,m_vSpace),如下:

FlowLayout :: FlowLayout(QWidget * parent,int margin,int hSpacing,int vSpacing)
            : QLayout(parent),m_hSpace(hSpacing),m_vSpace(vSpacing)
{
    setContentsMargins(margin,margin,margin,margin);
}

这里我们看到一个有趣的写法,实际上其等同与在方法中运行了:

QLayout(parent);
m_hSpace = hSpacing;
m_vSpace = vSpacing;

给出Layout的尺寸大小

Qt::Orientations FlowLayout::expandingDirections() const
{
    return 0;
}

这里我们要求button并会自动补充空白位置,所有给出0。对于Layout的尺寸大小,重要的是minimumSize()和sizeHint()两个。如下面。QSize可以通过要求增加某个尺寸大小的文字,它看自动进行调整计算,并需要我们精确计算。最佳大小,我们设置等同于最小尺寸。

QSize FlowLayout::minimumSize() const
{
    QSize size;
    QLayoutItem * item;
    foreach(item,itemList)
        size = size.expandedTo(item->minimumSize());
    size += QSize(2*margin(),2*margin());
    return size;
}

QSize FlowLayout::sizeHint() const
{
    return minimumSize();
}

我们补充继承两个方法,用于获取组件之间间隔大小:

int FlowLayout::horizontalSpacing() const
{
    if(m_hSpace >= 0)
        return m_hSpace;
    else
        return smartSpacing(QStyle::PM_LayoutHorizontalSpacing /* Default horizontal spacing for a QLayout.*/);
}

int FlowLayout::verticalSpacing() const
{
    if(m_vSpace >= 0 )
        return m_vSpace;
    else
        return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
}

int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const  //这是我们定义的private方法,用于从parent中获得widget之间的间隔
{
    QObject * parent = this->parent();
    if(!parent){
        return -1;
    }else if(parent->isWidgetType()){
        QWidget * pw = static_cast<QWidget *>(parent);
        return pw->style()->pixelMetric(pm,0,pw);
    }else{
        return static_cast<QLayout*>(parent)->spacing();
    }
    return 0;
}

进行布局

布局采用setGemetry,这个我们在QT(7)中也介绍过:

void FlowLayout::setGeometry(const QRect & rect)
{
    QLayout::setGeometry(rect);
    doLayout(rect,false);
}

下面我们根据需求,对doLayout进行说明:

int FlowLayout::doLayout(const QRect & rect, bool testOnly) const
{
    int left,top,right,bottom;
   
    getContentsMargins(&left,&top,&right,&bottom);
    QRect effectiveRect = rect.adjusted(left,top,-right,-bottom);
    int x = effectiveRect.x();
    int y = effectiveRect.y();
    int lineHeight = 0;

我们第一步,先计算有效的摆放widget的尺寸effectiveRect。

    QLayoutItem * item;
    foreach(item,itemList){
        //It then sets the proper amount of spacing for each widget in the layout, based on the current style.
        QWidget * wid = item->widget();
        int spaceX = horizontalSpacing();
        if(spaceX == -1)
            spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton,QSizePolicy::PushButton,Qt::Horizontal);    
        int spaceY = verticalSpacing();
        if(spaceY == -1)
            spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton,QSizePolicy::PushButton,Qt::Vertical);

在这里,我们获取一些基本的数据,包括每一个item,其大小为item->sizeHint(),在水平方向各组件之间的间隔spaceX以及竖直方向的间隔spaceY。我们将在effectiveRect内顺序排列widget。下面我们来进行计算,设置各个item的setGeometry,需要获取每个item的起始左上角坐标。

        int nextX = x + item->sizeHint().width() + spaceX; //下一个组件的左上角位置的x坐标
        if(nextX - spaceX > effectiveRect.right() && lineHeight > 0){ //如果超出位置,换行,重新计算(x,y)坐标
            x = effectiveRect.x();
            y = y + lineHeight + spaceY;
            nextX = x+item->sizeHint().width() + spaceX;
            lineHeight = 0;
        }
        if(!testOnly)  //设置item的位置
            item->setGeometry(QRect(QPoint(x,y),item->sizeHint()));
        x = nextX;
        lineHeight = qMax(lineHeight,item->sizeHint().height());
    }

    return y + lineHeight - rect.y() + bottom; //返回需要限制所有组件,layout至少要多高
}

对于setGeometry,我们并不需要返回值,但是我们发现,如果组件多,有多行摆放,有时无法全部显示,这在初始显示和我们改变window大小的时候可能会出现,而doLayout就返回了layout显示所有组件时至少需要的height。因此我在width改变是需要重新计算height,需要设置hasHeightForWidth()为true,并heightForWidth返回相应的值。

bool FlowLayout::hasHeightForWidth() const
{
    return true;
}

int FlowLayout::heightForWidth(int width) const
{
    int height = doLayout(QRect(0,0,width,0),true);
    return height;
}

相关链接:我的MeeGo/Moblin相关文章

一个小故事:民国元年的小学国文教科书中有一篇《少年》:一少年在兵营为鼓手,某日,将校会宴,大将劝饮,少年辞曰:吾不嗜酒。大将曰:汝终日击鼓甚劳,可少饮 酒以舒之。少年固辞不饮,大将不悦。副将在旁,欲试之,厉声曰:汝必饮一杯,是军令也,违令将斩汝!少年改容曰:军令不胜恐惧,然饮酒非兵士职。昔者,吾父以酒疾不起,吾入营时,吾母戒曰:汝终身勿饮酒。虽有大将之命,。不能破慈母之戒。声泪俱下。坐中将校莫不感动。由是少年益受大将信任,有名于时。

这个故事告诉我们,什么叫做原则,原则不是领导说一句话,就可以摇摆和动摇。现在春节了,在酒桌上该不喝酒就不喝酒,劝酒是一个陋习。当然还有其他很多事情,做一个有良知的人。

旋转仪表盘是一种常见的显示控件,可以用来展示一些数据,如速度、油量、温度等,下面我将介绍如何使用Qt编写一个旋转仪表盘控件。 首先,我们需要在Qt中创建一个新的自定义控件类。可以通过Qt Creator中的“添加新文件”功能来创建一个QWidget派生类。在这个类中,我们需要实现paintEvent()函数来绘制仪表盘。 在paintEvent()函数中,我们可以使用QPainter来绘制仪表盘的各个部分,包括刻度线、指针、文字等。具体实现可以参考以下代码: ```C++ void RotatingDial::paintEvent(QPaintEvent *event) { // 设置背景色 QPalette pal(palette()); pal.setColor(QPalette::Background, Qt::white); setAutoFillBackground(true); setPalette(pal); // 绘制刻度线 QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); painter.translate(width() / 2, height() / 2); painter.setPen(QPen(Qt::black, 2)); for (int i = 0; i < 60; i++) { painter.drawLine(0, -100, 0, -90); painter.rotate(6); } // 绘制指针 painter.save(); painter.rotate(m_angle); painter.setBrush(Qt::red); painter.setPen(Qt::NoPen); painter.drawConvexPolygon(m_pointer, 3); painter.restore(); // 绘制文字 QFont font("Arial", 12, QFont::Bold); painter.setFont(font); painter.drawText(-30, 50, "Speed"); } ``` 在上面的代码中,我们首先设置了控件的背景色为白色。然后使用QPainter绘制了60条刻度线,并旋转6度。接着,我们绘制了一个红色的指针,并根据m_angle属性来旋转指针的角度。最后,我们使用QPainter绘制了文字“Speed”。 在我们的自定义控件中,我们需要一个属性来控制指针的角度。我们可以使用Q_PROPERTY宏来定义这个属性,例如: ```C++ class RotatingDial : public QWidget { Q_OBJECT Q_PROPERTY(int angle READ angle WRITE setAngle) public: RotatingDial(QWidget *parent = nullptr); int angle() const; void setAngle(int angle); private: int m_angle; QPolygon m_pointer; }; ``` 在上面的代码中,我们使用Q_PROPERTY宏定义了angle属性,并提供了getter和setter函数。我们还定义了一个私有变量m_angle来存储当前的角度,以及一个QPolygon对象m_pointer来存储指针的形状。 在setAngle()函数中,我们设置m_angle属性,并根据新的角度计算指针的位置: ```C++ void RotatingDial::setAngle(int angle) { if (angle != m_angle) { m_angle = angle; m_pointer.setPoint(0, QPoint(0, -90)); m_pointer.setPoint(1, QPoint(5, 0)); m_pointer.setPoint(2, QPoint(-5, 0)); m_pointer.translate(0, 100); update(); } } ``` 在上面的代码中,我们首先判断新的角度是否与当前的角度相同。如果不同,我们就更新m_angle属性,并重新计算指针的位置。最后,我们调用update()函数来触发paintEvent()函数的调用,从而完成控件的重绘。 最后,我们可以在MainWindow类中使用我们的自定义控件,例如: ```C++ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { RotatingDial *dial = new RotatingDial(this); dial->setAngle(30); QHBoxLayout *layout = new QHBoxLayout; layout->addWidget(dial); setCentralWidget(new QWidget); centralWidget()->setLayout(layout); } ``` 在上面的代码中,我们创建了一个RotatingDial对象,并设置了初始角度为30度。然后,将其添加到QHBoxLayout布局中,并设置为主窗口的中央控件。 至此,我们已经完成了一个简单的旋转仪表盘的自定义控件。你可以根据需要对其进行扩展和优化,使其更加适合你的应用场景。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值