PDA

View Full Version : Style aware custom Widget



Dottorhouse
28th February 2017, 10:43
Hello,
I've been programming with Qt since a while but I've never played around with Stylesheets and style aware widgets. I was trying to create a custom title bar for my application, and I wanted its appearance to be defined by a stylesheet.
Reading the documentation and googling here and there I've understood that I cannot define a really custom stylesheet for my Widget. My ideal result would have been something like this:


CustomTitlebar
{
application-icon: url(:/icons/appicon.png);
background-color: #999999;
background-color-hover: #DDDDDD;
minimize-icon: url(:/icons/minimize.png);
maximize-icon: url(:/icons/maximize.png);
close-icon: url(:/icons/close.png);
text-color: #000000;
}

But it seems that I can only reuse QSS property names already defined for the base Widgets. I'm having a hard time finding all the fields I need, in fact I found only the following 2 matching:



CustomTitlebar
{
background-color: #999999;
color: #000000;
}


On my CustomTitlebar paintEvent function I'm using the styles drawing primitives:



void CustomTitleBar::paintEvent ( QPaintEvent *event )
{
QPainter painter (this);

QStyleOption styleOption;
styleOption.init (this);

style ()->drawPrimitive (QStyle::PE_Widget, &styleOption, &painter, this);
style ()->drawItemText (&painter, rect (), Qt::AlignCenter, styleOption.palette, true, mTitle);

if (mMouseOverCloseButtonFlag == true)
{
painter.save ();
painter.setPen (Qt::transparent);
painter.setBrush (mBrushHover);
painter.drawRect (width () - s_iButtonWidth - 1, 1, s_iButtonWidth, s_iButtonWidth - 1);
painter.restore ();
}

if (mMouseOverMinimizeButtonFlag == true)
{
painter.save ();
painter.setPen (Qt::transparent);
painter.setBrush (mBrushHover);
painter.drawRect (width () - s_iButtonWidth * 2 - 1, 1, s_iButtonWidth, s_iButtonWidth - 1);
painter.restore ();
}

style ()->drawItemText (&painter, QRect (width () - s_iButtonWidth * 2, 0, s_iButtonWidth, s_iButtonWidth), Qt::AlignCenter, styleOption.palette, true, "_");

style ()->drawItemText (&painter, QRect (width () - s_iButtonWidth, 0, s_iButtonWidth, s_iButtonWidth), Qt::AlignCenter, styleOption.palette, true, "X");
}


As you can see I had to hardcode quite a lot of things. I find frustrating having to use those few styled painting primitives, or (as I hope) there's something I'm missing and there's a nice, clean way to make a complex style aware custom widget. I thought I could come up with a paintEvent code more like this, totally controlled by the StyleSheet:



void CustomTitleBar::paintEvent ( QPaintEvent *event )
{
QPainter painter (this);

QStyleOption styleOption;
styleOption.init (this);

style ()->drawPrimitive (QStyle::PE_Widget, &styleOption, &painter, this);
style ()->drawItemText (&painter, rect (), Qt::AlignCenter, styleOption.palette, true, mTitle);

QRect rect1 (width () - s_iButtonWidth - 1, 1, s_iButtonWidth, s_iButtonWidth - 1);
QRect rect2 (width () - s_iButtonWidth - 1, 1, s_iButtonWidth, s_iButtonWidth - 1);

if (mMouseOverCloseButtonFlag == true)
{
style ()->drawRect (&painter, rect1, "CustomTitlebar::background-color-hover");
}

if (mMouseOverMinimizeButtonFlag == true)
{
style ()->drawRect (&painter, rect2, "CustomTitlebar::background-color-hover");
}

style ()->drawItemPixmap (&painter, rect1, Qt::AlignRight, "CustomTitlebar::close-icon");
style ()->drawItemPixmap (&painter, rect2, Qt::AlignRight, "CustomTitlebar::minimize-icon");
}


I know I made up some non existing methods, but it was just to give an idea on how I thought it could be implemented.
Is there a way to do something like this or I have to start hardcoding stuff inside the paintEvent, losing the separation between StyleSheet and C++ code?

Thanks for your time!

Dottorhouse
14th March 2017, 17:07
Answering myself after running into the solution almost by accident. The key lies into the "qproperty-" syntax supported by Qt StyleSheets. You can define a stylesheet like this one:



CustomTitlebar
{
qproperty-application-icon: url(:/icons/appicon.png);
qproperty-backgroundColor: #999999;
qproperty-backgroundColorHover: #DDDDDD;
qproperty-minimizeIcon: url(:/icons/minimize.png);
qproperty-maximizeIcon: url(:/icons/maximize.png);
qproperty-closeIcon: url(:/icons/close.png);
qproperty-textColor: #000000;
}


With all those wonderful custom style fields. In your custom widget's C++ code, you have to do the following in the Header file:



class CustomTitleBar : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor backgroundColorHover READ getBackgroundColorHover WRITE setBackgroundColorHover DESIGNABLE true)
Q_PROPERTY(QColor backgroundColor READ getBackgroundColor WRITE setBackgroundColor DESIGNABLE true)
// Every other custom style field shall be defined here as Q_PROPERTY with READ and WRITE methods

public:

QColor getBackgroundColorHover () const;

void setBackgroundColorHover ( QColor c );

QColor getBackgroundColor () const;

void setBackgroundColor ( QColor c );

// Every other READ/WRITE method shall be declared here...

private:

QColor backgroundColor;
QColor backgroundColorHover;

// Every other READ/WRITE property shall be declared here...

};



The implementation of READ/WRITE methods is trivial, you just get/set the proper fields.
And finally the paintEvent function shall draw in the old-fashioned way using QPainter, but using the Q_PROPERTIES defined earlier:



void CustomTitleBar::paintEvent ( QPaintEvent *event )
{
QPainter painter (this);
QBrush brushHover = QBrush (getHoverColor ());

// ...probably more drawing code here..

if (mMouseOverCloseButtonFlag == true)
{
painter.save ();
painter.setPen (Qt::transparent);
painter.setBrush (brushHover);
painter.drawRect (width () - s_iButtonWidth - 1, 1, s_iButtonWidth, s_iButtonWidth - 1);
painter.restore ();
}

// ...probably more drawing code here too..
}


Ugly and unpolished code, but it shows the way to do it. This way your custom widget can be style-aware and you can use as many custom style fields your heart desires.

Hope I've been useful to someone :-)

Cheers!