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

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

Python - Tkinter
2018年11月22日21:14に更新(約19日前)
2018年10月6日22:00に作成(約66日前)

旧ブログからの移行記事です。

概要

Tkinterで、簡易電卓を作るシリーズの1つです。 ボタンを押すと、何らかの動作をするようにしていきます。

イベントチュートリアル

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

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('おされた')

''というのは、左クリックのことを指します。 また、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))

calcメソッドは以下のようになります。

    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サンプル

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

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

ちゃんと逆順になります

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

記事にコメントする