PyQt5 メモ2020年12月30日 | |
はじめにQt 5.x の Python バインディング PyQt5 (および PySide2) のメモ。 環境
PyQt と PySide の違いQt の Python バインディングとして PyQt と PySide (Qt for Python) があるが、大きな違いはライセンスで、前者は GPL、後者は LGPL である。PySide は PyQt の GPL を嫌って開発されたものである。Qt 4.x、5.x に対応してそれぞれ PyQt4、PyQt5 あるいは PySide、PySide2 とパッケージが異なっている。両者のライセンスの違いは、Pyinstaller を使う時に問題になるかもしれない (DLL を配布することになるので)。 PySide2 の問題点オブジェクトの比較問題PySide2 でオブジェクトの比較ができない場合があるらしい。オブジェクトが同じかどうかを確認したいだけなら、id() 同士を比較すればよい。 参考 Cython 化問題また、特殊な状況だが、Cython 化するとシグナルで問題が起こる。 RecursionError: maximum recursion depth exceeded while calling a Python object おまじないを入れれば一応対処できる。 class Main(QMainWindow): def __init__(self, parent=None): super().__init__(parent) doAction = QAction("&Do", self) doAction.triggered.connect(self.do) setattr(self, "Main.do", self.do) # おまじない ... def do(self): ... 参考 VTKVTK が PySide2 に対応していないかもしれない。その場合、ちょっと修正すればよい (VTK 8.1.2)。 ~/miniconda3/lib/python3.7/site-packages/vtk/qt/QVTKRenderWindowInteractor.py if PyQtImpl is None: # Autodetect the PyQt implementation to use try: import PyQt5 PyQtImpl = "PyQt5" except ImportError: try: import PyQt4 PyQtImpl = "PyQt4" # except ImportError: # try: # import PySide # PyQtImpl = "PySide" # except ImportError: # raise ImportError("Cannot load either PyQt or PySide") except ImportError: try: import PySide2 PyQtImpl = "PySide2" except ImportError: try: import PySide PyQtImpl = "PySide" except ImportError: raise ImportError("Cannot load either PyQt or PySide") ... if PyQtImpl == "PyQt5": ... elif PyQtImpl == "PyQt4": ... elif PyQtImpl == "PySide2": if QVTKRWIBase == "QGLWidget": from PySide2.QtOpenGL import QGLWidget from PySide2.QtWidgets import QWidget from PySide2.QtWidgets import QSizePolicy from PySide2.QtWidgets import QApplication from PySide2.QtCore import Qt from PySide2.QtCore import QTimer from PySide2.QtCore import QObject from PySide2.QtCore import QSize from PySide2.QtCore import QEvent インストールPyQt5PIP でインストールできる。 $ pip install PyQt5 PySide2PySide2 は pip ではうまく入らないようで、conda を用いる。 $ conda install -c conda-forge pyside2 PyQt5 とは共存しないようである。 Miniconda3 を移動した場合 (Windows)変な使い方だが、Windows で Miniconda3 のフォルダをどこかに移動した場合、Qt のパスが変わってしまうため、正しく設定しなおす必要がある。(仮想環境を使っているものとして) env の仮想環境フォルダの直下に、以下のような内容の qt.conf を置く。 [Paths] Prefix = ./Library Binaries = ./Library/bin Libraries = ./Library/lib Headers = ./Library/include/qt TargetSpec = win32-msvc HostSpec = win32-msvc モジュールのインポートモジュールのインポートは、できるだけ個別に行うのが望ましいが、めんどくさかったら次のようにすればよい。 from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * PySide2 の場合も同様。 from PySide2.QtWidgets import * from PySide2.QtGui import * from PySide2.QtCore import * ウインドウ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 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): r = QFileDialog.getOpenFileName(self, "Open File", None, "Python Files (*.py)") filename = r[0] print(filename) def save(self): # r[0] = QFileDialog.getSaveFileName(self, "Save File", # None, "Python files (*.py)") # filename = r[0] dialog = QFileDialog(self) dialog.setWindowTitle("Save File") dialog.setNameFilters(["Python Files (*.py)", "All Files (*)"]) dialog.setAcceptMode(QFileDialog.AcceptSave) filename = "" if dialog.exec_(): r = dialog.selectedFiles() filename = r[0] 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 を渡すと、ネイティブのダイアログを使わない。場合によってはそうしたほうがよいことがある。 QFileDialog オブジェクトを使って、もっと細かい設定が可能である。たとえば、ディレクトリを開く場合: dialog = QFileDialog(self, "Select Directory") dialog.setLabelText(QFileDialog.FileName, "Directory name:") dialog.setLabelText(QFileDialog.Accept, "Open") dialog.setFileMode(QFileDialog.Directory) dialog.setAcceptMode(QFileDialog.AcceptOpen) dialog.setOptions(QFileDialog.ShowDirsOnly) path = "" if dialog.exec_(): r = dialog.selectedFiles() path = r[0] ディレクトリを保存する場合: dialog = QFileDialog(self, "Save Directory") dialog.setLabelText(QFileDialog.FileName, "Directory name:") dialog.setLabelText(QFileDialog.Accept, "Save") dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setOptions(QFileDialog.ShowDirsOnly) path = "" if dialog.exec_(): r = dialog.selectedFiles() path = r[0] dialog.setDirectory() でダイアログを開いた時のディレクトリのパスを設定できる。 日付の選択calendar.py import sys from PyQt5.QtWidgets import QDialog, QApplication from PyQt5.QtWidgets import QCalendarWidget from PyQt5.QtWidgets import QPushButton, QVBoxLayout class Main(QDialog): def __init__(self, parent=None): super().__init__(parent) self.calendar = QCalendarWidget() button = QPushButton("Check") button.clicked.connect(self.buttonClicked) layout = QVBoxLayout() layout.addWidget(self.calendar) layout.addWidget(button) self.setLayout(layout) self.setWindowTitle("calender") self.show() def buttonClicked(self): date = self.calendar.selectedDate() print("%d/%d/%d" % (date.year(), date.month(), date.day())) if __name__ == "__main__": app = QApplication(sys.argv) win = Main() sys.exit(app.exec_()) ![]() 別のウインドウからカレンダーを開き、選択した日付を返したい場合、選択された日付をクラスの属性に入れておけばよい。 class DateSelectDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.calendar = QCalendarWidget() self.date = None box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) box.accepted.connect(self.okButtonClicked) box.rejected.connect(self.cancelButtonClicked) layout = QVBoxLayout() layout.addWidget(self.calendar) layout.addWidget(box) self.setLayout(layout) self.setWindowTitle("Select date") self.show() def okButtonClicked(self): date = self.calendar.selectedDate() self.date = "%d/%d/%d" % (date.year(), date.month(), date.day()) self.close() def cancelButtonClicked(self): self.close() たとえば、次のように呼び出す。 def dateSelectButtonClicked(self): w = DateSelectDialog() w.exec() self.dateEdit.setText(w.date) ツールバー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().__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 を用いている。メニュー同様、ボタンにも "&" でアクセスキーを設定できる。 Enter でボタンを押せるようにするには、次のように設定すればよい。 button.setAutoDefault(True) ボタンにフォーカスを設定するには、次のようにする。 button.setDefault(True) Ok/Cancel ボタンが必要であれば、QDialogButtonBox を用いる方法がある。 button_box.py import sys from PyQt5.QtWidgets import QDialog, QApplication from PyQt5.QtWidgets import QDialogButtonBox, QVBoxLayout class Main(QDialog): def __init__(self, parent=None): super().__init__(parent) box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) box.accepted.connect(self.okButtonClicked) box.rejected.connect(self.cancelButtonClicked) layout = QVBoxLayout() layout.addWidget(box) self.setLayout(layout) self.setWindowTitle("button box") self.show() def okButtonClicked(self): print("ok") def cancelButtonClicked(self): print("cancel") if __name__ == "__main__": app = QApplication(sys.argv) win = Main() sys.exit(app.exec_()) ![]() ダイアログとウインドウダイアログは QDialog で作成できる。たとえば、タイトルのないダイアログを作ることもできる。 dialog = QDialog(parent, Qt.CustomizeWindowHint) あるいは dialog = QDialog(parent, Qt.FramelessWindowHint) QWidget から好きなウインドウタイプを作ることも可能。 dialog = QWidget(self) dialog.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint) ダイアログのサイズを固定することもできる。 dialog.setFixedSize(dialog.size()) これらを用いてスプラッシュスクリーンを作ることも可能。 メッセージボックス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().__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_()) ![]() show() の代わりに 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") アイコンの設定は、ウインドウに対して次のようにする。 from PyQt5.QtGui import QIcon self.setWindowIcon(QIcon("icon.png")) チェックボックスcheck_box.py import sys from PyQt5.QtWidgets import QDialog, QApplication from PyQt5.QtWidgets import QCheckBox, QPushButton, QVBoxLayout class Main(QDialog): def __init__(self, parent=None): super().__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().__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().__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_()) ![]() 指定の項目に設定するには次のようにする。 i = combo.findText(item) combo.setCurrentIndex(i) 入力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().__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_()) ![]() 入力を制限する場合、次のようなものを用意する。 from PyQt5.QtGui import QValidator class LimitedEdit(QLineEdit): def __init__(self, text="", parent=None): super().__init__(text, parent) self.textChanged.connect(self.textChangedFunc) self.valid = False def textChangedFunc(self, text): if self.validator() is None: return state = self.validator().validate(text, 0) if state[0] == QValidator.Intermediate: self.setValid(False) else: self.setValid(True) def setValid(self, b): self.valid = b if b: self.setStyleSheet("QLineEdit {background: white}") else: self.setStyleSheet("QLineEdit {background: pink}") もし、入力が正しくない限りフォーカスを移動させたくない場合は、次のメソッドを追加する。 def focusOutEvent(self, e): state = self.validator().validate(self.text(), 0) if state[0] == QValidator.Intermediate: self.setFocus() else: super().focusOutEvent(e) 入力を実数に限定する場合は、LimitedEdit を用いて次のように実装できる。 from PyQt5.QtGui import QDoubleValidator class DoubleEdit(LimitedEdit): def __init__(self, text="0", parent=None): super().__init__(text, parent) self.setValidator(QDoubleValidator()) self.textChangedFunc(text) def setBottom(self, bottom): self.validator().setBottom(bottom) 値を 0 以上に限定したい場合は、setBottom() で 0 を設定すればよい。 同様に、入力を整数に限定する場合は、次のようにする。 from PyQt5.QtGui import QIntValidator class IntEdit(LimitedEdit): def __init__(self, text="0", parent=None): super().__init__(text, parent) self.setValidator(QIntValidator()) self.textChangedFunc(text) def setBottom(self, bottom): self.validator().setBottom(bottom) たとえば、MAC アドレスを受け付ける場合は、次のようにできる。 class MacEdit(LimitedEdit): class MacValidator(QValidator): def validate(self, arg__1, arg__2): if len(arg__1) > 12: return (QValidator.Invalid,) m = re.match("[\da-f]*", arg__1) if m and m.group() == arg__1: if len(arg__1) < 12: return (QValidator.Intermediate,) else: return (QValidator.Acceptable,) else: return (QValidator.Invalid,) def __init__(self, text="", parent=None): super().__init__(text, parent) self.setValidator(self.MacValidator()) self.textChangedFunc(text) プログラムで入力を設定するには、setText() を使えば良い。 self.edit.setText("text") 編集させないようにするには、setEnabled(False) とするか、setReadOnly(True) とする。見た目が異なる。 テキスト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().__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().__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().__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) QTreeWidget オブジェクトのトップレベルのアイテム数を得たい場合は、topLevelItemCount() を使えばよい。アイテムは takeTopLevelItem(0) で 1 つ消せる。clear() ですべて消せる。 チェックボックス付きツリー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().__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, column): 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) 上の例では、親のチェックに応じて子のチェックを切り替えるようにしている。またポップアップメニューで全選択・解除を実行できるようにしている。 QTreeViewQTreeWidget の代わりに 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().__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().__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().__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_()) ![]() タブのラベルを横にすることもできる。 tab2.py (抜粋) ... from PyQt5.QtWidgets import QTabBar, QStylePainter,QStyleOptionTab, QStyle from PyQt5.QtCore import QRect, QPoint ... class TabBar(QTabBar): def tabSizeHint(self, index): s = QTabBar.tabSizeHint(self, index) s.transpose() return s def paintEvent(self, event): painter = QStylePainter(self) opt = QStyleOptionTab() for i in range(self.count()): self.initStyleOption(opt, i) painter.drawControl(QStyle.CE_TabBarTabShape, opt) painter.save() s = opt.rect.size() s.transpose() r = QRect(QPoint(), s) r.moveCenter(opt.rect.center()) opt.rect = r c = self.tabRect(i).center() painter.translate(c) painter.rotate(90) painter.translate(-c) painter.drawControl(QStyle.CE_TabBarTabLabel, opt); painter.restore() class Main(QWidget): def __init__(self): super().__init__() widget1 = RadioWidget(self) widget2 = CheckWidget(self) widget3 = ComboWidget(self) tab = QTabWidget() tab.setTabBar(TabBar(self)) tab.setTabPosition(QTabWidget.West) tab.addTab(widget1, "radio") tab.addTab(widget2, "check") tab.addTab(widget3, "combo") ... ![]() ウインドウの分割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().__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().__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().__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_()) ![]() QSplitter オブジェクトのサイズの指定は、次のようにする。 splitter.setSizes( [0.23*window_width, 0.27*window_width, 0.5*window_width] ) ステータスバー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().__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().__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().__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 もある。 grid_layout.py 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().__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) 単純に QLabel("") を入れるのでもよいかもしれない。 いろいろセパレータGUI の飾りとして使うセパレータは、次のように作成できる。 separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setFrameShadow(QFrame.Sunken) ウインドウにアクセスするwidget のクラスの中でウインドウにアクセスしたい場合は、次のようにすればよい。 self.window() ウインドウを表に出すウインドウを表に出すには、フォーカスを設定すればよい。 self.setFocus() ウインドウを常に表に出すウインドウを常に表に出しておきたい場合は、次のようにフラグを設定する。 self.setWindowFlags(Qt.WindowStaysOnTopHint) カーソルの変更たとえば、処理待ちのカーソルを表示するには次のようにする。 QApplication.setOverrideCursor(Qt.WaitCursor) ... QApplication.restoreOverrideCursor() タブストップの順番の設定たとえば、同じウインドウに属するウィジット a から b に移動する順番にしたい場合は、以下のようにする。 QWidget.setTabOrder(a, b) ウィジットの削除ウィジットを削除したい場合は、次のようにする (box は ボックスレイアウト)。 self.box.removeWidget(separator) separator.deleteLater() 終了確認アプリケーションの終了確認をしたい場合は、ウインドウの closeEvent() を拾えばよい。 def closeEvent(self, e): r = QMessageBox.question(self, program_name, "Quit the application?", QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Cancel) if r == QMessageBox.Cancel: e.ignore() メニューの終了処理も、quit ではなく close と接続する。 quit_action = QAction("&Quit", self) quit_action.setShortcut("Ctrl+Q") #quit_action.triggered.connect(qApp.quit) quit_action.triggered.connect(self.close) ラベルの文字サイズの設定from PyQt5.QtGui import QFont label = QLabel("text") font = QFont() font.setPointSize(32) label.setFont(font) 全文字サイズの設定スタイルシートを用いると、全文字サイズを設定できる。 app = QApplication(sys.argv) app.setStyleSheet("*{font-size: 11pt;}") ただ、これをやってしまうと、setFont() の設定が効かなくなるので、こちらもスタイルシートを用いる必要がある。 label = QLabel("test") #font = QFont() #font.setPointSize(32) #label.setFont(font) label.setStyleSheet("QLabel{font-size: 32pt;}") レイアウトの隙間部品の外側の隙間の設定は、次のようにする。引数はそれぞれ left、top、right、bottom。 layout.setContentsMargins(0, 0, 0, 0) 部品間の隙間の設定は次のようにする。 layout.setSpacing(10) レイアウトへのウィジットの追加と削除layout.addLayout(widget) layout.removeItem(widget) レイアウト内でのウィジット配置の調整上に寄せる。 layout.setAlignment(Qt.AlignTop) 絵の描画絵の描画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 これは、小さい側にはみ出したときにインデックスが負になるようにしている。 画像の表示"image.png" を読み込んで表示する。 image.py import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class Main(QDialog): def __init__(self, parent=None): super().__init__(parent) image = QImage("image.png") label = QLabel() label.setPixmap(QPixmap.fromImage(image)) layout = QVBoxLayout() layout.addWidget(label) self.setLayout(layout) self.setWindowTitle("image") self.show() if __name__ == "__main__": app = QApplication(sys.argv) win = Main() sys.exit(app.exec_()) タイマー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("*") 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.Initialize() inter.SetInteractorStyle(MouseInteractorStyle()) ren_win = inter.GetRenderWindow() ren_win.AddRenderer(ren) ren.ResetCamera() ren.GetActiveCamera().Zoom(1.5) ren_win.Render() 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_()) ![]() | |
PENGUINITIS |