PyQt5 メモ

2018年12月7日

はじめに

Qt 5.x の Python バインディング PyQt5 のメモ。

環境

  • Windows 7 64 bit
  • MSYS2 64 bit (MinGW w64)
  • Anaconda3 4.1.11
  • PyQt5 5.7
  • Ubuntu 16.04 LTS
  • PyQt5 5.11.3

PyQt と PySide の違い

Qt の Python バインディングとして PyQt と PySide があるが、大きな違いはライセンスで、前者は GPL、後者は LGPL である。PySide は PyQt の GPL を嫌って開発されたものである。Qt 4.x、5.x に対応してそれぞれ PyQt4、PyQt5 あるいは PySide、PySide2 とパッケージが異なっている。両者のライセンスの違いは、Pyinstaller を使う時に問題になるかもしれない。

PyQt のインストール

PIP でインストールできる。

$ pip install PyQt5

ウインドウ

window.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("window")

        self.resize(320, 240)
        self.centerOnScreen()
        self.show()

    def centerOnScreen(self):
        res = QDesktopWidget().screenGeometry()
        self.move((res.width()/2) - (self.frameSize().width()/2),
                  (res.height()/2) - (self.frameSize().height()/2))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

マウス

mouse.py

# -*- coding: utf-8 -*-

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtCore import Qt


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setMouseTracking(True)

        self.setWindowTitle("mouse")
        self.resize(320, 240)
        self.show()

    def mouseButtonKind(self, buttons):
        if buttons & Qt.LeftButton:
            print("LEFT")
        if buttons & Qt.MidButton:
            print("MIDDLE")
        if buttons & Qt.RightButton:
            print("RIGHT")

    def mousePressEvent(self, e):
        print("BUTTON PRESS")
        self.mouseButtonKind(e.buttons())

    def mouseReleaseEvent(self, e):
        print("BUTTON RELEASE")
        self.mouseButtonKind(e.buttons())

    def wheelEvent(self, e):
        print("wheel")
        print("(%d %d)" % (e.angleDelta().x(), e.angleDelta().y()))

    def mouseMoveEvent(self, e):
        print("(%d %d)" % (e.x(), e.y()))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

キー入力

key.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtCore import Qt


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("key")

        self.resize(320, 240)
        self.show()

    def keyPressEvent(self, e):
        def isPrintable(key):
            printable = [
                Qt.Key_Space,
                Qt.Key_Exclam,
                Qt.Key_QuoteDbl,
                Qt.Key_NumberSign,
                Qt.Key_Dollar,
                Qt.Key_Percent,
                Qt.Key_Ampersand,
                Qt.Key_Apostrophe,
                Qt.Key_ParenLeft,
                Qt.Key_ParenRight,
                Qt.Key_Asterisk,
                Qt.Key_Plus,
                Qt.Key_Comma,
                Qt.Key_Minus,
                Qt.Key_Period,
                Qt.Key_Slash,
                Qt.Key_0,
                Qt.Key_1,
                Qt.Key_2,
                Qt.Key_3,
                Qt.Key_4,
                Qt.Key_5,
                Qt.Key_6,
                Qt.Key_7,
                Qt.Key_8,
                Qt.Key_9,
                Qt.Key_Colon,
                Qt.Key_Semicolon,
                Qt.Key_Less,
                Qt.Key_Equal,
                Qt.Key_Greater,
                Qt.Key_Question,
                Qt.Key_At,
                Qt.Key_A,
                Qt.Key_B,
                Qt.Key_C,
                Qt.Key_D,
                Qt.Key_E,
                Qt.Key_F,
                Qt.Key_G,
                Qt.Key_H,
                Qt.Key_I,
                Qt.Key_J,
                Qt.Key_K,
                Qt.Key_L,
                Qt.Key_M,
                Qt.Key_N,
                Qt.Key_O,
                Qt.Key_P,
                Qt.Key_Q,
                Qt.Key_R,
                Qt.Key_S,
                Qt.Key_T,
                Qt.Key_U,
                Qt.Key_V,
                Qt.Key_W,
                Qt.Key_X,
                Qt.Key_Y,
                Qt.Key_Z,
                Qt.Key_BracketLeft,
                Qt.Key_Backslash,
                Qt.Key_BracketRight,
                Qt.Key_AsciiCircum,
                Qt.Key_Underscore,
                Qt.Key_QuoteLeft,
                Qt.Key_BraceLeft,
                Qt.Key_Bar,
                Qt.Key_BraceRight,
                Qt.Key_AsciiTilde,
            ]

            if key in printable:
                return True
            else:
                return False

        control = False

        if e.modifiers() & Qt.ControlModifier:
            print("Control")
            control = True

        if e.modifiers() & Qt.ShiftModifier:
            print("Shift")

        if e.modifiers() & Qt.AltModifier:
            print("Alt")

        if e.key() == Qt.Key_Delete:
            print("Delete")

        elif e.key() == Qt.Key_Backspace:
            print("Backspace")

        elif e.key() in [Qt.Key_Return, Qt.Key_Enter]:
            print("Enter")

        elif e.key() == Qt.Key_Escape:
            print("Escape")

        elif e.key() == Qt.Key_Right:
            print("Right")

        elif e.key() == Qt.Key_Left:
            print("Left")

        elif e.key() == Qt.Key_Up:
            print("Up")

        elif e.key() == Qt.Key_Down:
            print("Down")

        if not control and isPrintable(e.key()):
            print(e.text())


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

