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

Trouble with custom QSlider



  • I create a custom class that extends QSlider in order to write values next to the tick marks. Here is what the widget looks like:

    0_1558724516260_87cb4290-a72b-4e42-8647-4e65d92f8a78-image.png

    Here is my method paintEvents that overrides QSlider::paintEvent

    void AlarmLimitsSlider::paintEvent(QPaintEvent *ev) {
        QSlider::paintEvent(ev);
    
        auto painter = new QPainter(this);
        painter->setPen(QPen(Qt::black));
    
        auto rect = this->geometry();
    
        int numTicks = (maximum() - minimum())/tickInterval();
    
        QFontMetrics fontMetrics = QFontMetrics(this->font());
    
        if (this->orientation() == Qt::Horizontal) {
    
           ...not important....
    
        } else if (this->orientation() == Qt::Vertical) {
    
            int sliderHeight = rect.height();
            int heightOffset = 15;
    
            for (int i=0; i<=numTicks; i++){
    
                int tickNum = maximum() - (tickInterval() * i);
    
                QString toWrite = QString::number(tickNum);
                if (isPercentage) toWrite.append("%");
    
                double percentageOfHeight = static_cast<double>(i) / static_cast<double>(numTicks);
                int heightIntervalOffset = static_cast<int>(15 * percentageOfHeight);
    
                auto tickX = 0;
                auto tickY = heightOffset + ((sliderHeight/numTicks) * i) - heightIntervalOffset;
    
                painter->drawText(QPoint(tickX, tickY), toWrite);
            }
        } else {
            return;
        }
    
    }
    

    Here is my GUI setup for the Slider

    .....in constructor...
    mvLowSld(new AlarmLimitsSlider(Qt::Vertical)),
    mvHighSld(new AlarmLimitsSlider(Qt::Vertical)),
    mvMarkerSld(new AlarmLimitsSlider(Qt::Vertical)),
    
    .....
    
    mvLowSld->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    mvHighSld->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    mvMarkerSld->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    

    And here is where I create the Widget that contains the QSliders.

    QWidget*AlarmLimitsPanel::createMVLimitsPanel(){
    
        QLabel *mvLowLbl = new QLabel("MV Low");
        mvLowLbl->setObjectName("titleLbl");
        QLabel *mvHighLbl = new QLabel("MV High");
        mvHighLbl->setObjectName("titleLbl");
        QLabel *mvMarkerLbl = new QLabel("MV Marker");
        mvMarkerLbl->setObjectName("titleLbl");
    
        QGridLayout *mainLayout = new QGridLayout;
    
        mainLayout->addWidget(mvLowLbl, 0, 0, Qt::AlignHCenter);
        mainLayout->addWidget(mvLowValLbl, 1, 0, Qt::AlignHCenter);
        mainLayout->addWidget(mvLowSld, 2, 0, Qt::AlignHCenter);
    
        mainLayout->addWidget(mvHighLbl, 0, 1, Qt::AlignHCenter);
        mainLayout->addWidget(mvHighValLbl, 1, 1, Qt::AlignHCenter);
        mainLayout->addWidget(mvHighSld, 2, 1, Qt::AlignHCenter);
    
        mainLayout->addWidget(mvMarkerLbl, 0, 2, Qt::AlignHCenter);
        mainLayout->addWidget(mvMarkerValLbl, 1, 2, Qt::AlignHCenter);
        mainLayout->addWidget(mvMarkerSld, 2, 2, Qt::AlignHCenter);
    
        QWidget*mvLimitsPanel = new QWidget;
        mvLimitsPanel->setLayout(mainLayout);
    
        return mvLimitsPanel;
    
    }
    

    When I try to change the QSlider stylesheet to make the QSlider handle thinner and taller, the handle does not change shape.

                    "QSlider{font: 20px Verdana; padding: 25px}"
                    "QSlider::handle {"
                       "max-width: 5px;"
                       "height: 50px;"
                       "background-color: blue;"
                    "}"
    

    The background color changes, but the handle remains the same shape.

    I also get these errors in the console that print a bunch of times when the .paintEvent method is being called:

    QBackingStore::endPaint() called with active painter on backingstore paint device
    

    and when I end my application, I get:

    QPaintDevice: Cannot destroy paint device that is being painted
    

    These errors do not cause any issues in the application, but I wonder if they are related to my problems?

    So why is the handle shape not changing? When I change the SizePolicy of the AlarmLimitsSlider, the handle always extends the full horizontal length of the widget. Is there some other settings to change the width of the QSlider handle?


  • Lifetime Qt Champion

    Hi,

    You should use your subclass class name in your style sheet.



  • @SGaist Strangely enough, when I replace "QSlider" with "AlarmLimitsSlider" in the stylesheet, the style is not applied and all characteristics are the default, even though all sliders are of type AlarmLimitsSlider and are initialized using the AlarmLimitsSlider constructor. Is this indicative of some other problem?


  • Lifetime Qt Champion

    So after re-reading and checking:

    • You painter issue is because you are creating a new painter on the heap each time paintEvent is called without deleting it when done. Put it on the stack so it gets properly destroyed once the event is finished.
    • As for your style sheet, get back to QSlider and use the groove selector.
    "QSlider {font: 20px Verdana; padding: 25px}"
                    "QSlider::groove {"
                       "max-width: 2px;"
                       "height: 5px;"
                       "background-color: blue;"
                    "}"
    


  • @SGaist Sorry, by "put it on the stack" do you mean use a member variable? Or somehow access the painter object without instantiating it each time?

    I went back to QSlider but setting the max-width for the groove doesn't change the width of the handle, it still extends to the wide size policy of the widget.



  • @Smeeth
    I think he is referring to the

    auto painter = new QPainter(this);
    

    like that painter get destroyed only with the form
    so you should change that ...
    and also is getting a new place in memory everytime you touch the slider - careful with the memory management



  • @arsinte_andrei Got it. So how can I get the painter without calling new QPainter(this)?


  • Lifetime Qt Champion

    QPainter painter(this);



  • @Smeeth here you have two options -

    1. delete
    private:
    QPainter *painter;
    

    and

    painter = new QPainter(this);
    

    and inside the paint event add

    void AlarmLimitsSlider::paintEvent(QPaintEvent *ev) {
    QPainter painter;
    //TODO the painting
    
    1. options
      you have declared the painter as a global variable... so make a global init

    so in your constructor add

    painter = new QPainter(this);
    

    and delete it from the paint event



  • @arsinte_andrei Oh I see, for some reason I thought you had to create a new Painter for each time the paint event is triggered. I will try this solution.


  • Lifetime Qt Champion

    Based on the documentation of QPainter:

    The common use of QPainter is inside a widget's paint event: Construct and customize (e.g. set the pen or the brush) the painter. Then draw. Remember to destroy the QPainter object after drawing. For example:
    


  • so as SGaist sugest.. and he is right... at option 2 keep

    painter = new QPainter(this);
    

    the first line in the paintEvent and add

    delete painter;
    

    as your last line in the paint event...

    but anyway I do not understand why will you want option 2 when option 1 is the best???


  • Lifetime Qt Champion

    @arsinte_andrei said in Trouble with custom QSlider:

    so as SGaist sugest.. and he is right... at option 2 keep

    painter = new QPainter(this);
    

    the first line in the paintEvent and add

    delete painter;
    

    as your last line in the paint event...

    but anyway I do not understand why will you want option 2 when option 1 is the best???

    It's not what I suggested, use a stack base painter, there's really no need to allocate if on the heap.



  • @SGaist you pointed at the option 2.. but if you look at option 1 is exactly what you have said.. this is what I've recommended...

    void AlarmLimitsSlider::paintEvent(QPaintEvent *ev) {
    QPainter painter;
    //TODO the painting
    

    or

    void AlarmLimitsSlider::paintEvent(QPaintEvent *ev) {
    QPainter painter(this);
    //TODO the painting
    

    same thing.. - except the second one has parent


  • Lifetime Qt Champion

    It's not the same thing. It's not a parent, it's the paint device that the painter will be active on. If you don't pass it, you will have to call begin and end explicitly. See the constructor documentation.



  • Hmm.... After years pf doing Qt programming I have to agree that I still have a lot to learn about... I do apologize for the wrong information that I've provided....
    Best explanation... God bless SGaist...


Log in to reply