May 24, 2021

QtPyでGUIアプリを作るときにレイアウト構築を楽にする関数

QVBoxLayoutとか毎度書くのめんどくさいので便利な関数書いた

QtPyについて

QtPyはQtのPythonバインディングであるPyQtやPySideを抽象化したパッケージ.過去の記事で情報をまとめてある.

反面教師あり学習 - QtPyでウインドウを表示する最小のコード

使用例

単純な例

例として,次のようなテキストとボタンが配置されたウインドウについて考える:

img

このウインドウは以下のコードによって表示できる. ここで,utilsというモジュールに入っているhboxvboxが今回作った関数である:

# -*- coding: utf-8 -*-
import sys

from qtpy.QtWidgets import (
    QMainWindow, QApplication, QPushButton, QLabel, QWidget,
)
from qtpy.QtCore import QObject, Qt
from utils import hbox, vbox


class MainWindow(QMainWindow):
    def __init__(self, **kwargs):
        super(MainWindow, self).__init__(**kwargs)

        self.main_widget = QWidget(self)

        # --- レイアウトの構築(自作の関数を利用) ---
        layout = vbox([
            QLabel('Hello, World!'),
            hbox([QPushButton('OK'), QPushButton('Cancel')])
        ])
        # --------------------------------------------

        self.main_widget.setLayout(layout)
        self.setCentralWidget(self.main_widget)
        self.resize(160, 120)


def main():
    app = QApplication(sys.argv)

    view = MainWindow()

    view.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

同じ結果をQVBoxLayoutQHBoxLayoutクラスを用いて得るためのコードは下記のようになる(レイアウトの構築部分以外はほぼ同じなので省略):

from qtpy.QtWidgets import QVBoxLayout, QHBoxLayout

# --- レイアウトの構築(Qtのレイアウトのクラスを利用) ---
btn_layout = QHBoxLayout()
btn_layout.addWidget(QPushButton('OK'))
btn_layout.addWidget(QPushButton('Cancel'))

layout = QVBoxLayout()
layout.addWidget(QLabel('Hello, World!'))
layout.addLayout(btn_layout)
# --------------------------------------------------------

hboxvboxを使った場合の方がコーディング量が少ない上にコード上でどのようなレイアウトを組んでいるのかが分かりやすくなっていると思う.

また,QtでQLayoutに子要素を追加する際,

  • 子要素がQWidgetの場合はaddWidget
  • 子要素がQLayoutの場合はaddLayout

のように関数を使い分ける必要があるのだが,そのあたりもhbox,vboxの中でうまくラップしてある.

Widgetのサイズの比率を変える例

次のようにレイアウト内のWidgetのサイズの比率が異なるレイアウトを作る場合(QLabel : QSpinBox = 1 : 2), 要素の代わりに(要素, 比率)のタプルを渡すことで表現できるようにした.

ちなみに,比率を指定しなかったWidget w(w, 0)として扱われる.

img

# -*- coding: utf-8 -*-
import sys

from qtpy.QtWidgets import (
    QMainWindow, QApplication, QPushButton, QLabel, QSpinBox, QWidget,
)
from qtpy.QtCore import QObject, Qt
from utils import hbox, vbox


class MainWindow(QMainWindow):
    def __init__(self, **kwargs):
        super(MainWindow, self).__init__(**kwargs)

        self.main_widget = QWidget(self)

        value_layout = vbox([
            hbox([(QLabel('Value 1'), 1), (QSpinBox(), 2)]),
            hbox([(QLabel('Value 2'), 1), (QSpinBox(), 2)]),
            hbox([(QLabel('Value 3'), 1), (QSpinBox(), 2)]),
            hbox([(QLabel('Value 4'), 1), (QSpinBox(), 2)]),
            hbox([(QLabel('Value 5'), 1), (QSpinBox(), 2)]),
        ])
        btn_layout = hbox([QPushButton('OK'), QPushButton('Cancel')])

        layout = vbox([
            value_layout,
            btn_layout
        ])

        self.main_widget.setLayout(layout)
        self.setCentralWidget(self.main_widget)
        self.setMinimumSize(320, 240)


def main():
    app = QApplication(sys.argv)

    view = MainWindow()

    view.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

コード

ここまで関数の機能を紹介してきたが,utils.pyの実装は以下のようになってる:

# -*- coding: utf-8 -*-
from qtpy.QtWidgets import (
    QWidget, QLayout, QHBoxLayout, QVBoxLayout
)


def _generate_box_layout(t: str, widgets):

    if t == 'h':
        layout = QHBoxLayout()
    elif t == 'v':
        layout = QVBoxLayout()
    else:
        raise RuntimeError()

    for widget in widgets:
        if isinstance(widget, tuple):
            w, stretch = widget
            assert isinstance(stretch, int)
        else:
            w = widget
            stretch = 0

        if isinstance(w, QWidget):
            layout.addWidget(w, stretch)
        elif isinstance(w, QLayout):
            layout.addLayout(w, stretch)
        else:
            raise TypeError(f'Illegal type of widget is detected: {widget}')

    return layout


def hbox(widgets):
    return _generate_box_layout('h', widgets)


def vbox(widgets):
    return _generate_box_layout('v', widgets)

さいごに

大規模なアプリケーションを作る場合はちゃんとQt Designerを使いこなした方がいいと思うけど, ウインドウひとつだけの簡易のツール作る程度だったらコード上でGUI組む方がファイル数増えないしお手軽でいいと思う.

© eqs 2021