How to setStyleSheet for QLabel in a specific class in Qt - qt

I have a testing sample like this:In class B, which only has a QVector to store some QLabel like this:
class B : public QWidget
{
public:
B(QWidget *parent = 0);
QVector<QLabel*> mLbls;
};
And in class A, I have the following code:
A::A(QWidget *parent)
: QMainWindow(parent)
{
QWidget *mWidget = new QWidget(this);
QHBoxLayout *hLayout = new QHBoxLayout(mWidget);
B testingB;
testingB.mLbls.resize(5);
for(int i = 0; i < 5; i++) {
testingB.mLbls[i] = new QLabel(mWidget);
testingB.mLbls[i]->setText(QString::number(i));
hLayout->addWidget(testingB.mLbls[i]);
}
setStyleSheet("QLabel {background-color: red;}"); //I want to set the QLabel bg color to red
mWidget->setLayout(hLayout);
setCentralWidget(mWidget);
}
So what I am trying to do is set all QLabel that ONLY in Class B to red color. But the problem is, if I use setStyleSheet("QLabel {background-color: red;}");, all the QLabel(even the QLabel in Class A) will be set to red background color.
I do not know how to set all the stylesheet only for CLass B object. And I know I could use this way to implement what I am trying to do:
for(int i = 0; i < 5; i++) {
testingB.mLbls[i] = new QLabel(mWidget);
testingB.mLbls[i]->setText(QString::number(i));
testingB.mLbls[i]->setStyleSheet("QLabel {background-color: red;}"); //new added
hLayout->addWidget(testingB.mLbls[i]);
}
By using that way, I think that so troublesome to set stylesheet for them one by one individually, besides sometimes I may need to set some QLabel stylesheet differently and the rest of them just have the same stylesheet. If I have more objects and set each of them one by one, that may cost a lot of time.So I assume there is a way to set stylesheet for some objects in a class once for all. Hope that I explain my question clearly. To solve that problem, what can I do? Thanks.

Qt CSS syntax allows you to select children for the CSS rules (Qt doc), but it is based on the parentship of QObjects not in which class contains pointers to the QLabel.
This CSS will only apply to QLabel objects which are children or grand children of a YourContainerClassWidget object.
// C++
auto container = new YourContainerClassWidget;
auto label = QLabel(container); // The label is now a child of container
// CSS
YourContainerClassWidget QLabel { // Descendant selector
background-color: red;
}
// You can also apply only to direct children with this syntax
YourContainerClassWidget > QLabel { // Child selector
background-color: red;
}

Related

QLabel change font color without changing any other style

