t-hom’s diary

主にVBAネタを扱っているブログです。

VBAer向け、Python入門用 GUIサンプル

今回は、ちょっとPythonを触ってみたいVBAerに向けて、簡単なGUIサンプルを紹介する。
Excel VBAではマクロの実行結果がシートに反映されるので、ある意味それ自体がGUIプログラミングと言えるが、殆どの言語では黒い画面に"Hello, World!"から入門するのが通例で、GUIの話は入門書の後半に差し掛かるあたりでちょろっと紹介される程度だ。

一方でGUIについて詳しく書かれた専門書は、黒画面での修行が終わったことを前提にしているので難しい。

黒画面 → 華がないのでやる気がでない。
GUI → 難しいのでやる気がでない。

つまり、ちょっとPythonを触ってみて、GUIが使えることを実感して、モチベーションをあげてから本格的に入門したいという方に向けた体験用コードで、しかもVBAer向けというのがなかなか無いので、今回書いてみた次第。

作るもの

サンプルなのでシンプルに。
ボタンを押すとラベルのテキストが変わるだけのプログラム。
f:id:t-hom:20190928101107p:plain

コード

私にしては珍しく、逐一コメントを書いてみた。
後程、この#1~#8のフェーズごとに解説していく。

#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()

解説

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

import tkinter as tk

まずはこちら、VBAでいうところの「参照設定」に該当する。
あちらはメニューからGUI操作でモジュール名にチェックを入れるのに対し、こちらはコード自体に記述する方式。

tkinterというのはpythonの標準GUIモジュールである。長いのでここでtkという名前をつけて利用しやすくしている。
慣例的にtkという名前にしているが、ただの識別子なので別名でも動作する。
別名にした場合、コード各部のtkはその名前に置き換える必要がある。

なお、自分で書いた「ナントカ.py」も同じようにimport文で取り込むことができる。その場合拡張子「.py」は記述しない。

また、pythonではpipという同梱プログラムで外部モジュールをインターネットからお取り寄せできる。
tkinterはインストールした覚えがないので最初から入ってたと思うが、無いモジュールは「pip install モジュール名」というコマンドでインストール可能。
※pipが使えない場合、pip.exeの入ったパスを環境変数Pathに登録が必要

#2 ウインドウの生成と変数への格納

root_window = tk.Tk()
root_window.geometry("200x100")

Pythonではクラス名(VBAでいうクラスモジュール名に相当)を関数のように()を付けて呼び出すことで、インスタンスが生成される。
先ほどインポートしたtkinterにTkというウインドウを生成するクラスが定義されているので、tk.Tk()でインスタンスを生成して、root_window変数に代入している。
VBAとちがって、オブジェクトの代入にSetを使うなどの特殊な方法はない。普通にイコール記号だけで代入される。
また、VBAのDimのような変数宣言専用の書式は存在しないので、識別子に対していきなり代入する。Pythonの場合は作法的にもこれで合ってる。

geometryメソッドはウインドウのサイズを変更する命令である。
文字列で横x縦のサイズを引き渡すとそのサイズになる。
掛け算記号がアスタリスクではなく小文字のエックスなのが面白い。

さて、ここではウインドウは内部的に生成されただけで、まだ表示はされておらず、#8のmainloopメソッドで表示される。

#3 可変文字列の生成と変数への格納

label_text = tk.StringVar()

Pythonでは文字列は基本的に固定で、ラベル等に直接文字列を書いてしまうとプログラムで動的に変更ができなくなる。
そこで、tkモジュールのStringVarクラスから可変文字列のインスタンスを生成し、label_textという変数に格納している。
label_textはただの変数名なので、この時点でラベルにセットされたわけではないことに注意。

#4 可変文字列の初期化

label_text.set("Please click the button.")

先ほど生成した可変文字列に対し、具体的な文字列をsetメソッドでセット。

#5 ボタンが押された時用の関数を予め定義しておく。

def button_clicked():
    label_text.set("Button clicked.")

これはボタンがクリックされた時用に予め用意した関数。
さきほど作った可変長文字列label_textに新しい文字列をセットするコードだ。

関数と書いてるが、戻り値を持たないのでVBAでいうとSubプロシージャである。
Pythonでは呼び分けはせず、戻り値を持たなくても関数と呼ぶ。
defが「Sub」や「Function」に相当する宣言文で、button_clickedが関数名。

