QGraphicsPathItem different colors - python

Is there a way to get 2 different fill colors for a single QGraphicsPathItem object?
For example:
# Try to get a white text onto a grey rectangle
itemPath = QtGui.QPainterPath()
itemPath.setFillRule(QtCore.Qt.WindingFill)
self.setBrush( QtGui.QColor(100, 100, 100) )
itemPath.addRect(-10, -60, 150, 70)
itemFont = QtGui.QFont()
itemFont.setPointSize(50)
self.setBrush( QtGui.QColor(255, 255, 255) )
itemPath.addText(0, 0, itemFont, txt)
Right now it's just using the last brush color for the rectangle and text. I'd like to color them differently though, but still as the same QGraphicsPathItem. Or better yet, a way to give the text a background so it's easier to select.
Update
Here's an example. I want a white text with a red background, but I only get the last used brush color.
from PySide import QtGui, QtCore
class TextItem(QtGui.QGraphicsPathItem):
def __init__(self):
super(TextItem, self).__init__()
itemPath = QtGui.QPainterPath()
itemPath.setFillRule(QtCore.Qt.WindingFill)
# Create rectangle with red color
self.setBrush( QtGui.QColor(255, 0, 0) )
itemPath.addRect(-10, -50, 130, 60)
self.setPath(itemPath)
# Create text with white color
itemFont = QtGui.QFont()
itemFont.setPointSize(40)
self.setBrush( QtGui.QColor(255, 255, 255) )
itemPath.addText(0, 0, itemFont, 'Test!')
self.setPath(itemPath)
self.moveBy(100, 100)
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.resize(500, 500)
self.view = QtGui.QGraphicsView(self)
self.view.setScene( QtGui.QGraphicsScene(self) )
self.view.setSceneRect( 0, 0, 500, 500 )
self.view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
mainLayout = QtGui.QVBoxLayout()
mainLayout.addWidget(self.view)
self.setLayout(mainLayout)
newItem = TextItem()
self.view.scene().addItem( newItem )
def run(self):
self.show()
win = Window()
win.show()

The problem is that your first self.setPath and self.setBrush calls are just over-ridden by the second ones. These properties of the QGraphicsPathItem are not used until you get to the paint event. So the first settings do nothing and the values left in these properties after the initialisation are the ones used to paint the item.
It's not too difficult to do what you want but you have quite a lot of options, and you need to choose based on other aspects of your design.
The simplest option would be to just create a QGraphicsRectItem and then give it a QGraphicsSimpleTextItem child. This is particularly simple in that you wouldn't even need to create a subclass of your own: simply create the instances, set their Paths and Brushes and use them directly. I strongly recommend this approach if it does what you neeed.
rectItem = QtGui.QGraphicsRectItem(0, 0, 130, 60)
rectItem.setBrush(QtGui.QColor(255, 0, 0))
textItem = QtGui.QGraphicsSimpleTextItem("Test!",rectItem)
itemFont = QtGui.QFont()
itemFont.setPointSize(40)
textItem.setBrush( QtGui.QColor(200, 200, 200) )
textItem.setFont(itemFont)
textItem.setPos(10,0)
self.view.scene().addItem( rectItem )
rectItem.moveBy(100,100)
Another simple option would be just create the two items and then add them into a QGraphicsItemGroup. This makes sense if you might want to separate the items or work with them separately.
rectItem = QtGui.QGraphicsRectItem(0, 0, 130, 60)
rectItem.setBrush(QtGui.QColor(255, 0, 0))
self.view.scene().addItem( rectItem )
textItem = QtGui.QGraphicsSimpleTextItem("Test!")
itemFont = QtGui.QFont()
itemFont.setPointSize(40)
textItem.setBrush( QtGui.QColor(200, 200, 200) )
textItem.setFont(itemFont)
textItem.setPos(10,0)
self.view.scene().addItem( textItem )
group = QtGui.QGraphicsItemGroup()
group.addToGroup(rectItem)
group.addToGroup(textItem)
group.moveBy(100,100)
self.view.scene().addItem( group )
If you need to create a class of your own for other reasons then you have quite a few options. The most general will be to subclass QGraphicsItem. You can create a MyTextItem with, for example, two Paths and two Brushes, and use them both when you paint the Item. You will have to implement boundingRect and paint but in this case both will be quite easy. This is probably a good way to go if you foresee adding lots of additional functionality to the item.
Other options would be to subclass QRectItem and add a text path to it. Again you will need to re-implement paint, but that might be all that was needed. Or you could subclass QSimpleTextItem and add a rect to it.

