4

I am a beginner in Qt, and I want to use QPainter.

My process is like this: I receive data coordinates (x,y) from the serial port, like (1,1), (2,3), etc. I want to draw these points in a window every time I receive data.

I see the QPainter is used in events, and just paints one time. How can I use it every time I receive data? Just like a have a signal DataCome() and a slot Paint().\


By the Way ,thx a lot to the Answer.Your advise is very Useful .

In short ,updata() or repaint() is work in this case .

I have another question . Assume ,the serial port continuous to send the coordinate points to computer, and I want to display all the point in the window. Is there some method ,I can leave those points came early on the window,and I just need to paint the new points?Like "hold on " in matlab. Or I need a container to store the coordinates ,and paint all of them very time.

DarkPirate
  • 55
  • 1
  • 7

4 Answers4

4

QPainter can operate on any object that inherits from QPaintDevice.

One such object is QWidget. When one wants QWidget to re-render, you call repaint or update with the rectangular region that requires re-rendering.

repaint immediately causes the paintEvent to happen, whilst update posts a paintEvent on the event queue. Both these are slots, so it should be safe to hook them up to a signal from another thread.

Then you have to override the virtual method "paintEvent" and create a painter with the widget:

void MyWidget::paintEvent( QPaintEvent * evt )
{
  QPainter painter( this );
  //... do painting using painter.
}

You can look at the AnalogClock example that is distributed with Qt Help as example.

Werner Erasmus
  • 3,988
  • 17
  • 31
4

I've set a quick example that will hopefully help you understand the mechanisms you need to utilize to accomplish your task.

It consists of a Listener class which listens for data and sends it to the Widget for drawing. In my example I've set it it up so that the data is randomly generated and sent on regular intervals using a timer, but in your case that will be your serial port data.

Since I assume what you want to do is a plot, you cannot use the paintEvent to draw single points, because each time it will show only one point and the points data will not accumulate, so you need to draw to a pixmap, which you just display in the paintEvent.

Here are the Widget and Listener classes:

class Widget : public QWidget {
    Q_OBJECT

public:
    Widget(QWidget *parent = 0) : QWidget(parent) {
        resize(200, 200);
        p = new QPixmap(200, 200);
    }

protected:
    void paintEvent(QPaintEvent *) {
        QPainter painter(this);
        painter.drawPixmap(0, 0, 200, 200, *p);
    }

public slots:
    void receiveData(int x, int y) {
        QPainter painter(p);
        painter.setBrush(Qt::black);
        QPoint point(x, y);
        painter.drawPoint(point);
        data.append(point);
        repaint();
    }

private:
    QPixmap *p;
    QVector<QPoint> data;
};


class Listener : public QObject {
    Q_OBJECT

public:
    Listener(QObject *p = 0) : QObject(p) {
        QTimer * t = new QTimer(this);
        t->setInterval(200);
        connect(t, SIGNAL(timeout()), this, SLOT(sendData()));
        t->start();
    }

signals:
    void dataAvaiable(int, int);

public slots:
    void sendData() {
        emit dataAvaiable(qrand() % 200, qrand() % 200);
    }
};

... and main:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    Listener l;

    QObject::connect(&l, SIGNAL(dataAvaiable(int,int)), &w, SLOT(receiveData(int,int)));

    w.show();

    return a.exec();
}

So what happens is a random data will be generated every 200 msec, sent to the Widget, where it is added to the pixmap and the Widget is updated to show the new entry.

EDIT: Considering how small a point (pixel) is, you may want to draw small circles instead. You can also color the point based on its data values, so you can get a gradient, for example low values might be green, but the higher it gets it can turn yellow and finally red...

You also might want to add the received data to a QVector<QPoint> if you will need it later, this can be done in the receiveData slot.

Another thing that might be worth mentioning - in the example everything is in range 0-200, the data, the plot window - very convenient. In reality this won't be the case, so you will need to map the data to the plot size, which may be changing depending on the widget size.

