Subclassed QWidget does not move correctly - qt

I have subclassed QWidget as follows:
class myClass : public QWidget
{
public:
explicit myClass(QWidget *parent);
protected:
void paintEvent(QPaintEvent *event);
}
myWidget::myWidget(QWidget* parent) : QWidget(parent)
{
setGeometry(10,10,100,100);
}
void myWidget::paintEvent(QPaintEvent *event)
{
QPainter qp(this);
QBrush bBlue(QColor::blue);
qp.fillRect(geometry(), bBlue);
}
What I wanted was to create a blue background QWidget placed onto the QWidget parent at 10,10 of size 100,100.
What I'm getting is a default size for myWidget of something like 100,50 at 0,0 with a black background (or transparent) and a blue rectangle starting at 10,10 within myWidget and clipped by myWidget.
It's like the setGeometry moved a rectangle within myWidget, not the myWidget itself.
Fairly new to Qt and would love an explanation and fix of above...
Thank you in advance.
Gary.
...here is actual code:
this is myWidget
class piTemplateWidget : public QWidget
{
public:
explicit piTemplateWidget(QWidget* parent);
static QColor* white;
static QColor* black;
static QColor* lightGrey;
static QColor* lightGreen;
piTemplate* tplt;
protected:
void paintEvent(QPaintEvent *event);
};
QColor* piTemplateWidget::white = new QColor(15,15,15);
QColor* piTemplateWidget::black = new QColor(250,250,250);
QColor* piTemplateWidget::lightGrey = new QColor(100,100,100);
QColor* piTemplateWidget::lightGreen = new QColor(250,15,250);
piTemplateWidget::piTemplateWidget(QWidget* parent) : QWidget(parent)
{
tplt = NULL;
move(100,100);
resize(300,240);
}
void piTemplateWidget::paintEvent(QPaintEvent *event)
{
QPainter qp(this);
QBrush bWhite(*white);
qp.fillRect(this->geometry(), bWhite);
// if (tplt==NULL)
// return;
// tplt->render(&qp);
}
...and this is the parent widgets constructor which instantiates my widget
piTemplateEdit::piTemplateEdit(QWidget *parent) :
QWidget(parent),
ui(new Ui::piTemplateEdit)
{
ui->setupUi(this);
currentTemplate = NULL;
if (piTemplate::templates->count()>0)
{
currentTemplate = (piTemplate*)piTemplate::templates->atIndex(0);
}
templateWidget = new piTemplateWidget(this);
templateWidget->tplt = currentTemplate;
}
...I hopes this helps.
Thank you.

Setting the geometry during the constructor may get overridden by the show event that the parent widget calls on it.
A common main function can look like this:
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
// w.showMaxmized(); // This line would trump the "setGeometry() call
// in the constructor
return a.exec();
}
The geometry rect stored in a QWidget is described here:
http://qt-project.org/doc/qt-4.8/application-windows.html
http://qt-project.org/doc/qt-4.8/qwidget.html#pos-prop
I would not use this internal QWidget setting as how you fill your widget. If you do want to store some setting, make a QRect member variable and use that instead.
If you want to fill the entire box of your QWidget with a color you should try something like this:
void myWidget::paintEvent(QPaintEvent *event)
{
QPainter qp(this);
QBrush bBlue(QColor::blue);
qp.fillRect(QRect(0,0, this->width(), this->height()), bBlue);
}
Inside paint functions, they are relative to paintable area you are in.
http://qt-project.org/doc/qt-4.8/qwidget.html#mapTo
And like #LaszloPapp was saying, you need to use resize() and move(). And it wouldn't hurt to throw in a update() call after either one of those.
Also be sure to check out the show() method and all of its "See Also" items.
http://qt-project.org/doc/qt-4.8/qwidget.html#show
http://qt-project.org/doc/qt-4.8/qshowevent.html
If you #include <QShowEvent>, and call resize() when the show event happens, you may be good to go. If you are nesting this widget inside another widget you should look into using the size hint and setFixedSize or using Layouts properly.
http://qt-project.org/doc/qt-4.8/layout.html
Hope that helps.

