3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PySimpleGUIの使い方・後編

Posted at

 PySimpleGUIの使い方・前編の続きです。PySimpleGUIを扱っているということ以外とくに前後のつながりはないので、PySimpleGUIの基本的な扱いさえわかれば、後編からでも読み進められると思います。
 後編はPySimpleGUIの低レベルのフレームワークtkinterに言及する、PySimpleGUIの機能を使って、より細かなイベント操作を行うことが目的です。

1.ロードマップ

 PySimpleGUIのエレメントクラスはbind()関数を持っており、tkinterで規定されたイベントをエレメントに関連付けることができます。bind()されたこのエレメント上でクリックなどのtkinterのイベントのトリガーになるアクションが検知されると、PySimpleGUIのWindowインスタンスのread()関数が認知できるイベントが発生するようになり、さらにtkinterの管理するイベントに付随する情報に、エレメントのインスタンスを通してアクセスできるようになります。
 ここで次のようなことが問題になります。

  • tkinterで規定されたイベントとはどんなものか
  • bind()関数をどう使ったらよいのか
  • PySimpleGUI上でどんなイベントが発生するのか
  • イベントに付随する情報とはどんな形式なのか

 この記事ではこれらの問題を一つずつ潰していき、最後に低レベルのイベント操作を利用したGUIアプリの例を示したいと思います。

2.tkinterのイベント

 tkinterのイベントはtkinterのソースを見れば詳細がわかります。ソースは以下のリンクから確認できます。

 「Event」をキーワードに検索すれば次のような記述が見つかるはずです。

sample01.py
class EventType:
    KeyPress = '2'
    Key = KeyPress
    KeyRelease = '3'
    ButtonPress = '4'
    Button = ButtonPress
    ButtonRelease = '5'
    Motion = '6'
 #~中略~
    Colormap = '32'
    ClientMessage = '33'    # undocumented
    Mapping = '34'          # undocumented
    VirtualEvent = '35'     # undocumented
    Activate = '36'
    Deactivate = '37'
    MouseWheel = '38'

 全部で38種あるようですが、「undocumented」とコメントされているものは外からは利用できないものでしょう。
 しかしこの記述だけではイベントの細かい内容が判然としません。これらのイベントの詳細はつづくEventクラスの定義のドキュメンテーションに記述されています。例えば「KeyPress」と「KeyRelease」はキーボードの、「ButtonPress」と「ButtonRelease」はマウスのクリックに対応していることがわかります。

3.bind()関数の使い方

 bind()関数を利用するためには、まずWindowインスタンスのコンストラクタで次のように指定する必要があります。

sample02.py
window = sg.window('タイトルバー', layout, finalize=True)

 finalize=Trueを指定することで、PySimpleGUIのWindowインスタンスの生成と同時に、それを構成する低レベルのtkinterのオブジェクトも同時に生成されます。低レベルのtkinterオブジェクトへの言及を含むbind()関数を利用するためにはfinalize=Trueの指定が必須になります。
 なおfinalize=Trueを指定しない場合、tkinterのオブジェクトはWindowインスタンスのread()関数が呼び出されたタイミングではじめて生成されます。
 bind()関数のPySimpleGUIのコールリファレンスの記述は、どのエレメントでも次のようなものです。

sample03.py
bind(bind_string,
    key_modifier,
    propagate = True)

 エレメントのインスタンスにtkinterのイベントを関連付けるには、次の二つの引数を指定するだけです。

  • bind_string
    関連付けたいtkinterのイベントを文字列で指定します。2.の一覧の名称を半角の「<>:山かっこ」でくくって渡す必要があります。たとえば<KeyPress>(マークダウンの仕様のせいか半角の山かっこがうまく使えないので全角になっています)というようにです。
  • key_modifier
    このイベントにユーザーが自由につけることのできる識別子で、文字列で指定します。後述しますが、この内容がそのままイベントを示す文字列になるわけではありません。

 だいたいこんな感じです。

sample04.py
element_instance.bind('<ButtonPress>', 'b_press')

4.PySimpleGUI上でどんなイベントが発生するのか

 tkinterのイベントに関連付けられたエレメント上で、そのイベントのトリガーになるような動作が行われると、PySimpleGUIのWindowインスタンスのread()関数はそのイベントを示す文字列を返すようになります。
 このイベント文字列は、エレメントのkey=に指定した文字列と、bind()関数のkey_modifierに渡した文字列を合わせたものになります。
 例えば、key=TEXTと指定したTextエレメントのbind()関数に、b_pressという文字列をkey_modifierに渡すと、Textエレメント上でクリック操作が行われるたびに、TEXTb_pressというイベントが発生します。ただの足し算です。

