【Python】tkinterで電卓アプリを作成

電卓アプリの作り方解説ページアイキャッチ

このページでは Python で tkinter を用いて電卓アプリ(計算機アプリ)を作成する方法やサンプルスクリプトの紹介をしていきたいと思います。

電卓アプリか!

まさに GUI アプリ開発入門にうってつけのアプリだね

そうだね…

でも結構考えることや実装することも多くて結構大変なんだ…

簡単に作れる GUI アプリと聞くと、最初に電卓アプリが思い付く人も多いのではないでしょうか。

確かにシンプルなアプリですが、私は実際作ってみて結構苦労しました…。

苦労したのは、ボタンが押されるタイミングによって処理を切り替える必要があることが理由です。

ですので、この辺りに特に重点を置いて「電卓アプリ」の作り方について説明していきたいと思います。

作成する「電卓アプリ」

今回作成する「電卓アプリ」は下のアニメーションのような動きをするアプリになります。

電卓アプリの動作

基本的な動作は iPhone の計算機アプリを参考にしてます(ただし、後述するようにスクリプトを簡単にするためいろいろと制限を設けています)。

もう少しこの「電卓アプリ」の動作の詳細を説明しておきたいと思います。

1つ目の数字の入力

この「電卓アプリ」では、0 から 9 のボタンをクリックすることで数字を入力することができます。

下のアニメーションのように、0 から 9 のボタンをクリックすれば、一番上のラベルに表示されている数字の一番下の桁の値としてそのクリックされたボタンの数字 0 から 9 が追加されて表示されます。

数字ボタンを押した時の動作

スポンサーリンク

演算子の入力

1つ目の数字の入力が終わった後に +-×÷ をクリックすれば、計算に用いる演算子を登録することができます。

まだ2つ目の数字が入力されていないので、この時点では計算を実行しません。

2つ目の数字の入力

さらに「1つ目の数字」同様に2つ目の数字も入力できます。

計算の実行

最後に =+-×÷ をクリックすれば、ここまで入力してきた「1つ目の数字」「演算子」「2つ目の数字」を用いて計算が実行され、その結果が一番上のラベルに表示されます。

例えば下記のように入力された場合、

  • 1つ目の数字:20
  • 演算子:+
  • 2つ目の数字:8

下記の式で計算が行われ、その結果である 100 がラベルに表示されることになります。

20 + 80

+-×÷ がクリックされた場合は、計算結果を新たな「1つ目の数字」、クリックされた演算子を新たな「演算子」として計算を続けることが可能です。

この場合2つ目の数字の入力に戻り、そこで入力された数字を新たな「2つ目の数字」とし、続いて =+-×÷ がクリックされた時に、新たな「1つ目の数字」「演算子」「2つ目の数字」に基づいて計算が実行されます。

なので、下のアニメーションのように数字と演算子のクリックを繰り返して計算を連続で行うことができます。

電卓アプリの動作

スポンサーリンク

クリア

アプリには「AC」ボタンを用意しており、これをクリックすることでアプリが起動直後の状態に戻り、ラベルに表示される数字も 0 に戻るようにしています。

ACボタンクリック時の動作

作成するアプリの制限

あまり最初から作り込むとスクリプトが難しくなってしまうので、今回作成する電卓アプリでは下記に制限を設けようと思います。

  • 小数点以下の値は扱わない
  • 桁数に上限なし
    • 大きい数字になると上の方の桁が表示されない
  • 計算の優先度は考慮しない

思いつくのはこれくらいですが、他にももしかしたら普通の電卓と比べて動きがおかしいところがあるかもしれません…。

「電卓アプリ」の作り方

続いてここまで紹介して来た「電卓アプリ」の作り方について解説していきたいと思います。

スポンサーリンク

クラスの設計

クラスとしては Calculator クラスを用意しています。

この Calculator クラスがウィジェットの作成や配置、イベント受付や実際の計算処理などを行います。