VBAだとEnd SubやEnd Functionでプロシージャブロックの終わりを表すが、Pythonではインデントでブロックを表す。これについてはいろんなところで解説があるのでここでは省略する。

ちなみになぜボタンもまだ作ってないのに、押された後のことを先に書くかというと、Pythonコードは呼び出し先コードは呼び出し元よりも前に定義しおく必要があるから。

たとえばこういうコードを書くと、

hoge()

def hoge():
    print("Hi")

こういうエラーが出る。
f:id:t-hom:20190928092422p:plain

だから、呼ぶ前に教えてあげる必要がある。

def hoge():
    print("Hi")

hoge()

VBAだとモジュール内でプロシージャの順番を気にしなくても一旦全部探してくれるけど、Pythonだと順番を意識する必要がある。

#6 GUIパーツの生成と変数への格納

lbl = tk.Label(
    master=root_window,
    textvariable=label_text)

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

ここではtkモジュールのLabelクラスからインスタンスを生成している。
VBAと違って殆どのオブジェクト指向言語はインスタンス生成時に引数を渡すことができる。
master=とかtextvariable=といった表記は、名前付き引数である。VBAでいうところの「:=」 と同じ。
第一引数がmasterなので、他の解説ではmaster=は省略して単にウインドウを格納した変数を書くケースが多いが、tkinter初見だと意味が分からないと思うので明示的に書いた。

これはつまり、ラベルやボタンを作るときに、マスターを指定して親子関係の構造を作っているということ。
f:id:t-hom:20190928093929p:plain

サンプルではルートウインドウを指定していることが多いが、GUIパーツを纏めるフレームを使用する場合などは、フレームに配置するパーツのマスターをフレームに指定したり、複雑な構造を作ることができる。
f:id:t-hom:20190928094226p:plain

マスターの指定では論理的な構造を定義しているだけで、実際にパーツを配置しているわけではない。
マスター以外の引数は、読むとなんとなくわかると思うので省略。

今回ラベルの仮引数textvariableにlabel_textをセットしたので、label_textの変更があれば都度ラベルに反映される仕組みになっている。
なお、ラベルの値を変更する予定が無ければボタンと同じように、textvariableの代わりにtextにテキストを設定する。逆にボタンにtextvariableを指定することも可能である。

#7 GUIパーツの配置

lbl.place(x=5,y=5)
btn.place(x=5,y=30)

ここでは先ほど作ったGUIパーツを実際に配置している。
パーツの配置方法にはpack、grid、placeの3つのメソッドがあり、基本的に同じレイヤーで混在させることはできない。
(フレームを使うと、フレーム内を別メソッドでレイアウトすることが可能)

packは最も簡便であるが基本的に直列・並列に並べる用なので応用して自由なレイアウトをしたいと思うと難しい。
これを改造して何か作りたいと思ったときに難しいと思うので、ここでの紹介は割愛する。

gridは綺麗にレイアウトできてフレキシブルだけど、これも直感的にレイアウトするのはそれなりに難しい。
同じく、これを改造して何か作りたいと思ったときに難しいと思うので、ここでの紹介は割愛する。

placeは愚直にxyでの座標指定。面倒くさいけど、理解は簡単。改造も簡単なので今回はこれにした。

なお、lblとbtnは配置されたが、まだウインドウが表示されているわけではない。

#8 ウインドウ表示

root_window.mainloop()

mainloopメソッドを実行するとウインドウが表示される。

なんでloopという名前がついてるのかというと。。
基本的にプログラムって順次・反復・分岐で出てきているので、ウインドウにボタンが配置されてそれを押したらどうなるみたいなGUIプログラムも、「順次・反復・分岐」の応用でしかない。
つまりウインドウを表示するプログラムって、反復(つまりloop)の中で絶えず入力を監視して、入力(クリック・キー入力など)があったら分岐して処理し、さらに反復(入力待ち)にもどる。専門的にはメッセージループという。
メインループというのはそれを開始するって意味。

メインループについて知りたい場合、以下の本が参考になる。もはやPython関係ないけど。
「なか見"検索」でその辺りも読めるので興味があれば。

猫でもわかるWindowsプログラミング 第4版 猫でもわかるシリーズ

猫でもわかるWindowsプログラミング 第4版 猫でもわかるシリーズ

一見単純に見える何もないウインドウが裏で色々とやってるのが分かって面白い。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。