メニュー

menu.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtWidgets import QAction, qApp


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        exitAction = QAction("&Exit", self)
        exitAction.setShortcut("Ctrl+Q")
        exitAction.triggered.connect(qApp.quit)

        doAction = QAction("&Do", self)
        doAction.triggered.connect(self.do)

        menubar = self.menuBar()

        fileMenu = menubar.addMenu("&File")
        fileMenu.addAction(doAction)

        subMenu = fileMenu.addMenu("&Sub")
        subMenu.addAction(doAction)

        fileMenu.addSeparator()
        fileMenu.addAction(exitAction)

        self.setWindowTitle("menu")
        self.show()

    def do(self):
        print("Do")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

ポップアップメニュー

popup.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtWidgets import QMenu, qApp


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("popup")

        self.resize(320, 240)
        self.show()

    def contextMenuEvent(self, e):
        menu = QMenu(self)
        aQuit = menu.addAction("Quit")

        action = menu.exec_(self.mapToGlobal(e.pos()))

        if action == aQuit:
            qApp.quit()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

ファイルの選択

file_select.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtWidgets import QAction, qApp, QFileDialog


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        exitAction = QAction("&Exit", self)
        exitAction.setShortcut("Ctrl+Q")
        exitAction.triggered.connect(qApp.quit)

        openAction = QAction("&Open", self)
        openAction.triggered.connect(self.open)

        saveAction = QAction("&Save", self)
        saveAction.triggered.connect(self.save)

        menubar = self.menuBar()

        fileMenu = menubar.addMenu("&File")
        fileMenu.addAction(openAction)
        fileMenu.addAction(saveAction)

        fileMenu.addSeparator()
        fileMenu.addAction(exitAction)

        self.setWindowTitle("file select")
        self.show()

    def open(self):
        filename = QFileDialog.getOpenFileName(self, "Open File",
                None, "Python Files (*.py)")
        print(filename)

    def save(self):
        #filename = QFileDialog.getSaveFileName(self, "Save File",
        #        None, "Python files (*.py)")
        dialog = QFileDialog(self)
        dialog.setWindowTitle("Save File")
        dialog.setNameFilters(["Python Files (*.py)", "All Files (*)"])
        filename = dialog.exec_()
        print(filename)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

ファイルを選択するダイアログは、QFileDialog.getOpenFileName() などで直接ダイアログを開く方法と、一旦 QFileDialog のオブジェクトを作ってから exec_() で開く方法がある。

ディレクトリを選択する場合は、QFileDialog.getExistingDirectory() を用いる。

path = QFileDialog.getExistingDirectory(self, "Open Directory", "")

第 4 引数として QFileDialog.DontUseNativeDialog を渡すと、ネイティブのダイアログを使わない。場合によってはそうしたほうがよいことがある。

ツールバー

tool_bar.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtWidgets import QAction, QStyle
#from PyQt5.QtGui import QIcon


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        toolbar = self.addToolBar("")

        aButtonIcon = QAction(
                #QIcon("adelie.png"),
                QApplication.style().standardIcon(QStyle.SP_DirOpenIcon),
                "button",
                self)

        #aButtonIcon.setCheckable(True)

        aButtonIcon.triggered.connect(self.buttonIconPress)
        toolbar.addAction(aButtonIcon)

        self.setWindowTitle("tool bar")
        self.resize(320, 240)
        self.show()

    def buttonIconPress(self, active):
        if active:
            print("Active: TRUE")
        else:
            print("Active: FALSE")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

QAction() でアイコンを登録するが、自分で用意したアイコンを用いる場合は、QIcon で画像を指定する。Qt で用意されたアイコンを用いるのであれば standardIcon() で指定する。アイコンの種類は こちら の QStyle::StandardPixmap から選ぶ。setCheckable() で On/Off 可能なボタンにできる。

ボタン

button.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QPushButton, QVBoxLayout


class Main(QDialog):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        button = QPushButton("Button")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(button)

        self.setLayout(layout)

        self.setWindowTitle("button")
        self.show()

    def buttonClicked(self):
        sender = self.sender()
        print(sender.text())


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

QMainWindow の代りに QDialog を用いている。

メッセージボックス

message_box.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QPushButton, QVBoxLayout
from PyQt5.QtWidgets import QMessageBox