Related

Reparenting a Qt widget

I have a WidgetA widget, which is an owner-drawn widget. It's currently placed in QMainWindow's QVBoxLayout. After clicking a button, I'd like to "detach" WidgetA from this QVBoxLayout, insert QSplitter into this QVBoxLayout and "readd" WidgetA to this QSplitter. All this without destroying WidgetA, so it will preserve its drawing context, etc.
So, currently I have this (only one widget in a window):
I'd like to put a QSplitter between WidgetA and QMainWindow, and create a new widget, WidgetB, so I'd end up with:
Later I'd like it to split even further, so both WidgetA and WidgetB would still allow themselves to be detached and placed in a new QSplitter, so it would be possible to create f.e. this hierarchy:
And, to be complete, one more step:
I'm not very experienced in Qt, so what I'm trying to do may seem pretty obvious, but I couldn't find how to "reparent" widgets. Is this possible in Qt?
Please, see reparent example, may be it helps you:
//MyMainWindow.h
#include <QWidget>
#include <QPainter>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QSplitter>
class MyWidget: public QWidget
{
public:
MyWidget(QWidget* parent, int number)
: QWidget(parent),
m_number(number)
{
}
private:
virtual void paintEvent(QPaintEvent* e)
{
QWidget::paintEvent(e);
QPainter p(this);
p.fillRect( rect(), Qt::red);
p.drawText( rect(), Qt::AlignCenter, QString::number(m_number) );
}
private:
int m_number;
};
class MyMainWindow: public QWidget
{
Q_OBJECT
public:
MyMainWindow()
{
setFixedSize(300, 200);
m_mainLayout = new QVBoxLayout(this);
QHBoxLayout* buttonLayout = new QHBoxLayout;
m_mainLayout->addLayout(buttonLayout);
m_button = new QPushButton("Button", this);
buttonLayout->addWidget(m_button);
connect(m_button, SIGNAL(clicked()), this, SLOT(onButtonClickedOnce()));
m_initWidget = new MyWidget(this, 1);
m_mainLayout->addWidget(m_initWidget);
}
private slots:
void onButtonClickedOnce()
{
m_button->disconnect(this);
m_mainLayout->removeWidget(m_initWidget);
QSplitter* splitter = new QSplitter(Qt::Horizontal, this);
m_mainLayout->addWidget(splitter);
splitter->addWidget(m_initWidget);
MyWidget* newWidget = new MyWidget(splitter, 2);
splitter->addWidget(newWidget);
}
private:
QVBoxLayout* m_mainLayout;
QWidget* m_initWidget;
QPushButton* m_button;
};
//main.cpp
#include <QtWidgets/QApplication>
#include "MyMainWindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyMainWindow mainWindow;
mainWindow.show();
return a.exec();
}
When you operate with widget which is part of layout, then you need to use appropriate methods of QLayout (parent of QVBoxLayout) to detach the item from layout:
QLayout::removeWidget (removeItem if it is not widget, but spacer item or another layout)
QLayout::addWidget (addItem --/--)
Btw: even when widget moves between layouts, its parent may even stay same. I guess you have no need to call QWidget::setParent() as the calls of addWidget/removeWidget will do all work for you.

Qt : Child graphic items hover event

