Qt Widget with QLineEdit-like background - c++

I'm looking to create a custom widget where part of it is a "background" that looks like a QLineEdit (or QProgressBar),
e.g. but without the text.
I've come up with a couple of hacky ways to do this, but neither of them seem like a good solution:
1.
QPainter painter(this);
int penwidth = painter.pen().width();
int width = this->width();
int height = this->height() - 20;
QPoint tl(penwidth / 2, penwidth / 2 + 10);
QPoint bl(penwidth / 2, height - penwidth);
QPoint tr(width - penwidth, penwidth / 2);
QPoint br(width - penwidth, height - penwidth);
QRect rect(tl, br);
QStyleOptionFocusRect option;
option.initFrom(this);
option.backgroundColor = palette().color(QPalette::Button);
option.rect = rect;
this->style()->drawControl(QStyle::CE_ProgressBarGroove, &option, &painter, this);
This has the disadvantage of not being fully controllable, especially when inside a layout as I intend it to be
2.
Using a QLineEdit widget but setting it to NoFocus and ReadOnly.
This seems like overkill to me, as I'll never want any of the text functionality
What is the best solution to this?

Use QLabel with special stylesheet:
ui->label->setText("");
ui->label->setStyleSheet("QLabel{ border: 1px solid gray; background-color:white; border-radius:2px}");
Stylesheet:
QLabel
{
border: 1px solid gray;
background-color:white;
border-radius:2px
}
QLabel has no any other unnecessary things so it is better than QLineEdit or QProgressBar.
Result:

Related

Custom Widget in QScrollArea Badly Redrawing Only on Scroll

I'm trying to get a custom scrolling widget in QT, and I'm getting redraw errors on scroll. Alt-tab or other redrawing events redraw correctly.
I'm basing it on the example at http://doc.qt.io/qt-5/qtwidgets-widgets-charactermap-example.html
repeatingwidget.cpp (excerpt):
QSize RepeatingWidget::sizeHint() const {
return QSize(500, itemHeight * displayItems.size() + 1);
}
void RepeatingWidget::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.fillRect(event->rect(), QBrush(Qt::white));
painter.setFont(displayFont);
QRect itemRect = event->rect();
int top = itemRect.top();
QFontMetrics fontMetrics(*displayFont);
for (auto item : displayItems) {
painter.setPen(QPen(Qt::gray));
painter.drawRect(itemRect.left(), top, itemRect.right(), itemHeight);
painter.setPen(QPen(Qt::black));
painter.drawText(8, 4 + top + fontMetrics.ascent(), item.name);
top += itemHeight;
}
}
mainwindow.cpp (excerpt):
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
QMenu *filemenu = menuBar()->addMenu(tr("File"));
filemenu->addAction(tr("Quit"), this, &QWidget::close);
auto *centralWidget = new QWidget;
scrollArea = new QScrollArea;
repeatingArea = new RepeatingWidget();
scrollArea->setWidget(repeatingArea);
auto *centralLayout = new QVBoxLayout;
centralLayout->addWidget(scrollArea, 1);
centralWidget->setLayout(centralLayout);
setCentralWidget(centralWidget);
setWindowTitle(tr("Widget Test"));
}
This seems to match the example, but I'm getting redraw errors that don't happen in charmap.
I've tried setGeometry, setWidgetResizable, and different size policies, but I'm still getting these redraw errors.
After scrolling:
I don't know what I'm doing wrong because it's largely identical in important ways to the example code from the charmap.
This is the full code: https://gist.github.com/jonasbuckner/2acc1a960e457946ce4756199de3fb57
QPaintEvent is a method that allows you to make an intelligent painting, that is, to paint where necessary, thus saving resources, for example it gives us the information of the rectangle that must be painted through event->rect(), with this we can calculate the items that have to be painted since others will be hidden and therefore it is not necessary to paint them:
void RepeatingWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.fillRect(event->rect(), QBrush(Qt::white));
painter.setFont(displayFont);
QFontMetrics fontMetrics(displayFont);
int i = std::max(event->rect().top()/itemHeight, 0);
int j = std::min(event->rect().bottom()/itemHeight+1, displayItems.size());
QRect itemRect(0, i*itemHeight, width(), itemHeight);
for(; i < j; i++){
painter.setPen(QPen(Qt::gray));
painter.drawRect(itemRect);
painter.setPen(QPen(Qt::black));
painter.drawText(8, 4 + itemRect.top() + fontMetrics.ascent(), displayItems[i].name);
itemRect.translate(0, itemHeight);
}
}
Your original code didn't work because you were drawing all of the items, but using the event->rect, which may only be part of the RepeatingWidget.
Sometimes it is not easy to calculate which items are in the event->rect as #eyllanesc shows. In these cases, just use clientRect instead - Qt will clip the drawing for you.