class Main(QDialog):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        button = QPushButton("Show message box")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(button)

        self.setLayout(layout)

        self.setWindowTitle("message box")
        self.show()

    def buttonClicked(self):
        reply = QMessageBox.question(self, "Message", "Yes/No?",
                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes:
            print("Yes")
        else:
            print("No")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

情報を表示するだけなら、次のようにできる。

QMessageBox.information(self, "Information", "message")

エラーメッセージなら、次のようになる。

QMessageBox.critical(self, "Error", "message")

警告。

QMessage.warning(self, "Warning", "message")

プログラムの情報を表示する about。Window にアイコンが設定されていればそれが表示される。

QMessage.about(self, "About", "myprogram v0.1")

チェックボックス

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QCheckBox, QPushButton, QVBoxLayout


class Main(QDialog):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        self.check1 = QCheckBox("Check1")
        self.check2 = QCheckBox("Check2")
        self.check1.setChecked(True)

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(self.check1)
        layout.addWidget(self.check2)
        layout.addWidget(button)

        self.setLayout(layout)

        self.setWindowTitle("check box")
        self.show()

    def buttonClicked(self):
        print("Check1: %d" % self.check1.isChecked())
        print("Check2: %d" % self.check2.isChecked())


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

ラジオボタン

radio_button.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QRadioButton, QButtonGroup
from PyQt5.QtWidgets import QPushButton, QVBoxLayout


class Main(QDialog):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        radio1 = QRadioButton("Radio1")
        radio2 = QRadioButton("Radio2")

        self.group = QButtonGroup()
        self.group.addButton(radio1, 1)
        self.group.addButton(radio2, 2)
        radio1.toggle()

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(radio1)
        layout.addWidget(radio2)
        layout.addWidget(button)

        self.setLayout(layout)

        self.setWindowTitle("radio button")
        self.show()

    def buttonClicked(self):
        print("Radio: %d" % self.group.checkedId())


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

コンボボックス

combo_box.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QLabel, QComboBox
from PyQt5.QtWidgets import QPushButton, QVBoxLayout


class Main(QDialog):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        label = QLabel("Select")

        self.combo = QComboBox(self)
        self.combo.addItem("apple")
        self.combo.addItem("banana")
        self.combo.addItem("lemon")
        self.combo.addItem("orange")

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(label)
        layout.addWidget(self.combo)
        layout.addWidget(button)

        self.setLayout(layout)

        self.setWindowTitle("combo box")
        self.show()

    def buttonClicked(self):
        print("Combo: %d, %s"
                % (self.combo.currentIndex(), self.combo.currentText()))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

入力

input.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QLabel, QLineEdit
from PyQt5.QtWidgets import QPushButton, QVBoxLayout


class Main(QDialog):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        label = QLabel("Input")
        self.edit = QLineEdit()

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(label)
        layout.addWidget(self.edit)
        layout.addWidget(button)

        self.setLayout(layout)

        self.setWindowTitle("input")
        self.show()

    def buttonClicked(self):
        print(self.edit.text())


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

テキスト

text.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QTextEdit, QPushButton, QVBoxLayout


class Main(QDialog):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        self.text = QTextEdit()

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(self.text)
        layout.addWidget(button)

        self.setLayout(layout)

        self.setWindowTitle("text")
        self.show()

    def buttonClicked(self):
        print(self.text.toPlainText())


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

テーブル

table.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem
from PyQt5.QtWidgets import QPushButton, QVBoxLayout


class Main(QDialog):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        table = QTableWidget(2, 3)
        self.table = table

        header = ["A", "B", "C"]
        data = [["apple", "1", "100"], ["banana", "2", "200"]]

        table.setHorizontalHeaderLabels(header)

        for i in range(len(data)):
            for j in range(len(data[i])):
                table.setItem(i, j, QTableWidgetItem(data[i][j]))

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(table)
        layout.addWidget(button)

        self.setLayout(layout)

        self.setWindowTitle("table")
        self.show()

    def buttonClicked(self):
        for i in range(self.table.rowCount()):
            for j in range(self.table.columnCount()):
                print(self.table.item(i, j).text(), end=" ")
            print()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

リスト

list.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QListWidget, QPushButton, QVBoxLayout


class Main(QDialog):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        listWidget = QListWidget()
        self.listWidget = listWidget

        listWidget.addItem("apple")
        listWidget.addItem("banana")
        listWidget.addItem("lemon")
        listWidget.addItem("orenge")

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(listWidget)
        layout.addWidget(button)

        self.setLayout(layout)

        self.setWindowTitle("list")
        self.show()

    def buttonClicked(self):
        for i in range(self.listWidget.count()):
            print(self.listWidget.item(i).text())

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

ツリー

ツリーを作るには QTreeWidget を用いる。

tree.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem
from PyQt5.QtWidgets import QPushButton, QVBoxLayout
from PyQt5.QtCore import Qt


class Main(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        tree_widget = QTreeWidget()
        self.tree_widget = tree_widget
        tree_widget.setAlternatingRowColors(True)

        branch1 = QTreeWidgetItem()
        branch1.setText(0, "branch1")

        branch2 = QTreeWidgetItem()
        branch2.setText(0,"branch2")

        def addItem(branch, name, num, num2):
            item = QTreeWidgetItem(branch)
            item.setFlags(item.flags() | Qt.ItemIsEditable)
            item.setText(0, name)
            item.setText(1, str(num))
            item.setText(2, str(num2))

        addItem(branch1, "apple", 1, 100)
        addItem(branch1, "banana", 2, 200)
        addItem(branch2, "lemon", 3, 300)
        addItem(branch2, "orange", 4, 400)

        tree_widget.addTopLevelItem(branch1)
        tree_widget.addTopLevelItem(branch2)

        tree_widget.setColumnCount(3)
        tree_widget.setHeaderLabels(["A", "B", "C"])

        branch1.setExpanded(True)
        branch2.setExpanded(True)

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(tree_widget)
        layout.addWidget(button)

        self.setLayout(layout)

        self.setWindowTitle("tree")
        self.show()

    def buttonClicked(self):
        for i in range(self.tree_widget.topLevelItemCount()):
            branch = self.tree_widget.topLevelItem(i)
            print(branch.text(0))
            for j in range(branch.childCount()):
                item = branch.child(j)
                print("  ", end="")
                for k in range(item.columnCount()):
                    print(item.text(k), end=" ")
                print()

        print("find: lemon")
        items = self.tree_widget.findItems("lemon", Qt.MatchRecursive)
        item = items[0]
        print("  ", end="")
        for k in range(item.columnCount()):
            print(item.text(k), end=" ")
        print()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

ヘッダーを非表示にしたい場合は、次のようにする。

treeWidget.setHeaderHidden(True)

あるいは

treeWidget.header().hide()

アイテムを編集できるようにしたい場合は、次のようにする。

item.setFlags(item.flags() | Qt.ItemIsEditable)

チェックボックス付きツリー

check_tree.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QMenu, qApp
from PyQt5.QtCore import Qt


class Main(QDialog):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        tree_widget = QTreeWidget()
        self.tree_widget = tree_widget

        tree_widget.setContextMenuPolicy(Qt.CustomContextMenu)
        tree_widget.customContextMenuRequested.connect(self.contextMenu)

        branch1 = QTreeWidgetItem()
        branch1.setData(0, Qt.CheckStateRole, Qt.Checked)
        branch1.setText(0, "branch1")

        branch2 = QTreeWidgetItem()
        branch2.setData(0, Qt.CheckStateRole, Qt.Checked)
        branch2.setText(0,"branch2")

        def addItem(branch, name, num, num2):
            item = QTreeWidgetItem(branch)
            item.setData(0, Qt.CheckStateRole, Qt.Checked)
            item.setText(0, name)
            item.setText(1, str(num))
            item.setText(2, str(num2))

        addItem(branch1, "apple", 1, 100)
        addItem(branch1, "banana", 2, 200)
        addItem(branch2, "lemon", 3, 300)
        addItem(branch2, "orange", 4, 400)

        tree_widget.addTopLevelItem(branch1)
        tree_widget.addTopLevelItem(branch2)

        tree_widget.setColumnCount(3)
        tree_widget.setHeaderLabels(["A", "B", "C"])

        tree_widget.itemClicked.connect(self.selectItem)
        tree_widget.itemChanged.connect(self.changeItem)

        branch1.setExpanded(True)
        branch2.setExpanded(True)

        layout = QVBoxLayout()
        layout.addWidget(tree_widget)

        self.setLayout(layout)

        self.setWindowTitle("check_tree")
        self.show()

    def selectItem(self):
        if self.tree_widget.selectedItems() == []:
            return
        item = self.tree_widget.selectedItems()[0]
        print(item.text(0))

    def changeItem(self, item):
        if item.childCount() > 0:
            self.checkBranch(item, item.checkState(0))

        for i in range(self.tree_widget.topLevelItemCount()):
            branch = self.tree_widget.topLevelItem(i)
            print(branch.text(0))
            for j in range(branch.childCount()):
                item = branch.child(j)
                if item.checkState(0):
                    print("  ", end="")
                    for k in range(item.columnCount()):
                        print(item.text(k), end=" ")
                    print()

    def checkBranch(self, branch, check=2):
        for i in range(branch.childCount()):
            item = branch.child(i)
            item.setCheckState(0, check)

    def checkAll(self, check=2):
        for i in range(self.tree_widget.topLevelItemCount()):
            branch = self.tree_widget.topLevelItem(i)
            branch.setCheckState(0, check)
            self.checkBranch(branch, check)

    def contextMenu(self, point):
        menu = QMenu(self)
        check_all = menu.addAction("Check all")
        uncheck_all = menu.addAction("Uncheck all")

        action = menu.exec_(self.mapToGlobal(point))

        if action == check_all:
            self.checkAll()
        elif action == uncheck_all:
            self.checkAll(0)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

アイテムにチェックボックスをつけたい場合は、次のようにする。

item.setData(0, Qt.CheckStateRole, Qt.Checked)

上の例では、親のチェックに応じて子のチェックを切り替えるようにしている。またポップアップメニューで全選択・解除を実行できるようにしている。

QTreeView

QTreeWidget の代わりに QTreeView でもツリーを作ることができる。

tree2.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QPushButton, QVBoxLayout
from PyQt5.QtWidgets import QTreeView
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtCore import Qt


class Main(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        model = QStandardItemModel(0, 3, self)
        self.tree_model = model

        root = model.invisibleRootItem()
        branch1 = QStandardItem("branch1")
        branch2 = QStandardItem("branch2")

        def addItem(branch, name, num, num2):
            item_name = QStandardItem(name)
            item_num = QStandardItem(str(num))
            item_num2 = QStandardItem(str(num2))

            item_name.setEditable(False)
            item_num.setEditable(True)
            item_num2.setEditable(True)

            branch.appendRow([item_name, item_num, item_num2])

        addItem(branch1, "apple", 1, 100)
        addItem(branch1, "banana", 2, 200)
        addItem(branch2, "lemon", 3, 300)
        addItem(branch2, "orange", 4, 400)

        root.appendRow(branch1)
        root.appendRow(branch2)

        model.setHeaderData(0, Qt.Horizontal, "A")
        model.setHeaderData(1, Qt.Horizontal, "B")
        model.setHeaderData(2, Qt.Horizontal, "C")

        tree_view = QTreeView()
        self.tree_view = tree_view

        tree_view.setModel(model)
        tree_view.setAlternatingRowColors(True)
        tree_view.expandAll()

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(tree_view)
        layout.addWidget(button)

        self.setLayout(layout)

        self.setWindowTitle("tree")
        self.show()

    def buttonClicked(self):
        model = self.tree_model
        for i in range(model.rowCount()):
            branch = model.index(i, 0)
            print(branch.data())
            for j in range(branch.model().rowCount()):
                print("  ", end="")
                for k in range(branch.model().columnCount()):
                    print(branch.child(j, k).data(), end=" ")
                print()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

ファイルシステムの表示

TreeView を使うと、ファイルシステムの表示などができる。

filesystem.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QTreeView, QFileSystemModel
from PyQt5.QtCore import QDir


class Main(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        model = QFileSystemModel()
        index = model.setRootPath(QDir.currentPath())

        tree_view = QTreeView()
        tree_view.setModel(model)
        tree_view.setRootIndex(index)

        layout = QVBoxLayout()
        layout.addWidget(tree_view)
        self.setLayout(layout)

        self.setWindowTitle("filesystem")
        self.resize(640, 480)
        self.show()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

タブ

tab.py

import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtWidgets import QHBoxLayout, QTabWidget
from PyQt5.QtWidgets import QRadioButton, QButtonGroup
from PyQt5.QtWidgets import QCheckBox, QLabel, QComboBox
from PyQt5.QtWidgets import QPushButton, QVBoxLayout


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

        radio1 = QRadioButton("Radio1")
        radio2 = QRadioButton("Radio2")

        self.group = QButtonGroup()
        self.group.addButton(radio1, 1)
        self.group.addButton(radio2, 2)
        radio1.toggle()

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(radio1)
        layout.addWidget(radio2)
        layout.addWidget(button)

        self.setLayout(layout)

    def buttonClicked(self):
        print("Radio: %d" % self.group.checkedId())


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

        self.check1 = QCheckBox("Check1")
        self.check2 = QCheckBox("Check2")
        self.check1.setChecked(True)

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(self.check1)
        layout.addWidget(self.check2)
        layout.addWidget(button)

        self.setLayout(layout)

    def buttonClicked(self):
        print("Check1: %d" % self.check1.isChecked())
        print("Check2: %d" % self.check2.isChecked())


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

        label = QLabel("Select")

        self.combo = QComboBox(self)
        self.combo.addItem("apple")
        self.combo.addItem("banana")
        self.combo.addItem("lemon")
        self.combo.addItem("orange")

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(label)
        layout.addWidget(self.combo)
        layout.addWidget(button)

        self.setLayout(layout)

    def buttonClicked(self):
        print("Combo: %d, %s"
                % (self.combo.currentIndex(), self.combo.currentText()))


class Main(QWidget):
    def __init__(self):
        super().__init__()

        widget1 = RadioWidget(self)
        widget2 = CheckWidget(self)
        widget3 = ComboWidget(self)

        tab = QTabWidget()
        tab.addTab(widget1, "radio")
        tab.addTab(widget2, "check")
        tab.addTab(widget3, "combo")

        layout = QHBoxLayout(self)
        layout.addWidget(tab)

        self.setLayout(layout)

        self.setWindowTitle("tab")
        self.show()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

ウインドウの分割

splitter.py

import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtWidgets import QHBoxLayout, QFrame, QSplitter
from PyQt5.QtWidgets import QRadioButton, QButtonGroup
from PyQt5.QtWidgets import QCheckBox, QLabel, QComboBox
from PyQt5.QtWidgets import QPushButton, QVBoxLayout
from PyQt5.QtCore import Qt


class RadioFrame(QFrame):
    def __init__(self, parent=None):
        super(QFrame, self).__init__(parent)

        radio1 = QRadioButton("Radio1")
        radio2 = QRadioButton("Radio2")

        self.group = QButtonGroup()
        self.group.addButton(radio1, 1)
        self.group.addButton(radio2, 2)
        radio1.toggle()

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(radio1)
        layout.addWidget(radio2)
        layout.addWidget(button)

        self.setLayout(layout)

    def buttonClicked(self):
        print("Radio: %d" % self.group.checkedId())


class CheckFrame(QFrame):
    def __init__(self, parent=None):
        super(QFrame, self).__init__(parent)

        self.check1 = QCheckBox("Check1")
        self.check2 = QCheckBox("Check2")
        self.check1.setChecked(True)

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(self.check1)
        layout.addWidget(self.check2)
        layout.addWidget(button)

        self.setLayout(layout)

    def buttonClicked(self):
        print("Check1: %d" % self.check1.isChecked())
        print("Check2: %d" % self.check2.isChecked())


class ComboFrame(QFrame):
    def __init__(self, parent=None):
        super(ComboFrame, self).__init__(parent)

        label = QLabel("Select")

        self.combo = QComboBox(self)
        self.combo.addItem("apple")
        self.combo.addItem("banana")
        self.combo.addItem("lemon")
        self.combo.addItem("orange")

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(label)
        layout.addWidget(self.combo)
        layout.addWidget(button)

        self.setLayout(layout)

    def buttonClicked(self):
        print("Combo: %d, %s"
                % (self.combo.currentIndex(), self.combo.currentText()))


class Main(QWidget):
    def __init__(self):
        super().__init__()

        hbox = QHBoxLayout(self)

        frame1 = RadioFrame(self)
        frame1.setFrameShape(QFrame.Panel)

        frame2 = CheckFrame(self)
        frame2.setFrameShape(QFrame.Panel)

        splitter1 = QSplitter(Qt.Horizontal)
        splitter1.addWidget(frame1)
        splitter1.addWidget(frame2)
        splitter1.setHandleWidth(10)

        frame3 = ComboFrame(self)
        frame3.setFrameShape(QFrame.Panel)

        splitter2 = QSplitter(Qt.Vertical)
        splitter2.addWidget(splitter1)
        splitter2.addWidget(frame3)

        hbox.addWidget(splitter2)
        self.setLayout(hbox)

        self.setWindowTitle("splitter")
        self.show()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

ステータスバー

status_bar.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtWidgets import QStatusBar


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        statusBar = QStatusBar(self)
        statusBar.showMessage("status bar")
        self.setStatusBar(statusBar)

        self.setWindowTitle("status bar")

        self.resize(320, 240)
        self.show()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

ドック

dock.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtWidgets import QDockWidget, QWidget
from PyQt5.QtWidgets import QHBoxLayout, QTabWidget
from PyQt5.QtWidgets import QRadioButton, QButtonGroup
from PyQt5.QtWidgets import QCheckBox, QLabel, QComboBox
from PyQt5.QtWidgets import QPushButton, QVBoxLayout
from PyQt5.QtCore import Qt


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

        radio1 = QRadioButton("Radio1")
        radio2 = QRadioButton("Radio2")

        self.group = QButtonGroup()
        self.group.addButton(radio1, 1)
        self.group.addButton(radio2, 2)
        radio1.toggle()

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(radio1)
        layout.addWidget(radio2)
        layout.addWidget(button)

        self.setLayout(layout)

    def buttonClicked(self):
        print("Radio: %d" % self.group.checkedId())


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

        self.check1 = QCheckBox("Check1")
        self.check2 = QCheckBox("Check2")
        self.check1.setChecked(True)

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(self.check1)
        layout.addWidget(self.check2)
        layout.addWidget(button)

        self.setLayout(layout)

    def buttonClicked(self):
        print("Check1: %d" % self.check1.isChecked())
        print("Check2: %d" % self.check2.isChecked())


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

        label = QLabel("Select")

        self.combo = QComboBox(self)
        self.combo.addItem("apple")
        self.combo.addItem("banana")
        self.combo.addItem("lemon")
        self.combo.addItem("orange")

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QVBoxLayout()
        layout.addWidget(label)
        layout.addWidget(self.combo)
        layout.addWidget(button)

        self.setLayout(layout)

    def buttonClicked(self):
        print("Combo: %d, %s"
                % (self.combo.currentIndex(), self.combo.currentText()))


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        dock1 = QDockWidget("dock1", self)
        dock1.setWidget(RadioWidget(self))
        self.addDockWidget(Qt.LeftDockWidgetArea, dock1)

        dock2 = QDockWidget("dock2", self)
        dock2.setWidget(CheckWidget(self))
        self.addDockWidget(Qt.RightDockWidgetArea, dock2)

        self.setCentralWidget(ComboWidget(self))

        self.setWindowTitle("dock")
        self.resize(640, 480)
        self.show()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

ドックは取り外し可能で、移動したり、再度別の場所に組み込んだり、ドック同士を重ねたりできる。

グリッドレイアウト

QVBoxLayout、QHBoxLayout の他に、グリッド状に部品を配置する QGridLayout もある。

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtWidgets import QLabel, QLineEdit
from PyQt5.QtWidgets import QPushButton, QGridLayout
from PyQt5.QtCore import Qt


class Main(QDialog):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        label = QLabel("Input")
        self.edit = QLineEdit()

        button = QPushButton("Check")
        button.clicked.connect(self.buttonClicked)

        layout = QGridLayout()
        layout.setSpacing(10)
        layout.addWidget(label, 0, 0)
        layout.addWidget(self.edit, 0, 1)
        layout.addWidget(button, 1, 0, 1, 2)

        self.setLayout(layout)

        self.setWindowTitle("grid_layout")
        self.show()

    def buttonClicked(self):
        print(self.edit.text())


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

グリッドレイアウトをボックスレイアウトに入れてしまってもよい。

        grid = QGridLayout()
        grid.setSpacing(10)
        grid.addWidget(label, 0, 0)
        grid.addWidget(self.edit, 0, 1)
        grid.addWidget(button, 1, 0, 1, 2)

        layout = QVBoxLayout()
        layout.addLayout(grid)
        layout.addStretch(1)

最後の addStretch() は詰め物である。

スペースを作るには、QSpacerItem をアイテムとして追加すればよい。

from PyQt5.QtWidgets import QSpacerItem

layout.addItem(QSpacerItem(100, 10), 2, 0, 1, 2)

いろいろ

セパレータ

GUI の飾りとして使うセパレータは、次のように作成できる。

separator = QFrame()
separator.setFrameShape(QFrame.HLine)
separator.setFrameShadow(QFrame.Sunken)

ウインドウにアクセスする

widget のクラスの中でウインドウにアクセスしたい場合は、次のようにすればよい。

self.window()

絵の描画

絵の描画

draw.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtGui import QPainter, QColor, QFont, QPen
from PyQt5.QtCore import Qt


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("draw")

        self.resize(320, 240)
        self.centerOnScreen()
        self.show()

    def centerOnScreen(self):
        res = QDesktopWidget().screenGeometry()
        self.move((res.width()/2) - (self.frameSize().width()/2),
                  (res.height()/2) - (self.frameSize().height()/2))

    def paintEvent(self, e):
        w = self.frameSize().width()
        h = self.frameSize().height()
        painter = QPainter(self)
        painter.setPen(Qt.white)
        painter.setBrush(Qt.white)
        painter.drawRect(0, 0, w, h)

        pen = QPen(Qt.black)
        pen.setWidth(2)
        painter.setPen(pen)
        for y in range(0, int(240/5)):
            painter.drawPoint(20, y*5)

        painter.setPen(Qt.red)
        painter.setBrush(Qt.red)
        painter.drawRect(50, 50, 100, 50)

        painter.setPen(QColor(0, 0, 255))
        painter.setBrush(QColor(0, 0, 255, 128))
        painter.drawRect(80, 80, 100, 60)

        pen = QPen(Qt.DashLine | Qt.black)
        pen.setWidth(2)
        painter.setPen(pen)
        painter.setBrush(QColor(0, 0, 0, 0))
        painter.drawEllipse(130, 156, 100, 50)

        painter.setPen(Qt.black)
        painter.setFont(QFont("Serif", 24))
        painter.drawText(200, 100, "Text")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

絵の描画するには painEvent() をオーバーライドする。

マウスでクリックして線を引く

draw_line.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setMouseTracking(True)

        self.x0, self.y0 = 0, 0
        self.x1, self.y1 = 0, 0
        self.press_count = 0

        self.setWindowTitle("draw line")

        self.resize(320, 240)
        self.centerOnScreen()
        self.show()

    def centerOnScreen(self):
        res = QDesktopWidget().screenGeometry()
        self.move((res.width()/2) - (self.frameSize().width()/2),
                  (res.height()/2) - (self.frameSize().height()/2))

    def mousePressEvent(self, e):
        if self.press_count == 0:
            self.x0, self.y0 = e.x(), e.y()
            self.x1, self.y1 = e.x(), e.y()
            self.press_count = 1
        else:
            self.x1, self.y1 = e.x(), e.y()
            self.press_count = 0

        self.update()

    def paintEvent(self, e):
        w = self.frameSize().width()
        h = self.frameSize().height()
        painter = QPainter(self)
        painter.setPen(Qt.white)
        painter.setBrush(Qt.white)
        painter.drawRect(0, 0, w, h)

        pen = QPen(Qt.black)
        pen.setWidth(2)
        painter.setPen(pen)
        painter.drawLine(self.x0, self.y0, self.x1, self.y1)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

マウスをクリックしたあとに再描画させるには、update() を呼び出す。

箱をクリックして色を変える

select_box.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt
import numpy as np


class Box():
    def __init__(self, x0, y0, nx, ny, dx):
        self.x0 = x0
        self.y0 = y0
        self.nx = nx
        self.ny = ny
        self.dx = dx
        self.data = np.zeros([self.nx, self.ny])

    def get_index(self, x, y):
        i = int((x - self.x0)/self.dx + 1) - 1
        j = int((y - self.y0)/self.dx + 1) - 1
        if i < 0 or i >= self.nx:
            i = -1
        if j < 0 or j >= self.ny:
            j = -1
        return i, j


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setMouseTracking(True)

        self.box = Box(20, 40, 60, 40, 10)

        self.setWindowTitle("select box")

        self.resize(640, 480)
        self.centerOnScreen()
        self.show()

    def centerOnScreen(self):
        res = QDesktopWidget().screenGeometry()
        self.move((res.width()/2) - (self.frameSize().width()/2),
                  (res.height()/2) - (self.frameSize().height()/2))

    def mousePressEvent(self, e):
        i, j = self.box.get_index(e.x(), e.y())

        if i >= 0 and j >= 0:
            if self.box.data[i][j] == 1:
                self.box.data[i][j] = 0.
            else:
                self.box.data[i][j] = 1.
        self.update()

    def paintEvent(self, e):
        w = self.frameSize().width()
        h = self.frameSize().height()
        painter = QPainter(self)
        painter.setPen(Qt.white)
        painter.setBrush(Qt.white)
        painter.drawRect(0, 0, w, h)

        painter.setPen(Qt.black)
        for j in range(0, self.box.ny):
            for i in range(0, self.box.nx):
                if self.box.data[i][j] == 1.:
                    painter.setBrush(Qt.red)
                else:
                    painter.setBrush(Qt.white)

                x = i*self.box.dx + self.box.x0
                y = j*self.box.dx + self.box.y0
                painter.drawRect(x, y, self.box.dx, self.box.dx)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

クリックした位置が含まれる箱のインデックスを以下のように計算している。

        i = int((x - self.x0)/self.dx + 1) - 1
        j = int((y - self.y0)/self.dx + 1) - 1

これは、小さい側にはみ出したときにインデックスが負になるようにしている。

タイマー

timer.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtCore import QTimer


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update)
        self.timer.start(1000) # msec

        self.resize(320, 240)
        self.centerOnScreen()
        self.show()

    def update(self):
        print("*")


    def centerOnScreen(self):
        res = QDesktopWidget().screenGeometry()
        self.move((res.width()/2) - (self.frameSize().width()/2),
                  (res.height()/2) - (self.frameSize().height()/2))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

matplotlib の利用

plot.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication, QVBoxLayout

import matplotlib
matplotlib.use("Qt5Agg")

from matplotlib.backends.backend_qt5agg \
    import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np

import seaborn as sns
sns.set()


class Main(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        fig = Figure(figsize=(8, 6), dpi=80)
        axes = fig.add_subplot(111)

        axes.set_xlabel("x")
        axes.set_ylabel("y")

        x  = np.arange(0, 2.*np.pi, 0.1)
        y  = np.sin(x)
        axes.plot(x, y)

        canvas = FigureCanvas(fig)

        layout = QVBoxLayout()
        layout.addWidget(canvas)

        self.setLayout(layout)

        self.setWindowTitle("plot")

        self.show()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

実行時、次のようなエラーが出ることがある。

from PyQt5.QtCore import (QLineF, QPointF, QRectF, Qt, QTimer)
RuntimeError: the PyQt5.QtCore and PyQt4.QtCore modules both wrap the QObject class

これは PyQt4 と PyQt5 が両方あるときに matplotlib 側が出すエラーのようで、これを避けるために以下を追加している。これはその下に続く matplotlib 関連のモジュールのインポートに先立って設定する必要がある。

import matplotlib
matplotlib.use("Qt5Agg")

アニメーション

タイマーを使えばアニメーションさせることができる。

plot_anime.py

import sys
from PyQt5.QtWidgets import QDialog, QApplication, QVBoxLayout
from PyQt5.QtCore import QTimer

import matplotlib
matplotlib.use("Qt5Agg")

from matplotlib.backends.backend_qt5agg \
    import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np

import seaborn as sns
sns.set()


class Main(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        fig = Figure(figsize=(8, 6), dpi=80)

        axes = fig.add_subplot(111)
        self.axes = axes

        axes.set_xlabel("x")
        axes.set_ylabel("y")

        self.xmax = 0.

        x  = np.arange(0, self.xmax, 0.1)
        y  = np.sin(x)
        self.lines, = axes.plot(x, y, "o")

        canvas = FigureCanvas(fig)
        self.canvas = canvas

        layout = QVBoxLayout()
        layout.addWidget(canvas)

        self.setLayout(layout)

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.updatePlot)
        self.timer.start(100) # msec

        self.setWindowTitle("plot_anime")
        self.show()

    def updatePlot(self):
        self.xmax += 0.1
        x  = np.arange(0, self.xmax, 0.1)
        y  = np.sin(x)
        self.lines.set_data(x, y)
        self.axes.relim()
        self.axes.autoscale_view()
        self.canvas.draw()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

複数のデータをプロットする場合は、上記の self.lines を複数に (あるいはリストに) 必すればよい。

VTK の利用

qtvtk.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget
from PyQt5.QtWidgets import QFrame, QVBoxLayout

import vtk
from vtk.util.colors import tomato
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor


class MouseInteractorStyle(vtk.vtkInteractorStyleTrackballCamera):
    def __init__(self, parent=None):
        self.AddObserver("LeftButtonPressEvent", self.leftButtonPressEvent)

    def leftButtonPressEvent(self, obj, event):
        self.OnLeftButtonDown()


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        # source
        cylinder = vtk.vtkCylinderSource()
        cylinder.SetResolution(20)

        # mapper
        mapper = vtk.vtkPolyDataMapper()
        mapper.SetInputConnection(cylinder.GetOutputPort())

        # actor
        actor = vtk.vtkActor()
        actor.SetMapper(mapper)
        actor.GetProperty().SetColor(tomato)
        actor.RotateX(30.)
        actor.RotateY(-45.)

        # renderer
        ren = vtk.vtkRenderer()
        ren.AddActor(actor)
        ren.SetBackground(0.1, 0.2, 0.4)

        # interactor
        frame = QFrame()
        inter = QVTKRenderWindowInteractor(frame)
        inter.SetInteractorStyle(MouseInteractorStyle())

        ren_win = inter.GetRenderWindow()
        ren_win.AddRenderer(ren)

        ren.ResetCamera()
        ren.GetActiveCamera().Zoom(1.5)

        ren_win.Render()
        inter.Initialize()
        inter.Start()

        layout = QVBoxLayout()
        layout.addWidget(inter)
        frame.setLayout(layout)
        self.setCentralWidget(frame)

        self.setWindowTitle("qtvtk")
        self.resize(320, 240)
        self.centerOnScreen()
        self.show()

    def centerOnScreen(self):
        res = QDesktopWidget().screenGeometry()
        self.move((res.width()/2) - (self.frameSize().width()/2),
                  (res.height()/2) - (self.frameSize().height()/2))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

pythonOCC の利用

occ.py

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QDesktopWidget

from OCC.Display.backend import load_backend


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        load_backend("qt-pyqt5")
        from OCC.Display.qtDisplay import qtViewer3d

        canvas = qtViewer3d(self)
        canvas.resize(640, 480)

        canvas.InitDriver()
        display = canvas._display

        display.set_bg_gradient_color(153, 204, 255, 250, 252, 255)

        from OCC.BRepPrimAPI import BRepPrimAPI_MakeSphere, \
                BRepPrimAPI_MakeBox

        display.DisplayShape(
            BRepPrimAPI_MakeBox(1, 1, 1).Shape(),
            update=True
        )

        self.setCentralWidget(canvas)

        self.setWindowTitle("pythonOCC")

        self.resize(640, 480)
        self.centerOnScreen()
        self.show()

    def centerOnScreen(self):
        res = QDesktopWidget().screenGeometry()
        self.move((res.width()/2) - (self.frameSize().width()/2),
                  (res.height()/2) - (self.frameSize().height()/2))



if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())