python

Pythonを使ってのデスクトップアプリ開発

デスクトップアプリは基本GUI操作になります。

GUIとは?

GUI(Graphical User Interface)は、マウスなどでグラフィカルに操作するインターフェースのことです。

対して、キーボードからの操作のみを受け付けるものをCUIまたはCLIと言います。

例えば、ウェブブラウザ(FireFox、GoogleChrome etc)なんかもGUIアプリソフトウェアです。

Pythonでデスクトップアプリ開発をするためのライブラリは多数あります。

最近の有名どこだとKivyなんかがありますね。KivyはiOSやAndroidにも対応しているので使い所によってはかなり便利です。

今回はPySimpleGUIというライブラリが一番簡単にアプリ作成できるので、簡単なアプリを作り流れをみていきます。

公式サイト

https://pysimplegui.readthedocs.io/en/latest/

github

https://github.com/PySimpleGUI/PySimpleGUI

学べること

  • PySimpleGUIパッケージのインストール
  • 基礎的なユーザーインターフェースの要素作成
  • イメージービューアーアプリの作成(Finderみたいなのの簡易版)
  • デスクトップアプリ化

PySimpleGUIの特徴としてtkinter、QT(pyside2)、wxPython、REMIライブラリをラッパーしているので、簡単にGUIアプリを作れます。カバー範囲はライブラリによって異なるので目安を載せときます。

今回作るアプリは、基本となるPysimpleGUIを使います。

ライブラリ継承元カバー範囲
PysimpleGUItkinter全て
PysimpleGUIQtQT(pyside2)大半
PysimpleGUIWxwxPython少し
PysimpleGUIWebREMI少し

PySimpleGUIの基礎

PySimpleGUIはコード量が少なく、テンプレートが豊富なのが特徴です。

逆に自由度は若干低めなので、複雑なことをしたい場合には別のライブラリを選んだほうがいいです。

早速パッケージをインストールしていきます。

必要であれば、condaやpipenvなどで仮想環境を作った上でインストールしてください。

pip install pysimplegui

HTMLなどと同様に、pysimpleguiではボタンやテキストボックスがウィジェットとして提供されています。

試しにウィンドウを表示してみます。

main.py

import PySimpleGUI as sg

sg.Window(title="Hello World", layout=[[]], margins=(100, 50)).read()

python main.py

で実行するとこのように表示が確認できます。

このウィンドウの中に必要な要素を組み込んでアプリ開発を進めていきます。

windowメソッドには多数のオプションが用意されているので、気になる方は公式ドキュメントを参照してください。

次に、ウィンドウ内にボタンやテキストを表示させていきます。

レイアウトに要素を表示したい場合にはlistを使ってネストしていきます。上記のコードのlayout=[[]]部分です。

コードの可読性を高めるために、別の変数として切り出して記述していきます。

今回のケースはボタンとテキストをあらかじめネストしておき、ウィンドウを作成する時にその情報を渡します

import PySimpleGUI as sg

layout = [
    [sg.Text("Hello from PySimpleGui")],
    [sg.Button("OK")]
]

window = sg.Window("Demo", layout)

while True:
    event, values = window.read()
    if event == "OK" or event == sg.WIN_CLOSED:
        break

window.close()

先ほどと違うのはwhile以下です。

このコードはイベントをループさせています。GUIは中の方の処理でループ処理が走る必要があり、ループ処理が走ることでユーザーからのアクションを受け付けることができます。

例えば、ボタンをクリックした時にこのイベントはループ内で処理されます。

ループ処理をやめたければ、OKボタンを押すか、ウィンドウの閉じるボタンを押すとループ処理が止まりウィンドウが閉じます。

これが、基本的なpysimpleguiを使った開発です。

さて、本題のイメージービューアーアプリを作成していきます。

イメージービューアーアプリは画像を取り扱う必要があるのですが、ライブラリによって取り扱える画像の種類に差があります。

