Help us understand the problem. What is going on with this article?

Tkinterを使うのであればPySimpleGUIを使ってみたらという話

ページを読んでできるもの

  • Pythonで以下の入力内容のGUIが簡単に作れます
  • 基本的な入力(テキストボックス、チェックボックス)
    • テキストボックスにはデフォルトで入力が、チェックボックスにはチェックが入っています
    • 入力された内容をポップアップで表示する

起動直後の画面
2.png

実行ボタンを押した結果
2-2.png

Tkinterについて

Tkinterは標準のPythonのGUIライブラリです。個人的に考えるメリット、デメリットは以下の通りです。

  • メリット

    • 標準だからインストール不要
    • サンプルはそこそこ多い
  • デメリット

    • ウィジェット(UIパーツ)を配置する際、種類、オプション、レイアウトと順を追って設定しないといけない
    • ウィジェットの配置が増えると、レイアウトがコードを見てもよくわからない

Tkinterの例

実際にTkinterについて実際にコードを載せます

PythonでGUIアプリを作る方法【Tkinter】
の記事の最後のコードと実行結果をこちらに転載します

実行結果
image.png

テキストボックスに入力後に「実行ボタン」を押した結果
image.png

コードは以下になります

import tkinter
from tkinter import messagebox

#ボタンがクリックされたら実行
def button_click():
    input_value = input_box.get()
    messagebox.showinfo("クリックイベント",input_value + "が入力されました。")

#ウインドウの作成
root = tkinter.Tk()
root.title("Python GUI")
root.geometry("360x240")

#入力欄の作成
input_box = tkinter.Entry(width=40)
input_box.place(x=10, y=100)

#ラベルの作成
input_label = tkinter.Label(text="ラベル")
input_label.place(x=10, y=70)

#ボタンの作成
button = tkinter.Button(text="実行ボタン",command=button_click)
button.place(x=10, y=130)

#ウインドウの描画
root.mainloop()

ウィジェット(UIのパーツ)が縦一列に並んだ場合は比較的簡単です。
問題は横一列に「ラベル」+「テキストボックス」というようなよくあるレイアウトで複数のウィジェットを配置した場合です

実行結果
image.png

テキストボックスに入力後に「実行ボタン」を押した結果
image.png

コードはいかになります。

import tkinter
from tkinter import messagebox

#ボタンがクリックされたら実行
def button_click():
    input_name_value = input_name.get()
    input_address_value = input_address.get()
    input_phone_value = input_phone.get()

    show_message = "名前:" + input_name_value + 'が入力されました。\n'
    show_message += "住所:" + input_address_value + 'が入力されました。\n'
    show_message += "電話番号:" + input_phone_value + "が入力されました。"
    print(show_message)

    # 入力内容をポップアップ画面で表示
    messagebox.showinfo("入力内容" ,show_message)

#ウインドウの作成
root = tkinter.Tk()
root.title("Python GUI")
root.geometry("360x120")

# 名前
input_name_label = tkinter.Label(text="名前")
input_name_label.grid(row=1, column=1, padx=10,)

# 入力欄の作成
input_name = tkinter.Entry(width=40)
input_name.grid(row=1, column=2)


# 住所
input_address_label = tkinter.Label(text="住所")
input_address_label.grid(row=2, column=1, padx=10,)

# 住所入力欄の作成
input_address = tkinter.Entry(width=40)
input_address.grid(row=2, column=2)


# 電話番号
input_phone_label = tkinter.Label(text="名前")
input_phone_label.grid(row=3, column=1, padx=10,)

# 電話番号入力欄の作成
input_phone = tkinter.Entry(width=40)
input_phone.grid(row=3, column=2)

#ボタンの作成
button = tkinter.Button(text="実行ボタン",command=button_click)
button.place(x=10, y=80)

#ウインドウの描画
root.mainloop()

問題なのは以下のところです。

# 電話番号
input_phone_label = tkinter.Label(text="名前")
input_phone_label.grid(row=3, column=1, padx=10,)

Tkinterはウィジェットを配置する際はまず自分が何(ラベル、ボタン、テキストボックスetc)を設定します。
そしてそのあとに配置する場所を指定します。

Tkinterでグリッドレイアウトを用いる際は、grid関数にrow,columnを使ってレイアウトを調整します。今回は縦×横が3×2のレイアウトなのでまだいいですが、例えば5×10などウィジェットが増えていくと、コードをぱっと見ただけではレイアウトがよくわからなくなります。

PySimpleGUI

PySimpleGUIは2018年から開発が始まったライブラリーです。

