Qt初学者:QPainter和QRect

9

我该如何绘制一个矩形?

我尝试了两种不同的方法;

void MyWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(Qt::black);
    QRect rect = QRect(290, 20, 70, 40);
    painter.drawText(rect, Qt::AlignCenter,
                      "Data");
    painter.drawRect(rect);
}

这段代码本身没有问题(虽然参数没有命名也没被使用),但我不想使用 QPaintEvent *,因为它对我没有用。

所以我试着给我的函数改个名字;

void MyWidget::draw()
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(Qt::black);
    QRect rect = QRect(290, 20, 70, 40);
    painter.drawText(rect, Qt::AlignCenter,
                      "Data");
    painter.drawRect(rect);
}

这段代码并没有显示任何内容(但也没有错误)。

如果我不使用 QPaintEvent *,为什么它不能正常工作?


7
你为什么期望一个随机命名的函数去执行任何操作?你的第一种方法是可以的。在C++中,不给你还没用到的参数命名既合法又符合惯用法。 - Mat
好的,如果我没有参数但保持其余部分不变,第一个方法是否可行? - Ash
不,无论你是否使用它,该参数都是必需的。我不明白为什么你想要删除它 - 忽略它对你来说没有任何成本。 - Mat
这会在我的代码后面引起问题,因为我有其他传递的参数,例如const QString &data。有没有另一种绘制矩形的替代方法,不使用事件参数?感谢您的回复。 - Ash
3
事件函数的签名是固定的,你无法改变它们(除非可能添加具有默认值的参数,但即使它可行,我也不会这样做)。你需要在问题中更详细地描述你的问题,看起来你正在尝试以错误的方式做某事。 - Mat
显示剩余2条评论
5个回答

10

绘制事件是指当小部件需要重新绘制时,绘制系统调用的方法。这就是为什么仅仅命名自己的方法是不起作用的原因,因为它从未被绘制系统调用。

您真的应该使用QPaintEvent,它会提供需要绘制的矩形区域。该矩形的大小将基于小部件的大小,因此在绘制事件中不要使用显式矩形,而是将小部件设置为正确的大小。如果小部件移动、调整大小等,将生成绘制事件。

void MyWidget::paintEvent(QPaintEvent *event)
{
    QRect rect = event->rect();
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(Qt::black);
    painter.drawText(rect, Qt::AlignCenter,
                      "Data");
    painter.drawRect(rect);
}

现在,如果您想将绘图逻辑分离到另一个方法中,那么这很好。但是,您需要从绘图事件中调用它:

void MyWidget::paintEvent(QPaintEvent *event)
{
    QRect rect = event->rect();
    draw(rect);
}

void MyWidget::draw(QRect &rect)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(Qt::black);
    painter.drawText(rect, Qt::AlignCenter,
                      "Data");
    painter.drawRect(rect);
}

如果你想完全绕过像你说的那样的绘制事件,并且只想创建一个静态矩形来显示,一种方法是将其绘制到一个 pixmap 上并在 QLabel 中显示:

QPixMap pix(200,100);
QPainter painter(&pix);
// do paint operations
painter.end()
someLabel.setPixmap(pix)

哇,谢谢你的建议 :) 你真是个大牛!这正是我正在寻找的! - Ash
1
尽管你的第一个解决方案看起来很优雅,实际上它只是翻译了从事件继承的区域,并简单地增加了一次对堆栈的调用。用户应该在函数外部设置好你的“数据”,所以我认为这个双重调用并不有用。很抱歉我不同意你的第一个解决方案。你的第二个简单解决方案看起来不错。 - RTOSkit

2
任何 paintEvent() 需要的数据应该作为包含类的字段访问,对于您的情况来说,是 MyWidget 的私有字段。这些私有字段可以通过“setter”向 MyWidget 的客户端公开,这将在调用 update() 之前设置数据值,并触发对 paintEvent() 的调用。请保留 html 标签。

1

正如@Mat所告诉你的那样:"事件"是启动绘图器的正确方式。
只有在QPaintEvent事件之后才能调用QPainter,该事件携带了对象可以绘制的安全区域

因此,您必须找到另一种传输数据的策略,为了帮助您, 我将提出一种简单的方法,可以适用于许多情况。

widget.cpp

#include <QtGui>
#include "widget.h"

#define MIN_DCX    (0.1)
#define MAX_DCX    (5.0)

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{    
    dcx=MIN_DCX;
    setFixedSize(170, 100);
}

void Widget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event); 
    QPainter painter;
    painter.begin(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(Qt::black);
    pcx=dcx*2;
    QRect rect = QRect(50-dcx,25-dcx,60+pcx,40+pcx);
    painter.drawText(rect, Qt::AlignCenter,printData);
    painter.drawRect(rect);
    painter.end();

}

void Widget::setPrintData(QString value){
   printData = value;
   dcx=(dcx>MAX_DCX)?MIN_DCX:dcx+MIN_DCX;
}

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent);
    void setPrintData(QString value);

protected:
    void paintEvent(QPaintEvent *event);

private:
    QString printData;
    float dcx;
    float pcx;
};


#endif

window.cpp

#include <QtGui>
#include "widget.h"
#include "window.h"

#define MAX_SDCX  20

Window::Window()
    : QWidget()
{
    gobject = new Widget(this);

    textMode=1;
    rectMode=1;
    gobject->setPrintData(msgs[textMode]);

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(gobject, 0, 0);
    setLayout(layout);

    QTimer *timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(dataOnAir()));
    timer->start(10);

    setWindowTitle(tr("Rect Shaking"));
}



void Window::dataOnAir(){
    if((++rectMode)>MAX_SDCX){
        rectMode=0;
        textMode^=1;
    }
    gobject->setPrintData(msgs[textMode]);
    gobject->repaint();
}

window.h

#ifndef WINDOW_H
#define WINDOW_H

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

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private slots:
    void dataOnAir();

private:
    Widget *gobject;
    const QString msgs[2] = {"Hello","World"};
    int textMode;
    int rectMode;
};

#endif

main.cpp

#include <QApplication>
#include "window.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    Window window;
    window.show();
    return app.exec();
}

正如您在代码中所看到的,定时器是在“widget”对象之外执行的。

每10毫秒发送一次重绘小部件以重新绘制具有不同大小的“矩形”,并且每20个周期(200毫秒)将文本“hello”更改为“world”。

在此示例中,您可以看到无论如何都不需要覆盖QPainterDevice架构。

您还可以注意到“paintEvent”中的“event”被静音并且不直接使用,但它对于执行QPainter序列至关重要。


如果某些参数看起来被重新排序了,我深表歉意。我昨天在中欧时间22:00开始写回复,但后来不得不去吃“新年”的晚餐,现在才回来并发布完整的回复。我没有看到其他人已经写过了...抱歉。祝大家新年快乐! - RTOSkit
1
新年快乐!感谢您的帮助 :) - Ash

1

这个播放列表包含了最好的Qt教程,从第74个教程开始会对你有所帮助(Qpainter和QPen),第75个教程是如何使用QRect绘制矩形。


0
重写小部件的paintEvent()函数可以自定义该小部件,而且此函数会定期调用以重新绘制小部件。因此,所有的绘图都应该在此函数中进行。但是重写 paintEvent() 可能会导致一些性能问题。我建议使用QGraphicsScene和QGraphicsView,然后将矩形添加到场景中,这是一种常见的绘图方法。请查看GraphicsView框架。

http://qt-project.org/doc/qt-4.8/graphicsview.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接