How to change QPushButton icon/text spacing with stylesheets? - qt

I'm fairly new to Qt's method of stylesheets and I am looking to adjust the spacing between the icon and text on a QPushButton.
This is the gap I'm referring to: http://imageshack.us/scaled/thumb/593/4kem.png (stackoverflow won't let me post pics yet)
QPushButton {
qproperty-icon: theme_url("/button_action/add");
qproperty-iconSize: 14px;
}
Is there a parameter I can use to adjust this space? I've tried margin, padding, spacing? Perhaps there is a different selector that I can use to just grab the icon?

Actually with QPushButton you are out of luck. You can find a complete list of working options for styling push buttons in the qt documentation ( I am assuming you use qt5, but it is available for other versions as well).
What is often recommended if you want more control over the icon is to exchange your QPushButtons with QToolButtons wich are exactly the same except that they have extra features to make them compatible with toolbars -and- they have more options for icon placement. But this might not work for you because as you can see they are styled the exact same way as QPushButtons. But do look into the "toolButtonStyle" parameter which allows you to move the icon around.
Next on the list comes changing the artwork. If all you want is more space then just add the extra empty pixels to the artwork. This really hurts the perfectionist's mind but is effective.
Next on the list comes subclassing QPushButton and overriding the paint yourself. Sounds more daunting than it is. Subclassing in Qt has become a habit already and it works great. Here is a tutorial i googled in a jiffie (for 4.8 but should be about the same for 5) on the matter of subclassing. It even covers overriding the paint event.
Hope this was helpful.

At least with Qt5.5 (this may change in the future, I hope), besides overriding the paint event, which is complex and in my opinion messy for just a simple thing, there's no inherent Qt way to move that icon a few pixels here or there (small adjustments). If you remove the icon and use a stylesheet to set the background to your icon in your resource URL, you can use background-position property in the stylesheet, but unfortunately it only accepts words like center, left, etc., not pixel adjustments.
Here's how I resolved this problem. I created transparent PNGs that were slightly larger than I needed, where I had like a 4px margin around the icon itself inside the PNG. Then, I just moved the icon inside that canvas and reloaded it into the project resources file, and then mapped it again with the icon property in the designer for the QPushButton, and that worked. I now have icons that line up properly on the left with the text beside it. I can even widen the gap between icon and text.

As of Qt5.7 I don't know of any built-in functionality for the icon/text spacing.
What I usually do is give the image some more space to the right and set the icon size of the button accordingly.
Solution:
Resize your icon file to 32*16 px but leave its content as is.
Call setIconSize(QSize(32, 16)) on your button.
Result: Now there's 16 pixel empty space between its icon and text. 32 pixel is just my example, you can of course set the additional size to something that suites your style.
If you want to apply that on all your button elements simply create a tiny subclass that does it automatically in the constructor. There's really no need to override the paintEvent method for this.

Here is a workaround way to achieve that 'space between text and image':
QPushButton{
border:0px;
image: /*your url*/;
padding-top:16px;
image-position:top;
text-align:bottom;
padding-bottom:10px;
}
since qss doesn't support relative size, you need to implement resize() to change stylesheet when size changing to get a fixed space between text and image.