Is it possible to change the color of a QSlider's handle according to its position?

I pretty well understood how to customize a QSlider through style sheets, but I am wondering if it is possible to do the following thing:
I'd like the handle of the slider to go from blue to yellow. When set on the left, it's blue; and when you move it to the right, it will have a gradient from blue to yellow.
If it is possible through the style sheets, how? And if not, how can I implement that in the paintEvent of a subclass of a QSlider?
Actually you don't really have to do anything fancy, the stock QSlider already has the valueChanged(int) signal, so you can connect that to a function which mixes between the two colors based on the position and sets the style color. Here is a minimal example:
static QColor operator+(const QColor & a, const QColor & b) {
return QColor(a.red() + b.red(), a.green() + b.green(), a.blue() + b.blue());
}
static QColor operator*(const QColor & c, const qreal r) {
return QColor(c.red() * r, c.green() * r, c.blue() * r);
}
class Widget : public QWidget {
Q_OBJECT
public:
Widget(QWidget *parent = 0) : QWidget(parent), from(248, 181, 20), to(64, 150, 214) {
auto l = new QHBoxLayout(this);
setLayout(l);
s = new QSlider(Qt::Horizontal, this);
s->setMinimum(0);
s->setMaximum(100);
l->addWidget(s);
connect(s, &QSlider::valueChanged, this, &Widget::colorize);
colorize(s->value());
}
private:
void colorize(int v) {
int d = s->maximum() - s->minimum();
v = v - s->minimum();
qreal rv = qreal(v) / d;
QColor c = from * rv + to * (1.0 - rv);
s->setStyleSheet(QString("QSlider::handle:horizontal {background-color: %1;}").arg(c.name()));
}
QSlider * s;
QColor from, to;
};
This will work for any slider range and orientation, the code basically finds the relative handle position in range 0.0 to 1.0 and uses that to mix the from and to colors to set the handle color to the respective value. Oddly enough, QColor didn't have the operators to multiply and add, which can come quite in handy.
Additionally, instead of mixing between two colors you can construct a color in HSL format, which will give you a slightly different gradient. Changing from/to from QColor to hues 42 and 202 respectively, you can then:
QColor c = QColor::fromHsl(205 - (205 - 42) * rv, 200, 135);
This will give you a color sweep for the hue rather than mix between two fixed colors, which may be more applicable in the context of temperature:
Note that now in the middle you get a cyan-ish color rather than "zombie" green and you get through clean green before you get to orange.
I don't believe you can do this using a simple style sheet. But that's easily doable by specializing QSlider class and applying an appropriate stylesheet when user moves the cursor (i.e: when valueChanged is emited).
Here is a class I wrote that does the trick. It works for horizontal and vertical cursor and can be customized to use any color. It create a QImage from a QLinearGradient to store the gradient color map, then, when slider value changes, it extracts the appropriate color from the image based on slider's position and applies it through a stylesheet.
Tried to make the class generic for reusability, but it could be simplified if you don't need to customize colors and only use horizontal sliders.
gradientslider.h:
#include <QSlider>
#include <QImage>
#include <QColor>
class GradientSlider : public QSlider
{
Q_OBJECT
public:
GradientSlider( QColor from, QColor to, Qt::Orientation orientation, QWidget* parent );
private slots:
void changeColor( int );
private:
QImage gradient;
};
gradientslider.cpp:
#include "gradientslider.h"
#include <QLinearGradient>
#include <QPainter>
GradientSlider::GradientSlider( QColor from, QColor to, Qt::Orientation orientation, QWidget* parent ) :
QSlider( orientation, parent ),
gradient( QSize(100,100), QImage::Format_RGB32 )
{
// create linear gradient
QLinearGradient linearGrad( QPointF(0, 0), (orientation==Qt::Horizontal) ? QPointF(100, 0) : QPointF(0, 100) );
linearGrad.setColorAt(0, from);
linearGrad.setColorAt(1, to);
// paint gradient in a QImage:
QPainter p(&gradient);
p.fillRect(gradient.rect(), linearGrad);
connect( this, SIGNAL(valueChanged(int)), this, SLOT(changeColor(int)) );
// initialize
changeColor( value() );
}
void GradientSlider::changeColor( int pos )
{
QColor color;
if ( orientation() == Qt::Horizontal )
{
// retrieve color index based on cursor position
int posIndex = gradient.size().width() * ( pos - minimum() ) / (maximum() - minimum());
posIndex = std::min( posIndex, gradient.width() - 1 );
// pickup appropriate color
color = gradient.pixel( posIndex, gradient.size().height()/2 );
}
else
{
// retrieve color index based on cursor position
int posIndex = gradient.size().height() * ( pos - minimum() ) / (maximum() - minimum());
posIndex = std::min( posIndex, gradient.height() - 1 );
// pickup appropriate color
color = gradient.pixel( gradient.size().width()/2, posIndex );
}
// create and apply stylesheet!
// can be customized to change background and handle border!
setStyleSheet( "QSlider::handle:" + (( orientation() == Qt::Horizontal ) ? QString("horizontal"):QString("vertical")) + "{ \
border-radius: 5px; \
border: 2px solid #FFFFFF; \
width: 20px; \
margin: -5px 0; \
background: " + color.name() + "}" );
}
Now just do:
QHBoxLayout* layout = new QHBoxLayout( this );
// horizontal slider:
layout->addWidget( new GradientSlider( QColor(79,174,231), QColor(251,192,22), Qt::Horizontal, this ) );
// or, vertical slider:
layout->addWidget( new GradientSlider( QColor(79,174,231), QColor(251,192,22), Qt::Vertical, this ) );
Colors QColor(79,174,231) (~blue) and QColor(251,192,22) (~yellow) were picked up from the image in the original question post and can be replaced by Qt::blue, Qt::yellow (ending up with slightly different coloration).
This will do it:

