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:
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?
-
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?
-
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;" "}"
- You painter issue is because you are creating a new painter on the heap each time
-
@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 theauto 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)?
-
QPainter painter(this);
-
@Smeeth here you have two options -
- 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
- 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.
-
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???
-
@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
-
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...