Calculator クラスにはいくつか属性を持たせるようにしており、この属性によって、どのタイミングでボタンが押されたかの状態を管理できるようにしています。

この辺りは後述で解説していきます。

ウィジェットの作成と配置

ここからアプリを作り込んでいきます。

まずはアプリの画面を構成するウィジェットの作成と配置を行います。

今回作成するアプリは下記のようなウィジェットから構成するようにしています。

電卓アプリのウィジェット

黒色のウィジェットはラベル、灰色のウィジェットはボタンになります。

また、各ウィジェットは grid メソッドを用いて配置するようにしています。各ウィジェットの grid での配置位置(columnrow)は下の図のようになります。

電卓アプリのウィジェット配置

ウィジェットの配置や grid メソッドについては下記ページで詳細を解説していますので、詳しく知りたい方はこちらを参考にしていただければと思います。

ウィジェット配置方法解説ページのアイキャッチTkinterの使い方:ウィジェットの配置(pack・grid・place)

ボタンが多いからスクリプト書くの大変だね…

そうだね…

ループなどを上手く利用してスクリプト書く方が楽だと思うよ

楽にスクリプトを書くためにどうすれば良いかも考えながらプログラミングしてみよう!

ボタンが多いので、1つ1つに対して「ウィジェットの作成・配置・イベント設定」を行うと大変です。途中で心が折れます。

なので、ループや関数を利用して効率的にプログラミングすることをオススメします。

このウィジェットの作成と配置は、最後に紹介する「電卓アプリ」のサンプルスクリプトcreate_widgetscreate_button メソッドで行っていますので、ここまでの解説と合わせてスクリプトを読んでいただければと思います。

私なりにスクリプトが楽に書けるようにループや関数(メソッド)を利用したつもりです。

ボタンクリック時のイベント受付

また作成したボタンがクリックされた時に数字をラベルに表示したり、計算を実行したりできるようにボタンクリック時のイベント受付を行います。

イベントって何?と言う方は下記で解説していますのでコチラを参考にしていただければと思います。

イベント処理解説ページのアイキャッチTkinterの使い方:イベント処理を行う

ボタンウィジェットの場合、ボタンを作成する時に実行する tkinter.Buttoncommand 引数でボタンクリック時に実行する関数を設定することも可能ですが、「どのボタンが押されたか?」を判別したかったので bind メソッドを用いてイベントの設定を行うようにしています。

この辺りは下記ページで解説していますので興味のある方は読んでみてください!

【Python/tkinter】どのボタンが押されたかを判別する方法

ボタンは下記の3グループに分類し、それぞれのグループに対して同じイベントハンドラを設定するようにしています。

  • 数字ボタン:09 のボタン
  • 演算子ボタン:=+-×÷ のボタン
  • AC ボタン:AC ボタン

ほぼ同じ処理を行うボタンには同じイベントハンドラを設定してる感じです。

どのボタンも違う処理になるんだから、ボタンごとにイベントハンドラ設定するんだと思ってた!

それでもオッケーだよ!

だけど、その分イベントハンドラ用の関数を用意する必要があるのでちょっとプログラミングが大変かな

似てる処理は1つの関数にまとめる方がプログラミング自体は楽になるよ

今回紹介するスクリプトではこのように分類してイベントハンドラを設定していますが、もっと細かく分けたり、もっと大雑把に分けても良いです。実装しやすいように分けてしまって良いです。

このボタンクリック時のイベントの受付は、最後に紹介する「電卓アプリ」のサンプルスクリプトcreate_button メソッドで行っていますので、ここまでの解説と合わせてスクリプトを読んでいただければと思います。

要は create_widgets メソッドから指定されたイベントハンドラをボタンクリック時のイベントハンドラとして設定しているだけです。

スポンサーリンク

数字ボタンがクリックされた時の処理

ここからは先程設定したイベントハンドラで行う処理の作り方を紹介していきます。

で、この電卓アプリのボタンのイベントハンドラを作る上で大変なのが「どのタイミングでクリックされたかで実行する処理が違う」ところです。