QWidget's background applied to all its QWidget children

I chose to use Qt to manage the GUI of a project I am working on.
After finding how to apply a picture at the bottom of my QWidget, I noticed that it has an impact on all the components that are added to it.
Whatever the style applied through the setStyleSheet method or even with a QPixmap, the background of these elements is always the image defined for the QWidget container.
How can I avoid this behavior ?
Here is my code :
MainMenu::MainMenu(QWidget* Parent, const QPoint& Position, const QSize& Size) : QWidget(Parent) {
QString qwidgetStyle = "QWidget {background-image: url(background.jpg); border: 5px solid rgba(3, 5, 28, 1);}";
QString buttonStyle = "color: rgba(73, 123, 176, 1); font-size:30px; background-color: rgba(73, 123, 176, 1);";
move(Position);
resize(Size);
setStyleSheet(qwidgetStyle);
// Menu title
QLabel *title = new QLabel(this);
title->setText("Menu");
title->setStyleSheet(buttonStyle);
title->setAlignment(Qt::AlignCenter);
// Menu buttons
// Play
buttonPlay = new QPushButton("Play");
(*buttonPlay).setEnabled(true);
(*buttonPlay).setStyleSheet(buttonStyle);
connect(buttonPlay, SIGNAL(clicked()), this, SLOT(handleButton()));
// Option
buttonOptions = new QPushButton("Options", this);
(*buttonOptions).setEnabled(true);
(*buttonOptions).setGeometry(250, 175, 100, 50);
(*buttonOptions).setStyleSheet(buttonStyle);
connect(buttonOptions, SIGNAL(clicked()), this, SLOT(handleButton()));
// Quit
buttonQuit = new QPushButton("Quit", this);
(*buttonQuit).setEnabled(true);
(*buttonQuit).setGeometry(250, 275, 100, 50);
(*buttonQuit).setStyleSheet(buttonStyle);
connect(buttonQuit, SIGNAL(clicked()), this, SLOT(handleButton()));
// Layout
QGridLayout *layout = new QGridLayout;
layout->setMargin(50);
layout->addWidget(title, 0, 0, 1, 5);
layout->addWidget(buttonPlay, 3, 1, 2, 3);
layout->addWidget(buttonOptions, 4, 1, 2, 3);
layout->addWidget(buttonQuit, 5, 1, 2, 3);
setLayout(layout);
show();
}
The behavior you encountered is perfectly normal, because of the following lines :
QString qwidgetStyle = "QWidget {background-image: url(background.jpg); border: 5px solid rgba(3, 5, 28, 1);}";
...
setStyleSheet(qwidgetStyle);
Here, you just told Qt to apply qwidgetstyle to every QWidget of your application, with the keyword QWidget. That's why in Qt, you better set a name to your object if you want to apply a style to this particular object.
In your code, QLabel and QPushButton both inherit from QWidget, so they will have the style you defined for a QWidget, unless you name them or you specify the style for each one.
If you want to set style sheet for your MainMenu which inherits directly from QWidget (which is what you are doing in the first place), you have to set a name, and then apply the style :
setObjectName("MainMenu");
QString qwidgetStyle = "QWidget#MainMenu {background-image: url(background.jpg); border: 5px solid rgba(3, 5, 28, 1);}";
setStyleSheet(qwidgetStyle); // here, only your MainMenu will have the style "qwidgetstyle"
Notice that you can, for example, set the same style sheet for every QWidget, and only add a particular color for your MainMenu :
// this is in a CSS, but you can apply it directly from the MainMenu constructor of course
QWidget, QWidget#MainMenu {
background-image: url(background.jpg);
border: 5px solid rgba(3, 5, 28, 1);
} // aplied to all QWidget
QWidget#MainMenu {
color : #9b9b9b; // a nice grey, only applied to MainMenu
}
Again, be specific when using style sheets or you will end up having strange colors/alignments everywhere in your application :). Hope that helps!
NB : you can also thank #PaulRooney who gave a very good link in the comments.