I have two items of type QGraphicsRectItem. One over the other.
The first one is a custom class called Wall. Inside the wall there are windows and doors.
In fact, i have a list of Doors and Windows inside this custom Wall class.
The Doors are Items too, and are drawn inside the wall.
When i move the mouse over the door, the hover function of the wall is emited, but the hover of the door is not. Both of them correctly copied one from each other as virtual void protected.
Why is that happening? How can i make the door and window items realize about the hover?.
I have tried with a custom QGraphicsItem instead of a custom QGraphicsRectItem. It seems the hover event handler is successfully called for both Wall and Door. This is happening when explicitly setting QGraphicsItem with setAcceptHoverEvents(true). This is not tested with custom QGraphicsRectItem.
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsItem>
#include <QPainter>
class Item : public QGraphicsItem
{
QBrush m_brush;
public:
explicit Item(bool nested = true, QGraphicsItem* parent = 0) : QGraphicsItem(parent), m_brush(Qt::white)
{
if (nested) {
Item* item = new Item(false, this);
item->setPos(10,10);
m_brush = Qt::red;
}
setAcceptHoverEvents(true);
}
QRectF boundingRect() const
{
return QRectF(0,0,100,100);
}
void hoverEnterEvent(QGraphicsSceneHoverEvent *)
{
m_brush = Qt::red;
update();
}
void hoverLeaveEvent(QGraphicsSceneHoverEvent *)
{
m_brush = Qt::white;
update();
}
void paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
{
p->setBrush(m_brush);
p->drawRoundRect(boundingRect());
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene scene;
scene.addItem(new Item);
QGraphicsView view;
view.setScene(&scene);
view.setMouseTracking(true);
view.show();
return app.exec();
}

Qt Beginner QPainter and QRect

How would I go about drawing a rectangle?
I have tried two different ways;
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
QRect rect = QRect(290, 20, 70, 40);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
Which works fine (even though the parameter is not named nor used), but I don't want to use the QPaintEvent * I have no use for it.
So I tried just renaming my function;
void MyWidget::draw()
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
QRect rect = QRect(290, 20, 70, 40);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
This doesn't display anything (yet has no errors).
Why would it not work if I don't use QPaintEvent * ??
The paint event is the method that is called by the paint system when a widget needs to be redrawn. That is why simply naming your own method does not work. It is never called by the paint system.
You really should be using the QPaintEvent. It gives you the rect that needs to be drawn. This rect will be based upon the size of the widget, so instead of using an explicit rect in your paint event, set your widget to the right size. A paint event will be generated should your widget ever move, resize, etc.
void MyWidget::paintEvent(QPaintEvent *event)
{
QRect rect = event->rect();
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
Now if you want to separate your paint logic into another method, that is fine. But you would need to have it called from the paint event:
void MyWidget::paintEvent(QPaintEvent *event)
{
QRect rect = event->rect();
draw(rect);
}
void MyWidget::draw(QRect &rect)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
If you want to completely bypass the paint events as you said, and only want to create a static rectangle to display, one way is to just draw it once to a pixmap and display it in a QLabel:
QPixMap pix(200,100);
QPainter painter(&pix);
// do paint operations
painter.end()
someLabel.setPixmap(pix)
Any data that your paintEvent() needs should be accessible as fields of the containing class, in your case, private fields of MyWidget. These private fields can be exposed to clients of MyWidget via "setters" which would set the data values before calling update() on MyWidget which will trigger a call to paintEvent().
This playlist contains the best Qt tutorials , starting tutorial 74 would be useful for you (Qpainter and QPen), tutorial 75 is how to draw rectangles using QRect.
As well #Mat told you: the "event" is the correct way to launch a painter.
QPainter can only be evoked after a QPaintEvent event, which carries the safe region where the object may be drawn.
So you must find another strategy to transport your data, to help
I will propose a method simple, which can be adjusted to many cases.
widget.cpp
#include <QtGui>
#include "widget.h"
#define MIN_DCX (0.1)
#define MAX_DCX (5.0)
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
dcx=MIN_DCX;
setFixedSize(170, 100);
}
void Widget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter;
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
pcx=dcx*2;
QRect rect = QRect(50-dcx,25-dcx,60+pcx,40+pcx);
painter.drawText(rect, Qt::AlignCenter,printData);
painter.drawRect(rect);
painter.end();
}
void Widget::setPrintData(QString value){
printData = value;
dcx=(dcx>MAX_DCX)?MIN_DCX:dcx+MIN_DCX;
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent);
void setPrintData(QString value);
protected:
void paintEvent(QPaintEvent *event);
private:
QString printData;
float dcx;
float pcx;
};
#endif
window.cpp
#include <QtGui>
#include "widget.h"
#include "window.h"
#define MAX_SDCX 20
Window::Window()
: QWidget()
{
gobject = new Widget(this);
textMode=1;
rectMode=1;
gobject->setPrintData(msgs[textMode]);
QGridLayout *layout = new QGridLayout;
layout->addWidget(gobject, 0, 0);
setLayout(layout);
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(dataOnAir()));
timer->start(10);
setWindowTitle(tr("Rect Shaking"));
}
void Window::dataOnAir(){
if((++rectMode)>MAX_SDCX){
rectMode=0;
textMode^=1;
}
gobject->setPrintData(msgs[textMode]);
gobject->repaint();
}
window.h
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
#include "widget.h"
class Window : public QWidget
{
Q_OBJECT
public:
Window();
private slots:
void dataOnAir();
private:
Widget *gobject;
const QString msgs[2] = {"Hello","World"};
int textMode;
int rectMode;
};
#endif
main.cpp
#include <QApplication>
#include "window.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}
As you can see in the code is executed a timer, outside the object "widget"
every 10ms sends a repaint the widget to redraw a "rect" with a different size and every 20 cycles (200ms) changes the text "hello" for "world"
In this example you can see that in any way need overwrite the QPainterDevice architecture.
You may also notice that the "event" within the "paintEvent" is silenced and not used directly, but it is essential to execute a sequence QPainter.
Overriding the paintEvent() function of a widget enables you to customize the widget and this function is called periodically to redraw the widget. Therefore any drawing should be made in this function. However overriding paintEvent() may cause some performance issues. I would prefer using a QGraphicsScene and QGraphicsView then I would add a rectangle to the scene which is the common way of doing this kind of drawing stuff. Please check the GraphicsView Framework
http://qt-project.org/doc/qt-4.8/graphicsview.html

