PySide6でデスクトップアプリを作る #4 Qt DesignerとUIファイル — 画面を描いて読み込む
「PySide6でデスクトップアプリを作る #2 ウィジェットとレイアウト — 画面を組み立てる」では、レイアウトをすべてコードで組み立てました。ウィジェット数個の画面ならその方式で十分ですが、フォームが大きくなるとコードを読むだけでは画面の姿を思い描きにくくなり、配置を少し変えるだけでもコードの複数箇所を直すことになります。Qtはこの問題のために、画面をマウスで描く専用ツールを提供しています。今回のテーマであるQt Designerです。
全7回で構成します。
- #1 PySide6とは — QtとPythonでデスクトップアプリ
- #2 ウィジェットとレイアウト — 画面を組み立てる
- #3 シグナルとスロット — イベント処理の核心
- #4 Qt DesignerとUIファイル — 画面を描いて読み込む ← この記事
- #5 モデルとビュー — リスト・テーブルにデータをつなぐ
- #6 スレッドとタイマー — 止まらないUI
- #7 パッケージングと配布 — PyInstallerで実行ファイルを作る
今回はDesignerでToDoアプリの画面を描き直し、その成果物である.uiファイルをPythonコードから読み込む2つの方式を比較したうえで、シグナル接続をコードに残すワークフローで締めくくります。
Qt Designerの起動 — インストールは済んでいます #
Qt Designerに別途のインストールは不要です。PySide6をインストールすると、コマンドラインツールとして一緒に入ります。
pyside6-designer起動すると新しいフォームの選択ダイアログが開きます。独立したウィンドウを作るのでMain Windowを選びます。ダイアログ1つだけの画面なら、Dialog系のテンプレートを選んでも構いません。
画面構成 — 覚えるパネルは3つだけです #
Designerの画面は最初は複雑に見えますが、実際の作業で使うパネルは3つです。
- ウィジェットボックス(左) — 使えるウィジェットの一覧です。ここからフォームへドラッグして配置します。
- オブジェクトインスペクタ(右上) — フォームに載せたウィジェットのツリーです。親子関係とレイアウト構造がひと目で分かります。
- プロパティエディタ(右下) — 選択したウィジェットのプロパティを編集します。この中で最も重要なプロパティが
objectNameです。
objectNameを強調するのには理由があります。後で.uiファイルをコードから読み込むと、各ウィジェットがobjectNameそのままのPython属性になるためです。デフォルトのpushButtonやlineEditのような名前のまま残すと、コード側でどのボタンなのか区別できません。ウィジェットを載せるたびにaddButton、todoInputのように役割が伝わる名前をすぐ付ける習慣が必要です。
フォーム作成の実習 — ToDoアプリの画面 #
#2でコードで作った画面を、Designerで作り直します。手順は3つです。
- ウィジェットボックスからLine Editをドラッグしてフォーム上部に置き、objectNameを
todoInputにします。 - Push Buttonをその隣に置いてobjectNameを
addButton、textプロパティを「追加」にします。 - List Widgetを下に置いてobjectNameを
todoListにします。
この時点では、ウィジェットはフォーム上の絶対座標に浮いている状態です。ウィンドウの大きさを変えてもウィジェットは付いてきません。レイアウトを適用する必要があります。
レイアウトの適用とスペーサー #
Designerのレイアウトは、コードのQHBoxLayout、QVBoxLayoutと同じ概念を視覚的に適用するものです。
todoInputとaddButtonをまとめて選択し、ツールバーのLay Out Horizontallyを押します。2つのウィジェットが横のレイアウトにまとまります。- フォームの空いた場所をクリックしてウィンドウ自体を選択し、Lay Out Verticallyを押します。先ほどの横のまとまりと
todoListが縦に配置され、ウィンドウの大きさを変えるとウィジェットが追従して伸びます。
ウィジェットの間に空きが必要なら、ウィジェットボックスのHorizontal Spacer、Vertical Spacerをドラッグして入れます。スペーサーは画面には見えませんが余った空間を吸収するので、ボタンを右端に寄せるような配置を作れます。
作業が終わったらmain_window.uiとして保存します。
.uiファイルの正体 — XML #
保存されたファイルをエディタで開いてみると、特別なものではありません。ウィジェットのツリーとプロパティをそのまま収めたXML文書です。
<widget class="QLineEdit" name="todoInput"/>
<widget class="QPushButton" name="addButton">
<property name="text">
<string>追加</string>
</property>
</widget>
<widget class="QListWidget" name="todoList"/>核心は、このファイルには画面構造だけがあって動作がないという点です。何を押したら何が起きるかは、依然としてPythonコードの仕事です。画面とロジックがファイル単位で分離されるので、配置の修正はDesignerで、動作の修正はコードで、それぞれ進められます。
読み込み方式1 — pyside6-uicでコード生成 #
1つ目の方式は、.uiファイルをPythonコードに変換しておいてインポートするものです。
pyside6-uic main_window.ui -o ui_main_window.py生成されたui_main_window.pyの中にはUi_MainWindowクラスが入っていて、setupUiメソッドがDesignerで描いたウィジェットツリーをコードで再現します。使い方のパターンは次のとおりです。
import sys
from PySide6.QtWidgets import QApplication, QMainWindow
from ui_main_window import Ui_MainWindow
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.addButton.clicked.connect(self.add_todo)
def add_todo(self):
text = self.ui.todoInput.text().strip()
if text:
self.ui.todoList.addItem(text)
self.ui.todoInput.clear()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()self.ui.addButtonのように、Designerで付けたobjectNameがそのまま属性になります。生成されたファイルはインポートするだけの対象なので、直接編集しません。Designerでフォームを直したら、変換コマンドを再実行して更新します。
読み込み方式2 — QUiLoaderでランタイム読み込み #
2つ目の方式は、変換の手順なしに実行時へ.uiファイルを直接読むものです。
import sys
from PySide6.QtWidgets import QApplication
from PySide6.QtUiTools import QUiLoader
app = QApplication(sys.argv)
loader = QUiLoader()
window = loader.load("main_window.ui")
window.addButton.clicked.connect(
lambda: window.todoList.addItem(window.todoInput.text())
)
window.show()
app.exec()ファイルを直して再実行すればすぐ反映されるので、手間が少なくて済みます。その代わり、windowがどのウィジェットを持つのかコードだけでは分からないため、エディタの自動補完と型チェックの助けを受けられません。またloadが完成したウィジェットを返す構造なので、ウィンドウクラスを継承で拡張するパターンとも相性がよくありません。
2つの方式の比較 #
| 項目 | pyside6-uic(コード生成) | QUiLoader(ランタイム読み込み) |
|---|---|---|
| 変換の手順 | 必要(.uiを直すたびに再実行) | なし |
| 自動補完・型チェック | 生成コードを基準に動作 | 非対応 |
| クラス継承での拡張 | setupUiパターンで自然 | ぎこちない |
| 配布 | 生成された.pyだけ含めればよい | .uiファイルを一緒に配布 |
| 向いている場面 | 規模のあるアプリ、チーム開発 | 素早い実験、小規模ツール |
このシリーズの残りの回はsetupUiパターンを基準に進めます。自動補完が効く環境のほうが学習にも有利だからです。
.uiファイルが複数あるなら、まとめて変換する1行のシェルスクリプトやMakefileターゲットを作っておくやり方が一般的です。シグナル接続はコードで #
Designerにも、シグナルとスロットを画面上でつなぐ編集モードがあります。F4を押すと、ウィジェットをドラッグしてシグナルをつなぐ画面に切り替わります。ただしこのシリーズでは使いません。接続関係が.uiファイルの中に隠れると、どのボタンがどの動作を呼ぶのかをコード検索で見つけられなくなるためです。画面構造はDesignerが、動作と接続はコードが受け持つという境界を保つほうが、アプリが大きくなるほど有利です。
clickedやtextChangedのようなシグナルをコードのメソッドにconnectでつなぐという点は、画面をどう作ったかとは無関係に同じです。まとめ #
今回の核心は3つです。
- Qt DesignerはPySide6のインストールに含まれる画面編集ツールで、成果物の
.uiファイルは画面構造だけを収めたXMLです。objectNameがコードからウィジェットを呼ぶ名前になります。 - 読み込みは2方式です。
pyside6-uicでコードを生成するsetupUiパターンが自動補完と拡張に有利で、QUiLoaderは変換なしで直接読む代わりに型情報を失います。 - シグナル接続はDesignerではなくコードに置きます。画面と動作の境界がはっきりしていてこそ、規模が大きくなっても管理できます。
ここまでのToDoリストはQListWidgetに文字列を直接入れる方式でした。項目に期限と完了状態まで持たせるには、データと表示を分離する構造が必要です。次回の「PySide6でデスクトップアプリを作る #5 モデルとビュー — リスト・テーブルにデータをつなぐ」で、Qtのモデル/ビュー構造を扱います。