5.イベントに付随する情報とはどんな形式なのか

 エレメントにbind()したtkinterイベントの詳細は、エレメントのuser_bind_event属性にtkinterのEventクラスのインスタンスとして保持されています。クリックした位置座標などの、イベントに付随する情報はこのEventクラスの属性として記録されています。
 tkinterのEventクラスの持つ属性は、Eventクラスの定義に記されたドキュメントに詳細が記述されています。

sample05.py
"""Container for the properties of an event.
#~中略~
        serial - serial number of event
    num - mouse button pressed (ButtonPress, ButtonRelease)
    focus - whether the window has the focus (Enter, Leave)
    height - height of the exposed window (Configure, Expose)
    width - width of the exposed window (Configure, Expose)
    keycode - keycode of the pressed key (KeyPress, KeyRelease)
    state - state of the event as a number (ButtonPress, ButtonRelease,
                            Enter, KeyPress, KeyRelease,
                            Leave, Motion)
    state - state as a string (Visibility)
    time - when the event occurred
    x - x-position of the mouse
    y - y-position of the mouse
    x_root - x-position of the mouse on the screen
             (ButtonPress, ButtonRelease, KeyPress, KeyRelease, Motion)
    y_root - y-position of the mouse on the screen
             (ButtonPress, ButtonRelease, KeyPress, KeyRelease, Motion)
    char - pressed character (KeyPress, KeyRelease)
    send_event - see X/Windows documentation
    keysym - keysym of the event as a string (KeyPress, KeyRelease)
    keysym_num - keysym of the event as a number (KeyPress, KeyRelease)
    type - type of the event as a number
    widget - widget in which the event occurred
    delta - delta of wheel movement (MouseWheel)
    """

 これらの属性はすべてのイベントで有効な値を持つわけではありません。たとえばマウスのクリックが検知され「ButtonPress」イベントが発生したとき、キーボードのどのキーが押されたかを記録するkeycode属性は、値の持ちようがありません。
 このような場合には、Eventオブジェクトは値のつけようのない属性に、文字列で??という値を与えます。

6.Canvasエレメントを利用したGUIアプリ

 これまでの内容とPySimpleGUIのCanvasエレメント利用して、マウス操作で円を描くGUIアプリをつくってみました。
 PySimpleGUIのCanvasエレメントは、名前の通りエレメント上に図形などの描画を行えるエレメントです。しかし、Canvasエレメントクラス自体には描画を制御するすべはなく、低レベルのtkinterのCanvasクラスをtk_canvasから操作する必要があります。
 tkinterのCanvasクラスについて、以下のことを押さえておけば、コードの意味は大体わかると思います。

  • 図の描画はCanvasクラスのcreate_~系の関数で行う
  • create_~系の関数が呼び出されるたびに描画の内容が記録され、順番に整数値でIDが与えられる
  • delete()関数にIDを指定すると、描画の内容が消去される
  • delete()してもIDの続き番号はリセットされない。百回分の描画をすべてdelete()した後に行った描画に割り当てられるIDは101になる
sample06
import PySimpleGUI as sg

switch = False

canvas = sg.Canvas(size=(500, 500), key='CANVAS', background_color='white')

window = sg.Window("Canvas Test", [[canvas]], finalize=True)

canvas.bind('<Motion>', 'motion')
canvas.bind('<ButtonPress>', 'click_on')
canvas.bind('<ButtonRelease>', 'click_off')

while True:
    event, values = window.read()

    if event == sg.WIN_CLOSED:
        break
    if event == 'CANVASclick_on':#マウスの左クリックが押されたときの処理
        switch = True
        v = canvas.user_bind_event
        x1 = v.x
        y1 = v.y
        item_id = canvas.tk_canvas.create_bitmap(x1, y1)
    if event == 'CANVASmotion':#マウスの移動があった際の処理
        if switch:
            v = canvas.user_bind_event
            canvas.tk_canvas.delete(item_id)
            x2 = v.x
            y2 = v.y
            item_id = canvas.tk_canvas.create_oval(x1, y1, x2, y2)
    if event == 'CANVASclick_off':#押された状態のマウスの左クリックが離されたときの処理
        if switch:
            canvas.tk_canvas.delete(item_id)
            v = canvas.user_bind_event
            x2 = v.x
            y2 = v.y
            item_id = canvas.tk_canvas.create_oval(x1, y1, x2, y2)
            switch = False
window.close()

canvas.png

7.おわりに

 前編と合わせて初投稿でしたが、記事を書いてる途中でめんどくさくなってしまい、前編の序盤とこの後編でずいぶんノリの違う記事になってしまったような気がします。Qiitaに何度も助けられた身としてはなんとか貢献できたらとの思いでしたが、うまくいかないものです。わかりやすい記事を載せてくれる執筆者の方々には本当に頭が下がります。

8.参考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?