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)

        vbox = QtWidgets.QVBoxLayout(self)
        button = QtWidgets.QPushButton('Hello')
        button.clicked.connect(partial(self.speak, 'World'))

    def speak(self, name):
        print(f'Hello {name}')

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    w = HelloWidget()


Example how to center a widget in the Houdini main window. This works if the main window is not full screen or if it is moved to a second monitor.

main_window = hou.qt.mainWindow()
my_widget = MyWidget()
my_widget.setParent(main_window, QtCore.Qt.Window)

pos = main_window.rect().center() - my_widget.rect().center()



  • Clipboard
    • Data to clipboard
      from PySide2 import QtWidgets
      clipboard = QtWidgets.QApplication.clipboard()
      clipboard.setText('Hello World!')
    • Get Data from clipboard
      from PySide2 import QtWidgets
      def get_clipboard_data():
          clipboard = QtWidgets.QApplication.clipboard()
      # You need an app to use use the clipboard, so if you want one while deving...
      if __name__ == '__main__':
          import sys
          app = QtWidgets.QApplication(sys.argv)

Custom Painting

Style Sheets

Colored Border

Here is a way to draw a colored bottom border to your line edits. Can be handy if you want to color code them rgb / xyz (like in Houdini). Note! you need to apply the style sheet with different hex value of the third component of the border-color property.

ss = '''
QLineEdit {
    border-width: 2px;
    border-style: solid;
    border-color: #2C2C2C #2C2C2C #B33921 #000000;
line_edit = QtWidgets.QLineEdit()

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.

stylesheet and custom drawing

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

    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)
        rect = QtCore.QRect(self._height, 0, self.width(), self._height)
        qp.drawText(rect, QtCore.Qt.AlignVCenter, self._name)
        qp.drawEllipse(QtCore.QPoint(self._height*.5, self._height*.5), self._height*.20, self._height*.20)

    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)


        vbox = QtWidgets.QVBoxLayout(self)

        # add widgets

        for i in range(3):

            dot = DotLabel('Petfactory {}'.format(i), 20+i*20)

        # apply stylesheet

        s_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'stylesheet.qss')
        with open(s_path, 'r') as f:

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.drawLine(...) # drawing code


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)

            qp = QtGui.QPainter(self.viewport())
            qp.setPen(QtGui.QColor(175, 175, 175))
            rect = QtCore.QRect(qp.fontMetrics().boundingRect(self._text))
            qp.drawText(rect, QtCore.Qt.AlignCenter, self._text)



Add a spacers to a form Layout

form_layout = QtWidgets.QFormLayout(self)
form_layout.setLabelAlignment((QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter))
# form_layout.setHorizontalSpacing(10)

# form_layout.setVerticalSpacing(10)

form_layout.addRow('Label 0', QtWidgets.QLabel('Zero'))

spacer = QtWidgets.QSpacerItem(0, 40)
form_layout.setItem(form_layout.rowCount(), QtWidgets.QFormLayout.SpanningRole, spacer)

form_layout.addRow('Label 1', QtWidgets.QLabel('One'))

spacer = QtWidgets.QSpacerItem(0, 0, vData=QtWidgets.QSizePolicy.Expanding)
form_layout.setItem(form_layout.rowCount(), QtWidgets.QFormLayout.SpanningRole, spacer)

form_layout.addRow('Label 2', QtWidgets.QLabel('Two'))

Get the label of a form layout

label = form_layout.itemAt(0, QtWidgets.QFormLayout.LabelRole).widget()
label.setToolTip('Hello World!')



Get the center of the display

# get the available screens

screens = QtGui.QGuiApplication.screens()
# print the center of the first screen


Visual properties

view.setColumnWidth(0, 200)

Drag & Drop

from PySide2 import QtGui
from PySide2 import QtCore
from PySide2 import QtWidgets

class MaterialTableView(QtWidgets.QTableView):

    def __init__(self, parent=None):
        super(MaterialTableView, self).__init__(parent)

    def mousePressEvent(self, event):
        q_index = self.indexAt(event.pos()) = None
        if q_index.isValid():
   = self.model().index(q_index.row(), 0).data()


    def mouseMoveEvent(self, event):

        if not

        mimeData = QtCore.QMimeData()
        drag = QtGui.QDrag(self)
        drag.exec_(QtCore.Qt.CopyAction | QtCore.Qt.MoveAction, QtCore.Qt.CopyAction)

class MeshTreeView(QtWidgets.QTreeView):

    def __init__(self, parent=None):
        super(MeshTreeView, self).__init__(parent)

    def dragEnterEvent(self, event):

    def dragMoveEvent(self, event):

    def dropEvent(self, event):

        q_index = self.indexAt(event.pos())
        if q_index.isValid():

        mime_data = event.mimeData()
        if mime_data.hasText():


  • Remove the blue-tinted highlight of selected icons
    • One way to get rid of the blue tint of selected icons (for instance in a QListView) is to explicitly set the icon of the selected state (as well as the normal). You can assign the same pixmap or create different sizes, colors or whatever you need.
pixmap_normal = create_your_pixmap(size)
pixmap_selected = create_your_pixmap(size)

icon = QtGui.QIcon()

icon.addPixmap(pixmap_normal, QtGui.QIcon.Normal, QtGui.QIcon.On)
icon.addPixmap(pixmap_selected, QtGui.QIcon.Selected, QtGui.QIcon.Off)



Set the view to resize to fill space

self.splitter = QtWidgets.QSplitter()
self.splitter.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)


To make an item checkable but not editable using flags

item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)

Signal & Slot

Chaining signals

Or mayabe signal forwarding? Lets say we have a widget (the “parent”) that has a widget (the “child”) which in turn have a widget (the “grandchild”). If we interact with this “grandchild” widget and emit a signal we might want the signal to reach the top “parent” widget. What I have done before is to emit a signal from the “grandchild” connect it to a slot in the “child” and there emit a new signal and connect it to a slot in the parent. This works but you can also connect a signal to a signal creating a chain of signals. Below is a snippet from the qt docs on Signal & Slots

It is even possible to connect a signal directly to another signal. (This will emit the second signal immediately whenever the first is emitted.)

Below is a simple example of how this works:

import sys
from functools import partial

from PySide2 import QtWidgets
from PySide2 import QtCore
from PySide2 import QtGui

class GrandChildWidget(QtWidgets.QWidget):

    notify = QtCore.Signal(str, int)

    def __init__(self, parent=None):
        super(GrandChildWidget, self).__init__(parent)

        vbox = QtWidgets.QVBoxLayout(self)
        btn = QtWidgets.QPushButton('Test')
        btn.clicked.connect(partial(self.notify.emit, 'Hello World!', 42))

class ChildWidget(QtWidgets.QWidget):

    notify = QtCore.Signal(str, int)

    def __init__(self, parent=None):
        super(ChildWidget, self).__init__(parent)

        vbox = QtWidgets.QVBoxLayout(self)
        grand_child_widget = GrandChildWidget()

class ParentWidget(QtWidgets.QWidget):

    def __init__(self, parent=None):
        super(ParentWidget, self).__init__(parent)

        vbox = QtWidgets.QVBoxLayout(self)
        child_widget = ChildWidget()

    def notify_slot(self, message, value):
        print(f'{message=}, {value=}')

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    w = ParentWidget()

Stylesheets & Resources

Compile .qrc

  • To compile a qrc file
    • Activate the venv that has PySide2 installed
    • Then cd to the project dir where you have your resources
    • Compile the icons.qrc:
      • pyside2-rcc icons.qrc -o