Here's an opportunistic approach to customize the spacing between text and icon. There's currently no regular way to change the width, so we need to add a transparent area to the right of the icon which is applied to paint device.
Firstly, add a new member variable "m_spacing"(double) and initialize it to 0.0 in constructor, as a factor of expanding the width.
Then, override the "paintEvent", "sizeHint" and "minimumSizeHint".
class CPushButton : public QPushButton {
Q_OBJECT
public:
double spaceRatio() const;
void setSpaceRatio(double ratio);
protected:
double m_spacing;
void paintEvent(QPaintEvent *event) override;
......
QSize CPushButton::sizeHint() const {
QSize sz = QPushButton::sizeHint();
int offset = iconSize().width() * m_spacing;
return QSize(sz.width() + offset, sz.height());
}
QSize CPushButton::minimumSizeHint() const {
QSize sz = QPushButton::minimumSizeHint();
int offset = iconSize().width() * m_spacing;
return QSize(sz.width() + offset, sz.height());
}
void CPushButton::paintEvent(QPaintEvent *event) {
QSize sz = iconSize();
QPixmap tmp = icon().pixmap(sz); // Get the pixmap to apply with right size
sz.rwidth() *= 1 + m_spacing; // Multiply width
QPixmap exp(sz); // Expended
exp.fill(Qt::transparent);
QPainter painter(&exp);
painter.drawPixmap(QRect(QPoint(), tmp.size()), tmp);
QStylePainter p(this); // From Qt source
QStyleOptionButton option; // From Qt source
initStyleOption(&option); // From Qt source
option.icon = QIcon(exp); // Change to real icon
option.iconSize = sz; // Change to real size
p.drawControl(QStyle::CE_PushButton, option); // From Qt source
Q_UNUSED(event)
}
Now you can create a CPushButton instance and set the spacingRatio to 0.1 or other positive real. Visually, it does increase the spacing between text and icon.

The simplest way to go for it is to create your own class inheriting from the QPushButton and override the PaintEvent and then manually place the pixmap of the icon where ever you want.

Best way to solve this problem is adding some blank space in the beginning of QPushButton text. it works well for me!
See here

Related

Qt: How to disable the "shrink-to-fit" for a QGridLayout? [duplicate]

This question already has an answer here:
QScrollArea with dynamically changing contents
(1 answer)
Closed 6 years ago.
I'm using Qt5 on Windows7.
In my current app I try to display and remove a number of push-buttons.
widget = new ButtonWidget(ui->frame); // frame is a QScrollArea
connect(ui->addBtns, SIGNAL(clicked()), widget, SLOT(addButtons()));
connect(ui->deleteBtns, SIGNAL(clicked()), widget, SLOT(deleteButtons()));
And the ButtonWidget class is here:
ButtonWidget::ButtonWidget(QWidget * parent) : QWidget(parent)
{
gridLayout = new QGridLayout(parent);
}
static QStringList texts{...}; // various content (see explanations below)
void ButtonWidget::addButtons()
{
for(auto i = 0; i < texts.size(); i++)
{
gridLayout->addWidget(new QPushButton(texts[i], this), i / 5, i % 5);
}
}
void ButtonWidget::deleteButtons()
{
while(gridLayout->count())
{
delete gridLayout->itemAt(0)->widget();
}
}
If I set QStringList texts{"1\nok"}; I get this:
...which is ugly (centered and expanded horizontally on all layout).
If I set QStringList texts{"1\nok",...,"24\nok"}; I get this:
...which is/seems quite ok.
And finally, if I set QStringList texts{"1\nok",...,"36\nok"}; I get this:
...which is very bad, garbled, etc.
So, the question: Is there any way to fix this, i.e. to disable somehow this "shrink-to-fit" on the layout? A default button size would be just fine.
I would like to take advantage of the vertical-scroll feature of the QScrollArea, not to stuff and cram all the buttons in the (limited) available space...
If you merely add child widgets to a QScrollArea, you won't get any scrolling, since these widgets are not managed by the area in any way. You need to set the widget using QScrollArea::setWidget instead:
widget = new ButtonWidget;
ui->frame->setWidget(widget);
So, the question: Is there any way to fix this, i.e. to disable
somehow this "shrink-to-fit" on the layout? A default button size
would be just fine.
Of the top of my head, one can set a minimum size per button. One can calculate what the minimum size should be from the font (but first just play with setting the minimum size).
Here is a reference. You will notice that minimumSize takes precedence over layout. It might also change the SizePolicy, which you can also look at. I suggest you play with the designer, as it respects sizing and layouts. One can learn allot from it.
Also look at minimumSizeHint, as the layout interacts with this. See also MasterBLB's comments here

wrap text in the tabBar of QTabWidget

Im changing text of QTabWidget dynamically and also changing its font family. so the text in the tabBar header is exceeding the actual width. how i can control the text to wrap inside the tabBar width like
"Text.."
im setting the font family using stylesheet
QString menuBarStyle = QString("QTabBar::tab {font-family: %1;font-style: %2;font-size: 11pt;font-weight: %3;width: 130px}").arg(m_currentFont.family()).arg(fontStyle).arg(fontWeight);
TabWidget->setStyleSheet(menuBarStyle);
Qt stylesheet technique may or may not let to solve it that but I once resolved similar problem like that:
QString elidedText(const QFont& someFont, int width)
{
QFontMetrics metrix( someFont );
return metrix.elidedText(text, Qt::ElideRight, width);
}
So, in addition to font object you have to have the width to fit the text in. Also frequently applying changes via the stylesheet for the widget can hardly be optimal solution, it does a bit too much to get the small change.

QScrollArea: auto-scroll to newly added widget

it's not the first time that I want a scroll area which behaves like the following (imagine a log or chat window, but too complex to use a simple QTextBrowser):
I want to add multiple widgets, which appear one below the other (like when placed in a QVBoxLayout)
Each widget within this layout should have a fixed size or a height-for-width (like a simple label)
The scroll area should auto-scroll to the most recently added one
(optional) When there is space left (scroll bar not yet enabled), the contents should be aligned to bottom
Using QScrollArea:
My attempt in the past was using a QScrollArea using a QVBoxLayout inside. But this seems to be not as simple as I thought: Whenever I add a widget to the layout, the layout doesn't resize the scroll area content widget immediately, resulting in a delayed adjustment of the contents. For one short moment, the widgets contained in the layout are resized so that the total size equals the total size before the add operation, resulting in a too small size per widget. Also, scrolling to the newly added widget is thus not possible until the layout corrected its size to the new total size of widgets, so even a QTimer::singleShot(0, ...) doesn't help here. Even with a timeout of 20 or so, there are situations in which the layout needs more time to resize. It's not deterministic, and thus far away from a nice solution.
In order to get the bottom alignment behaviour, I initially place a spacer item in the layout. It won't require any space as soon as there is no space left and scrolling gets enabled.
Using QListView:
As my items are too complex, they need to be QWidgets. They can't have the focus, aren't selectable, so an item-based solution seems to be just "the wrong way". Also, this solution sounds too "heavy".
I just can't believe that there is no easy way, so I think I just haven't seen it yet!
QListView should be fine. You claim that your items are static, there's no interaction with them: no focus, no selection. It'd seem that a QWidget is an overkill for such items. You only need something that has a fixed size and can draw itself. That is precisely what delegates in the Qt's model-view system are for. Just implement one or more QAbstractItemDelegates for your items, and provide an implementation of a model for the data they will be rendering. The QAbstractItemView is is already a QAbstractScrollArea!
If you want to paint HTML within a delegate, it's easy to do -- again, QWidget is an overkill for a static display! There is a very food reason why it's "hard" to use QWidget for this -- the API guides you to the correct solution. Assuming your model contains html for each item, here's how you can paint it. You can go fancy with the sizeHint, of course, and should be caching the text document, ideally storing it in the model I'd think.
void MyDelegate::paint(QPainter* p, const QStyleOptionViewItem & opt, const QModelIndex & index) const
{
QTextDocument doc;
doc.setHtml(index.data().toString());
doc.drawContents(p, QRect(QPoint(0,0), sizeHint(opt, index)));
}
QSize MyDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const
{
return QSize(100, 200);
}

Cannot add a background image to a QDockWidget

As far as I can tell, the way you make the background of a widget to be an image is to modify the qstylesheet to say:
background-image: url(<your image here>);
Which seems to work, unless it's the QDockWidget's main widget.
This is what I did:
Make a new qt widget project
Make a QWidget object, maybe put a button or two there to make sure you know it's being used.
Change the style sheet to the widget made in step 2 to contain: background-image: url(<your image here>);
In the MainWindow ui file, add the QDockWidget, promote it's main widget to the QWidget made in step #2.
When you run the program, you notice that the QDockWidget's background is still the default, however any buttons or labels you added to the widget have changed their backgrounds to the image you selected.
When you change the qss line from background-image to background-color, and select a color rather than an image, the widget does change color.
Does anyone have any suggestions for giving a QDockWidget a background image (and not just a background color)?
Apparently all you need to do is overwrite the widget's pain event with:
void MyWidget::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

Resizing QT's QTextEdit to Match Text Height: maximumViewportSize()

I am trying to use a QTextEdit widget inside of a form containing several QT widgets. The form itself sits inside a QScrollArea that is the central widget for a window. My intent is that any necessary scrolling will take place in the main QScrollArea (rather than inside any widgets), and any widgets inside will automatically resize their height to hold their contents.
I have tried to implement the automatic resizing of height with a QTextEdit, but have run into an odd issue. I created a sub-class of QTextEdit and reimplemented sizeHint() like this:
QSize OperationEditor::sizeHint() const {
QSize sizehint = QTextBrowser::sizeHint();
sizehint.setHeight(this->fitted_height);
return sizehint;
}
this->fitted_height is kept up-to-date via this slot that is wired to the QTextEdit's "contentsChanged()" signal:
void OperationEditor::fitHeightToDocument() {
this->document()->setTextWidth(this->viewport()->width());
QSize document_size(this->document()->size().toSize());
this->fitted_height = document_size.height();
this->updateGeometry();
}
The size policy of the QTextEdit sub-class is:
this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
I took this approach after reading this post.
Here is my problem:
As the QTextEdit gradually resizes to fill the window, it stops getting larger and starts scrolling within the QTextEdit, no matter what height is returned from sizeHint(). If I initially have sizeHint() return some large constant number, then the QTextEdit is very big and is contained nicely within the outer QScrollArea, as one would expect. However, if sizeHint gradually adjusts the size of the QTextEdit rather than just making it really big to start, then it tops out when it fills the current window and starts scrolling instead of growing.
I have traced this problem to be that, no matter what my sizeHint() returns, it will never resize the QTextEdit larger than the value returned from maximumViewportSize(), which is inherited from QAbstractScrollArea. Note that this is not the same number as viewport()->maximumSize(). I am unable to figure out how to set that value.
Looking at QT's source code, maximumViewportSize() is returning "the size of the viewport as if the scroll bars had no valid scrolling range." This value is basically computed as the current size of the widget minus (2 * frameWidth + margins) plus any scrollbar widths/heights. This does not make a lot of sense to me, and it's not clear to me why that number would be used anywhere in a way that supercede's the sub-class's sizeHint() implementation. Also, it does seem odd that the single "frameWidth" integer is used in computing both the width and the height.
Can anyone please shed some light on this? I suspect that my poor understanding of QT's layout engine is to blame here.
Edit: after initially posting this, I had the idea to reimplement maximumViewportSize() to return the same thing as sizeHint(). Unfortunately, this did not work as I still have the same problem.
I have solved this issue. There were 2 things that I had to do to get it to work:
Walk up the widget hierarchy and make sure all the size policies made sense to ensure that if any child widget wanted to be big/small, then the parent widget would want to be the same thing.
This is the main source of the fix. It turns out that since the QTextEdit is inside a QFrame that is the main widget in a QScrollArea, the QScrollArea has a constraint that it will not resize the internal widget unless the "widgetResizable" property is true. The documentation for that is here: http://doc.qt.io/qt-4.8/qscrollarea.html#widgetResizable-prop. The documentation was not clear to me until I played around with this setting and got it to work. From the docs, it seems that this property only deals with times where the main scroll area wants to resize a widget (i.e. from parent to child). It actually means that if the main widget in the scroll area wants to ever resize (i.e. child to parent), then this setting has to be set to true.
So, the moral of the story is that the QTextEdit code was correct in overriding sizeHint, but the QScrollArea was ignoring the value returned from the main frame's sizeHint.
Yay! It Works!
You may try setting minimumSize property of the QTextEdit to see if that force the layout to grow.
I don't understand most of Qt's layout scheme but setting minimum and maximum size pretty much does what I want it to do. Well, most of the time anyways.

Resources