なので、どのタイミングでクリックされた時にどのような処理を行うのかの観点で解説していきたいと思います。

基本的には表示されている数字を追加する

数字ボタンが押された時の基本的な動作は「表示されている数字の右側にそのボタンの数字を追加する」になります。

ただし、下記のような場合は単に数字を追加するだけでダメで、もうちょっと違った処理を行う必要があります。

  • 直前に演算子ボタンがクリックされていた時
  • 表示されている数字が 0 の時

直前に演算子ボタンがクリックされていた時

演算子ボタンがクリックされ、その次に数字ボタンが押された時は、計算に用いる次の新たな数字が入力された時と考えられます。

ですので、現在表示されている数字を、クリックされたボタンの数字に置き換える必要があります。

で、この動きを実現するためには「直前に演算子ボタンがクリックされたかどうか」を判断するための情報が必要になります。

そのため、Calculator クラスには下記の属性を持たせています。

  • op_clicked:直前に演算子ボタンがクリックされているときに True となるフラグ

ですので、数字ボタンがクリックされた時には、この op_clickedTrueFalse のどちらであるかによって処理を切り替えるようにします。

なるほど!

この op_clicked でアプリの状態を管理するわけだね!

そういうこと!

後述するように、他にもいろんな属性を用いてアプリの状態を管理するよ

表示されている数字が 0 の時

表示されている数字が 0 の時に数字ボタンがクリックされたからといって、数字の右側にそのボタンの数字を追加すると余分に 0 が表示されることになります。

ですので、表示されている数字が 0 の時に数字ボタンがクリックされた場合も、表示されている数字をクリックされたボタンの数字で置き換える必要があります。

数字ボタンがクリックされた時の処理は、最後に紹介する「電卓アプリ」のサンプルスクリプトnum_click メソッドで行っていますので、ここまでの解説と合わせてスクリプトを読んでいただければと思います。

演算子ボタンがクリックされた時の処理

続いて演算子ボタンがクリックされたときの処理を考えていきましょう。

今回作成する電卓アプリの動作のアニメーションを再掲しておきます。

電卓アプリの動作

このアニメーションを見てみると分かるように、1つ目の数字入力中に演算子ボタンがクリックされても計算は実行されません。

2つ目の数字の入力に遷移するだけです。

そして、2つ目の数字の入力中に演算子ボタンがクリックされた際に実際の計算が行われます。このときの計算は、その時にクリックされた演算子ではなく、事前にクリックされていた(1つ目の数字中にクリックされた)演算子で計算を行っています。

この辺りがポイントですね!

計算に用いる演算子がそのときにクリックされたボタンのものじゃなくて、

事前にクリックされたボタンのものになるのがややこしいね…

そこがこの電卓アプリの難しいところだね…

事前にクリックされたボタンも覚えておく必要があるし

タイミングとして考えると、下記の3つのパターンで実行する処理が異なることになります。

  • 1つ目の数字入力中にクリックされた時
  • 2つ目の数字入力中にクリックされた時
  • 演算子ボタンクリック直後に再度クリックされた時

各パターンにおいてどのような処理を行うのかについて説明していきます。

1つ目の数字入力中にクリックされた時

1つ目の数字入力中に演算子ボタンがクリックされた場合は、計算は行いません。

単に入力中の数字(ラベルに表示されている数字)を「1つ目の数字」、クリックされた演算子ボタンに表示されている演算子を「次の計算時に用いる演算子」として覚えておくだけです。

最後に紹介するサンプルスクリプトでは、これらの情報を覚えておくために Calculator クラスに下記の属性を持たせるようにしています。

  • item1:1つ目の数字
  • operation:次の計算時に用いる演算子

次の「2つ目の数字入力中にクリックされた時」では、これらの情報を用いて実際の計算を行います。

また、数字ボタンがクリックされた時の処理でも説明したように、直前に演算子ボタンがクリックされたかどうかでアプリの動作を制御できるように「直前に演算子ボタンがクリックされたかどうか」を判断するための情報を記憶しておく必要があります。

