Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Can't paint points on my widget



  • I'm trying to set up a widget that takes in x-y-z data and paints those as points on a widget in it's form.ui file. Currently I'm just trying to get one point to paint, before I get all ambitious and start taking in live data to paint multiple points. Right now I just have the paint function called from the constructor, but once I can actually paint points successfully, I'll rework it to take in data from a parent class.

    I believe I followed the documentation, but I must be missing something, because nothing happens, and I get the following errors:
    QWidget::paintEngine: Should no longer be called
    QPainter::begin: Paint device returned engine == 0, type: 1
    QPainter::setPen: Painter not active
    QPainter::drawPoints: Painter not active
    QPainter::end: Painter not active, aborted

    #include "pointviewer.h"
    #include "ui_pointviewer.h"
    #include <QPalette>
    #include <QPainter>
    
    PointViewer::PointViewer(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::PointViewer)
    {
        ui->setupUi(this);
    
        QPalette pal = palette();
        pal.setColor(QPalette::Window, Qt::black);
    
        ui->liveFeedWidget->setAutoFillBackground(true);
        ui->liveFeedWidget->setPalette(pal);
    
        int x = 50;
        int y = 100;
        int depth = 50;
        paintMarkers(x, y, depth);
    }
    
    PointViewer::~PointViewer()
    {
        delete ui;
    }
    
    void PointViewer::paintMarkers(int x, int y, int z)
    {
        QPen pen(Qt::green, z, Qt::SolidLine, Qt::RoundCap);
        QPainter painter;
        painter.begin(ui->liveFeedWidget);
        painter.setPen(pen);
        painter.drawPoint(x,y);
        painter.end();
    }
    

    The only part of this that works is liveFeedWidget gets a black background...


  • Lifetime Qt Champion

    Hi
    You are ONLY allowed to paint in paintEvent function so you must add the function
    to your class

    PointViewer:.paintEvent(QPaintEvent *e) {
    QPen pen(Qt::green, z, Qt::SolidLine, Qt::RoundCap);
    QPainter painters(this); <<<<<<<<<<<<<<<<< notice the change.
    painter.begin(ui->liveFeedWidget);
    painter.setPen(pen);
    painter.drawPoint(x,y);
    painter.end();
    }

    To easy add it.
    Go to your .h file, right click on class PointViewer, select refactor menu and
    insert virtual base function. then find in list.
    alt text

    alt text

    and then press ok.



  • @mrjj So first off, the example code provided by Qt, "basicdrawing" doesn't use this at all, so how is it able to work?

    Second, if I do as you suggest, I can only paint once. I need to be able to repaint the points when new data is provided.


  • Lifetime Qt Champion

    @graniteDev
    show me link. most likely it paints on image. (which is only case where allowed outside)
    you can call widget->update() at any time to repaint.



  • @mrjj
    Qt example I am trying to follow from: https://doc.qt.io/qt-5/qtwidgets-painting-basicdrawing-example.html

    How do I pass parameters into the function? I suppose I could set global variables with the data, and then call that data from the override "paintEvent" but I'd prefer to just pass it in by reference if I can.


  • Lifetime Qt Champion

    @graniteDev
    Ah. well it paints via
    void RenderArea ::paintEvent(QPaintEvent *event) override;

    You cant change paintEvent signature so you will have to copy the points to member variables
    and then call update and in paintEvent use those member variables.
    No reason for global vars. just let PointViewer store them and let paintEvent use those member vars.
    something like
    PointViewer::ShowPoint(int x, int y, int z) {
    m_x=x; m_y=y; m_z=z;
    update();
    }

    and paintEvent uses m_x,m_y etc.
    you could let it point to original data in other class if its heavy but for some ints i dont think it matters.



  • @mrjj Yes, that's what I meant to say... I think of member variables as global because of their scope, but I didn't not mean global to the whole application.

    I tried the code as you provided, but still nothing happened.

    void PointViewer::paintEvent(QPaintEvent *)
    {
        QPen pen(Qt::green, 20, Qt::SolidLine, Qt::RoundCap);
        QPainter painter(this);
        painter.begin(ui->liveFeedWidget);
        painter.setPen(pen);
        painter.drawPoint(100,200);
        painter.end();
    }
    


  • @mrjj I usually create a new class, MyCanvas, and promote the Widget to it, mainly because I what to capture mouse and other events. Which is the Qt preferred way?


  • Lifetime Qt Champion

    @ofmrew
    Well that is indeed a good way as it allows both design time handling and
    custom drawing. I think PointViewer is sort of a MyCanvas type.


  • Lifetime Qt Champion

    @graniteDev
    Did you use override ?
    so we ar sure its correct ?
    try put qDebug() << "im in paint";
    to check it is indeed called.

    Also what is
    begin(ui->liveFeedWidget);
    that looks wrong.
    Are you trying to draw to multiple widgets from PointViewer ?
    try with
    painter.begin();

    also, make sure widget is at least 100,200 in size.



  • @mrjj

    begin(ui->liveFeedWidget);
    

    is in the code you provided. That is the widget within my ui that I want to paint to. I don't want to paint directly to "this" as "this" has buttons, and labels. I want to paint to the QWidget inside it's form ui->liveFeedWidget

    My header file

    protected:
        void paintEvent(QPaintEvent *) override;
    

    My source file

    void PointViewer::paintEvent(QPaintEvent *)
    {
        QPen pen(Qt::green, 20, Qt::SolidLine, Qt::RoundCap);
        QPainter painter(this);
        painter.begin(ui->liveFeedWidget);
        painter.setPen(pen);
        painter.drawPoint(100,200);
        painter.end();
    }
    

  • Lifetime Qt Champion

    Hi
    Sorry didnt spot it before.
    So goal is to paint from
    PointViewer to ui->liveFeedWidget
    or IS the ui->liveFeedWidget a PointViewer instance?

    If liveFeedWidget is completely other widget , then it need to have the paintEvent and not
    PointViewer.

    You cannot paint from other widget to other widgets.



  • @mrjj Ah, ok

    So, liveFeedWidget is a widget I added to the PointViewer class's pointviewer.ui form file. Is it possible to access it from PointViewer and override it's paintEvent without my making another class and promoting it to that class? I don't like to make extra classes if I can avoid it, it makes the code harder to follow for those that come behind me. Self contained classes are best if it can be achieved.


  • Lifetime Qt Champion

    @graniteDev said in Can't paint points on my widget:

    liveFeedWidget

    But is it a custom control ?
    What type is it ?



  • @mrjj it's just a QWidget

    0_1526045360721_69e31ace-face-4a69-8f1e-9d381138b063-image.png


  • Lifetime Qt Champion

    @graniteDev
    Ok, you could use QLabel and paint to a pixmap and assign pixmap
    to the label.
    Else only way is to add paintEvent to custom widget.
    PaintEvent is virtual and only way to override is to subclass.



  • @mrjj

    This still didn't work

    void PointViewer::paintEvent(QPaintEvent *)
    {
        QPen pen(Qt::green, 20, Qt::SolidLine, Qt::RoundCap);
        QPainter painter(this);
        painter.setPen(pen);
        painter.drawPoint(100,200);
    }
    

    I'm expecting, that at 20 pixels I should be able to see SOME dot on the screen, but I see nothing


  • Lifetime Qt Champion

    @graniteDev

    There should be a point at 100,200

    insert qDebug() to be sure its called.

    If you really dont want a custom widget for drawing, you can do like

    QPixmap pix(200, 200);
    pix.fill(Qt::blue);
    QPainter painter(&pix);
    painter.drawpoint(50, 50);
    ui->label->setPixmap(pix); 
    


  • @mrjj WE HAVE A DOT!!!!!

    UGH, I have no idea why the previous code did not work. But this looks like it does. I'm going to see if this works for me. I'm hoping that as it's updated with new data it does not flicker.


  • Lifetime Qt Champion

    @graniteDev
    \o/
    Long live the DOT.

    Qwidgets uses double buffer system internally so often there is no flicker.

    Did you use pixmap or paintEvent ?



  • @mrjj Crud, now I have a different problem, how do I draw different points with different sizes at the same time? Drawing many points of the same size is easy, that's readily handled, but I need to draw different sized points to denote how far or near a marker is that the point represents.



  • @mrjj said in Can't paint points on my widget:

    @graniteDev
    \o/
    Long live the DOT.

    Qwidgets uses double buffer system internally so often there is no flicker.

    Did you use pixmap or paintEvent ?

    void PointViewer::paintMarkers(int x, int y, int z)
    {
        QPen pen(Qt::green, z, Qt::SolidLine, Qt::RoundCap);
        QPixmap pix(800,600);
        pix.fill(Qt::black);
        QPainter painter(&pix);
        painter.setPen(pen);
        painter.drawPoint(x,y);
        ui->pointViewerLabel->setPixmap(pix);
    }
    

    and I got a large green dot about where I expected to


  • Lifetime Qt Champion

    @graniteDev
    Just include pointSize with the points.
    like
    struct PointData {
    int x;
    int y;
    int z;
    int DotSize
    }

    and give that to drawing routine.
    And in paintEvent / for image, simply set penSize from data member
    Its easy to handle as a struct as else u get many loose variables.

    Since you clear the pixmap pr run.
    pix.fill(Qt::black);
    you need to draw all points each time. or keep pixmap as member in class and
    draw on top each time.

    where does the points come from ?



  • @mrjj There is a tracking camera that provides live x-y-z data. I'll have to apply a transform to make it fit in my viewing space...but that feels like the easy part right now, as I already have ready access to that data.

    I don't quite follow what your saying, won't that only draw one dot at a time with the specified size? Then clear, and paint another dot of a different size, etc?

    I'm using the z data to determine size, but the issue as I'm looking at the pen, is I can't assign a size to the point, because the size is applied to the pen, and the pen is applied to the painter, not the individual points that "drawPoints" draws.


  • Lifetime Qt Champion

    @graniteDev
    maybe i didnt understand properly

    Currently one x,y,z is painted. if you call
    paintMarkers again, it will only show the last point.
    If plan it to show many sets of x,y,z, you must handle that.
    One options is to move pixmap to member and not clear it in
    paintMarkers. that way you can draw over/keep the other points.
    That is the goal correct ?
    To draw many 3d points on same pixmap?
    Alternatively, it should store the points and redraw all each time.



  • @mrjj Yes that is the goal, to have many points on a member variable pixMap that are all a unique size and position as determined by their x-y-z values.



  • @mrjj Interesting, so I added this code to my header file:

    QPixmap m_pix;
    

    This to my constructor:

    PointViewer::PointViewer(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::PointViewer),
        m_pix(800,600)
    {
        ui->setupUi(this);
    
        qDebug() << "test 11";
    
        m_pix.fill(Qt::black);
        ui->pointViewerLabel->setPixmap(m_pix);
    }
    

    And this to the paintMarkers function

    void PointViewer::paintMarkers(int x, int y, int z)
    {
        QPen pen(Qt::green, z, Qt::SolidLine, Qt::RoundCap);
        QPainter painter(&m_pix);
        painter.setPen(pen);
        painter.drawPoint(x,y);
        ui->pointViewerLabel->update();
    }
    

    and then called paintMarkers() with a button

    void PointViewer::on_pushButton_clicked()
    {
        paintMarkers(200, 300, 50);
    }
    

    and this did not work, so I'm not sure how to use a pixmap as a member variable and still get this to work. The pixmap did appear as a black 800x600 rectangle, so that part is working, but no green dot.

    Thank you for you help btw!


  • Lifetime Qt Champion

    Hi
    You are most welcome.
    I think the error is
    ui->pointViewerLabel->update();
    as that would just make it draw the copy of pixmap it has.
    you must call setPixmap again. (no need for update as that it will do it self)



  • oh...ok so
    ui->pointViewerLabel->update() doesn't work, it needs to be ui->pointViewerLabel->setPixmap(m_pix);


  • Lifetime Qt Champion

    @graniteDev
    Yes as update will just make it draw the COPY of the m_pix u give it in ctor.



  • @mrjj Ok that is working , but I can't get anymore than one dot to appear. How do I add points to draw with different pens? I tried this, just to see if it would work - ideally I'll need to iterate through a list of points in the future...

        QPen pen1(Qt::green, 50, Qt::SolidLine, Qt::RoundCap);
        QPen pen2(Qt::green, 10, Qt::SolidLine, Qt::RoundCap);
        QPainter painter(&m_pix);
        painter.setPen(pen1);
        painter.drawPoint(300,200);
        painter.setPen(pen2);
        painter.drawPoint(200,300);
        ui->pointViewerLabel->setPixmap(m_pix);
    

    but I only got the first dot.


  • Lifetime Qt Champion

    @graniteDev
    Well lets first test if
    void PointViewer::on_pushButton_clicked()
    {
    paintMarkers(200, 300, 50);
    paintMarkers(210, 300, 50);
    paintMarkers(220, 300, 50);
    }
    does what we want. We could then add new paramter for size or color


  • Lifetime Qt Champion

    @graniteDev
    Wont it be a green small dot in big green dot ?
    try with red
    QPen pen1(Qt::green, 50, Qt::SolidLine, Qt::RoundCap);
    QPen pen2(Qt::red, 10, Qt::SolidLine, Qt::RoundCap);
    QPainter painter(&m_pix);
    painter.setPen(pen1);
    painter.drawPoint(300,200);
    painter.setPen(pen2);
    painter.drawPoint(200,300);
    ui->pointViewerLabel->setPixmap(m_pix);



  • @mrjj OH yes, I must have had the same exact point specified, as I tried that before and it didn't work, however this code yielded

    void PointViewer::on_pushButton_clicked()
    {
        paintMarkers(200, 300, 50);
        paintMarkers(250, 250, 40);
        paintMarkers(300, 200, 30);
    }
    

    0_1526050320415_d2784871-88a0-4299-a315-a086edce69a7-image.png


  • Lifetime Qt Champion

    @graniteDev
    Ok super.
    So now i wondering if we could do

    void PointViewer::on_pushButton_clicked()
    {
    paintMarkers(200, 300, 50, Qt::red , 32);
    paintMarkers(250, 250, 40, Qt::blue,128 );
    paintMarkers(300, 200, 30, QColor(255,0,0), 64);
    }

    so header/function be
    void PointViewer::paintMarkers(int x, int y, int z, QColor dotColor, int dotSize)

    would that be good enough ?
    and u need to use them of course

    void paintMarkers(int x, int y, int z, QColor dotColor, int dotSize)  {
    QPen pen(dotColor, dotSize, Qt::SolidLine, Qt::RoundCap);
    // could we use z to adjust dotSize?
    QPainter painter(&m_pix);
    painter.setPen(pen);
    painter.drawPoint(x,y);
    ui->pointViewerLabel->setPixmap(m_pix);
    


  • @mrjj Wow, ok this is working!! Thank you again so much for the help in understanding how to use these features.

    I tried calling m_pix.fill(Qt::black); to clear m_pix to black again before painting new dots and this worked fine. However is this the most effective/efficient means of resetting m_pix before painting the new dots? I'll be dealing with live data at about ~50hz so I want to make this is as efficient as possible so that it looks live and fairly smooth to the user.


  • Lifetime Qt Champion

    @graniteDev
    Well fill is pretty fast and setPixmap(m_pix) even if it makes copy should be pretty fast too due to implicit sharing. But the downside of using this image approach is that it will be slightly more heavy than a custom paint event. However, since the pixmap acts as a caches you get free speed
    upgrade since you dont need to draw all points.
    But say u make pixmap 1920x1080, this might be too heavy so also depends on image size.
    ( it takes time for QLabel to draw pixmap)
    Also on small pc its far more heavy than say on big fat i7 desktop one.

    so you might need a custom widget with paintEvent for optimal performance but depending on how many points and how big image must be, this might also work just fine even not the most
    efficient way.



  • @mrjj Ok, I'll test this first. If it's too slow, I'll try the custom class, although I'm concerned there as before in our attempts I couldn't get a paint event to occur on even PointViewer class.


  • Lifetime Qt Champion

    @graniteDev
    Well if it is too slow. come back and ill help you get paintEvent running.
    Its not complicated and we can use almost same code except not using pixmap.
    Would take maybe 10 mins to make it a custom control and promote the widget you have in UI to be
    the new drawDots class.


Log in to reply