ライブラリPNGGIFJPGTIFFBMP
PysimpleGUI×××
Pillow
PysimpleGUIQT

これが理由で最初にPysimpleGUIQTも一緒にインストールしました。

ここまでで、アプリ開発のための基礎的な知識を得ました。

早速イメージビューアーアプリを作っていきます。

import PySimpleGUI as sg
import os.path

file_list_column = [
    [
        sg.Text("Image Folder"),
        sg.In(size=(25, 1), enable_events=True, key="-FOLDER-"),
        sg.FolderBrowse(),
    ],
    [
        sg.Listbox(
            values=[], enable_events=True, size=(40, 20),
            key="-FILE LIST-"
        )
    ],
]

file_list_columnでは、2行のレイアウトを作成します。

一行目に、テキスト表示、インプットフィールド、PCのフォルダを参照するためのボタンを設置

二行目に参照したフォルダ内のファイル一式を表示するためのリストボックスを設置

ここで重要なのがkeyの箇所です。JSでいうところのIDみたいなもので、特定の要素を判別するために用います。

後はイベント毎に行いたい処理を記述すれば普通のコードと同様に動作します。

画像を表示するための枠も欲しいので、下記のようなレイアウトにすることを想定して、
別変数で定義します。

import PySimpleGUI as sg
import os.path

file_list_column = [
    [
        sg.Text("Image Folder"),
        sg.In(size=(25, 1), enable_events=True, key="-FOLDER-"),
        sg.FolderBrowse(),
    ],
    [
        sg.Listbox(
            values=[], enable_events=True, size=(40, 20),
            key="-FILE LIST-"
        )
    ],
]

image_viewer_column = [
    [sg.Text("Choose an image from the list on the left:")],
    [sg.Text(size=(40, 1), key="-TOUT-")],
    [sg.Image(key="-IMAGE-")],
]

layout = [
    [
        sg.Column(file_list_column),
        sg.VSeparator(),
        sg.Column(image_viewer_column),
    ]
]

window = sg.Window("Image Viewer", layout)

これでレイアウトを構成する要素は書き出し終わりました。

後はこれをループ内で必要な処理を書いてあげれば完成です。

最初にやったことよりも複雑になっていますがやっていることは変わりません。

一つずつ実装していって、都度確認していきます。

最初は、以下のコードでフォルダを選択してパスが表示されることを確認しましょう

import PySimpleGUI as sg
import os.path

file_list_column = [
    [
        sg.Text("Image Folder"),
        sg.In(size=(25, 1), enable_events=True, key="-FOLDER-"),
        sg.FolderBrowse(),
    ],
    [
        sg.Listbox(
            values=[], enable_events=True, size=(40, 20),
            key="-FILE LIST-"
        )
    ],
]

image_viewer_column = [
    [sg.Text("Choose an image from the list on the left:")],
    [sg.Text(size=(40, 1), key="-TOUT-")],
    [sg.Image(key="-IMAGE-")],
]

layout = [
    [
        sg.Column(file_list_column),
        sg.VSeparator(),
        sg.Column(image_viewer_column),
    ]
]

window = sg.Window("Image Viewer", layout)

while True:
    event, values = window.read()
    if event == "Exit" or event == sg.WIN_CLOSED:
        break

window.close()

先ほど作成したインプットフィールドを参照しているフォルダキーとイベントを照合します。イベントが終了すると、ユーザーがフォルダを選択したことがわかります。

そして、os.listdirを使用してファイルリストを取得し、そのリストを拡張子がpngまたはgifのファイルだけに絞り込みます。pillowもしくはpysimpleguiqtを使用すれば、画像タイプを絞り込む必要はありません。

import PySimpleGUI as sg
import os.path

file_list_column = [
    [
        sg.Text("Image Folder"),
        sg.In(size=(25, 1), enable_events=True, key="-FOLDER-"),
        sg.FolderBrowse(),
    ],
    [
        sg.Listbox(
            values=[], enable_events=True, size=(40, 20),
            key="-FILE LIST-"
        )
    ],
]

