2

How do I properly add a property to a custom control, which when changed triggers the paint event? The property only accepts an integer value of 0, 1 or 2.

The setter of the property would be called setPattern, so an example use would be setPattern(0).

This property value will eventually be used to control the pattern drawn in the paint event.

labelheader.h

#ifndef LABELHEADER_H
#define LABELHEADER_H

#include <QLabel>

class LabelHeader : public QLabel
{
    Q_OBJECT
public:
    explicit LabelHeader(QWidget *parent = nullptr);
    explicit LabelHeader(const QString &text, QWidget *parent = nullptr);

signals:

public slots:

    // QWidget interface
protected:
    void paintEvent(QPaintEvent *event);
};

#endif // LABELHEADER_H

labelheader.cpp

#include "labelheader.h"
#include <QPainter>

LabelHeader::LabelHeader(QWidget *parent) :
    QLabel(parent)
{

}

LabelHeader::LabelHeader(const QString &text, QWidget *parent) :
    QLabel(text, parent)
{

}

void LabelHeader::paintEvent(QPaintEvent *event)
{
    // calculate font width
    QFontMetrics metrics(font());
    int text_width = metrics.boundingRect(text()).width();

    // calculate dimensions
    int y = height() * 0.5;
    int x = text_width + 4;

    // create pattern
    QPixmap px(4, 4);
    px.fill(Qt::transparent);

    QPainter pattern_painter(&px);

    // Create ashed 3 dots
    pattern_painter.setPen(Qt::NoPen);
    pattern_painter.setBrush(QBrush(palette().color(QPalette::WindowText), Qt::SolidPattern));
    pattern_painter.drawRect(0, 0, 1, 1);
    pattern_painter.drawRect(2, 2, 1, 1);

    // draw
    QPainter painter(this);

    // Draw dashed 3 dots
    painter.drawTiledPixmap(x, y-2, width()-x, 5, px);

    // Draw solid line
    //painter.drawLine(x,y,width(),y);

    QLabel::paintEvent(event);
}
scopchanov
  • 7,966
  • 10
  • 40
  • 68
JokerMartini
  • 5,674
  • 9
  • 83
  • 193
  • What do you mean by property? Property registered by `Q_PROPERTY`? What exactly from the [documentation](http://doc.qt.io/qt-5/properties.html#a-simple-example) isn't clear? – Jaa-c Sep 11 '18 at 18:04
  • Yeah exactly like that, how would i make it trigger a paint event when the value is changed? – JokerMartini Sep 11 '18 at 18:07

3 Answers3

3

You can simply call update() in the setter. If you want to only accept limited range of values, you can limit that in a setter or use an enum instead of an int.

#include <QLabel>

class LabelHeader : public QLabel
{
    Q_OBJECT
public:
    Q_PROPERTY(Pattern pattern READ(getPattern) WRITE(setPattern))

    enum Pattern { FIRST = 0, SECOND, THIRD };
    Q_ENUM(Pattern)

...

    void setPattern(Pattern value)
    {
        if (pattern != value)
        {
            pattern = value;
            update();
        }
    }

    Pattern getPattern() const { return pattern; }
...

private:
    Pattern pattern { FIRST };
};
Jaa-c
  • 5,017
  • 4
  • 34
  • 64
  • I like the enum idea much more like you suggested. If you have an example or modify this to showcase that, much appreciated :) – JokerMartini Sep 11 '18 at 18:22
  • because the one thing I'm also curious about is how to i define the default enum value – JokerMartini Sep 11 '18 at 18:26
  • @JokerMartini: I've updated the question. It's basically the exact example from the documentation I linked to your question... – Jaa-c Sep 11 '18 at 18:56
  • @JokerMartini I dunno about newer Qt version s(above 5.1) but before that enum was a way to get a sore rump, trying to figure out what you did wrong IF you use Designer. Designer didn't work properly with enums. Even with Q_ENUM used. Worse, if you try to use stylesheets with selectors by enum values taken from Widget properties, that seem to not work even now. – Swift - Friday Pie Sep 11 '18 at 19:15
2

This is how I do it. NOTE: I haven't written an actual custom control in a "library", I usually just use "promoted widgets".

Header File:

#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QObject>
#include <QWidget>

class MyWidget: public QWidget
{
    Q_OBJECT
    Q_PROPERTY(int value READ value WRITE setValue)

    private:
        int m_value;

    public:
        MyWidget(QWidget *parent=0);

        inline int value() const {return m_value;}
        void setValue(int value);

    signals:
        void valueChanged();
};

#endif // MYWIDGET_H

Source File:

#include "MyWidget.h"

MyWidget::MyWidget(QWidget *parent)
    :QWidget(parent)
{
    m_value = 0;
}