これを記憶するためのフラグとして Calculator クラスの属性に op_clicked を用意していますので、これを True にセットします。

2つ目の数字入力中にクリックされた時

2つ目の数字入力中に演算子ボタンがクリックされた際には、実際に計算を行います。

ただし、ここでクリックされた演算子ボタンの演算子ではなく、事前にクリックされて登録されている演算子を用いて計算を行うところに注意です。

2つ目の数字入力中ということは、1つ目の数字入力中にクリックされた時の処理により、「1つ目の数字」と「演算子」が登録ずみの状態になっています(item1operation にデータが格納されている)。

なので、この「1つ目の数字」と「演算子」と「入力中の数字(2つ目の数字)」を用いて計算を行います(ただし「演算子」が "=" の場合、つまり operation"=" の場合は計算できないので計算は行いません)。

さらに、計算結果をラベルに表示し、この計算結果を新たな「1つ目の数字」、さらにクリックされた演算子ボタンの演算子を新たな「演算子」として登録します(item1operation にデータを格納)。

また、ここでも当然演算子ボタンがクリックされていますので、直前に演算子ボタンがクリックされたことが分かるように op_clickedTrue に設定します。

演算子ボタンクリック直後に再度クリックされた時

演算子ボタンクリック直後に再度クリックされた場合も計算は行いません。

クリックされた演算子ボタンの演算子を、新たな「演算子」として登録するだけです。

要は operation にクリックされた演算子ボタンの演算子を登録を行います。

これにより、次に計算を実行するときは、この新たに登録した「演算子」を用いて計算が行われるようになります。

直前にも演算子ボタンがクリックされているので、わざわざ op_clickedTrue に設定しなおす必要もないのですが、今回紹介するスクリプトでは他の処理と共通化するために op_clickedTrue に設定する処理も行うようにしています。

演算子ボタンがクリックされた時の処理は、最後に紹介する「電卓アプリ」のサンプルスクリプトoperation_click メソッドで行っていますので、ここまでの解説と合わせてスクリプトを読んでいただければと思います。

AC ボタンがクリックされた時の処理

続いて最後のボタンである「AC ボタン」がクリックされた時の処理について説明します。

AC ボタンがクリックされた時には、アプリを初期状態(起動直後の状態)に戻す処理を行います。

この処理としてまず思いつくのはラベルの表示を 0 に戻すことだと思います。

もちろんこれも必要ですが、他にもアプリの状態を管理している下記の属性を初期値に戻す処理も行う必要があります。

  • op_clickedFalse に設定
  • item1None に設定
  • operationNone に設定

要は、上記により「直前に演算子ボタンがクリックされていない」「1つ目の数字も演算子も登録されていない」状態に戻すことができます。

これにより AC ボタンがクリックされた後は、アプリに起動直後と同じ動作をさせることができます。

AC ボタンがクリックされた時の処理は、最後に紹介する「電卓アプリ」のサンプルスクリプトAC_click メソッドで行っていますので、ここまでの解説と合わせてスクリプトを読んでいただければと思います。

スポンサーリンク

「電卓アプリ」のサンプルスクリプト

最後にここまで解説してきた内容に基づいて作成した「電卓アプリ」のサンプルスクリプトの全体を紹介しておきます。

スクリプト

今回紹介した電卓アプリのスクリプトは下記のようになります。

電卓アプリ
import tkinter

# フォント設定
FONT = ("", 40)

class Calculator():
	def __init__(self, master):
		'''コンストラクタ'''

		# 親ウィジェット
		self.master = master
		# 第1項
		self.item1 = None
		# 次に演算子ボタンが押された時に行う演算
		self.operation = None
		# 直前に演算子ボタンが押されたかどうかのフラグ
		self.op_clicked = False
		# ラベルウィジェット
		self.label = None

		# ウィジェット作成・配置・イベント設定
		self.create_widgets()
	
	def create_widgets(self):
		'''ウィジェットの作成・配置・イベント設定'''

		numbers = ("7","8","9","4","5","6","1","2","3","0")
		operators = ("÷", "×", "-", "+", "=")

		# 計算結果表示用のラベル
		self.label = tkinter.Label(
			self.master,
			text="0", # 表示文字
			font=FONT, # フォント設定
			anchor=tkinter.E, # テキスト右寄せ
			bg="black", # 背景の色
			fg="white" # 文字の色
		)
		self.label.grid(
			column=0,
			row=0,
			columnspan=4,
			sticky=tkinter.NSEW # 左右上下に引き伸ばす
		)

		# ACボタン作成
		self.create_button(text="AC", x=0, y=1, size=3, func=self.AC_click)

		# 数字ボタン作成
		i = 0
		for number in numbers:
			if number == "0":
				# ボタン 0 だけボタン3つ分のサイズで作成
				self.create_button(text=number, x=i%3,y=i//3+2,size=3,func=self.num_click)
			else:
				self.create_button(text=number, x=i%3,y=i//3+2,size=1,func=self.num_click)

			i += 1

		# 演算子ボタン作成
		i = 0
		for operator in operators:
			self.create_button(text=operator, x=3,y=i+1,size=1,func=self.operation_click)
			i += 1

	def create_button(self, text, x, y, size, func):
		'''ボタンウィジェットの作成・配置・イベント設定'''

		button = tkinter.Button(
			self.master,
			text=text, # 表示する文字列
			font=FONT, # FONTサイズ設定
			anchor=tkinter.CENTER, # 中央よせ
		)
		button.grid(
			column=x, # 表示位置(横)
			row=y, # 表示位置(縦)
			columnspan=size, # 表示サイズ(横)
			sticky = tkinter.NSEW # 左右上下に引き伸ばし
		)

		# イベント設定
		button.bind("<ButtonPress>", func)

	def AC_click(self, event):
		'''ACボタンがクリックされたときの処理'''
	
		# ラベルを 0 にセット
		self.label.config(text="0")

		# 初期値に設定
		self.item1 = None
		self.op_clicked = False
		self.operation = None

	def num_click(self, event):
		'''数字ボタンがクリックされたときの処理'''

		# ボタンの数字取得
		num_text = event.widget.cget("text")

		# ラベル表示中の数字取得
		label_text = self.label.cget("text")


		if self.op_clicked or label_text == "0":
			# 演算子ボタンクリック直後 or 表示が 0 の場合は表示中の数字置き換え
			self.label.config(
				text=num_text
			)
		else:
			# それ以外は最後の桁に数字を追加
			self.label.config(
				text=label_text + num_text
			)

		# 直前に押されたのが演算子ボタンでないことを設定
		self.op_clicked = False

	def operation_click(self, event):
		'''演算子ボタンがクリックされたときの処理'''

		# 押されたボタンの演算子を取得
		next_operation = event.widget.cget("text")

		# 表示中の数字を取得
		label_text = self.label.cget("text")

		if self.item1 is None:
			# 1つ目の数字入力中にクリックされた場合の処理

			# 表示中の数字を1つ目の数字として登録
			self.item1 = int(label_text)

		elif self.op_clicked:
			# 演算子ボタン入力直後にクリックされた場合の処理

			# ここでは何もしない(最後の演算子の登録のみ行う)
			pass

		else:
			# 2つ目の数字入力中にクリックされた場合の処理

			if self.operation != "=":
				# 表示中の数字を2つ目の数字とする
				item2 = int(label_text)

				# 計算実行
				result_num = self.calc(self.item1, item2, self.operation)

				# 計算結果を表示
				self.label.config(
					text=str(result_num)
				)

				# 計算結果を1つ目の数字として更新
				self.item1 = result_num


		# 直前に演算子が押されたことを設定
		self.op_clicked = True

		# 次に実行する演算子を設定
		self.operation = next_operation

	def calc(self, item1, item2, operation):
		'''計算を実行する'''

		# 計算を実行
		if operation == "+":
			result_num = item1 + item2
		elif operation == "-":
			result_num = item1 - item2
		elif operation == "÷":
			if item2 == 0:
				result_num = 0
			else:
				result_num = item1 // item2
		elif operation == "×":
			result_num = item1 * item2

		return result_num

app = tkinter.Tk()
app.title("電卓")
calc = Calculator(app)
app.mainloop()

スクリプトの設定

スクリプト先頭の下記でフォントを設定できるようにしています。

フォントの設定
# フォント設定
FONT = ("", 40)

フォントに関しては下記で解説していますので、こちらを参考にして変更にしてみてください。

フォント指定解説ページのアイキャッチTkinterの使い方:フォントの指定方法

スポンサーリンク

スクリプトの解説

最後にスクリプトの解説を簡単に行っていきます。

といっても、作り方に関しては「電卓アプリ」の作り方で解説していますので、主にスクリプト実装上のポイントについての解説になります。

Calculator クラスのメソッドごとに説明していきます。

__init__

ポイントは、起動直後の状態であるように、しっかり状態やタイミングを管理する属性の初期化を行うところです。

この初期化は下記で行っています。

状態管理属性の初期化
# 第1項
self.item1 = None
# 次に演算子ボタンが押された時に行う演算
self.operation = None
# 直前に演算子ボタンが押されたかどうかのフラグ
self.op_clicked = False

create_widgetscreate_button

作り方に関してはウィジェットの作成と配置ボタンクリック時のイベント受付で説明しています。

ポイントは、create_button メソッドを用意し、ループの中でこのメソッドを実行することで楽にスクリプトが書けるようにしているところです。

create_button メソッドには、ボタンごとに下記を設定して引数を指定するようにしています。

  • text:表示するテキスト
  • x, y:grid メソッドで配置する位置
  • size:横方向のサイズ(何セル分か)
  • func:ボタンクリック時に実行するイベントハンドラ

実際にボタンウィジェットの作成・配置・ボタンウィジェットへのイベントの bind を行っているのは create_button メソッドになります。

AC_click

作り方に関しては AC ボタンがクリックされた時の処理で説明しています。

ここは特にポイントがないので説明は省略します。

num_click

作り方に関しては数字ボタンがクリックされた時の処理で説明しています。

数字ボタンがクリックされた時の処理で説明した通り、状態によって(ラベルの数字や op_clicked の値によって)、処理を if 文で切り替えるところがポイントです。

また、クリックされたボタンのテキストやラベル表示中のテキストの取得は下記で行っています。

ラベルやウィジェットの情報取得
# ボタンの数字取得
num_text = event.widget.cget("text")

# ラベル表示中の数字取得
label_text = self.label.cget("text")

特に、どのウィジェットからイベントが発生したかを取得するのには event.widget が便利です。

この辺りは下記で説明したテクニックを利用しています。

【Python/tkinter】どのボタンが押されたかを判別する方法

operation_click

作り方に関しては演算子ボタンがクリックされた時の処理で説明しています。

こちらも演算子ボタンがクリックされた時の処理で説明したように、主にクリックされた3つのタイミングで処理を切り替えるように作っているところがポイントです。

まとめ

このページでは Python での tkinter を用いた電卓アプリの作り方の解説を行いました。

解説を読んだり、実際に作ったりすると「思ったよりも難しい」と感じる方も多いのではないかと思います。

解説でも触れたように、アプリの状態を管理しながら動作させる必要があるところが難しいかなぁと思います。

この状態については上手く属性や変数を用いて管理できるようにしましょう!

また今回紹介した電卓アプリのように、ウィジェットが多い場合はループや関数を用いていかにして効率的にスクリプトを書くかを考えることもプログラミングの楽しいところの1つだと思います!

是非皆さんもどうすれば楽にスクリプトが書けるかも考えながらプログラミングしてみてください!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です