I created a simple widget with a button, a slot for the button, a resize event and a paint event.
I expect when I click on the button it draws an ellipse at a random position and the button disappears.
But I get: the ellipse is drawn and the button is not hidden after this->update.
Even stranger, when I uncomment the button->hide(); every time I click it draws a new eclipse but the old ellipses are still there. Something is wrong with updating and the paint event.
If I resize the window by dragging with the mouse the update of the paint event works as expected. Only the last ellipse stays and the button is hidden.
My Qt version is Qt_5_15_2_MinGW_32_bit
Here is the code of the widget:
PATrackSetter::PATrackSetter(QWidget *parent) : QWidget(parent){
button = new PAButton(this);
connect(button,SIGNAL(clicked(int, QString, QString)),this,SLOT(on_TileClicked(int, QString, QString)));
button->setFixedSize(100, 100);
button->move(0,0);
button->show();
}
void PATrackSetter::paintEvent(QPaintEvent *){
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QPen pen = QPen();
pen.setColor(Qt::yellow);
painter.setPen(pen);
painter.drawEllipse(100,rand() % 500 +10,5,5);
}
void PATrackSetter::resizeEvent(QResizeEvent *)
{
}
void PATrackSetter::on_TileClicked(int buttonID, QString buttonText, QString newButtonStatus){
button->hide();
this->update();
}
Can anyone see what I did wrong?
Edit:
I added more code to the project and I run into the same issue. I added the following lines into the MainWindow class and the updating inside the PATrackSetter widget doesn't work anymore as expected. I really dont understand why. But if I uncomment these lines it works again well.
QPalette paletteBGColor;
QBrush brush;
brush.setColor(Qt::black);
paletteBGColor.setBrush(QPalette::Background, brush);
this->setPalette(paletteBGColor);
Case closed.
If the button is not hidden then slot is not called. I guess you didn't put void on_TileClicked(int, QString, QString) in slots: section in header file, or signal/slot signatures don't match (in which case there must be warning in debug output in runtime).
When you are reimplementing paintEvent you should expect that every update on the QWidget, even manually or by the parent window, will call the paintEvent once. So, it's up to you to handle cleaning the previous state or draw on the previous drawings. The behavior you explained is quite normal.
It seems that you are not calling setGeometry on the PATrackSetter when you are instantiating it. So, in the update hierarchy, its size is not known and you should expect partial redraws and undefined behaviors.
Related
How can I make the button go beyond the edge of QToolbar?
Below is the code as I create the toolbar:
mainwindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0)
private:
QToolBar* _toolBar;
};
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
_toolBar = new QToolBar;
QAction *actionAdd = new QAction(QIcon(":/images/add.png"), "", this);
_toolBar->addAction(actionAdd);
addToolBar(Qt::ToolBarArea::TopToolBarArea, _toolBar);
}
style.qss
QToolBar {
background: #018ac4;
height: 150px;
}
As said before, it is not possible to solve this correctly using QtWidgets.
However I see two options to visually create that effect:
Take the button out of the tool bar and add it to the main window instead, but do not add it to a layout. Usually i would say reposition it on resize events, but since it is in the top left, you might as well just call setGeometry() once on startup and not worry about it later. You probably have to add last, or call rise() though.
Make it look like the button sticks out, while it really doesn't. Make the toolbar as large as the button, but paint the lower part of the toolbar in the brighter blue, so that it looks like it is part of the widget below it.
It is not possible with widgets. A QWidget can not paint outside of its area. See this answer : https://stackoverflow.com/a/48302076/6165833.
However, the QToolBar is not really the parent of the QAction because addAction(QAction *action) does not take the ownership. So maybe the QMainWindow could paint your QAction the way you want but AFAIK this is not doable through the public API of Qt.
What you could do is use QML (but you would need to use QML for the whole window then).
I want to draw a graph on my main form, so I figured I'd use a QLabel and Override that. Like this:
// drawlabel.h
class DrawLabel : public QLabel
{
Q_OBJECT
public:
DrawLabel(QWidget *parent = 0);
private:
void paintEvent(QPaintEvent *);
};
// drawlabel.cpp
DrawLabel::DrawLabel(QWidget *parent)
: QLabel(parent)
{
}
void DrawLabel::paintEvent(QPaintEvent *)
{
qDebug() << "paint event" ;
QPainter painter(this);
painter.setPen(QPen(QBrush(QColor(0,0,0,180)),1,Qt::DashLine));
painter.setBrush(QBrush(QColor(255,255,255,120)));
QRect selectionRect(10, 10, 100, 101);
painter.drawRect(selectionRect);
}
On my main window I droppde a QLabel, sized it to about 500x200 and promoted it to DrawLLabel. When the application is run, a dashed square is drawn on the form.
All good so far.
If I add the line:
this->setText("123456");
into the DrawLabel constructor, or add it into the paintEvent() I don't see the text. I'd also like to be able to have a border around the DrawLabel, but
this->setFrameShape(QFrame::Box);
in the constructor doesn't work either.
What should I be doing to get these to work?
Well, I think you should call paintEvent of base class. Add parameter name e to method:
void DrawLabel::paintEvent(QPaintEvent *e)
And then at end of method add
QLabel::paintEvent (e);
The second option do all painting by yourself directly at paintEvent.
If you want something custom, then implement a custom widget inheriting QWidget. Then you get to draw whatever you want and have whatever members you want.
Your problem is you have overridden the label's paint event, so the code to draw the label text is not executed.
You could call the method from QLabel as Evgeny suggested, but it is better to implement a custom widget instead.
Calling the method from the base class might for example corrupt any previous drawing, unless the method was implemented with calling form derived classes in mind. I don't expect that is the case for stock widgets. I haven't tried doing it with QLabel re-implementations in particular, but I have tried it with other stock widgets and it did not work as expected.
I'm new to Qt and started developing an app based on one of the sample projects qt provides: "Image Viewer".
Here's the examle project itself: http://doc.qt.io/qt-4.8/qt-widgets-imageviewer-example.html
To make sure I didn't do anything wrong thying to extend project's functionality, I created a new project and simply copied files from example project (see section "Files" at the top of "Image Viewer Example" page)
Then, I edited imageviewer.h file:
added
#include <QPainter>
and
protected:
void paintEvent(QPaintEvent *);
to ImageViewer class.
Then I defined paintEvent in imageviewe.cpp:
void ImageViewer::paintEvent(QPaintEvent * e)
{
QPainter p(this);
p.drawLine(0,0,100,100);
}
I expected that to draw a line in the top-left corner, but it didn't.
It seems to me, that the point is that scrollArea overlaps the line, making it "invisible". So, I commented out the line
setCentralWidget(scrollArea);
within ImageViewer::ImageViewer() definition.
The line appeared, but the images were not showed, of coruse.
Then I tried drawing on scrollArea itself, changing paintEvent code:
void ImageViewer::paintEvent(QPaintEvent * e)
{
QPainter p(scrollArea);
p.drawLine(0,0,100,100);
}
which resulted in messages like
QPainter::begin: Paint device returned engine == 0, type: 1
Tried googling it, but the solutions have nothing to do with scrollArea overlapping the line (or just don't work).
Would appreciate any help on this.
I've got a window full of QPushButtons and QLabels and various other fun QWidgets, all layed out dynamically using various QLayout objects... and what I'd like to do is occasionally make some of those widgets become invisible. That is, the invisible widgets would still take up their normal space in the window's layout, but they wouldn't be rendered: instead, the user would just see the window's background color in the widget's rectangle/area.
hide() and/or setVisible(false) won't do the trick because they cause the widget to be removed from the layout entirely, allowing other widgets to expand to take up the "newly available" space; an effect that I want to avoid.
I suppose I could make a subclass of every QWidget type that override paintEvent() (and mousePressEvent() and etc) to be a no-op (when appropriate), but I'd prefer a solution that doesn't require me to create three dozen different QWidget subclasses.
This problem was solved in Qt 5.2. The cute solution is:
QSizePolicy sp_retain = widget->sizePolicy();
sp_retain.setRetainSizeWhenHidden(true);
widget->setSizePolicy(sp_retain);
http://doc.qt.io/qt-5/qsizepolicy.html#setRetainSizeWhenHidden
The only decent way I know of is to attach an event filter to the widget, and filter out repaint events. It will work no matter how complex the widget is - it can have child widgets.
Below is a complete stand-alone example. It comes with some caveats, though, and would need further development to make it complete. Only the paint event is overridden, thus you can still interact with the widget, you just won't see any effects.
Mouse clicks, mouse enter/leave events, focus events, etc. will still get to the widget. If the widget depends on certain things being done upon an a repaint, perhaps due to an update() triggered upon those events, there may be trouble.
At a minimum you'd need a case statement to block more events -- say mouse move and click events. Handling focus is a concern: you'd need to move focus over to the next widget in the chain should the widget be hidden while it's focused, and whenever it'd reacquire focus.
The mouse tracking poses some concerns too, you'd want to pretend that the widget lost mouse tracking if it was tracking before. Properly emulating this would require some research, I don't know off the top of my head what is the exact mouse tracking event protocol that Qt presents to the widgets.
//main.cpp
#include <QEvent>
#include <QPaintEvent>
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QGridLayout>
#include <QDialogButtonBox>
#include <QApplication>
class Hider : public QObject
{
Q_OBJECT
public:
Hider(QObject * parent = 0) : QObject(parent) {}
bool eventFilter(QObject *, QEvent * ev) {
return ev->type() == QEvent::Paint;
}
void hide(QWidget * w) {
w->installEventFilter(this);
w->update();
}
void unhide(QWidget * w) {
w->removeEventFilter(this);
w->update();
}
Q_SLOT void hideWidget()
{
QObject * s = sender();
if (s->isWidgetType()) { hide(qobject_cast<QWidget*>(s)); }
}
};
class Window : public QWidget
{
Q_OBJECT
Hider m_hider;
QDialogButtonBox m_buttons;
QWidget * m_widget;
Q_SLOT void on_hide_clicked() { m_hider.hide(m_widget); }
Q_SLOT void on_show_clicked() { m_hider.unhide(m_widget); }
public:
Window() {
QGridLayout * lt = new QGridLayout(this);
lt->addWidget(new QLabel("label1"), 0, 0);
lt->addWidget(m_widget = new QLabel("hiding label2"), 0, 1);
lt->addWidget(new QLabel("label3"), 0, 2);
lt->addWidget(&m_buttons, 1, 0, 1, 3);
QWidget * b;
b = m_buttons.addButton("&Hide", QDialogButtonBox::ActionRole);
b->setObjectName("hide");
b = m_buttons.addButton("&Show", QDialogButtonBox::ActionRole);
b->setObjectName("show");
b = m_buttons.addButton("Hide &Self", QDialogButtonBox::ActionRole);
connect(b, SIGNAL(clicked()), &m_hider, SLOT(hideWidget()));
QMetaObject::connectSlotsByName(this);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"
You can use a QStackedWidget. Put your button on the first page, a blank QWidget on the second, and change the page index to make your button vanish while retaining its original space.
I've 3 solutions in my mind:
1) Subclass your QWidget and use a special/own setVisible() replacement method witch turns on/off the painting of the widget (if the widget should be invisible simply ignore the painting with an overridden paintEvent() method). This is a dirty solution, don't use it if you can do it other ways.
2) Use a QSpacerItem as a placeholder and set it's visibility to the opposite of the QWidget you want to hide but preserve it's position+size in the layout.
3) You can use a special container widget (inherit from QWidget) which gets/synchronizes it's size based on it's child/children widgets' size.
I had a similar problem and I ended up putting a spacer next to my control with a size of 0 in the dimension I cared about and an Expanding sizeType. Then I marked the control itself with an Expanding sizeType and set its stretch to 1. That way, when it's visible it takes priority over the spacer, but when it's invisible the spacer expands to fill the space normally occupied by the control.
May be QWidget::setWindowOpacity(0.0) is what you want? But this method doesn't work everywhere.
One option is to implement a new subclass of QWidgetItem that always returns false for QLayoutItem::isEmpty. I suspect that will work due to Qt's QLayout example subclass documentation:
We ignore QLayoutItem::isEmpty(); this means that the layout will treat hidden widgets as visible.
However, you may find that adding items to your layout is a little annoying that way. In particular, I'm not sure you can easily specify layouts in UI files if you were to do it that way.
Here's a PyQt version of the C++ Hider class from Kuba Ober's answer.
class Hider(QObject):
"""
Hides a widget by blocking its paint event. This is useful if a
widget is in a layout that you do not want to change when the
widget is hidden.
"""
def __init__(self, parent=None):
super(Hider, self).__init__(parent)
def eventFilter(self, obj, ev):
return ev.type() == QEvent.Paint
def hide(self, widget):
widget.installEventFilter(self)
widget.update()
def unhide(self, widget):
widget.removeEventFilter(self)
widget.update()
def hideWidget(self, sender):
if sender.isWidgetType():
self.hide(sender)
I believe you could use a QFrame as a wrapper. Although there might be a better idea.
Try void QWidget::erase (). It works on Qt 3.
I want to draw an image, pixel by pixel at run time. I use QPainter and paintEvent to draw. But when paintEvent is called each time, the previously drawn image is cleared and the new point has been drawn.
How to avoid clearing the previously drawn parts? I just want to append the new pixel point to the previously drawn points.
Lines::Lines(QWidget *parent)
: QWidget(parent)
{
m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_timer->start();
m_x = 0;
m_y = 0;
}
void Lines::paintEvent(QPaintEvent *event)
{
QPen pen(Qt::black, 2, Qt::SolidLine);
QPainter painter(this);
painter.setPen(pen);
painter.drawPoint(m_x, m_y);
}
void Lines::updateStatus()
{
m_x++;
m_y++;
update();
}
paintEvent is supposed to do a complete redraw of the widget region specified in the event.
So you are responsible for buffering previous results.
It doesn't really make sense to change the desired output in paintEvent, as it may be randomly called and when it is called is out of your control.
If you want to avoid that you can use a QGraphicsView.
Buffering could be done using a QPixmap, which would be part of the Lines class. You draw the pixel in the pixmap (not in the paint event, in updateStatus), and draw the pixmap in the paint event.
QWidget::setAttribute( WA_OpaquePaintEvent, true );
prevents clearing the widget. However, this is just for optimization in case the widget does a complete repaint anyway.
You should follow Dr. Hirsch's advice.