image_viewer_column = [
    [sg.Text("Choose an image from the list on the left:")],
    [sg.Text(size=(40, 1), key="-TOUT-")],
    [sg.Image(key="-IMAGE-")],
]

layout = [
    [
        sg.Column(file_list_column),
        sg.VSeparator(),
        sg.Column(image_viewer_column),
    ]
]

window = sg.Window("Image Viewer", layout)

while True:
    event, values = window.read()
    if event == "Exit" or event == sg.WIN_CLOSED:
        break
    if event == "-FOLDER-":
        folder = values["-FOLDER-"]
        try:
            file_list = os.listdir(folder)
        except:
            file_list = []

        fnames = [
            f
            for f in file_list
            if os.path.isfile(os.path.join(folder, f))
            and f.lower().endswith(('.png', '.gif'))
        ]
        window["-FILE LIST-"].update(fnames)

window.close()

最後に、リストボックスから選択したファイルの画像を右カラムに表示させて完成です。

import PySimpleGUI as sg
import os.path

file_list_column = [
    [
        sg.Text("Image Folder"),
        sg.In(size=(25, 1), enable_events=True, key="-FOLDER-"),
        sg.FolderBrowse(),
    ],
    [
        sg.Listbox(
            values=[], enable_events=True, size=(40, 20),
            key="-FILE LIST-"
        )
    ],
]

image_viewer_column = [
    [sg.Text("Choose an image from the list on the left:")],
    [sg.Text(size=(40, 1), key="-TOUT-")],
    [sg.Image(key="-IMAGE-")],
]

layout = [
    [
        sg.Column(file_list_column),
        sg.VSeparator(),
        sg.Column(image_viewer_column),
    ]
]

window = sg.Window("Image Viewer", layout)

while True:
    event, values = window.read()
    if event == "Exit" or event == sg.WIN_CLOSED:
        break
    if event == "-FOLDER-":
        folder = values["-FOLDER-"]
        try:
            file_list = os.listdir(folder)
        except:
            file_list = []

        fnames = [
            f
            for f in file_list
            if os.path.isfile(os.path.join(folder, f))
            and f.lower().endswith(('.png', '.gif'))
        ]
        window["-FILE LIST-"].update(fnames)
    elif event == "-FILE LIST-":
        try:
            filename = os.path.join(
                values["-FOLDER-"], values["-FILE LIST-"][0]
            )
            window["-TOUT-"].update(filename)
            window["-IMAGE-"].update(filename=filename)
        except:
            pass

window.close()

デスクトップアプリ化

今のままだと、python環境の上で動かしているのでpythonが入っていない環境では動作しません。

そこで、python環境がない状態でも動作するデスクトップアプリとしてリリースします。

アプリ化の方法はいくつかありますが、今回はpyinstallerを使って簡単に行っていきます。

パッケージをpipでインストールしておきます。

pip install pyinstaller

使い方は、「pyinstaller <xxx>.py」でできます。

よく使うオプションとして、–noconsoleと–onefileがあります。

・–noconsoleはアプリ実行時にコンソールを表示しない

・–onefileは一つのoneファイルにコンパイルして出力する

–onefileに関してはデメリットもあります。一つのファイルにするとライブラリもまとめて一つにするので、ファイルが重くなります。

アプリケーションが重たい動作をする際にはやめたほうがいい時もあるので注意です。

pyinstaller img_viewer.py –noconsole –onefile

を実行が完了するとdistフォルダの中にexeファイルが作成されています。

このファイルを起動してあげるとpython上で実行したとき同様の結果が確認できます。

以上でアプリの完成です。

今回はやらないけど他にできること

AI関連と連携させるために、matplotlibやOpenCVなどのコンピュータービジョン系のライブラリも使うことができるので、python上で動いていたものをデスクトップアプリ化も可能です。