naritoブログ

このブログはDjangoとBootstrap4で作成されました
ソースコード

tkinterで、簡易電卓を作るシリーズ②ボタンにイベントを設定する

プログラミング関連 Python Tkinter 約59日前
2017年8月19日20:57
前回、ある程度の見た目を作りました。
今回はボタンを押したら反応するようにしていきます。

まず、非常に単純な例です。
これを起動するとボタン一つのウィンドウが立ち上がり、ボタンを押すとコンソールに「おされた」と表示されます。
from tkinter import *
from tkinter import ttk


class CalcApp(ttk.Frame):
    """電卓アプリ."""
 
    def __init__(self, master=None):
        super().__init__(master)
        self.create_widgets()
 
    def create_widgets(self):
        button = ttk.Button(self, text='押して', command=self.push)
        button.grid(column=0, row=0, sticky=(N, S, E, W))
        self.grid(column=0, row=0, sticky=(N, S, E, W))
    
    def push(self):
        print('おされた')
        

def main():
    root = Tk()
    root.title('簡単電卓')
    CalcApp(root)
    root.mainloop()


if __name__ == '__main__':
    main()



ボタンを押すと...


このように出力されます


command=self.push のようにして、押された際に呼び出す関数を渡しています。
ここには、関数オブジェクト自体を渡すことを覚えておきましょう。
    button = ttk.Button(self, text='押して', command=self.push)
    ...
    ...
    def push(self):
        print('おされた')



今回はクラスのメソッドとしてpushを定義しましたが、別に普通の関数でも大丈夫です。関数オブジェクトを渡せば動きます。
いくつかのウィジェットで共通して使うイベントなんかがあれば、こうして定義するのもアリです。
def push():
    print('おされた')


class CalcApp(ttk.Frame):
    """電卓アプリ."""
 
    def __init__(self, master=None):
        super().__init__(master)
        self.create_widgets()
 
    def create_widgets(self):
        button = ttk.Button(self, text='押して', command=push)
        button.grid(column=0, row=0, sticky=(N, S, E, W))
        self.grid(column=0, row=0, sticky=(N, S, E, W))



ボタンにはcommandというオプションがありますが、これを使わない汎用的な方法もあります。それはbindです。
以下のコードは、先程と同じように動作します。
class CalcApp(ttk.Frame):
    """電卓アプリ."""
 
    def __init__(self, master=None):
        super().__init__(master)
        self.create_widgets()
 
    def create_widgets(self):
        button = ttk.Button(self, text='押して')
        button.bind('<Button-1>', self.push)
        button.grid(column=0, row=0, sticky=(N, S, E, W))
        self.grid(column=0, row=0, sticky=(N, S, E, W))
        
    def push(self, event):
        print('おされた')



'<Button-1>'というのは、左クリックのことを指します。
また、pushメソッドにeventという引数が増えています。bindを使うとeventオブジェクトも渡されるのです。
    button.bind('<Button-1>', self.push)
    ...
    ...
    def push(self, event):
        print('おされた')



渡されたeventオブジェクトからは、色々取得できます。
例えば、押されたウィジェットのテキスト部分を取得する例は以下です。
print(event.widget['text'])



bindは汎用的で、ボタンに限りません。
試しに、ButtonをLabelに変えてみてもちゃんと動作します。
        label = ttk.Label(self, text='押して')
        label.bind('<Button-1>', self.push)
        label.grid(column=0, row=0, sticky=(N, S, E, W))



あるウィジェットに、複数のイベントを紐付けることだって可能です。
以下はボタンを離した際にも反応するようにしています。
label.bind('<ButtonRelease-1>', self.release)が、それに当たります。
class CalcApp(ttk.Frame):
    """電卓アプリ."""
 
    def __init__(self, master=None):
        super().__init__(master)
        self.create_widgets()
 
    def create_widgets(self):
        label = ttk.Label(self, text='押して')
        label.bind('<Button-1>', self.push)
        label.bind('<ButtonRelease-1>', self.release)
        label.grid(column=0, row=0, sticky=(N, S, E, W))
        self.grid(column=0, row=0, sticky=(N, S, E, W))
        
    def push(self, event):
        print('おされた')

    def release(self, event):
        print('はなした')