Paint/Draw on top of docked widgets in QDodckWidget

I have a class in Qt that inherits QDockWidget. And that class contains another widget.
Is there any possibility to define a function in my QDockWidget inherited class that draws stuff on top of the contained widget? Like the painting to be independent of the contained widget but to be linked to the inherited class.
Thank you
Sure it's possible. It is fairly simple to do, in fact. You need to place a child widget that sits on top of everything else in your QDockWidget. To do it so, it must be the last child widget you add to your dockwidget. That widget must not to draw its background, and it can then draw over any children of the dockwidget. The widget's size must track the size of the parent widget.
Below is a self-contained example.
// https://github.com/KubaO/stackoverflown/tree/master/questions/overlay-line-11034838
#include <QtGui>
#if QT_VERSION > QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
class Line : public QWidget {
protected:
void paintEvent(QPaintEvent *) override {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.drawLine(rect().topLeft(), rect().bottomRight());
}
public:
explicit Line(QWidget *parent = nullptr) : QWidget(parent) {
setAttribute(Qt::WA_TransparentForMouseEvents);
}
};
class Window : public QWidget {
QHBoxLayout layout{this};
QPushButton left{"Left"};
QLabel right{"Right"};
Line line{this};
protected:
void resizeEvent(QResizeEvent *) override {
line.resize(size());
}
public:
explicit Window(QWidget *parent = nullptr) : QWidget(parent) {
layout.addWidget(&left);
right.setFrameStyle(QFrame::Box | QFrame::Raised);
layout.addWidget(&right);
line.raise();
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Window w;
w.show();
return app.exec();
}
AFAIK: No.
Widgets are drawn in depth order, so whatever your QDockWidget derived class paints, will be drawn over by the contained widgets when they are updated (immediately afterwards no doubt, because paint updates are propagated to child widgets).
Python version for the accepted answer:
# Created by BaiJiFeiLong#gmail.com at 2022/1/15 10:22
from PySide2 import QtWidgets, QtGui, QtCore
app = QtWidgets.QApplication()
widget = QtWidgets.QWidget()
line = QtWidgets.QFrame(widget)
line.paintEvent = lambda _: QtGui.QPainter(line).drawLine(line.rect().topLeft(), line.rect().bottomRight())
line.setAttribute(QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents)
widget.setLayout(QtWidgets.QGridLayout(widget))
widget.layout().addWidget(QtWidgets.QPushButton("CLICK ME", widget))
widget.resizeEvent = lambda event: line.resize(event.size())
line.raise_()
widget.show()
app.exec_()
Note that this will not work for QSplitter, in this condition, you should use QMainWindow as parent widget.

How to make a QWidget alpha-transparent

I need to create an alpha transparent widget, it's basically a navigation bar with a shadow and the widgets below need to be partially visible through the shadow. The widget loads a PNG then draws it on the paint event. The problem is that the shadow is all black and is not alpha-transparent.
This is the code I'm currently using:
NavigationBar::NavigationBar(QWidget *parent) : XQWidget(parent) {
backgroundPixmap_ = new QPixmap();
backgroundPixmap_->load(FilePaths::skinFile("NavigationBarBackground.png"), "png");
setAttribute(Qt::WA_NoBackground, true); // This is supposed to remove the background but there's still a (black) background
}
void NavigationBar::paintEvent(QPaintEvent* event) {
QWidget::paintEvent(event);
QPainter painter(this);
int x = 0;
while (x < width()) {
painter.drawPixmap(x, 0, backgroundPixmap_->width(), backgroundPixmap_->height(), *backgroundPixmap_);
x += backgroundPixmap_->width();
}
}
Does anybody know what I need to change to make sure the widget is really transparent?
You're doing too much work :-)
The setAttribute call is not necessary. By default, a widget will not draw anything on its background (assuming Qt >= 4.1). Calling QWidget::paintEvent is also unnecessary - you don't want it to do anything.
Rather than doing the pattern fill yourself, let Qt do it with a QBrush:
NavigationBar::NavigationBar(QWidget *parent) : XQWidget(parent) {
backgroundPixmap_ = new QPixmap();
backgroundPixmap_->load(FilePaths::skinFile("NavigationBarBackground.png"), "png");
// debug check here:
if (!backgroundPixmap_->hasAlphaChannel()) {
// won't work
}
}
void NavigationBar::paintEvent(QPaintEvent* event) {
QPainter painter(this);
painter.fillRect(0, 0, width(), height(), QBrush(*backgroundPixmap));
}
Adjust the height parameter if you don't want the pattern to repeat vertically.
Are you sure your PNG file is actually transparent? The following (which is essentially what you are doing) is working for me. If this fails on your machine, perhaps include what version of Qt you are using, and what platform.
#include <QtGui>
class TransparentWidget : public QWidget {
public:
TransparentWidget()
: QWidget(),
background_pixmap_(":/semi_transparent.png") {
setFixedSize(400, 100);
}
protected:
void paintEvent(QPaintEvent *) {
QPainter painter(this);
int x = 0;
while (x < width()) {
painter.drawPixmap(x, 0, background_pixmap_);
x += background_pixmap_.width();
}
}
private:
QPixmap background_pixmap_;
};
class ParentWidget : public QWidget {
public:
ParentWidget() : QWidget() {
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(new TransparentWidget);
layout->addWidget(new QPushButton("Button"));
setLayout(layout);
setBackgroundRole(QPalette::Dark);
setAutoFillBackground(true);
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
ParentWidget w;
w.show();
return app.exec();
}

Resources