Qt Balloon Window from QFrame

I would like to create my own Balloon window for tips in Qt. I'm starting by creating a window with round corners.
I'm using a class inherited from QFrame. The class's constructor contains:
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
Pal.setColor(QPalette::Background, Qt::yellow);
this->setAutoFillBackground(true);
this->setPalette(Pal);
this->setStyleSheet("QFrame {border-style: solid; border-width: 10px;"
"border-radius: 100px;"
"min-width: 10em; background-clip: padding; background-origin: content;}");
But this is not creating round corners when showing using the show() member function. I'm getting this:
How can I get rid of those rectangular edges and have them transpararent colored?
If you require any additional information, please ask.
If my guess is correct you are looking for something like setMask !
Basically what you need to do is draw a rectangle with your desired radius and then convert it to QRegion to use it with setMask. See below one way:
QPainterPath path;
path.addRoundedRect(rect(), 100, 100);
QRegion region = QRegion(path.toFillPolygon().toPolygon());
setMask(region);
And that will be the result:
Hope that helps!
auto frame = new QWidget(parent, Qt::Popup);
frame->setStyleSheet("background-color: red; border: 1px solid green; border-radius: 6px;");
QPainterPath path;
path.addRoundedRect(frame->rect(), 6, 6);
frame->setMask(path.toFillPolygon().toPolygon());
frame->show();

Customized color on progressbar delegate

