Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using QPainter when every time I receive some data

Tags:

c++

qt

qpainter

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.

like image 714
DarkPirate Avatar asked Jan 12 '23 12:01

DarkPirate


2 Answers

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.

like image 156
dtech Avatar answered Jan 21 '23 17:01

dtech


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.

like image 41
Werner Erasmus Avatar answered Jan 21 '23 16:01

Werner Erasmus