PySide2 - Notes
Pyside2
Install PySide2
To install PySide2 (including Shiboken2 & Qt) make sure that we first activate our venv
pip install PySide2
Hello World app
Lets build a simple ui and run with sublimeText. Select the build system we just created and build Ctrl B
from PySide2 import QtCore
from PySide2 import QtWidgets
from PySide2 import QtGui
import sys
from functools import partial
class HelloWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(HelloWidget, self).__init__(parent)
self.setGeometry(400, 400, 250, 50)
self.setWindowTitle('Hello')
vbox = QtWidgets.QVBoxLayout(self)
button = QtWidgets.QPushButton('Hello')
button.clicked.connect(partial(self.speak, 'World'))
vbox.addWidget(button)
def speak(self, name):
print(f'Hello {name}')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = HelloWidget()
w.show()
app.exec_()
Custom Painting
Style Sheets
I am working on a custom widget to create a collapsable frame, like the one used in the Maya attribute editor. In this widget I have a label and an arrow to indicate the expanded/collapsed state of the widget. I wanted to learn how to implement a stylesheet to be able to set a few visual properties of the widget. Some of the properties comes for free like the background-color, border-radius, font-size and color etc. But I also wanted to be able to set the color of the arrow. The image below is an example of a simplified version of the widget.
In the article “Qt Style Sheets and Custom Painting Example” from the Qt docs if found a C++ example of how to do this. After some further googling and some trial and error I got it to work with PySide2.
Below is a simplified example of the widgets.
class DotLabel(QtWidgets.QFrame):
def __init__(self, name, height=20, parent=None):
super(DotLabel, self).__init__(parent)
self._dot_color = QtGui.QColor(0, 0, 0)
self._name = name
self._height = height
self.setFixedHeight(height)
def get_dot_color(self):
return self._dot_color
def set_dot_color(self, color):
self._dot_color = color
def paintEvent(self, e):
qp = QtGui.QPainter(self)
qp.setRenderHint(QtGui.QPainter.Antialiasing)
rect = QtCore.QRect(self._height, 0, self.width(), self._height)
qp.drawText(rect, QtCore.Qt.AlignVCenter, self._name)
qp.setBrush(self.get_dot_color())
qp.setPen(QtCore.Qt.NoPen)
qp.drawEllipse(QtCore.QPoint(self._height*.5, self._height*.5), self._height*.20, self._height*.20)
qp.end()
dotColor = QtCore.Property(QtGui.QColor, get_dot_color, set_dot_color)
And in the “main” widget.
class TestWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TestWidget, self).__init__(parent)
self.setGeometry(100,240,400,200)
vbox = QtWidgets.QVBoxLayout(self)
# add widgets
for i in range(3):
dot = DotLabel('Petfactory {}'.format(i), 20+i*20)
dot.setObjectName('dot_{}'.format(i))
vbox.addWidget(dot)
# apply stylesheet
s_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'stylesheet.qss')
with open(s_path, 'r') as f:
self.setStyleSheet(f.read())
Example from the style sheet:
DotLabel {
font-family: "Futura";
}
DotLabel#dot_0 {
border-radius: 2px;
border: 1px solid rgb(253, 151, 32);
color: rgb(253, 151, 32);
font-size: 12px;
qproperty-dotColor: rgb(253, 151, 32);
}
DotLabel#dot_1 {
border-radius: 4px;
color: rgb(166, 226, 46);
font-size: 24px;
qproperty-dotColor: rgb(166, 226, 46);
background-color: rgb(90,90,90);
}
DotLabel#dot_2 {
border-radius: 6px;
color: rgb(102, 217, 239);
font-size: 40px;
qproperty-dotColor: rgb(102, 217, 239);
background-color: rgb(90,90,90);
}
While in the research phase I came across a nice post by Dhruv Govil not exactly related, more on dynamic properties and stylesheets. There is also this article from the Qt docs
Empty Model
In a tool I am writing I have a table view in which I want to display some informative text to the user when the view is “empty” i.e. the model has no rows. My initial idea was to overlay the view with a label but this felt a bit to hacky. I ended up on a SO post that subclassed a QTableView and implemented som custom drawing in the paintEvent when the model was empty. I liked this approach and implemented that in Python. Below is some information from the PySide docs on QPainter. I wanted to understand if I needed to call the begin() and end() when I did the painting.
- class PySide.QtGui.QPainter(arg__1)
- Parameters: arg__1 – PySide.QtGui.QPaintDevice
Constructs a painter that begins painting the paint device immediately.
This constructor is convenient for short-lived painters, e.g. in a QWidget.paintEvent() and should be used only once. The constructor calls PySide.QtGui.QPainter.begin() for you and the PySide.QtGui.QPainter destructor automatically calls PySide.QtGui.QPainter.end() .
Here’s an example using PySide.QtGui.QPainter.begin() and PySide.QtGui.QPainter.end() :
def paintEvent(self, paintEvent):
p = QPainter()
p.begin(self)
p.drawLine(...) # drawing code
p.end()
The same example using this constructor:
def paintEvent(self, paintEvent):
p = QPainter(self)
p.drawLine(...) # drawing code
And this is the CustomTableView
class CustomTableView(QtWidgets.QTableView):
def __init__(self, *args, **kwargs):
super(CustomTableView, self).__init__(*args, **kwargs)
self._text = ' Right Click to Add / Remove '
def paintEvent(self, event):
if self.model() and self.model().rowCount() > 0:
super(CustomTableView, self).paintEvent(event)
else:
qp = QtGui.QPainter(self.viewport())
qp.setPen(QtGui.QColor(175, 175, 175))
rect = QtCore.QRect(qp.fontMetrics().boundingRect(self._text))
rect.moveCenter(self.viewport().rect().center())
qp.drawText(rect, QtCore.Qt.AlignCenter, self._text)
QTreeView
QStandardItem
To make an item checkable but not editable using flags
item.setCheckable(True)
item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)