PDA

View Full Version : QPainter.fillRect not filling what I tell it to



chezifresh
14th January 2011, 00:16
I'm trying to do the simplest of painting using a QPainter but I'm running into one hell of an oddity. Here's some example PyQt code which demonstrates the problem

When the line edit has focus, the background should be red. when it doesnt have focus the background should be blue. Very simple... but it doesnt work. You can toss print statements in there, and you'll see that fillRect is being called every time focus changes.

Here's the kicker, if you resize it, it repaints using the correct color


from PyQt4 import QtGui
from PyQt4.QtCore import Qt

class MyWidget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.lineedit = QtGui.QLineEdit(self)
self.button = QtGui.QPushButton(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.lineedit)
layout.addWidget(self.button)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
if self.lineedit.hasFocus():
painter.fillRect(self.rect(), Qt.red)
else:
painter.fillRect(self.rect(), Qt.blue)

w = MyWidget()
w.show()

Any idea?

norobro
14th January 2011, 04:49
The following code works in C++:
void Widget::paintEvent ( QPaintEvent * event ) {
QPainter painter;
painter.begin(this);
if(lineEdit->hasFocus()) painter.fillRect(rect(), QBrush(Qt::red));
else painter.fillRect(rect(), QBrush(Qt::blue));
painter.end();
update();
}

I got the same results as you in C++ without painter.begin(), painter.end() and update(). When I tried plugging them into your program it didn't change anything, but I don't know much about Python.

chezifresh
14th January 2011, 18:06
It does repaint correctly in Python too, however, it dramatically slows things down because calling update() from the paintEvent() is nearly a recursive call.
I'm sure I can find a way to only call update() one time after focus changes. Too bad an update call in focusOutEvent doesnt do it.

here's what I added


void Widget::paintEvent ( QPaintEvent * event ) {
QPainter painter;
painter.begin(this);
if(lineEdit->hasFocus()) painter.fillRect(rect(), QBrush(Qt::red));
else painter.fillRect(rect(), QBrush(Qt::blue));
painter.end();

// New Code Here
if(lineEdit->hasFocus() != _lineEditHadFocus) {
_lineEditHadFocus = lineEdit->hasFocus();
update();
}
}

norobro
14th January 2011, 20:25
Also since paintEvent() gets called so often, you could put any tests & update() in a slot:
Widget::Widget(QWidget *parent) :
QWidget(parent), _brush(Qt::red)
{
...
connect(qApp,SIGNAL(focusChanged(QWidget*,QWidget* )),this,SLOT(changedFocus(QWidget*,QWidget*)));
}

void Widget::changedFocus(QWidget *old,QWidget *now){
Q_UNUSED(old);
if(QString(now->metaObject()->className())=="QLineEdit") _brush.setColor(Qt::red);
else _brush.setColor(Qt::blue);
update();
}

void Widget::paintEvent ( QPaintEvent * event ) {
Q_UNUSED(event);
QPainter painter;
painter.begin(this);
painter.fillRect(rect(), _brush);
painter.end();
}

chezifresh
19th January 2011, 22:42
I'm not sure if this is better or not but here's what I ended up with:


Widget::Widget(QWidget *parent) :
QWidget(parent), _brush(Qt::red)
{
...
_lineEdit->installEventFilter(this);
}

void Widget::eventFilter(QObject *obj, QEvent * event){
if(obj == _lineEdit && (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut))
update();
return False;
}


I also ended up changing the widget around a bit, now its a scroll area with a custom viewport which has line edit inside of it, so I'm not sure if this works with my original setup, either way, these all appear to work for various problems

wysota
20th January 2011, 01:44
A cleaner approach would be to raise/clear a flag in the event filter so that the widget doesn't have to query its child during the paint event. Still it's not clear for me why you want to paint this widget's background in this situation. I could understand painting the line edit's background but not what you do now. And if you just want to add a button to the line edit, you can do it easier - subclass the line edit, set a margin for its contents and place a button in the area of the margin. Then you end up with QLineEdit being your main widget (and keeping its whole API) and you can colour just the line edit.

chezifresh
20th January 2011, 02:11
So I'm trying to implement a tag entry widget. You can check out this post for more details: http://www.qtcentre.org/threads/37529-Creating-a-tag-keyword-entry-widget-using-a-line-edit

What I have is a scroll area, a custom viewport, a bunch of custom 'tag' widgets, and a line edit with no frame, the line edit also acts as a focus proxy for the scroll area. The trick is I want the whole widget to look like a normal line edit and for the most part act like a normal line edit. The problem I ran into was the focus frame. When the line edit receives focus, or loses focus the scroll area's focus rect isnt repainted. So if you tab from my tag line edit to another line edit, the focus rectangle will be on both the tag line edit and the currently focused line edit. Bottom line, the focus frame wasn't behaving correctly on the scroll area.

When I started the post I wasn't using a scroll area I was drawing the entire widget myself by using QStyle::drawPrimitive, which still exhibits the same problem as above. When the focus proxy gets/loses focus, the parent widget gets a paintEvent and I'm able to modify my QStyleOptionFrame to paint the frame with or without focus but its almost as if the paint method (or drawPrimitive method in this case) was ignored. The first example I posted was just a dead simple way to show what was happening. If you throw some print statements in the paintEvent when choosing bg colors you'll see what I mean.

Anyway, I have a functioning tag widget, I like it, but I've implemented a workaround, not necessarily a solution. That is unless there is a good, documented reason why my fillRect in the first example doesnt work. Otherwise, this seems like a bug to me. I'd love to understand why the example in my original post didnt work, it was a huge time sink, but I have a workaround.

In case others aren't PyQt4 users here's the same example in C++



#include <QtGui>
#include <iostream>

class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget * parent);
private:
void paintEvent(QPaintEvent * event);
QLineEdit * _lineEdit;
QPushButton * _button;
QVBoxLayout * _layout;
};

Widget::Widget(QWidget * parent) : QWidget(parent)
{
_lineEdit = new QLineEdit(this);
_button = new QPushButton(this);
_layout = new QVBoxLayout(this);
_layout->addWidget(_lineEdit);
_layout->addWidget(_button);
}

void Widget::paintEvent ( QPaintEvent * event ) {
QPainter painter;
painter.begin(this);
if(_lineEdit->hasFocus()) {
painter.fillRect(rect(), QBrush(Qt::red));
std::cerr<<"Line edit has focus, bg should be red"<<std::endl;
}
else {
painter.fillRect(rect(), QBrush(Qt::blue));
std::cerr<<"Line edit does NOT have focus, bg should be blue"<<std::endl;
}
painter.end();
}

wysota
20th January 2011, 11:03
To be honest your "tag widget" is an obvious candidate for a QAbstractItemView subclass or even for a QListView subclass. If you just use QListView then all you need is a custom delegate for drawing your tags and editing the tag.

chezifresh
20th January 2011, 18:32
Had I thought of that when I started I probably would have gone that route :) I ended up implementing the QFlowLayout and using that in a scroll area. I guess the QListView already does that when you put it in icon mode. Good call.