では、前回作ったアプリのボタンにイベントを設定しましょう。
以下のようになります。
from tkinter import *
from tkinter import ttk
 
 
# 2次元配列のとおりに、gridでレイアウトを作成する
LAYOUT = [
    ['7', '8', '9', '/'],
    ['4', '5', '6', '*'],
    ['1', '2', '3', '-'],
    ['0', 'C', '=', '+'],
]
 
 
class CalcApp(ttk.Frame):
    """電卓アプリ."""
 
    def __init__(self, master=None):
        super().__init__(master)
        self.create_widgets()
 
    def create_widgets(self):
        """ウィジェットの作成."""
        # 計算結果の表示ラベル
        dispay_label = ttk.Label(self, text='0')
        dispay_label.grid(column=0, row=0, columnspan=4, sticky=(N, S, E, W))
 
        # レイアウトの作成
        for y, row in enumerate(LAYOUT, 1):
            for x, char in enumerate(row):
                button = ttk.Button(self, text=char)
                button.grid(column=x, row=y, sticky=(N, S, E, W))
                button.bind('<Button-1>', self.calc)
        self.grid(column=0, row=0, sticky=(N, S, E, W))
 
        # 横の引き伸ばし設定
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=1)
        self.columnconfigure(2, weight=1)
        self.columnconfigure(3, weight=1)
 
        # 縦の引き伸ばし設定。0番目の結果表示欄だけ、元の大きさのまま
        self.rowconfigure(0, weight=0)
        self.rowconfigure(1, weight=1)
        self.rowconfigure(2, weight=1)
        self.rowconfigure(3, weight=1)
        self.rowconfigure(4, weight=1)
 
        # ウィンドウ自体の引き伸ばし設定
        self.master.columnconfigure(0, weight=1)
        self.master.rowconfigure(0, weight=1)
 
    def calc(self, event):
        print(event.widget['text'])  # 押されたウィジェットのテキスト部分を表示


def main():
    root = Tk()
    root.title('簡単電卓')
    CalcApp(root)
    root.mainloop()
 
 
if __name__ == '__main__':
    main()
 



追加したのはforループ中でのbutton.bind()と、calcメソッドです。
calcメソッドは、単純に押されたキーを表示するだけにしています。
押されたボタンやボタンの種類ごとに違うメソッドを呼んでもいいのですが、今回はシンプルにcalcメソッドだけにします。
                button.bind('<Button-1>', self.calc)
...
...
...
    def calc(self, event):
        print(event.widget['text'])



calcメソッドをもう少し改良しましょう。
ボタンが押されたら、結果表示欄のラベルに押されたボタンを表示するようにしてみます。
その下準備として、dispay_labelにアクセスするための手段が必要です。


方法は大まかに2つです。
self.display_label...とし、インスタンスの属性として利用できるようにします。
    def create_widgets(self):
        """ウィジェットの作成."""
        self.dispay_label = ttk.Label(self, text='0')
        self.dispay_label.grid(column=0, row=0, columnspan=4, sticky=(N, S, E, W))



そして、calcメソッドで以下のように表示テキストを変更します。
    def calc(self, event):
        char = event.widget['text']  # 押されたウィジェットのテキスト部分を取得
        self.dispay_label['text'] = char  # display_labelの表示テキストに、上のcharを入れる



そして次の方法です。基本的には、こちらを使う方が良いです。
    def create_widgets(self):
        """ウィジェットの作成."""
        self.display_var = StringVar()
        self.display_var.set('0')  # 初期値を0にする

        dispay_label = ttk.Label(self, textvariable=self.display_var)
        dispay_label.grid(column=0, row=0, columnspan=4, sticky=(N, S, E, W))


    def calc(self, event):
        char = event.widget['text']
        self.display_var.set(char)



ボタンを押すと、ちゃんと結果表示欄にボタンのテキストが表示されるようになりました。



StringVarは文字列を保持する変数で、ウィジェットに紐付けることができます。
self.var=StringVar()のようにし、それをラベル等のウィジェットにtextvariable=self.var とするだけです。
その後は、self.var.get()で値の取得が、self.var.set()で値の設定ができます。

self.varに値をset()すると、それが画面上のウィジェットにもすぐに反映されます。
また、例えばEntry等の入力ができるウィジェットであれば、画面上で入力をすると、その値がself.varに直ぐ格納されます。

以下はStringVarとEntryウィジェットを使ったサンプルです。(これはクラスやFrameを使わない、シンプルなプログラムの例でもあります)
画面で入力欄に文字を入力し、エンターを押すと、入力欄の文字が逆順になります。
from tkinter import *
from tkinter import ttk


def reverse_string(event):
    input_value = var.get()  # 画面で入力した文字列を取得
    var.set(input_value[::-1])  # 取得した文字列を、逆順にしてsetする


root = Tk()
var = StringVar()
entry = ttk.Entry(root, textvariable=var)
entry.grid(column=0, row=0)
entry.bind('<Return>', reverse_string)
root.mainloop()




文字列を入力しエンターを押すと...


ちゃんと逆順になります