void MyWidget::setValue(int value)
{
    if (m_value == value) return;

    m_value = value;
    emit valueChanged();

    update();   // This will cause repaint
}
Marker
  • 972
  • 6
  • 9
  • And that is, indeed, how it should be done. The `QObject` header is redundant, though - in fact, headers for *all* base classes are redundant, only the most derived class's header should be included, for the entire class forest. – Kuba hasn't forgotten Monica Sep 12 '18 at 12:40
  • @KubaOber, yes you are right about the headers, surely QWidget already include QObject. Added it before I decided to derive from QWidget as an example. Although, I have had times where the Qt Creator intellisense seems to work better if Qt classes is explicitly included. It shouldn't really be a problem or cause a significant compile time hit, because QObject likely has include guards. – Marker Sep 12 '18 at 13:40
2

Solution

To accomplish what you need you do not actually need QProperty, but a private attribute of your LabelHeader class. Here is a step-by-step guide, covering all your requirements:

  1. Create the enumeration type

In LabelHeader.h add:

enum PatternType {
    PatternOne = 0,
    PatternTwo,
    PatternThree
};
  1. Declare a private attribute to hold the current pattern

In LabelHeader.h add:

private:
    PatternType m_pattern;
  1. Right click m_pattern and select Refactor->Create Getter and Setter Member Functions
  2. In the implementation of the setter invoke a paintEvent by calling update(), but only if the new pattern is different than the current one:

    if (m_pattern == pattern)
        return;
    
    m_pattern = pattern;
    update();
    
  3. In the constructor add m_pattern to the initializer list to set its value by default

  4. Use the value in the LabelHeader::paintEvent to draw the corresponding pattern

    // draw tiled pixmap
    QPainter painter(this);
    
    switch (m_pattern) {
    case PatternOne:
        painter.drawTiledPixmap(x, y-2, w, 5, px);
        break;
    case PatternTwo:
        painter.drawTiledPixmap(x, y, w, 5, px);
        break;
    default:
        painter.drawTiledPixmap(x, y+2, w, 5, px);
    }
    

Example

I have extended the example from this answer to cover your current requirements as well. The full code is available on GitHub.

Remarks

Regarding the use of Q_PROPERTY, here is an important explanation kindly provided by @KubaOber:

Q_PROPERTY is a Qt idiom used to both convey a meaning to a human reader, and expose the property as such to the metatype system, making it easily scriptable from QML, etc.

scopchanov
  • 7,966
  • 10
  • 40
  • 68
  • Do you have any pros or cons on why i shouldn't or should use QProperty? – JokerMartini Sep 11 '18 at 19:52
  • not quite sure what this is for or if it's a type in your example m_pattesrn line 28 of the header file – JokerMartini Sep 11 '18 at 19:53
  • @JokerMartini, I am sorry, it was a mistake. It is fixed now. Thank you for mentioning that! As for the QProperty, there is nothing wrong with it. I've mentioned that explicitly, because you said that you are new to C++ and the proper term for what you need is a class attribute. Property is understood as QProperty. Just wanted to clarify that. – scopchanov Sep 11 '18 at 20:02
  • 1
    You are 100% correct, I needed a class attribute. Thank you for that :) – JokerMartini Sep 12 '18 at 02:57
  • 1
    Do you happen to do freelance work at all? Just curious to know? – JokerMartini Sep 12 '18 at 02:57
  • 1
    `Q_PROPERTY` is a Qt idiom used to *both* convey a meaning to a human reader, *and* expose the property as such to the metatype system, making it easily scriptable from QML, etc. Everything in this answer is correct, but orthogonal to the use of `Q_PROPERTY` - which is still required. – Kuba hasn't forgotten Monica Sep 12 '18 at 12:36
  • 1
    It should also perhaps be made explicit that the setter (`setPattern`) should compare the new setting to the current one, and do nothing if they are equal - the update then would be redundant, and it may simplify other code if the assumption that setting the same value multiple times is cheap. It is in all of Qt's property setters, so making the user code depart from that ideal, while not dramatically bad, may come as unexpected, and can certainly introduce performance bugs. – Kuba hasn't forgotten Monica Sep 12 '18 at 12:39
  • @KubaOber, you are completely right, of course. My point was not *do not use QProperty*, but *this is not the proper term*, as the OP mentioned in a connected question, that he comes from Python and is new to C++. Again, thanks for the valuable input! I will modify the code and the answer accordingly. – scopchanov Sep 12 '18 at 12:42
  • @scopchanov did you update your github to reflect the suggestions made by Kuba. I would like to see the proper and recommended way of writing this in code. – JokerMartini Sep 12 '18 at 13:03
  • @JokerMartini, yes, I just did. – scopchanov Sep 12 '18 at 13:04
  • 1
    @scopchanov ok great, ill check it out. I'd like to make sure i am doing this proper and i appreciate you guys all so kindly helping. Do you do paid freelance work at all as well. I was curious? – JokerMartini Sep 12 '18 at 13:09