Been trying to do this for quite a while and took advice from every forum post i could find but i still cant solve it. This is my current code and I would really like to change the color of the chunk on the progress bar. Every other setting is working, except the colors.
In my workspace object that fills up one subview on the MainWindow.
Workspace::Workspace( QWidget* parent) : QWidget( parent )
{
QTableView* tableView = new QTableView();
// ...
tableView->setItemDelegate(new ProgressBarDelegate);
}
The delegate.cpp looks like this:
ProgressBarDelegate::ProgressBarDelegate( QObject* parent )
: QStyledItemDelegate(parent)
{
}
void ProgressBarDelegate::paint( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.column() == 2)
{
int progressPercentage = index.model()->data(index, Qt::DisplayRole).toInt();
QStyleOptionProgressBarV2 progressBarOption;
progressBarOption.rect = QRect(option.rect.x(), option.rect.y() + 5 , option.rect.width(), option.rect.height() / 1.5);
progressBarOption.minimum = 0;
progressBarOption.maximum = 100;
progressBarOption.progress = progressPercentage;
QPalette pal = progressBarOption.palette;
QColor col = QColor(35, 35,25);
pal.setColor(QPalette::Highlight, col); // or QPalette::Window doesnt matter
progressBarOption.palette = pal;
if(option.state & QStyle::State_Selected)
{
}
QApplication::style()->drawControl( QStyle::CE_ProgressBar,
&progressBarOption,
painter);
}
else
{
QStyledItemDelegate::paint(painter, option, index);
}
}
Currently, no matter what I do the color doesnt change from OSX standard light-gray.
Running OSX 10.6.7 and Qt 4.8.1 if that matters. thank you!
Edit:
I was able to do the following:
app.setStyleSheet("QScrollBar:horizontal { border: 2px solid green;background: cyan;height: 15px;margin: 0px 20px 0 20px;}");
But when I do this:
app.setStyleSheet("QProgressBar:horizontal { border: 1px solid gray; border-radius: 3px; background: white; padding: 1px; }");
NOTHING changes on the progressbar. I am in theory not creating any progressbar objects, im just settings a style how I'm viewing my data in my delegate. But surely, I cant be the first person who would want to do this right?
Also, if this doesnt work, how can I do this (having a styled progressbar) in a tableview?
You should use Qt Style Sheet, which allows us to customize UI of many controls to give unique look and feel across platforms. Check this.
Create a new simple Qt Gui project, open UI Form editor and add a Progress Bar control from under 'Display Widgets' in tool window. Now write following code in constructor of MainWindow..
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
// Customize progress-bar's style..
QString style = "QProgressBar {border: 2px solid grey; border-radius: 5px; text-align: center;}";
style += "QProgressBar::chunk {background-color: #CD96CD; width: 10px; margin: 0.5px;}";
// Assuming objectName is 'progressBar'..
ui->progressBar->setStyleSheet(style);
}
Compile and run.
If you just want to change that single QProgressBar control, then above method is sufficient, but if you want to apply styles at application level (say all QProgressBar controls and some other controls), then proper way is to create a *.css file, write styles using Qt Style Sheet Reference and then read that file in Qt and call
QApplication::setStyleSheet(QString style).
Besides, style sheet uses the same syntax as CSS and also supports various selectors.
Edit:
I agree that above method works only with controls and not delegates. I found something for delegates also. Try following paint function.
void ProgressBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (index.column() == 2)
{
QProgressBar renderer;
int progressPercentage = index.model()->data(index, Qt::DisplayRole).toInt();
// Customize style using style-sheet..
QString style = "QProgressBar { border: 2px solid grey; border-radius: 5px; }";
style += "QProgressBar::chunk { background-color: #05B8CC; width: 20px; }";
renderer.resize(option.rect.size());
renderer.setMinimum(0);
renderer.setMaximum(100);
renderer.setValue(progressPercentage);
renderer.setStyleSheet(style);
painter->save();
painter->translate(option.rect.topLeft());
renderer.render(painter);
painter->restore();
}
else
QStyledItemDelegate::paint(painter, option, index);
}
So here the point is that instead of using QStyleOption, we can use directly the control itself as a renderer. Hope this helps..
Instead of pal.setColor(QPalette::Window, col);
Use pal.setColor(QPalette::Highlight, col);
This should work.
I used the code in Paintevent
void Dialog::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(ShowPrintDialog()));
QStyleOptionProgressBarV2 progressBarOption;
progressBarOption.rect = QRect(20,20,30,30);
progressBarOption.minimum = 0;
progressBarOption.maximum = 100;
progressBarOption.progress = 75;
QPalette pal = progressBarOption.palette;
QColor col = QColor(0,255,0);
pal.setColor(QPalette::Highlight, col);
progressBarOption.palette = pal;
QApplication::style()->drawControl(QStyle::CE_ProgressBar,&progressBarOption, &painter);
}