I've got four QLineEdit placed inside of a QLineEdits, where I want the first the parent to look as if it is in focus when any of the containing ones is selected. Note: I don't want the focus to actually change, just the "focus frame" (the thin blue border) to appear on the parent LineEdit.
I've tried to draw a rect, but while it works on Windows I'm running into issues of the drawn rectangle not looking like a proper rectangle on ex. Linux, where it is supposed to be rounded. Is there a way to fix this OR, if possible, just make it draw itself as focused despite focus not being on it?
Here's my attempt at drawing a custom rect, but haven't been able to make it successfully mirror the OS style properly.
if (childHasFocus) {
QPainter painter(this);
QLineEdit textBox;
QColor color = textBox.palette().color(QPalette::Highlight);
painter.setPen(color);
QRect rect;
rect.setTopLeft(QPoint(0,0));
rect.setWidth(this->width() - 1);
rect.setHeight(this->height() - 1);
painter.drawRect(rect);
}
EDIT: Added an image of the desired look. Note that I'm trying to get it to look like other LineEdits focusframe independent of OS, so hardcoding a blue rectangle won't work due to ex. Linux having a rounded focusframe.
Desired look:
Here's how to do it. Its a very basic class that draws the focus frame if any of the childs have focus. On focus change, we do an update (which can probably be optimized a bit to avoid unnecessary repaints).
Screenshot:
class IPEdit : public QWidget
{
public:
IPEdit(QWidget *parent = nullptr)
: QWidget(parent)
{
delete layout();
auto l = new QHBoxLayout(this);
setFocusProxy(&a);
setAttribute(Qt::WA_Hover);
for (auto *w : {&a, &b, &c, &d}) {
l->addWidget(w);
w->installEventFilter(this);
}
}
bool eventFilter(QObject *o, QEvent *e) override
{
if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) {
update();
}
return QWidget::eventFilter(o, e);
}
void paintEvent(QPaintEvent *e) override
{
QStyleOptionFrame opt;
opt.initFrom(this);
opt.frameShape = QFrame::StyledPanel;
opt.state |= QStyle::State_Sunken;
// clear mouseOver and focus state
// update from relevant widgets
opt.state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver);
const auto widgets = {&a, &b, &c, &d};
for (const QWidget *w : widgets) {
if (w->hasFocus()) {
opt.state |= QStyle::State_HasFocus;
}
}
opt.rect = contentsRect();
QPainter paint(this);
paint.setClipRegion(e->region());
paint.setRenderHints(QPainter::Antialiasing);
style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this);
}
private:
QLineEdit a;
QLineEdit b;
QLineEdit c;
QLineEdit d;
};
QlineEdit class is also a qwidget, use the setFocus method
https://doc.qt.io/qt-6/qwidget.html#setFocus
Related
I am implementing a round button by subclassing QPushButton and handling the paintEvent. I want to show the text set by the user and then draw a circle.
The application crashes at QPainter::fillRect after calling the QPushButton::paintEvent method. If QPushButton::paintEvent is not called it does not crash, but the button text is not shown.
Here is my code:
class CRoundAnimatingBtn : public QPushButton
{
Q_OBJECT
public:
explicit CRoundAnimatingBtn(QWidget *parent = nullptr) : QPushButton(parent) {}
protected:
void resizeEvent(QResizeEvent *) { setMask(QRegion(rect(), QRegion::Ellipse)); }
void paintEvent(QPaintEvent * e) {
QPainter painter(this);
QPointF center(width()/2, height()/2);
QRadialGradient radialGradient(center, qMin(width(), height())/2, center);
QPushButton::paintEvent(e); // Application crashes if this is called
if (isDown()) {
radialGradient.setColorAt(0.0,Qt::transparent);
radialGradient.setColorAt(0.79, Qt::transparent);
radialGradient.setColorAt(0.80, Qt::gray);
radialGradient.setColorAt(0.95, Qt::black);
radialGradient.setColorAt(0.90, Qt::gray);
radialGradient.setColorAt(0.91, Qt::transparent);
} else {
radialGradient.setColorAt(0.0,Qt::transparent);
radialGradient.setColorAt(0.84, Qt::transparent);
radialGradient.setColorAt(0.85, Qt::gray);
radialGradient.setColorAt(0.90, Qt::black);
radialGradient.setColorAt(0.95, Qt::gray);
radialGradient.setColorAt(0.96, Qt::transparent);
}
painter.fillRect(rect(), radialGradient); // Application crashes here
}
};
How to fix the crash?
Cause
You first create a painter, passing a QPaintDevice *device to the constructor of QPainter, which calls QPainter::begin:
QPainter painter(this);
Then you call the base class implementation of
paintEvent:
QPushButton::paintEvent(e);
which creates a new painter QStylePainter p on the same paint device, before you are done with the first one:
void QPushButton::paintEvent(QPaintEvent *)
{
QStylePainter p(this);
QStyleOptionButton option;
initStyleOption(&option);
p.drawControl(QStyle::CE_PushButton, option);
}
Finally, you try to draw with the first painter QPainter painter using:
painter.fillRect(rectangle, radialGradient);
Important: Such approach is not allowed, as the documentation of QPainter::begin clearly says:
Warning: A paint device can only be painted by one painter at a time.
Solution
Having this in mind, I would suggest you to avoid having two active painters at the same time by moving QPushButton::paintEvent(e); to the very beginning of CRoundAnimatingBtn::paintEvent (before everything else in this event handler).
Note: If you put QPushButton::paintEvent(e); at the very end of CRoundAnimatingBtn::paintEvent, the default implementation will overpaint your custom drawing and it would not be visible.
Example
Here is how the CRoundAnimatingBtn::paintEvent might look like:
void paintEvent(QPaintEvent * e) {
QPushButton::paintEvent(e);
QPainter painter(this);
QPointF center(width()/2, height()/2);
QRadialGradient radialGradient(center, qMin(width(), height())/2, center);
...
painter.fillRect(rect(), radialGradient);
}
The example produces the following result:
As you see, the text is shown together with your custom drawing.
I am trying to create a round button by subclassing and setting the region mask so that I can reuse it in my project. I know we can override paintEvent method and draw a circle to show it as a round button. But the problem with this approach is that if user clicks outside the circle (but within button rect) it will be treated as a button click. This problem we don't see when set the region mask.
I tried to set the region by calling setmask method inside resizeEvent/paintEvent. In either of case, button will be blank. I am trying to figure out the place inside the subclass to set the region mask.
RoundAnimatingButton.h ->
#include <QPushButton>
namespace Ui {
class CRoundAnimatingBtn;
}
class CRoundAnimatingBtn : public QPushButton
{
Q_OBJECT
public:
explicit CRoundAnimatingBtn(QWidget *parent = nullptr);
~CRoundAnimatingBtn();
void StartAnimation(QColor r);
void StopAnimation();
public slots:
void timerEvent(QTimerEvent *e);
private:
Ui::CRoundAnimatingBtn *ui;
bool m_Spinning;
// QWidget interface
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent * e) override;
};
#endif // ROUNDANIMATINGBTN_H
RoundAnimatingButton.cpp
CRoundAnimatingBtn::CRoundAnimatingBtn(QWidget *parent)
: QPushButton (parent)
, ui(new Ui::CRoundAnimatingBtn)
, m_Spinning(false)
{
ui->setupUi(this);
}
CRoundAnimatingBtn::~CRoundAnimatingBtn()
{
delete ui;
}
void CRoundAnimatingBtn::paintEvent(QPaintEvent *e)
{
QPushButton::paintEvent(e);
if(m_Spinning)
{
// Animating code
}
}
void CRoundAnimatingBtn::StartAnimation(QColor r)
{
m_Spinning=true;
startTimer(5);
}
void CRoundAnimatingBtn::StopAnimation()
{
m_Spinning=false;
this->update();
}
void CRoundAnimatingBtn::timerEvent(QTimerEvent *e)
{
if(m_Spinning)
this->update();
else
killTimer(e->timerId());
}
void CRoundAnimatingBtn::DrawRing()
{
}
void CRoundAnimatingBtn::resizeEvent(QResizeEvent *event)
{
// -----------------------------------
// This code didn't work
// -----------------------------------
QRect rect = this->geometry();
QRegion region(rect, QRegion::Ellipse);
qDebug() << "PaintEvent Reound button - " << region.boundingRect().size();
this->setMask(region);
// ----------------------------------
// ------------------------------------
// This code worked
// -------------------------------------
int side = qMin(width(), height());
QRegion maskedRegion(width() / 2 - side / 2, height() / 2 - side / 2, side,
side, QRegion::Ellipse);
setMask(maskedRegion);
}
Qt doc. provides a sample for “non-rectangular” widgets – Shaped Clock Example.
(Un-)Fortunately, I remembered this not before I got my own sample running.
I started in Qt doc. with
void QWidget::setMask(const QBitmap &bitmap)
Causes only the pixels of the widget for which bitmap has a corresponding 1 bit to be visible. If the region includes pixels outside the rect() of the widget, window system controls in that area may or may not be visible, depending on the platform.
Note that this effect can be slow if the region is particularly complex.
The following code shows how an image with an alpha channel can be used to generate a mask for a widget:
QLabel topLevelLabel;
QPixmap pixmap(":/images/tux.png");
topLevelLabel.setPixmap(pixmap);
topLevelLabel.setMask(pixmap.mask());
The label shown by this code is masked using the image it contains, giving the appearance that an irregularly-shaped image is being drawn directly onto the screen.
Masked widgets receive mouse events only on their visible portions.
See also mask(), clearMask(), windowOpacity(), and Shaped Clock Example.
(When reading this, I still missed the link to example.)
At first, I prepared a suitable pixmap for my purpose – dialog-error.png:
for which I converted an SVG from one of my applications.
I tried to apply it to a QPushButton as icon and as mask. This looked very strange. I'm not quite sure what exactly was the problem:
- using the resp. QPushButton as toplevel widget (i.e. main window)
- the fact that QPushButtons icon rendering and the mask may not match concerning position or size.
Without digging deeper, I changed the code and fixed both issues in next try:
making a derived button (like described by OP)
using the button as non-toplevel widget.
This worked soon. I added some code to make the effect more obvious:
a mouse press event handler for main window to show whether shape is considered correctly
a signal handler to show whether clicks on button (in shape) are received correctly.
So, I came to the following sample – testQPushButtonMask.cc:
#include <QtWidgets>
class MainWindow: public QWidget {
public:
explicit MainWindow(QWidget *pQParent = nullptr):
QWidget(pQParent)
{ }
virtual ~MainWindow() = default;
MainWindow(const MainWindow&) = delete;
MainWindow& operator=(const MainWindow&) = delete;
protected:
virtual void mousePressEvent(QMouseEvent *pQEvent) override;
};
void MainWindow::mousePressEvent(QMouseEvent *pQEvent)
{
qDebug() << "MainWindow::mousePressEvent:" << pQEvent->pos();
QWidget::mousePressEvent(pQEvent);
}
class RoundButton: public QPushButton {
private:
QPixmap _qPixmap;
public:
RoundButton(const QPixmap &qPixmap, QWidget *pQParent = nullptr):
QPushButton(pQParent),
_qPixmap(qPixmap)
{
setMask(_qPixmap.mask());
}
virtual ~RoundButton() = default;
RoundButton(const RoundButton&) = delete;
RoundButton& operator=(const RoundButton&) = delete;
virtual QSize sizeHint() const override;
protected:
virtual void paintEvent(QPaintEvent *pQEvent) override;
};
QSize RoundButton::sizeHint() const { return _qPixmap.size(); }
void RoundButton::paintEvent(QPaintEvent*)
{
QPainter qPainter(this);
const int xy = isDown() * -2;
qPainter.drawPixmap(xy, xy, _qPixmap);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QPixmap qPixmap("./dialog-error.png");
// setup GUI
MainWindow qWin;
qWin.setWindowTitle(QString::fromUtf8("QPushButton with Mask"));
QVBoxLayout qVBox;
RoundButton qBtn(qPixmap);
qVBox.addWidget(&qBtn);
qWin.setLayout(&qVBox);
qWin.show();
// install signal handlers
QObject::connect(&qBtn, &RoundButton::clicked,
[](bool) { qDebug() << "RoundButton::clicked()"; });
// runtime loop
return app.exec();
}
The corresponding Qt project file testQPushButtonMask.pro
SOURCES = testQPushButtonMask.cc
QT += widgets
Compiled and tested on cygwin64:
$ qmake-qt5 testQPushButtonMask.pro
$ make && ./testQPushButtonMask
Qt Version: 5.9.4
MainWindow::mousePressEvent: QPoint(23,22)
MainWindow::mousePressEvent: QPoint(62,24)
MainWindow::mousePressEvent: QPoint(62,61)
MainWindow::mousePressEvent: QPoint(22,60)
RoundButton::clicked()
Concerning the output:
I clicked into the four corners of button.
I clicked on the center of button.
I am implementing a text editor on Windows using Qt in C++ and am using a QWidget as the surface on which I am displaying text using OpenGL. So far, I have my own layout engine and document model and am able to get the text to display on the widget.
Now I am trying to implement a text cursor that will be used similar to the one Qt provides, but QTextCursor is closely tied to its QTextDocument model and I am not able to subclass it for reuse using my model. Is there any way to reuse just the cursor without the model?
If not, how do I go about implementing a text cursor using Qt?
Note: I did go through the Caret methods that Windows provides here, but am hoping to avoid using them directly.
TL;DR: You can't. It's not a caret.
QTextCursor is an iterator, it has nothing to do whatsoever with the on-screen cursor. You can certainly reuse it if it is useful as an iterator, and iff your own text representation is built on top of QTextDocument. But it's not a caret.
The visible cursor control is provided by the QTextEdit implementation. Recall that QTextEdit is a view for a QTextDocument - it is entirely devoted to the graphical rendering of the text, controlling the visible cursor, etc.
There is no public Qt API that you can use for the caret. The WINAPI caret methods are completely useless when you're using Qt for rendering. You need your own caret implementation. Given that you already have a text representation and a renderer, you presumably have an iterator that works on the text representation, so implementing the caret should be a trivial affair.
The WINAPI caret is very simple and trivial to reimplement with the power of Qt:
class Caret : public QWidget {
Q_OBJECT
Q_PROPERTY(int period READ period WRITE setPeriod)
QPicture m_shape;
BasicTimer m_timer;
int m_period;
void updateSize() {
auto size = m_shape.boundingRect().size();
setFixedSize(size);
resize(size);
}
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_timer.timerId()) return;
if (isVisible()) hide(); else show();
}
public:
QPicture defaultShape(int w, int h) {
QPicture pic;
QPainter p(&pic);
p.fillRect(0, 0, w, h, Qt::black);
return pic;
}
Caret(QWidget * parent = 0, const QPicture & pic = defaultShape()) :
QWidget(parent), m_shape(pic), m_period(250) {
setAttribute(Qt::WA_TransparentForMouseEvents);
setAttribute(Qt::WA_TranslucentBackground);
m_timer.start(m_period);
updateSize();
}
void setShape(const QPicture & pic) {
m_shape = pic;
updateSize();
update();
}
void setPeriod(int period) {
if (period < 1) {
m_timer.stop();
if (m_period > 0) show();
} else
m_timer.start(period);
m_period = period;
}
int period() const { return m_period; }
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE {
auto painter = QPainter(this);
painter.fillRect(rect(), Qt::transparent);
painter.drawPicture(QPoint(), m_shape);
}
};
When I set the placeholder text with QLineEdit::setPlaceholderText(), it appears gray.
Is there any way to change the color to something else, for example red?
You'll have to subclass QLineEdit and paint your own placeholder in the paintEvent().
class CustomColorPlaceholderLineEdit : public QLineEdit
{
public:
CustomColorPlaceholderLineEdit(QWidget * parent = 0) : QLineEdit(parent) { color = QColor(0,0,0,128); }
void setCustomPlaceholderText(const QString &text) { this->mText = text; }
const QString &customPlaceholderText() const { return mText; }
void setCustomPlaceholderColor(const QColor &color) { this->color = color; }
const QColor &customPlaceholderColor() const { return color; }
void paintEvent(QPaintEvent *event) {
QLineEdit::paintEvent(event);
if (!hasFocus() && text().isEmpty() && !mText.isEmpty()) {
// QLineEdit's own placeholder clashes with ours.
Q_ASSERT(placeholderText().isEmpty());
QPainter p(this);
p.setPen(color);
QFontMetrics fm = fontMetrics();
int minLB = qMax(0, -fm.minLeftBearing());
QRect lineRect = this->rect();
QRect ph = lineRect.adjusted(minLB + 3, 0, 0, 0);
QString elidedText = fm.elidedText(mText, Qt::ElideRight, ph.width());
p.drawText(ph, Qt::AlignVCenter, elidedText);
}
}
private:
QString mText;
QColor color;
};
There is another a bit hacky but simple and reliable way.
connect(lineEdit, &QLineEdit::textChanged, this, &YourClass::updateLineEditStyleSheet);
void YourLineEdit::updateLineEditStyleSheet()
{
if (lineEdit->text().isEmpty()) {
lineEdit->setStyleSheet("#lineEdit { color: lightGray;"); // Set your color but remember that Qt will reduce alpha
} else {
lineEdit->setStyleSheet("#lineEdit { color: black;"); // usual color
}
}
also you can use this way to derived from QLineEdit class
If you want to use QSS instead of QPalette, try the following:
setStyleSheet("QLineEdit{"
" color: red;" //TEXT COLOR
"}"
"QLineEdit[text=\"\"]{"
" color: gray;" //TEXTHOLDER COLOR
"}");
connect(ui->lineEdit, &QLineEdit::textChanged, [=]{ style()->polish(ui->lineEdit); });
You can change the color, but bare in mind there is an alpha factor set in the placeholder from the source code (as mentioned in another comment) that cannot be removed. Therefore you will always see the placeholder darker (no white possible with this option).
You can't, at least with the current QLineEdit code.
As you can see from the source code, the placeholder text is simply taking the foreground brush of the palette and making it partially transparent, see QLineEdit::paintEvent:
if (d->shouldShowPlaceholderText()) {
if (!d->placeholderText.isEmpty()) {
QColor col = pal.text().color();
col.setAlpha(128);
QPen oldpen = p.pen();
p.setPen(col);
QRect ph = lineRect.adjusted(minLB, 0, 0, 0);
QString elidedText = fm.elidedText(d->placeholderText, Qt::ElideRight, ph.width());
p.drawText(ph, va, elidedText);
p.setPen(oldpen);
}
}
You can work with upstream into a more general solution, though. In particular I one would expect that color to be added to the palette, or in general provided by the current QStyle (for instance as a style hint).
If you want to change placeholder text color for a QLineEdit you have to customize the component's QPalette object.
QPalette p = lineEdit->palette();
p.setColor(QPalette::Mid, Qt::red); // assuming Mid is the color you want to change.
lineEdit->setPalette(p);
I don't recall exactly which QPalette::ColorRole is appropriate for changing QLineEdit's placeholder text color though.
#Meefte solution is quite good given the situation that Qt gives placeholder the same color as for the text, except it adds 50% opacity. So, there is little choice to set placeholder color to be different than the text. However, even this solution could be improved by making sure that you would not need to set some other variable than the default one Qt provides you.
The need to use default placeholderText() might arise from the situation when you have lots of QLineEdit controls which are already promoted to some control overriding QLineEdit behavior, and placeholderText() is already set through code or through Qt Creator, i.e. it would be a bit painful to introduce another dynamic property. However, if you did not promote to some child control, then it would be a necessity to do so in order to use such solution.
class CustomColorPlaceholderLineEdit : public QLineEdit
{
public:
CustomColorPlaceholderLineEdit(QWidget * parent = 0) : QLineEdit(parent) { color = QColor(0,0,0,128); }
const QString &customPlaceholderText() const { return mText; }
void setCustomPlaceholderColor(const QColor &color) { this->color = color; }
const QColor &customPlaceholderColor() const { return color; }
void paintEvent(QPaintEvent *event)
{
if(color.isValid() && text().isEmpty() && (!placeholderText().isEmpty() || !mText.isEmpty()))
{
if(!placeholderText().isEmpty())
{
// In this way, placeholderText() is taken into local variable 'mText' care. Whenever placeholderText() will change, there it will be taken care of.
mText = placeholderText();
// This will ensure Qt will not draw placeholder for us.
setPlaceholderText("");
}
// By this, we make sure Qt will paint QLineEdit default parts properly.
QLineEdit::paintEvent(e);
// And now #Meefte code is reused here.
QPainter p(this);
p.setPen(color);
QFontMetrics fm = fontMetrics();
int minLB = qMax(0, -fm.minLeftBearing());
QRect lineRect = this->rect();
QRect ph = lineRect.adjusted(minLB + 3, 0, 0, 0);
QString elidedText = fm.elidedText(mText, Qt::ElideRight, ph.width());
p.drawText(ph, Qt::AlignVCenter, elidedText);
return; // No need to paint again.
}
// Default Qt's painting behavior for QLineEdit.
QLineEdit::paintEvent(e);
}
private:
QString mText;
QColor color;
};
QT still has this problem)
I solved it like this:
bool CustomLineEdit::event(QEvent *event)
{
bool eventResult = QLineEdit::event(event);
if (event->type() == QEvent::StyleChange) {
QPalette pal = palette();
pal.setColor(QPalette::PlaceholderText, Qt::red);
setPalette(pal);
}
return eventResult;
}
I need to implement a "Loading..." window in my application but I do prefer to cover the whole QMainWindow with a dark transparent layer with a text above. Does anybody know how to do that? I am not sure how to overlap widgets/layouts in Qt. Any help will be appreciated.
This answer is in a series of my overlay-related answers: first, second, third.
The most trivial solution is to simply add a child transparent widget to QMainWindow. That widget must merely track the size of its parent window. It is important to properly handle changes of widget parentage, and the z-order with siblings. Below is a correct example of how to do it.
If you want to stack overlays, subsequent overlays should be the children of OverlayWidget, in the z-order. If they were to be siblings of the OverlayWidget, their stacking order is undefined.
This solution has the benefit of providing minimal coupling to other code. It doesn't require any knowledge from the widget you apply the overlay to. You can apply the overlay to a QMainWindow or any other widget, the widget can also be in a layout.
Reimplementing QMainWindow's paint event would not be considered the best design. It makes it tied to a particular class. If you really think that a QWidget instance is too much overhead, you better had measurements to show that being the case.
It is possible, of course, to make the overlay merely a QObject and to put the painting code into an event filter. That'd be an alternative solution. It's harder to do since you have to also properly deal with the parent widget's Qt::WA_StaticContents attribute, and with the widget potentially calling its scroll() method. Dealing with a separate widget is the simplest.
// https://github.com/KubaO/stackoverflown/tree/master/questions/overlay-widget-19362455
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
class OverlayWidget : public QWidget
{
void newParent() {
if (!parent()) return;
parent()->installEventFilter(this);
raise();
}
public:
explicit OverlayWidget(QWidget * parent = {}) : QWidget{parent} {
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
newParent();
}
protected:
//! Catches resize and child events from the parent widget
bool eventFilter(QObject * obj, QEvent * ev) override {
if (obj == parent()) {
if (ev->type() == QEvent::Resize)
resize(static_cast<QResizeEvent*>(ev)->size());
else if (ev->type() == QEvent::ChildAdded)
raise();
}
return QWidget::eventFilter(obj, ev);
}
//! Tracks parent widget changes
bool event(QEvent* ev) override {
if (ev->type() == QEvent::ParentAboutToChange) {
if (parent()) parent()->removeEventFilter(this);
}
else if (ev->type() == QEvent::ParentChange)
newParent();
return QWidget::event(ev);
}
};
class LoadingOverlay : public OverlayWidget
{
public:
LoadingOverlay(QWidget * parent = {}) : OverlayWidget{parent} {
setAttribute(Qt::WA_TranslucentBackground);
}
protected:
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.fillRect(rect(), {100, 100, 100, 128});
p.setPen({200, 200, 255});
p.setFont({"arial,helvetica", 48});
p.drawText(rect(), "Loading...", Qt::AlignHCenter | Qt::AlignVCenter);
}
};
int main(int argc, char * argv[])
{
QApplication a{argc, argv};
QMainWindow window;
QLabel central{"Hello"};
central.setAlignment(Qt::AlignHCenter | Qt::AlignTop);
central.setMinimumSize(400, 300);
LoadingOverlay overlay{¢ral};
QTimer::singleShot(5000, &overlay, SLOT(hide()));
window.setCentralWidget(¢ral);
window.show();
return a.exec();
}
I would suggest execute a modal, frameless dialog on top and add a graphics effect on the background widget.
This is IMHO a very flexible and short solution without touching the event system directly.
The performance might be bad - one could improve that by calling drawSource(), but I haven't a reliable solution here yet.
class DarkenEffect : public QGraphicsEffect
{
public:
void draw( QPainter* painter ) override
{
QPixmap pixmap;
QPoint offset;
if( sourceIsPixmap() ) // No point in drawing in device coordinates (pixmap will be scaled anyways)
pixmap = sourcePixmap( Qt::LogicalCoordinates, &offset );
else // Draw pixmap in device coordinates to avoid pixmap scaling;
{
pixmap = sourcePixmap( Qt::DeviceCoordinates, &offset );
painter->setWorldTransform( QTransform() );
}
painter->setBrush( QColor( 0, 0, 0, 255 ) ); // black bg
painter->drawRect( pixmap.rect() );
painter->setOpacity( 0.5 );
painter->drawPixmap( offset, pixmap );
}
};
// prepare overlay widget
overlayWidget->setWindowFlags( Qt::FramelessWindowHint | Qt::Dialog | Qt::WindowStaysOnTopHint );
// usage
parentWidget->setGraphicsEffect( new DarkenEffect );
overlayWidget->exec();
parentWidget->setGraphicsEffect( nullptr );
If you are talking about using a separate layout/widget over your 'Main Window', you could just make the "Loading..." window modal either through the UI editor or in the constructor for your UI.