I want to change the color of the text in a QLabel dynamically.
I have defined the color and style of the QLabel in the ui file and I want to change it when a certain event takes place.
I want to change the color without changing any other style of my QLabel.
I have found several answers adressing the issue of changing text color in a QLabel (1, 2, 3) and they all use the function setStyleSheet. This function works fine but it changes my font size and other styles related to the QLabel.
I have seen that the problem is related to setStyleSheet ignoring any previous style. The solution proposed there involves retrieving all the styles I want to maintain and setting them again together with the text color change.
This is cumbersome and difficult to maintain. If more styles were defined in the future I would need to review this part of the code to be able to reset all of them.
I would like to be able to change QLabel text color without altering any other syle. Is it possible?
If you want to manage the text color of QLabel you could wrap it with customized class.
For example:
class ColorLabel : public QLabel
{
public:
ColorLabel(const QString &text, QWidget *parent = nullptr)
: QLabel(text, parent)
{
setAutoFillBackground(true);
}
void setTextColor(const QColor &color)
{
QPalette palette = this->palette();
palette.setColor(this->backgroundRole(), color);
palette.setColor(this->foregroundRole(), color);
this->setPalette(palette);
}
};
And to use it in your code:
ColorLabel * poColorLabel = new ColorLabel("My string", this);
poColorLabel->setTextColor(Qt::red); // set label text in red color
FYI: I tested it on Fedora, Qt5.12 and it works fine.
A pragmatic approach:
Utilize the cascadingness of CSS.
Wrap your QLabel in a QWidget (don't forget a QLayout).
Set your default style on the surrounding QWidget.
Set the font color as the QLabel's only style.
You can create some style class to control a widget's style:
class WidgetStyleSheet
{
public:
// change some style's value
void setValue(const QString& styleKey, const QString& value)
{
_styleMap[styleKey] = value;
}
// to default state
void reset() {}
// form stylesheet
QString toStyleSheet() const
{
QString styleSheet;
QMapIterator<QString, QString> iter(_styleMap);
while( iter.hasNext() )
styleSheet += QString("%1: %2").arg(iter.key()).arg(iter.value());
return styleSheet;
}
private:
QMap<QString, QString> _styleMap;
}
Somewhere in your code:
WidgetStyleSheet labelSS;
// ...
labelSS.setValue("color", QString("%1").arg( QColor(255, 10, 0).name() );
labelSS.setValue("background-color", "...");
// ...
label->setStyleSheet(labelSS);
The following works fine. But it is not that elegant. This is in python. You have to pass the button name (or any other) to the following as is defined in the array
btns = ['self.hBeamBtn','self.lBeamBtn','self.allTestBtn','self.prnStatusBtn']
for btn in btns:
if str(btn_name) == str(btn):
styl = btn+'.setStyleSheet("font: bold;background-color: red;font-size: 12px;height: 28px;width: 80px;")'
eval(styl)

Can you hide a QGroupBox frame but preserve it's content visible?

I have a QGroupBox. Depending on the context, it's title may be redundent (displayed in another place of the GUI), so I then need to make as if the QGroupBox was not here....but I must preserve it's content visible (so I don't want to call QGroupBox::hide())!
I need to do this dynamically at runtime and would like to avoid creating/destroying the QGroupBox + reparenting it's content....there must be an easier way to do this.
What I tried so far:
QGroupBox visible:
QGroupBox::setTitle("") removes the text.
QGroupBox::setFlat(true) makes the frame be a single line.
I end up with this:
Not too bad...but a line remains....is there a way to completely hide the QGroupBox frame but preserve it's content visible?
My option:
QGroupBox theBox;
theBox.setFlat(true);
//This removes the border from a QGroupBox named "theBox".
theBox.setStyleSheet("QGroupBox#theBox {border:0;}");
//This removes the border from the group box and all of its children
theBox.setStyleSheet("border:0;");
You can derive your own Group Box from the QGroupBox and reimplement the paintEvent() method. It should be very simple. Original QGroupBox::paintEvent() looks like this:
void QGroupBox::paintEvent(QPaintEvent *)
{
QStylePainter paint(this);
QStyleOptionGroupBox option;
initStyleOption(&option);
paint.drawComplexControl(QStyle::CC_GroupBox, option);
}
What you need to do is just to modify the style option right before the widget is painted:
void CMyGroupBox::paintEvent(QPaintEvent *)
{
QStylePainter paint(this);
QStyleOptionGroupBox option;
initStyleOption(&option);
// This should disable frame painting.
option.features = QStyleOptionFrame::None;
paint.drawComplexControl(QStyle::CC_GroupBox, option);
}
You can use QFrame + QGridLayout (or some more complex combination of layouts) + QSS instead of a QGroupBox.
Considering a QGroupBox only, a trivial solution via QSS could be:
static const char kSavedTitle[] = "_savedTitle";
void hideBoxFrame(QGroupBox * box) {
box->setProperty(kSavedTitle, box->title());
box->setTitle(QString());
box->setStyleSheet("border:none");
}
void showBoxFrame(QGroupBox * box) {
box->setTitle(box->property(kSavedTitle).toString());
box->setStyleSheet(QString());
}
Here's an example that does it by swapping the widgets and reparenting the children. It works for any widget that has direct children, not only QGroupBox. It would require special case handling for widgets such as QScrollArea and QMainWindow that wrap children in a special sub-widget.
See this question for a related discussion of programmatically promoting widgets.
// https://github.com/KubaO/stackoverflown/tree/master/questions/group-reparent-36603051
#include <QtWidgets>
/// Replaces the visible widget with a hidden widget, preserving the layout of the
/// children, and making the new widget visible.
void swapWidgets(QWidget * a, QWidget * b)
{
auto src = a->isVisible() ? a : b;
auto dst = a->isVisible() ? b : a;
Q_ASSERT(dst->isHidden());
/// Move the children to the destination
dst->setLayout(src->layout());
/// Replace source with destination in the parent
auto layout = src->parentWidget()->layout();
delete layout->replaceWidget(src, dst);
/// Unparent the source, otherwise it won't be reinsertable into the parent.
src->setParent(nullptr);
/// Only the destination should be seen.
src->hide();
dst->show();
}
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget w;
QGridLayout wLayout{&w};
QPushButton swapBtn{"Swap"};
wLayout.addWidget(&swapBtn);
QWidget noBox;
QGroupBox box{"Group"};
wLayout.addWidget(&box);
QGridLayout boxLayout{&box};
for (int i = 0; i < 16; ++i)
boxLayout.addWidget(new QLabel(QString("Tr%1").arg(i)), i/8, i%8);
swapBtn.connect(&swapBtn, &QPushButton::clicked, [&] { swapWidgets(&box, &noBox); });
w.show();
return app.exec();
}
Yes there is a alternative that you can Try.
You can morph into a QFrame which will keep the behavior But make the container boundaryless
You can simply right click on the Group Box in the QDesigner and Select the 'Morph Into' option to select from

How to add some padding between QPushButton’s boundary and its inner text?

Longer strings don’t look too good in a QPushButton, because there’s no spacing between the text and button’s frame, i. e. the text is placed tightly within the button. Is there a way to add some padding?
You can set the padding of a QPushButton via its stylesheet.
myButton->setStyleSheet("padding: 3px;");
Or
myButton->setStyleSheet("padding-left: 5px; padding-right: 3px;"
"padding-top: 1px; padding-bottom: 1px;");
More information on stylesheets can be found here.
Rather than having to set a stylesheet for each button, I found it easier to update the sytle so that the minimum size for each button is a little bigger. You can subclass QProxyStyle which is the easiest way to modify styles, since it will apply changes to whatever style is selected ie QWindowsXPStyle, QWindowsVistaStyle, QMacStyle etc.
Override sizeFromContents like this to make the minimum size of buttons a little bigger:
class ProxyStyle : public QProxyStyle
{
public:
QSize sizeFromContents(ContentsType ct, const QStyleOption* opt, const QSize & csz, const QWidget* widget = 0) const
{
QSize sz = QProxyStyle::sizeFromContents(ct, opt, csz, widget);
if (ct == CT_PushButton)
sz.rwidth() += 20;
return sz;
}
};
And then after you create your application, but before you create your first window call:
a.setStyle(new ProxyStyle);
You can set the padding for all buttons in a window/widget with:
QPushButton { padding: 10px; }
Much better than applying to each subwidget.

C++ Qt - How can (virtual) QWidget points to its children?

I am trying to change the border of a QFrame. BUT.
There is a Window::ui, inside of which there is a class inheriting from QWidget.
In that class, there is a QFrame, set with a vertical Layout, which holds 2 other QFrame and do their QFrame business.
Now, this structure is repeated a lot in Window::ui, so I simply added it to a vertical layout named kingdom_decks.
So far, so good.
Let's say, I want to select one of those element. To mark the selection, I want to change the border, from black to red, or just make it thicker. With a QFrame, very easy. BUT
My event handler is a slot in Window::ui. ui goes to kingdom_decks layout, and go to the item selected. itemAt retourn a QLayoutItem, that I can cast as QWidget with widget()... but then?
ui->kingdom_decks->itemAt(idx_prev)->widget()
I tried unsuccessfully
ui->kingdom_decks->itemAt(idx_prev)->widget()->childAt(0,0)
I believe it failed because there is a Qframe in a Layout, instead of a geometry form with real coordonates, or maybe I didn't go deep enough?
Anyway, thank you very much in advance for any ideas on that! Thanks for your time.
EDIT
Code for window.cpp
#include "window.h"
#include "ui_window.h"
Window::Window(Game_state * p, Card_generator * d, QWidget *parent) :
QDialog(parent),
ui(new Ui::Window)
{
ui->setupUi(this);
/*Stuff happen here*/
/* Display cards*/
for (int i = 0; i < 10; ++i){
/*Add widget to layout*/
ui->kingdom_decks->addWidget(new Cards_kingdom(decks->decks_kingdom[i]),0,i);
/*Connect widget to map*/
connect(ui->kingdom_decks->itemAt(i)->widget(), SIGNAL(mousePressEvent()), signal_mapper, SLOT(map()));
/*Map widget and data*/
signal_mapper->setMapping(ui->kingdom_decks->itemAt(i)->widget(), i);
}
/* Action "Select cards" */
connect(signal_mapper, SIGNAL(mapped(int)), this, SLOT(card_kingdom_selected(int)));
}
Window::~Window()
{
delete ui;
}
/*Implementation of SLOT*/
void Window::card_kingdom_selected(int idx){
/*...*/
//?????????????? What to do here???????????
//ui->findChild<QLabel *>("img");
//ui->kingdom_decks->itemAt(idx)->widget()->????;
}
So what happen here is that I have a layout kingdom_decks, in which I loop to add a widget Cards_kingdom, overloading the function addWidget.
ui->kingdom_decks->addWidget(new Cards_kingdom(decks->decks_kingdom[i]),0,i);
This Object Cards_kingdom is a class, such that:
cards_kingdom.h :
class Cards_kingdom : public QWidget {
public:
Cards_kingdom(Deck_kingdom * input_deck); /* Constructor */
bool isSelected();
QLabel * get_img();
/*
* Price and Counter are display in the same label, then are wrapped with Icon in a vertical layout, inside a frame.
*/
private:
QLabel *img; /* Icon */
QLabel *info; /* Nb Cards left*/
QVBoxLayout *layout; /* Layout */
QFrame *pack; /* Frame */
Deck_kingdom *deck; /* Type Deck */
bool select;
};
In window.cpp, i try to retrieve the QLabel * img, to put a border on this image, such that the user sees that it has been selected.
To answer to #Nicholas Smith, how can findChild, find the exact instanciation of Card_kingdom?
EDIT:
OK, I could change my architecture to something like this:
Create a vector of Cards_kingdom * vec
In the for loop,
vec.push_back(new Card_kingdom *);
ui->layout->addWIdget(vec[i])
So now, I think that would work, because layout is holding a pointer to my widget, so I pass by reference, therefore, if I change something in my object, it will appear in the GUI even if I didn't pass by there...
Right? :)
The fun thing with Qt is layouts can become nests and layers and mazes, but I've just had this exact issue with a different twist, I'd personally go for something along the lines of ui->findChild<QFrame *>("frameObjectName") (if you've created QFrame as pointer, if not just drop the *) and access it from there. You'll need to make sure you have an object name filled it for the frame, but that's not too hard and has other benefits.

Property selectors in QT CSS

I have a tree widget that I'm using for a user/room concept. How can I style the rooms independently from the users in the rooms? I'm assuming it has something to do with QT's property selector in CSS? I would like to be able to do this:
QTreeView::item[isUser="true"] { background: blue; }
QTreeView::item[isRoom="true"] { background: red; }
Since the items in a model are not QObjects (nor QWidgets), you will not be able to add a property to the item, or style them with stylesheets.
I have two suggestions for doing what you want to do :
1) (C++ only) Attach your QTreeView to a QStandardItemModel, and when you add items as QStandardItem objects, you can call QStandardItem::setBackground() with either Qt::blue or Qt::red depending of whether the item is a room or a user.
2) (C++ and CSS) Define a QStyledItemDelegate that you attach to your QTreeView. In your reimplementation of QStyledItemDelegate::paint() method, use a QLabel to display the content of the item, then set a property on that QLabel. You will then be able to use a stylesheet to customize the look of the label :
QLabel[isUser="true"] { background: blue; }
QLabel[isRoom="true"] { background: red; }
I was able to accomplish what I needed by creating a label, using the setProperty method on that label, and then using the setItemWidget function to attach that QLabel to the appropriate QTreeWidgetItem. So I wouldn't be "styling the QTreeWidgetItem", but rather styling the QLabel that was overlayed on top of the QTreeWidgetItem. The following example sets my topLevelItem in the QTreeWidget to be ready to be styled as a room:
QTreeWidgetItem *topItem = ui->treeWidget->topLevelItem(0);
currentLabel = new QLabel;
currentLabel->setProperty("room",true);
currentLabel->setText(QString("Room Lobby"));
ui->treeWidget->setItemWidget(topItem,0,currentLabel);`
I can then select it in the stylesheet with
QLabel[room="true"] { background: red; }

Resources