Introducing GUI: PyQt5 in Python

Why Python with GUI?


Why Python instead of Ruby, JS, C #?

  1. Easy to write Python language itself (vs JS)
  2. Works on both Windows and Mac (vs Ruby, C #)
  3. Extensive library for data analysis and image processing
  4. C / C ++ code can be called with ctypes and Cython

Python's main GUI libraries


Introducing PyQt5


Qt is an OSS application framework

--1992 ~ (currently v5.8) -** Made by C ++ ** -** Cross-platform ** (drawing in OS native style) -** Rich widgets ** _ -** Easy-to-use API ** (Signal, Layout, MV) -** Tools ** (IDE, UI Designer)


PyQt5 is a wrapper for Qt5.x

A tool called SIP generates the C ++ <=> Python part.

--You can use almost all the functions of Qt --The easy-to-use API of Qt remains the same --More Pythonic writing --Convert strings and arrays to Python types --You can pass lambda or method to the method


Example of PyQt5


Place standard widgets

Display the current value of the scroll bar as a character string

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys

class Sample1(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        s = QScrollBar(orientation=Qt.Horizontal)
        t = QLabel()

        s.valueChanged.connect(lambda v: t.setText(str(v)))

        l = QVBoxLayout()
        l.addWidget(s)
        l.addWidget(t)
        self.setLayout(l)

app = QApplication(sys.argv)
s = Sample1()
s.show()
sys.exit(app.exec_())

Example 1


Layout

--Approximately the same as .Net etc. --Even if the window size changes, it expands and contracts appropriately


signal

--Each widget has its own signal --Notify of value changes (valueChanged), clicks (clicked), etc. --Register the destination function or method with .connect --Observer pattern

→ Prevent widgets from tightly coupling with the model layer → More flexible than the callback method


Connect signals in both directions

--You may loop connect / setValue --You may connect to the same signal multiple times, or connect to multiple signals.

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys

class Sample2(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        l = QVBoxLayout()
        
        t = QLabel()
        s1 = QScrollBar(orientation=Qt.Horizontal)
        s1.valueChanged.connect(lambda v: t.setText(str(v)))

        s2 = QScrollBar(orientation=Qt.Horizontal)
        s3 = QSpinBox()
        s4 = QSpinBox()
        s5 = QSpinBox()

        s1.valueChanged.connect(s2.setValue)
        s2.valueChanged.connect(s3.setValue)
        s3.valueChanged.connect(s4.setValue)
        s4.valueChanged.connect(s5.setValue)
        s5.valueChanged.connect(s1.setValue)

        l.addWidget(s1)
        l.addWidget(s2)
        l.addWidget(s3)
        l.addWidget(s4)
        l.addWidget(s5)
        l.addWidget(t)

        self.setLayout(l)

app = QApplication(sys.argv)
s = Sample2()
s.show()
sys.exit(app.exec_())

Example 2


Keima jump

--Implemented data model widget --You can also define signals in your model


#Data (current board)
class BoardState:

#model
class BoardModel(QObject):
    stateChanged = pyqtSignal(BoardState)
    def moveKnightTo(self, x, y):
    def rollback(self):

#Widget (each cell)
class BoardCellWidget(QLabel):
    knightMoved = pyqtSignal(tuple)
    def setState(self, state):

#A widget that has a model and displays 64 cells
class BoardWidget(QWidget):

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys

class BoardState:
    def __init__(self, x, y):
        self._x = x
        self._y = y
        
    def knightIn(self, x, y):
        return (self._x, self._y) == (x, y)
    
    def canKnightMoveTo(self, x, y):
        dx = abs(self._x - x)
        dy = abs(self._y - y)
        return (dx, dy) in [(1, 2), (2, 1)]
        

class BoardModel(QObject):
    stateChanged = pyqtSignal(BoardState)

    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self._states = [BoardState(0, 0)]

    def moveKnightTo(self, x, y):
        newState = BoardState(x, y)
        self._states.append(newState)
        self.stateChanged.emit(newState)

    def rollback(self):
        if len(self._states) <= 1:
            return
        self._states = self._states[:-1]
        self.stateChanged.emit(self._states[-1])

    def state(self):
        return self._states[-1]


class BoardCellWidget(QLabel):
    knightMoved = pyqtSignal(tuple)

    def __init__(self, parent, x, y):
        super().__init__(parent=parent)
        self._x = x
        self._y = y

        self.setMinimumSize(QSize(32, 32))
        self.setMouseTracking(True)
        self.setAcceptDrops(True)

    def setState(self, state):
        self._state = state
        self.update()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            drag = QDrag(self)
            data = QMimeData()
            data.setText("knight")
            drag.setMimeData(data)
            drag.exec()

            event.accept()

    def dropEvent(self, event):
        if self._state.canKnightMoveTo(self._x, self._y):
            self.knightMoved.emit((self._x, self._y))
            event.acceptProposedAction()

    def dragEnterEvent(self, event):
        if self._state.canKnightMoveTo(self._x, self._y):
            event.acceptProposedAction()

    def paintEvent(self, event):
        p = QPainter(self)

        if (self._x + self._y) % 2 == 1:
            p.setBackground(QColor(0xD3, 0x8C, 0x40))
        else:
            p.setBackground(QColor(0xFF, 0xCF, 0x9B))
        p.eraseRect(self.rect())

        rect = QRect(4, 4, self.width() - 8, self.height() - 8)
        if self._state.knightIn(self._x, self._y):
            p.fillRect(rect, Qt.white)
        elif self._state.canKnightMoveTo(self._x, self._y):
            p.fillRect(rect, Qt.green)


class BoardWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        self._model = BoardModel(parent=self)

        l = QGridLayout()
        for x in range(8):
            for y in range(8):
                cell = BoardCellWidget(parent, x, y)
                cell.setState(self._model.state())
                self._model.stateChanged.connect(cell.setState)
                cell.knightMoved.connect(lambda xy: self._model.moveKnightTo(*xy))
                l.addWidget(cell, x, y)

        l1 = QHBoxLayout()
        l1.addLayout(l, 1)
        self._rollback = QPushButton("rollback", parent=self)
        self._rollback.pressed.connect(self._model.rollback)
        l1.addWidget(self._rollback, 0)
        self.setLayout(l1)

app = QApplication(sys.argv)

b = BoardWidget()
b.show()
sys.exit(app.exec_())

Example 3


Supplement

--There is also a DSL called QML / QtQuick --You can embed a browser with QtWebEngineWidgets (→ React.js can also be used) --Even on Android, you can create an app by building using NDK. --In the old days, it was difficult to build. Now OK with pip install


Unfortunately

--License is GPL or paid ――Is there any problem for internal / individual use? --Installation work required --PyInstaller can be used to generate executable files for Windows --There should be a similar tool on Mac and Linux


Q&A

** Q. ** Why doesn't an infinite loop occur even if the widgets are connected in a loop with ʻa.valueChanged.connect (b.setValue) ? ** A. ** valueChanged` is only issued" when the value is changed ".

Recommended Posts

Introducing GUI: PyQt5 in Python
[GUI in Python] PyQt5 -Event-
[GUI in Python] PyQt5-Dialog-
Introducing Python in Practice (PiP)
[GUI with Python] PyQt5 -Preparation-
[GUI with Python] PyQt5 -Paint-
GUI programming in Python using Appjar
[GUI with Python] PyQt5 -Widget II-
[GUI with Python] PyQt5 -Custom Widget-
[GUI in Python] PyQt5-Menu and Toolbar-
GUI creation in python using tkinter 2
Try to make it using GUI and PyQt in Python
Quadtree in Python --2
Python in optimization
Metaprogramming in Python
Python 3.3 in Anaconda
Geocoding in python
SendKeys in Python
GUI creation in python using tkinter part 1
Meta-analysis in Python
Unittest in python
Create a simple GUI app in Python
Epoch in Python
Discord in Python
Create Qt designer parts in Python (PyQt)
Sudoku in Python
nCr in python
N-Gram in Python
Programming in python
Plink in Python
Lifegame in Python.
FizzBuzz in Python
Sqlite in python
N-gram in python
LINE-Bot [0] in Python
Csv in python
Disassemble in Python
Constant in python
nCr in Python.
format in python
Scons in Python3
Puyo Puyo in python
python in virtualenv
PPAP in Python
Quad-tree in Python
Reflection in Python
Chemistry in Python
Hashable in python
DirectLiNGAM in Python
LiNGAM in Python
Flatten in python
flatten in python
I made a GUI application with Python + PyQt5
GUI (WxPython) executable file (pyInstaller) [Windows] in Python3
Sorted list in Python
Daily AtCoder # 36 in Python
Daily AtCoder # 2 in Python
Implement Enigma in python
Daily AtCoder # 32 in Python
Daily AtCoder # 18 in Python
Singleton pattern in Python