Here is a template I commonly use to normalize values in some range. You may want to simplify it a bit depending on your requirements.

template <typename Source, typename Target>
Target normalize(Source s, Source max, Source min, Target floor, Target ceiling) {
    return ((ceiling - floor) * (s - min) / (max - min) + floor);
}

Edit2: Added the data vector to store all the received points in numerical form.

dtech
  • 47,916
  • 17
  • 112
  • 190
  • I think it is best if you add a resizeEvent to your code to resize the pixmap according to the widget. – erelender Aug 28 '13 at 08:23
  • I also think that you are giving the op a buffering solution without knowing his use case, whereas he is simply asking how to update the widget. Nevertheless, it's a good and explanatory answer. – erelender Aug 28 '13 at 08:24
  • I am giving the op a bare-bone, simple example, it is up to him to implement a real-world usage scenario. – dtech Aug 28 '13 at 08:25
  • Thx a lot ,it really helps. I wander whether there is a function in QT like "hold on " in Matlab?It holds the things a draw before,and will not disappear when I plot next time. Or I need set up a buffer to store all the data I have received and draw them every time. – DarkPirate Aug 28 '13 at 09:06
  • @DarkPirate - you want to store all points and add each new point to the plot? – dtech Aug 28 '13 at 09:10
  • @ddriver Yes,I want to show all the points I have received from the serial port. So is there any method that I can let the points leaved on the screen and next time I receive the points ,I just plot them on the screen . – DarkPirate Aug 28 '13 at 11:37
  • @DarkPirate - the example from my answer will do exactly this - show all points that are received. But it won't store them as numbers, only as image, if you want to store the points, you can add them to a vector before you paint them, I will update the answer in a moment. – dtech Aug 28 '13 at 11:46
  • @ddriver Ok , I got it . I have another question , when using gui to design the window , what widget I can use to display? I see only label can use to display .Others like plaintext , the drawing seems under the widget ,not display on the widget. – DarkPirate Aug 28 '13 at 13:13
  • You can use any of the classes Qt provides, I've used `QWidget` because it is the simplest one, but if you want you can use a `QMainWindow` which supports menus, tabs and whatnot. Don't use specialized widgets like plain text edit, because if you override the pain event they won't show their default content, only what you draw. `QWidget` is an empty widget, no such problem there. – dtech Aug 28 '13 at 13:24
  • That is a top rate example. Qt should add it to their docs. This really helped me. – Stephen Hazel Nov 07 '20 at 17:39
1

You use QPainter only in the paintEvent of a QWidget. You can do it like this:

Keep a list of received points as a member and in the paintEvent, traverse this list and paint the required points. When a new point is received, add it to the list and call widget->update(). This tells the widget to refresh itself, and the widget will call paintEvent when the time is right.

erelender
  • 6,175
  • 32
  • 49
  • All that traversing will just take CPU time, especially considering that the OP stated he wants an update every time new data is received. So drawing to a pixmap to accumulate the data points and displaying that will be more efficient that traversing and drawing a bunch of points every time. – dtech Aug 28 '13 at 06:58
  • Yes you are right but using a pixmap does not let you maintain the list of points i.e. you can not reference a pixmap for a point search or in the future what happens when a point is removed? You have to paint the whole pixmap again. One can argue that using a pixmap is still more efficient in terms of performance, but i think that using a pixmap is not a solution to this specific problem but it is more of a generic buffering approach. – erelender Aug 28 '13 at 08:18
  • You can still store the points if it is needed, but it may be just for visualization purposes. I am pretty sure a pixmap is drawn faster than a bunch of points. – dtech Aug 28 '13 at 08:18
0

Create a QPixmap instance, then draw on that like this:

QPixmap pixmap(100, 100);
QPainter p(&pixmap);
// do some drawing

You can then do with the pixmap whatever you want: paint it in the paint event, write it to disk...

user1095108
  • 14,119
  • 9
  • 58
  • 116