Related

I'm trying to hawe two QFrames take exactly half of the QMainWindow. An offset is being forced and I can't seem to get rid of it

I'm just experimenting with PyQt5 so I can figure out some problems in another program I'm writing, but the biggest thing is that I can't seem to figure out how exactly to change a widget's geometry.
I wrote this small test window with two frames of different colors and I'm trying to have them split the entire possible space.
class Test(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Test")
self.setGeometry(QRect(QPoint(300, 300), QSize(300, 300)))
self._centralWidget = QWidget()
self._centralLayout = QHBoxLayout()
self.setCentralWidget(self._centralWidget)
self._centralWidget.setLayout(self._centralLayout)
self.initGUI()
self.show()
def initGUI(self):
self.leftFrame = QFrame()
self.leftFrame.setStyleSheet("background-color:red")
self.leftFrame.setGeometry(QRect(QPoint(0, 0), QSize(150, 300))) # Same as QRect(0, 0, 150, 300)
self.rightFrame = QFrame()
self.rightFrame.setStyleSheet("background-color:green")
self.rightFrame.setGeometry(QRect(QPoint(150, 0), QSize(150, 300))) # Same as QRect(150, 0, 150, 300)
self._centralLayout.addWidget(self.leftFrame)
self._centralLayout.addWidget(self.rightFrame)
The MainWindow is 300x300, so I set the two frames to be 150x300. When I run it though, there's an annoying offset of 9x9. So the true sizes of the two frames are: left frame: QRect(9, 9, 138, 282) | right frame: QRect(153, 9, 138, 282).
I have a suspicion that it might be the QHBoxLayout that is messing with sizes but I'm not sure. Any advice is appreciated!
If you want to manually set the geometry then you should not use layouts since the very objective of layouts is to automate their handling based on their characteristics.
def initGUI(self):
self.leftFrame = QFrame(self._centralWidget)
self.leftFrame.setStyleSheet("background-color:red")
self.leftFrame.setGeometry(QRect(QPoint(0, 0), QSize(150, 300)))
self.rightFrame = QFrame(self._centralWidget)
self.rightFrame.setStyleSheet("background-color:green")
self.rightFrame.setGeometry(QRect(QPoint(150, 0), QSize(150, 300)))
If you want to use layouts then it is not necessary to use setGeometry in the QFrames, you must also set the margins and spaces of the layout to 0:
def initGUI(self):
self.leftFrame = QFrame()
self.leftFrame.setStyleSheet("background-color:red")
self.rightFrame = QFrame()
self.rightFrame.setStyleSheet("background-color:green")
self._centralLayout.addWidget(self.leftFrame)
self._centralLayout.addWidget(self.rightFrame)
self._centralLayout.setContentsMargins(0, 0, 0, 0)
self._centralLayout.setSpacing(0)

How to prevent window and widgets in a pyqt5 application from changing size when the visibility of one widget is altered

I want to create a dialog, in which the user should first select one item in a drop down, and for some choices specify an additional parameter. For the sake of the example let's say that the possible choices are A and B and for B the user has to enter a text. The text field should not be visible when A is selected.
Here is a MWE:
#!/usr/bin/env python
import sys
from PyQt5.QtWidgets import QApplication, QComboBox, QDialog, QGridLayout, QLineEdit
class Example(QDialog) :
def __init__(self, parent=None) :
super(QDialog, self).__init__(parent)
self.mainLayout = QGridLayout()
self.setLayout(self.mainLayout)
self.comboBox = QComboBox()
self.comboBox.addItems(['A', 'B'])
self.mainLayout.addWidget(self.comboBox, 0, 0)
self.lineEdit = QLineEdit('')
self.lineEdit.setMinimumWidth(50)
self.mainLayout.addWidget(self.lineEdit, 0, 1)
self.comboBox.activated[str].connect(self.update)
self.update(str(self.comboBox.currentText()))
def update(self, choice) :
if 'B' in choice :
self.lineEdit.setVisible(True)
else :
self.lineEdit.setVisible(False)
if __name__ == '__main__':
app = QApplication(sys.argv)
example = Example()
example.show()
sys.exit(app.exec_())
The problem is, that when initially choice A is presented, the size of the dialog is just enough for the comboBox. When option B is selected, the window is expanded and everything is as it should be. However, when option A is selected again, the comboBox' width increases, taking up all of the avalaible space, instead of leaving empty space to the right.
How can I have space allocated for the text field, no matter if visible or not? What am I missing here?
EDIT The answer by S.Nick solves the problem of the MWE in a way, but not the way I was hoping for: As soon as the scenario is more complex, widgets get reallocated again, e.g. if a QLabel is added in front of the comboBox
self.label = QLabel('label')
self.mainLayout.addWidget(self.label, 0, 0)
self.comboBox = QComboBox()
self.comboBox.addItems(['A', 'B'])
self.mainLayout.addWidget(self.comboBox, 0, 1, alignment=Qt.AlignLeft)
self.lineEdit = QLineEdit('', self)
self.lineEdit.setMinimumWidth(50)
self.mainLayout.addWidget(self.lineEdit, 0, 2)
then the comboBox is flipped around when changing the selection. What I want is that, once in the beginning space and position is allocated for each widget and that the space and position is permanent no matter if any widget is visible or not.
You could try something like this:
def __init__(self, parent=None) :
super(QDialog, self).__init__(parent)
self.mainLayout = QGridLayout()
self.setLayout(self.mainLayout)
self.label = QLabel('label')
self.mainLayout.addWidget(self.label, 0, 0)
self.comboBox = QComboBox()
self.comboBox.addItems(['A', 'B'])
self.mainLayout.addWidget(self.comboBox, 0, 1)
self.lineEdit = QLineEdit('', self)
self.lineEdit.setMinimumWidth(200)
self.mainLayout.addWidget(self.lineEdit, 0, 2)
self.comboBox.activated[str].connect(self.update)
self.mainLayout.setColumnStretch(2,1)
self.adjustSize()
self.update(str(self.comboBox.currentText()))
self.mainLayout.setColumnStretch(2,1) will make sure that the last column will take up all the extra horizontal space even when the line edit widget is hidden.
self.adjustSize() adjusts the size of the main window to the sum of the sizes of all its child widgets. Since at this point the line edit widget is still visible, its size is taken into account as well when the size of the main window is adjusted.
Screenshots
Initial window:
After selecting B:

Making Highly Customized, Hoverable, Overlappable, Widgets

I want to up my game in UI design using PyQt5. I feel like the resources for UI customization in PyQt5 are not easy to find. It is possible to try and make personalized widget, but the overall method seems non-standardized.
I need to build an arrow widget that is hoverable, overlappable with other widgets and highly customized. As I read in this tutorial and some other posts, it possible to do exactly what you need using paintEvent. Thus that is what I tried, but overall, I feel like the method is quite messy, and I'd like some guidelines on building complex Customized, general widget. Here's what I have:
Customized Shape: I built my code based on this
Hoverable property: I read everywhere that modifying the projects styleSheet is usually the way to go, especially if you want to make your Widget general and adapt to colors, the problem is that I wasn't able to find how to use properly self.palette to fetch the current colors of the QApplication styleSheet. I feel like i's have to maybe use enterEvent and leaveEvent, but I tried to redraw the whole widget with a painter in those functions and it said
QPainter::begin: Painter already active
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setRenderHint: Painter must be active to set rendering hints
Overlappable Property: I found a previous post which seemed to have found a solution: create a second widget that is children of the main widget, in order to be able to move the children around. I tried that but it seems that it doesn't want to move, no matter the position I give the widget.
Here is my code:
import sys
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QGraphicsDropShadowEffect, QApplication, QFrame, QPushButton
from PyQt5.QtCore import Qt, QPoint, QLine
from PyQt5.QtGui import QPainter, QPen, QColor, QPalette
class MainWidget(QWidget):
def __init__(self):
super(MainWidget, self).__init__()
self.resize(500, 500)
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.myPush = QPushButton()
self.layout.addWidget(self.myPush)
self.arrow = ArrowWidget(self)
position = QPoint(-40, 0)
self.layout.addWidget(self.arrow)
self.arrow.move(position)
class ArrowWidget(QWidget):
def __init__(self, parent=None):
super(ArrowWidget, self).__init__(parent)
self.setWindowFlag(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.w = 200
self.h = 200
self.blurRadius = 20
self.xO = 0
self.yO = 20
self.resize(self.w, self.h)
self.layout = QHBoxLayout()
# myFrame = QFrame()
# self.layout.addWidget(myFrame)
self.setLayout(self.layout)
self.setStyleSheet("QWidget:hover{border-color: rgb(255,0,0);background-color: rgb(255,50,0);}")
shadow = QGraphicsDropShadowEffect(blurRadius=self.blurRadius, xOffset=self.xO, yOffset=self.yO)
self.setGraphicsEffect(shadow)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.begin(self)
# painter.setBrush(self.palette().window())
# painter.setPen(QPen(QPalette, 5))
ok = self.frameGeometry().width()/2-self.blurRadius/2-self.xO/2
oky = self.frameGeometry().height()/2-self.blurRadius/2-self.yO/2
painter.drawEllipse(QPoint(self.frameGeometry().width()/2-self.blurRadius/2-self.xO/2, self.frameGeometry().height()/2-self.blurRadius/2-self.yO/2), self.w/2-self.blurRadius/2-self.yO/2-self.xO/2, self.h/2-self.blurRadius/2-self.yO/2-self.xO/2)
painter.drawLines(QLine(ok-25, oky-50, ok+25, oky), QLine(ok+25, oky, ok-25, oky+50))
painter.end()
if __name__ == '__main__':
app = QApplication(sys.argv)
testWidget = MainWidget()
testWidget.show()
sys.exit(app.exec_())
If someone could help me make this work and explain along the way to help us better understand the structure of customized widgets and explain a better method that isn't messy like this one, I believe it would be a plus to the beginners like me using PyQt5 as a main Framework for UI making.
There is no "standard" method for custom widgets, but usually paintEvent overriding is required.
There are different issues in your example, I'll try and address to them.
Overlapping
If you want a widget to be "overlappable", it must not be added to a layout. Adding a widget to a layout will mean that it will have its "slot" within the layout, which in turn will try to compute its sizes (based on the widgets it contains); also, normally a layout has only one widget per "layout slot", making it almost impossible to make widget overlap; the QGridLayout is a special case which allows (by code only, not using Designer) to add more widget to the same slot(s), or make some overlap others. Finally, once a widget is part of a layout, it cannot be freely moved nor resized (unless you set a fixedSize).
The only real solution to this is to create the widget with a parent. This will make it possible to use move() and resize(), but only within the boundaries of the parent.
Hovering
While it's true that most widgets can use the :hover selector in the stylesheet, it only works for standard widgets, which do most of their painting by themself (through QStyle functions). About this, while it's possible to do some custom painting with stylesheets, it's generally used for very specific cases, and even in this case there is no easy way to access to the stylesheet properties.
In your case, there's no need to use stylesheets, but just override enterEvent and leaveEvent, set there any color you need for painting and then call self.update() at the end.
Painting
The reason you're getting those warnings is because you are calling begin after declaring the QPainter with the paint device as an argument: once it's created it automatically calls begin with the device argument. Also, it usually is not required to call end(), as it is automatically called when the QPainter is destroyed, which happens when the paintEvent returns since it's a local variable.
Example
I created a small example based on your question. It creates a window with a button and a label within a QGridLayout, and also uses a QFrame set under them (since it's been added first), showing the "overlapping" layout I wrote about before. Then there's your arrow widget, created with the main window as parent, and that can be moved around by clicking on it and dragging it.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class ArrowWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
# since the widget will not be added to a layout, ensure
# that it has a fixed size (otherwise it'll use QWidget default size)
self.setFixedSize(200, 200)
self.blurRadius = 20
self.xO = 0
self.yO = 20
shadow = QtWidgets.QGraphicsDropShadowEffect(blurRadius=self.blurRadius, xOffset=self.xO, yOffset=self.yO)
self.setGraphicsEffect(shadow)
# create pen and brush colors for painting
self.currentPen = self.normalPen = QtGui.QPen(QtCore.Qt.black)
self.hoverPen = QtGui.QPen(QtCore.Qt.darkGray)
self.currentBrush = self.normalBrush = QtGui.QColor(QtCore.Qt.transparent)
self.hoverBrush = QtGui.QColor(128, 192, 192, 128)
def mousePressEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
self.mousePos = event.pos()
def mouseMoveEvent(self, event):
# move the widget based on its position and "delta" of the coordinates
# where it was clicked. Be careful to use button*s* and not button
# within mouseMoveEvent
if event.buttons() == QtCore.Qt.LeftButton:
self.move(self.pos() + event.pos() - self.mousePos)
def enterEvent(self, event):
self.currentPen = self.hoverPen
self.currentBrush = self.hoverBrush
self.update()
def leaveEvent(self, event):
self.currentPen = self.normalPen
self.currentBrush = self.normalBrush
self.update()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
# painting is not based on "pixels", to get accurate results
# translation of .5 is required, expecially when using 1 pixel lines
qp.translate(.5, .5)
# painting rectangle is always 1px smaller than the actual size
rect = self.rect().adjusted(0, 0, -1, -1)
qp.setPen(self.currentPen)
qp.setBrush(self.currentBrush)
# draw an ellipse smaller than the widget
qp.drawEllipse(rect.adjusted(25, 25, -25, -25))
# draw arrow lines based on the center; since a QRect center is a QPoint
# we can add or subtract another QPoint to get the new positions for
# top-left, right and bottom left corners
qp.drawLine(rect.center() + QtCore.QPoint(-25, -50), rect.center() + QtCore.QPoint(25, 0))
qp.drawLine(rect.center() + QtCore.QPoint(25, 0), rect.center() + QtCore.QPoint(-25, 50))
class MainWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout()
self.setLayout(layout)
self.button = QtWidgets.QPushButton('button')
layout.addWidget(self.button, 0, 0)
self.label = QtWidgets.QLabel('label')
self.label.setAlignment(QtCore.Qt.AlignCenter)
layout.addWidget(self.label, 0, 1)
# create a frame that uses as much space as possible
self.frame = QtWidgets.QFrame()
self.frame.setFrameShape(self.frame.StyledPanel|self.frame.Raised)
self.frame.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# add it to the layout, ensuring it spans all rows and columns
layout.addWidget(self.frame, 0, 0, layout.rowCount(), layout.columnCount())
# "lower" the frame to the bottom of the widget's stack, otherwise
# it will be "over" the other widgets, preventing them to receive
# mouse events
self.frame.lower()
self.resize(640, 480)
# finally, create your widget with a parent, *without* adding to a layout
self.arrowWidget = ArrowWidget(self)
# now you can place it wherever you want
self.arrowWidget.move(220, 140)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
testWidget = MainWidget()
testWidget.show()
sys.exit(app.exec_())

PyQt - Animations without redrawing everything?

So I'm trying to learn animations in PyQt and throughout all the examples I can find online, they all seem to use the self.update() or self.repaint() methods to increment the animations. This means basically the code has to erase then redraw the entire widget for every frame, even though much of what I intend to animate is static.
For example the code below, this generates a circular progress pie. The important bit is the paint() method under ProgressMeter (first class): for every frame in the animation this example paints the background, the actual progress pie, and the percentage indicator.
If I change the code to something like:
if self.angle > 120:
# do not draw background
then after 120 frames, the background does not get drawn anymore.
This seems terribly inefficient because (logically) the background should only be drawn once, no?
What would you recommend for animations like these?
Addendum: I have lurked a lot on this site to steal examples and code, but haven't posted for a long time. Please let me know about proper etiquette etc if I am not following it properly.
import sys
from PyQt4 import QtGui, QtCore
class ProgressMeter(QtGui.QGraphicsItem):
def __init__(self, parent):
super(ProgressMeter, self).__init__()
self.parent = parent
self.angle = 0
self.per = 0
def boundingRect(self):
return QtCore.QRectF(0, 0, self.parent.width(),
self.parent.height())
def increment(self):
self.angle += 1
self.per = int(self.angle / 3.6)
if self.angle > 360:
return False
else:
return True
def paint(self, painter, option, widget):
self.drawBackground(painter, widget)
self.drawMeter(painter, widget)
self.drawText(painter)
def drawBackground(self, painter, widget):
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtCore.Qt.NoPen)
p1 = QtCore.QPointF(80, 80)
g = QtGui.QRadialGradient(p1 * 0.2, 80 * 1.1)
g.setColorAt(0.0, widget.palette().light().color())
g.setColorAt(1.0, widget.palette().dark().color())
painter.setBrush(g)
painter.drawEllipse(0, 0, 80, 80)
p2 = QtCore.QPointF(40, 40)
g = QtGui.QRadialGradient(p2, 70 * 1.3)
g.setColorAt(0.0, widget.palette().midlight().color())
g.setColorAt(1.0, widget.palette().dark().color())
painter.setBrush(g)
painter.drawEllipse(7.5, 7.5, 65, 65)
def drawMeter(self, painter, widget):
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(widget.palette().highlight().color())
painter.drawPie(7.5, 7.5, 65, 65, 0, -self.angle * 16)
def drawText(self, painter):
text = "%d%%" % self.per
font = painter.font()
font.setPixelSize(11)
painter.setFont(font)
brush = QtGui.QBrush(QtGui.QColor("#000000"))
pen = QtGui.QPen(brush, 1)
painter.setPen(pen)
# size = painter.fontMetrics().size(QtCore.Qt.TextSingleLine, text)
painter.drawText(0, 0, 80, 80,
QtCore.Qt.AlignCenter, text)
class MyView(QtGui.QGraphicsView):
def __init__(self):
super(MyView, self).__init__()
self.initView()
self.setupScene()
self.setupAnimation()
self.setGeometry(300, 150, 250, 250)
def initView(self):
self.setWindowTitle("Progress meter")
self.setRenderHint(QtGui.QPainter.Antialiasing)
policy = QtCore.Qt.ScrollBarAlwaysOff
self.setVerticalScrollBarPolicy(policy)
self.setHorizontalScrollBarPolicy(policy)
self.setBackgroundBrush(self.palette().window())
self.pm = ProgressMeter(self)
self.pm.setPos(55, 55)
def setupScene(self):
self.scene = QtGui.QGraphicsScene(self)
self.scene.setSceneRect(0, 0, 250, 250)
self.scene.addItem(self.pm)
self.setScene(self.scene)
def setupAnimation(self):
self.timer = QtCore.QTimeLine()
self.timer.setLoopCount(0)
self.timer.setFrameRange(0, 100)
self.animation = QtGui.QGraphicsItemAnimation()
self.animation.setItem(self.pm)
self.animation.setTimeLine(self.timer)
self.timer.frameChanged[int].connect(self.doStep)
self.timer.start()
def doStep(self, i):
if not self.pm.increment():
self.timer.stop()
self.pm.update()
app = QtGui.QApplication([])
view = MyView()
view.show()
sys.exit(app.exec_())
The Qt's documentation about QWidget repaint slot says:
Repaints the widget directly by calling paintEvent() immediately, unless updates are disabled or the widget is hidden.
We suggest only using repaint() if you need an immediate repaint, for example during animation. In almost all circumstances update() is better, as it permits Qt to optimize for speed and minimize flicker.
Warning: If you call repaint() in a function which may itself be called from paintEvent(), you may get infinite recursion. The update() function never causes recursion.
That should give you an answer about when to use or not repaint or update slots.
About making animations I'd suggest you also to take a look to the Qt4's animation framework or Qt5's animation framework, which is a really powerful way to animate widgets on Qt.

How do I stop paintEvent from painting children widgets?

I'm trying to add rounded corners to a QDialog. I'm defining my own paintEvent method to create rounded corners. It's working, but it's adding rounded borders to everything. Even the cursor is getting a border. Is there any way to disable this behavior?
Example code:
from PySide import QtCore, QtGui
class RenameDialog(QtGui.QDialog):
def __init__(self, parent=None, **kwargs):
super(RenameDialog, self).__init__(
parent=parent, f=QtCore.Qt.CustomizeWindowHint)
self.fieldA = QtGui.QLineEdit(self)
self.fieldB = QtGui.QLineEdit(self)
self.setLayout(QtGui.QHBoxLayout())
self.layout().addWidget(self.fieldA)
self.layout().addWidget(self.fieldB)
# Set background transparent. Only items drawn in paintEvent
# will be visible.
palette = QtGui.QPalette()
palette.setColor(QtGui.QPalette.Base, QtCore.Qt.transparent)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
self.setPalette(palette)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
fillColor = QtGui.QColor(75, 75, 75, 255)
lineColor = QtCore.Qt.gray
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtGui.QPen(QtGui.QBrush(lineColor), 2.0))
painter.setBrush(QtGui.QBrush(fillColor))
painter.drawRoundedRect(event.rect(), 15, 15)
I'm trying to do this with a paintEvent because:
QDialog stylesheets cannot use border-radius. Curved borders do show up, but corners are still visible.
QDialogs.setMask() works, but there is no way (that I know of) to anti-alias the mask.
Here is what that looks like:
Paint events are sent to a window/widget with the precise rectangle that needs updating not the whole bounding rectangle of the widget. When you call event.rect() it returns the rectangle that needs updating (As far as I know)
Try changing this line
painter.drawRoundedRect(event.rect(), 15, 15)
To this
painter.drawRoundedRect(self.rect(), 15, 15)
EDIT:
You also need to add this line anywhere in the constructor
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
Hope this helps.
I've found a work-around for now. You can hide the extra borders by using QPainter.eraseRect on the children and having the correct stylesheet set. I've also found that painting over the offending area with QPainter.fillRect works too.
def paintEvent(self, event):
painter = QtGui.QPainter(self)
fillColor = QtGui.QColor(75, 75, 75, 255)
lineColor = QtCore.Qt.gray
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtGui.QPen(QtGui.QBrush(lineColor), 2.0))
painter.setBrush(QtGui.QBrush(fillColor))
painter.drawRoundedRect(event.rect(), 15, 15)
# Sketchy fix:
painter.eraseRect(self.childrenRect())
# OR
painter.fillRect(self.childrenRect(), QtGui.QBrush(fillColor))
This doesn't answer my original question though. I'd like to avoid this behavior rather than masking it. So I'm not going to mark this as the answer.

Categories

Resources