PythonでマルチプラットフォームのGUIアプリを作成(PySide6)

Windows、Mac、Linuxといったマルチプラットフォーム(マルチOS)で動くGUIのアプリケーションを作成するとしたら、どうしますか?

ElectronやJavaなどの選択肢もありますね。

そんな中でも、算術ライブラリや機械学習のライブラリを使えて、全部のOSでも動くGUIアプリを作れるのがPythonです。

PythonでGUIアプリを作成するにも、いくつか選択肢があるのですが、個人的に使いやすいと思っている「PySide6」を使った方法をご紹介します。

まずは、シンプルなGUIアプリ(HelloWorld)を作成

初めに、お決まりのHelloWorld的なGUIアプリを作成してみます。

その後、PySide2の概要を解説し、もう少し複雑なアプリにつなげたいと思います。

なお、アプリの作成に使う統合開発環境は「PyCharm」を使い、PythonとPyCharmはインストールしてあるものとします。

プロジェクトと仮想開発環境の作成

早速、新しくプロジェクトを作成します。

「Location」には、以下(D:\MyDownload\env\python\projects\Sample001)のように、プロジェクトを配置するディレクトリを指定します。

次の

Python Interpreter: New Virtualenv environment

は、新たにPythonの仮想開発環境を作成するということです。

(※Pythonの仮想開発環境について、くわしくは、この記事を参考にしてください。)

複数バージョンのPython2系と3系をvenv(virtualenv)で切り替えて使用する環境構築まとめ(windows編)複数バージョンのPython2系と3系をvenv(virtualenv)で切り替えて使用する環境構築まとめ(windows編)

New environment using「Virtualenv」を指定すると、プロジェクトのディレクトリの直下に「venv」というディレクトリが作成され、そこに仮想開発環境ができます。

他の開発環境とは別に、このGUIアプリ用のライブラリをセットアップできるので、便利ですね。

Base interpreter は、自分のPCにインストールしてあるpython.exeを指定します。
(多分、デフォルトで選択されています。)

PySide6のインストール

プロジェクトができたら、PySide6をpipでインストールします。

PyCharmの下部のタブで「Terminal(ターミナル)」を選び、そこで、

pip install PySide6

とコマンドを打ちます。

HelloWorldアプリを作成して実行

それでは、main.pyに以下のソースを書くか、貼り付けます。

import sys
from PySide6.QtWidgets import QApplication, QLabel
                                                     
if __name__ == "__main__":
    app = QApplication(sys.argv)
    label = QLabel("Hello World")
    label.show()
    sys.exit(app.exec_())

できましたか?

それでは、実行してみましょう。

main.pyの右クリックして「Run」すると、Guiアプリが起動します。

ちょっと小さいですよね?心配ありません。HelloWorldを表示するシンプルなラベルだけを持つアプリなので、当然です。

次の章で、もう少し、アプリらしくしていきます。

メニューなどを持ったひな形アプリを作成する

本格的なGUIアプリを作る場合は、アプリ上部にメニューバーがあったり、下部にステータスバーがあったりします。

また、クリックすると何らかの動作や作業を行うボタンなども必要ですよね。

ここからは、本格的なGUIアプリを作る際に必要となってくる、これらの要素を取り入れて、ひな形として使えるようなアプリを作成してみたいと思います。

作成するのは以下のようなもの。

ボタンを押すと、画面に「Hello + 入力した名前」を表示するシンプルなものですが、メニューバー、ステータスバーも備えていますし、ボタンやテキストボックスなどの部品も、きれいに「レイアウト」してあります。

また、ボタンを押すと、関数が呼ばれて、動作するという「イベントハンドル」の仕組みも勉強できるひな形になっています。

QMainWindowを使う

PySideでGUIアプリケーションを作る場合には、「QApplication」という大きな入れ物に、部品を載せていくのですが、一般的なアプリに必要なメニューバーやステータスバーを備えたアプリを作る場合には、「QApplication」に「QMainWindow」の派生クラスを載せて、肉付けしていった方が、簡単です。

ちょうど、以下のような感じですね。

QMainWindowには、メニューバーやステータスバーを表示する領域があらかじめ備わっているので、便利なんですよね。

そして、そのQMainWindowに、メインのQWidgetを載せて、そこに部品を載せていくと、わかりやすい構造になります。

レイアウトを使いながら部品を載せていく

そのQWidgetに部品を載せていく場合には、「レイアウト」の仕組みを使うと便利です。

例えば以下のようなレイアウトの仕組みを使うと、追加した部品が自動で上の方に、または右の方に詰まってくれるので、部品の位置の調整が簡単に、しかもきれいにできます。

QVBoxLayout・・・追加した部品が縦方向に、上の方に詰められる。

QHBoxLayout・・・追加した部品が横方向に、右の方に詰められる。

イベントハンドリングの仕組み

部品を載せたら、それをクリックしたときの動きを定義します。

例では、「on_btn_clicked」という関数に処理を書き、

self.btn_load.clicked.connect(self.on_btn_clicked)

という記述で、ボタンの「clicked」のアクションにこの「on_btn_clicked」をつなげているということになります。

具体的なソース

以下に、具体的なソースを載せます。

import sys

from MainWindow import MainWindow
from PySide6.QtWidgets import QApplication


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()

    window.show()

    sys.exit(app.exec_())
import sys

from PySide6.QtCore import Slot
from PySide6.QtWidgets import QMainWindow, QWidget
# PySide2の時はPySide6.QtWidgets.QActionだった。
from PySide6.QtGui import QAction
from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout
from PySide6.QtWidgets import QLineEdit, QLabel, QPushButton
from PySide6.QtCore import QSize
from PySide6.QtCore import Qt


class MainWindow(QMainWindow):

    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("サンプルGUIアプリ")
        self.resize(QSize(500, 400))

        # Menu
        self.menu = self.menuBar()
        self.file_menu = self.menu.addMenu("File")

        # Exit QAction
        exit_action = QAction("終了", self)
        exit_action.setShortcut("Ctrl+Q")
        exit_action.triggered.connect(self.exit_app)

        self.file_menu.addAction(exit_action)

        # Status Bar
        self.status = self.statusBar()
        self.status.showMessage("Status...")

        self.__controls()
        self.__layout()

    def __controls(self):
        self.lbl_01 = QLabel("名前")
        self.txt_01 = QLineEdit()
        self.btn_load = QPushButton("変換", self)
        self.btn_load.clicked.connect(self.on_btn_clicked)

        self.lbl_contents = QLabel("")
        self.lbl_contents.setTextFormat(Qt.RichText)
        self.lbl_contents.setWordWrap(True);
        self.lbl_contents.setStyleSheet("border:1px solid black;padding:10px;line-height: 0.5px;")
        self.lbl_contents.setText('');

    def __layout(self):
        self.main_widget = QWidget(self)

        self.v_box = QVBoxLayout()
        self.h_box = QHBoxLayout()
        self.h_contents = QHBoxLayout()

        self.h_box.addWidget(self.lbl_01)
        self.h_box.addWidget(self.txt_01)
        self.h_box.addWidget(self.btn_load)

        self.h_contents.addWidget(self.lbl_contents)

        self.v_box.addLayout(self.h_box)
        self.v_box.addLayout(self.h_contents)

        self.main_widget.setLayout(self.v_box)

        self.setCentralWidget(self.main_widget)

    @Slot()
    def exit_app(self, checked):
        sys.exit()

    def on_btn_clicked(self):
        print("call on_btn_clicked")

        self.lbl_contents.setText('Hello ' + self.txt_01.text())