tkinter、Qt、WxPython、Remiののラッパーで公式ではもとのライブラリーで書く場合を比べて、コード量が2分の1から10分の1程度でかけると書いてあります。
私自身も実際に書いてみてTkinterで書くよりもはるかに短いコード量でかけると思いました。

最大の特徴は、リストを使用して、UIのレイアウトを配置することです。
実際に使用して見ましょう。

インストール方法はpipで簡単にインストールできます

pip install pysimplegui
or
pip3 install pysimplegui

簡単な使い方は以下のとおりです。公式の以下のサンプルをもとに作成しています。

import PySimpleGUI as sg

#  セクション1 - オプションの設定と標準レイアウト
sg.theme('Dark Blue 3')

layout = [
    [sg.Text('Python GUI')],
    [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')],
    [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')],
    [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')],
    [sg.Submit(button_text='実行ボタン')]
]

# セクション 2 - ウィンドウの生成
window = sg.Window('住所を入力', layout)

# セクション 3 - イベントループ
while True:
    event, values = window.read()

    if event is None:
        print('exit')
        break

    if event == '実行ボタン':
        show_message = "名前:" + values[0] + 'が入力されました。\n'
        show_message += "住所:" + values[1] + 'が入力されました。\n'
        show_message += "電話番号:" + values[2] + "が入力されました。"
        print(show_message)

        # ポップアップ
        sg.popup(show_message)

# セクション 4 - ウィンドウの破棄と終了
window.close()

実行結果はいかになります。

実行結果
1.png

実行ボタンを押した結果
1-2.png

いかがでしょうか?
レイアウトは以下で定義しています。2次元のリストでレイアウトを定義しています。

layout = [      
          [sg.Text('Python GUI')],      
          [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')],      
          [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')],      
          [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')],     
          [sg.Submit(button_text='実行ボタン')]      

そして取得した値は以下で取得できます

    if event == '実行ボタン':  
        show_message = "名前:" + values[0] + 'が入力されました。\n'
        show_message += "住所:" + values[1] + 'が入力されました。\n'
        show_message += "電話番号:" + values[2] + "が入力されました。"

詳しくは公式ドキュメントを読むことをおすすめしますが、
Tkinterでは54行でかけます。しかしPySimpleGUIですと28行でかけます。
なにより、Tkinterで書いたものよりもレイアウトや値の取得方法が格段にわかりやすいです。

checkboxを使ってみる

先程のコードにデフォルトでチェックが入っているチェックボックスを追加してみます。

コードはいかになります

import PySimpleGUI as sg

#  セクション1 - オプションの設定と標準レイアウト
sg.theme('Dark Blue 3')

layout = [
    [sg.Text('Python GUI')],
    [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')],
    [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')],
    [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')],
    [sg.Checkbox('同意する', default=True)],
    [sg.Submit(button_text='実行ボタン')]
]

# セクション 2 - ウィンドウの生成
window = sg.Window('住所を入力', layout)

# セクション 3 - イベントループ
while True:
    event, values = window.read()

    if event is None:
        print('exit')
        break

    if event == '実行ボタン':
        show_message = "名前:" + values[0] + 'が入力されました。\n'
        show_message += "住所:" + values[1] + 'が入力されました。\n'
        show_message += "電話番号:" + values[2] + "が入力されました。"
        show_message += "同意のチェックは:" + str(values[3]) + "です"
        print(show_message)

        # ポップアップ
        sg.popup(show_message)

# セクション 4 - ウィンドウの破棄と終了
window.close()

実行結果はいかになります。

起動直後の画面
2.png

実行ボタンを押した結果
2-2.png

チェックボックスの値はBool方でture,falseで取得できます。
Tkinterでチェックボックスでデフォルトの値を設定しようとおもうとちょっとめんどうだったものが簡単に設定できます。

Tkinterのチェックボックスの設定方法については以下の記事が参考になります。

PySimpleGUIは他にもTkinterを生で使うよりも、簡単にウィジェットが使用できます。また公式のサンプルが豊富です。matplotlibやopenCvを使用した例などが豊富にあります。

表示内容の更新方法について

Tkinterの内部の構造については以下の記事が参考になります

こちらの記事のサンプルではシンプルな例として、tkinterを使用してボタンをクリックするとラベルの内容が更新されるプログラムを紹介しています。
紹介しているプログラムは以下になります。

#1 tkinterモジュールをtkという名前でインポート
import tkinter as tk

#2 ウインドウの生成と変数への格納
root_window = tk.Tk()
root_window.geometry("200x100")

#3 可変文字列の生成と変数への格納
label_text = tk.StringVar()

#4 可変文字列の初期化
label_text.set("Please click the button.")

#5 ボタンが押された時用の関数を予め定義しておく。
def button_clicked():
    label_text.set("Button clicked.")

#6 GUIパーツの生成と変数への格納
lbl = tk.Label(
    master=root_window,
    textvariable=label_text)

btn = tk.Button(
    master=root_window,
    text="Button",
    command=button_clicked
    )

#7 GUIパーツの配置
lbl.place(x=5,y=5)
btn.place(x=5,y=30)

#8 ウインドウ表示
root_window.mainloop()

個人的にはちっともシンプルに思えないです・・・

理由としては、tkinterは元々Tcl/Tk の Tk 部分を Python で利用できるようしているので使いかたがPython的ではないです。
例えば、tkintrではウィンドウサイズをroot_window.geometry("横x縦") という風に縦と横の数値を小文字のエックス(x)で記述した文字列でないといけないです。Pythonで記載するのであれば(縦,横)のタプルで記載したいです。
またラベルの表示などの文字列をこうする場合にはウィジェットを設定する際にあらかじめTk.StringVar()を使わないといけないです。

覚えないといけないことが多くて大変です。
もっと簡単にわかりやすく書きたいです。

PySimpleGUIで書くと以下のようになります

import PySimpleGUI as sg

sg.theme('Dark Green 6')

layout = [[sg.Text('Please click the button', key='-TEXT-')],
          [sg.Submit(button_text='実行ボタン')]]

# ウィンサイズはsizeに(縦の大きさ,横の大きさ)で記載
window = sg.Window(title='show input text', size=(200, 100)).Layout(layout)

# イベントループ
while True:
    event, values = window.read()
    if event is None:
        break
    if event == '実行ボタン':
        update_text = "Button clicked."
        # 表示内容を更新する際はウィジェットに指定されたkeyの値に.update("文字列")を入れることで可能
        window['-TEXT-'].update(update_text)

window.close()

実行結果は以下になります

起動直後の画面
3-1.png

実行ボタンを押した結果
3-2.png

わかりやすいですね。

exe化について

exe化はPyInstllerを使用して簡単にできます

以下は公式の以下の説明からの引用です

単一ファイルの作り方は以下になります

pip install PyInstaller
pyinstaller -wF my_program.py

-wF をつけることで単一ファイルができます

また以下のエラーが出た場合は、--hidden-import tkinter をオプションに加えます。

ValueError: script '.......\src\tkinter' not found

Macだと以下でできるそうです(公式では確かめていないそうです)

pyinstaller --onefile --add-binary = '/ System / Library / Frameworks / Tk.framework / Tk': 'tk' --add-binary = '/ System / Library / Frameworks / Tcl.framework / Tcl': ' tcl 'your_program.py

この情報はRedditにあり、ソースはhttps://github.com/pyinstaller/pyinstaller/issues/1350 から引用しています。

複雑なレイアウトについて

PySimpleGUIでは入れ子の構造のような複雑なレイアウトも簡単に書くことができます。
例えば、住所を1つのテキストボックスに記載していますが、「郵便番号」、「都道府県」、「住所」という風に細かく分けてみます。

このような感じになるかと思います。

実行時

4-1.png

入力結果
4-2.png

これは以下の内容で実現できます

import PySimpleGUI as sg

#  セクション1 - オプションの設定と標準レイアウト
sg.theme('Dark Blue 3')

frame1 = sg.Frame('住所', [[sg.Text('郵便番号', size=(15, 1))],
                         [sg.InputText(key='-POST-NUM-')],
                         [sg.Text('都道府県', size=(15, 1), )],
                         [sg.InputText(key='-PREFECTURES-')],
                         [sg.Text('住所', size=(15, 1))],
                         [sg.InputText(key='-ADDRESS-')],
                         ],
                  relief=sg.RELIEF_SUNKEN, tooltip='住所をいれてね!')

layout = [
    [sg.Text('Python GUI')],
    [sg.Text('名前', size=(15, 1)), sg.InputText(
        default_text='○○〇×××', key='-USER-NAME-')],
    [sg.Text('住所を入れてね', size=(15, 1)), frame1],  # frame1のレイアウトを入れ子にして入れている
    [sg.Text('電話番号', size=(15, 1)), sg.InputText(
        default_text='xxx-xxx-xxx', key='-PHONE-NUM-')],
    [sg.Submit('実行ボタン')]
]

# セクション 2 - ウィンドウの生成
window = sg.Window('住所を入力', layout)

# セクション 3 - イベントループ
while True:
    event, values = window.read()

    if event is None:
        print('exit')
        break

    if event == '実行ボタン':
        print(values)
        show_message = "名前:" + values['-USER-NAME-'] + 'が入力されました。\n'
        show_message += "郵便番号:" + values['-POST-NUM-'] + 'が入力されました。\n'
        show_message += "都道府県:" + values['-PREFECTURES-'] + 'が入力されました。\n'
        show_message += "住所:" + values['-ADDRESS-'] + 'が入力されました。\n'
        show_message += "電話番号:" + values['-PHONE-NUM-'] + "が入力されました。"

        # ポップアップ
        sg.popup(show_message)

# セクション 4 - ウィンドウの破棄と終了
window.close()

住所の部分は変数frame1リストとして別に定義して、layoutリストに入れています。
こうすることで入れ子のような複雑なレイアウトも簡単にできます。

frame1 =  sg.Frame(layout=[[sg.Text('郵便番号', size=(15, 1))], 
                    [sg.InputText('', key='post_num')],
                    [sg.Text('都道府県', size=(15, 1), )], 
                    [sg.InputText('', key='prefectures')],
                    [sg.Text('住所', size=(15, 1))], 
                    [sg.InputText('', key='address')],
                    ], 
                    title='住所', relief=sg.RELIEF_SUNKEN, tooltip='住所をいれてね!')

またウィジェット(pySimpleGUIではElementと呼びます)の値ですがkeyを指定することで該当の値をそのkey名で取得できます。

print(values)
{0: 'テスト太郎', 'post_num': '123-456', 'prefectures': 'VVV県', 'address': 'XXXX市○○○○', 1: 'xxx-xxx-xxx'}

見た目について

公式のMore Ways to "Dress Up Your Windows"で以下の3つの要素の説明があります。

  • ウィンドウの透明度の設定
  • タイトルバーの非表示の設定
  • ボタンをbase64エンコードで画像に差し替える方法

そのうち「ウィンドウの透明度の設定」、「タイトルバーの非表示」について説明します。
「ボタンをbase64エンコードで画像に差し替える方法」については画像ファイルを直接指定して適用する方法もありますので、それとまとめて後日、個別に説明します。

 ウィンドウの透明度の設定

実行結果は以下になります。透明度を50%に設定して実施

ex_transparency.png
windowエレメントにalpha_channelの要素を1~0.01で指定することで透明度が設定できます。今回ですと以下のように設定しています。

window = sg.Window('住所を入力', layout, alpha_channel=.5)

小数点表記の場合は頭の0はつけなくても動作します。もちろんalpha_channel=0.5と設定しても動作します。

タイトルバーの非表示の設定

実行結果は以下になります

ex_no_title_bar.png

windowエレメントにno_titlebarTrueで指定することでタイトルバーを非表示に設定できます。ただしドラッグしてアプリの移動ができなくなってしまいます。ドラッグして移動を行うためにはgrab_anywhereTrueを設定します
今回ですと以下のように設定しています。

window = sg.Window('住所を入力', layout, no_titlebar=True, grab_anywhere=True)

ライセンスについて

PySimpleGUIはライセンスはLGPL3.0になります。
MITライセンスではないですが、再配布しない限り社内や個人で利用するにあたっての使用は自由なはずですので通常使用する分には問題がないはずです。

日本語のPySimpleGUIの解説

数が少ないですがPySimpleGUIを日本語での紹介をしている記事があるので紹介します

今後の予定

時間があれば公式のサンプルプログラムからいくつかの紹介とTkinterで作られたGUIをPySimpleGUIで書き直した場合のサンプルを書こうかと考えています。

参考 Macでのtkinterのインストールについて

Macでpyenvを使用する場合は、tkinterを使用するとエラーになることがあります。
その際は以下の方法でインストールするとよいと思いますのでリンクを貼っておきます

pyenvのpythonでtkinterを使用する方法

Macでの使用について

Macでの使用ですが、tkeinterをインストールするのは手間ががかるのと日本語を入力する場合IMEが開かないという問題があります。
Macの場合はPySimpleGUIWx(wxPythonのラッパー)を使用した方がよいかもしれません。
wxPythonの場合は日本語の入力でも問題はありませんでした。
基本的な使い方としてはwxPythonとPySimpleGUIWxをインストール後に

import PySimpleGUI as sg

となっているところを、

import PySimpleGUIWx as sg

とすれば同じコードで実行が可能になります。※PySimpleGUIWxをインストール前にwxPythonをインストールする必要があります

以下が実行結果になります。左がPySimpleGUIWxで実行した画面、右がPySimpleGUIでの実行画面になります。

スクリーンショット 2020-01-09 10.49.35.png

dario_okazaki
プログラム書いたり、映像作ったりしています。
http://studio-overdrive.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした