今回は電卓アプリを制作するための機能実装編です。
UI実装編で作成したボタンに応じた処理を作成して、
電卓アプリを完成させていきましょう!
あと少しで完成です!楽しんでいきましょう~
事前準備
今回は前編で作成したUI実装の続きになります。
”まだUIを作ってないよ~”というかたは、先に電卓のUIを作っておきましょう!
作成する仕様は以下の記事になります。
特に計算処理の説明である『機能の仕様』と
UIと機能のつながりを示す『UIの挙動』の章がメインになりますので、チェックしておきましょう。
機能実装の方針
機能実装は以下の6ステップで進めていきます。
- 計算に使うパラメータの定義
数値や演算子など計算に使用するパラメータの定義を最初に作成。 - 数値入力・計算結果の表示
表示部分を先に作成することで、以降に作成する入力・出力をUI上で
確認できる状態にする。 - 数値の入力
数値ボタンを押したときの挙動 - 演算子の入力
演算子ボタンを押したときの挙動 - 計算結果の出力
=ボタンを押したときの挙動 - 入力・出力のクリア
ACボタンを押したときの挙動
仕様で使った図を参考にすると、紫色の”機能の仕様”の部分がメインとなります。
『数値入力・計算結果の表示』の更新も機能実装とUI実装の両方をそろえる必要があるので、
今回対応していきます。
計算に使うパラメータの定義保存
入力した数値や演算子を保存する変数を作っていきます。
変数の定義
仕様の通り、計算は数値1
また、数字 入力することで10桁以上の数値を表現することができます。
以下の4点の変数を用意します。
- 1つ前に入力したコマンド
数値の後に演算子を入力したか等、
入力の種類が切り替わったタイミングを判定できるように保存します。
- 表示中の数値
『数値入力と計算結果の表示』のラベルに表示中の数値を保存します。
数値を連続で入力するときは、この数値をもとに更新します。
- 計算モード
演算子が入力されたときに、計算の種類を保存しておきます。ex. 足し算、引き算など
この後に数値2の入力に進むので、計算が終わるまでは保持できるようにします。
- 1つ前の数値の保存
数値1 演算子 数値2の数値1を保存する場所を作ります。
まず、上記4点をまとめて管理できるように辞書型のグローバル変数を定義します。
#g_result_label = Noneの下に.
g_calc_param = {}
次に辞書型のキーとなるパラメータの種類を定義していきます。
# パラメータの種類.
class CalcParam(Enum):
PRE_COMMAND = 0 # 1つ前に入力したコマンド.
DISPLAY_VALUE = 1 # 表示中の数値.
CALC_MODE = 2 # 計算モード.
OLD_INPUT_VALUE = 3 # 1個前の入力数値.
g_calc_param[CalcParam.PRE_COMMAND]のように使用します。
計算モードの定義
+、ー、×、÷の入力に応じて、計算モードを定義します。
これはCalcParam.CALC_MODEで管理する値です。
# 計算モード.
class CalcMode(Enum):
NONE = 0
PLUS = 1 # +.
MINUS = 2 # ー.
MUL = 3 # ×.
DIV = 4 # ÷.
# 計算モードの取得.
def get_calc_mode(btn_type):
if btn_type == BtnType.BTN_PLUS:
return CalcMode.PLUS
elif btn_type == BtnType.BTN_MINUS:
return CalcMode.MINUS
elif btn_type == BtnType.BTN_MUL:
return CalcMode.MUL
elif btn_type == BtnType.BTN_DIV:
return CalcMode.DIV
else:
return CalcMode.NONE
BtnTypeの演算子を保存しておく事でも計算モードを表現することは可能です。
ただBtnTypeの用途を多くすると、わかりづらくなるので定義を分けてます。
変数の初期化
g_calc_paramを初期化します。
mainの一番最初の処理として追加します。
以下は初期化する関数です。
ついでにもう1個のグローバル変数であるg_result_labelも初期化しておきましょう。
# パラメータの初期化.
def init_param():
global g_result_label
global g_calc_param
#初期化.
g_result_label = None
g_calc_param = dict()
g_calc_param[CalcParam.PRE_COMMAND] = BtnType.BTN_NOT
g_calc_param[CalcParam.CALC_MODE] = CalcMode.NONE
g_calc_param[CalcParam.DISPLAY_VALUE] = '0'
g_calc_param[CalcParam.OLD_INPUT_VALUE] = '0'
mainに追加します。
def main():
# 初期化.
init_param()
# UI作成.
make_ui()
変数の更新
ボタンを押したときの処理に、1つ前に入力したコマンドを更新する処理を追加します。
他の変数の更新は、各入力処理の実装時に対応していきます。
# ボタンを押したときの処理を返す関数.
def push_btn_command(btn_type):
def callback():
# 1つ前に入力したコマンドを保存.
global g_calc_param
g_calc_param[CalcParam.PRE_COMMAND] = btn_type
return callback
数値入力・計算結果の表示
「入力値・計算結果の表示」ラベルを更新する処理を作成します。
数値の表示が乱れないように、表示前の数値を整形する関数も用意します。
数値の整形
数値を整形する関数を作成します。
文字列として表示する都合で数値の管理は基本文字型です。
しかし、0の後に数値を入力すると066のように先頭に0が残ったままになってしまうため、
数値型に戻して不要なものを取り除く処理を加えます。
# 文字型を数値型に変換.
def str_to_value(str_param):
# 0を連続で入力した場合のみ、0のまま.
# 1.0のような不要な小数の削除.
if '.' in str_param:
disp_val = float(str_param)
else:
disp_val = int(str_param)
return disp_val
# 文字型の数値を整形.
def make_display_text(str_value):
disp_val = str_to_value(str_value)
return str(disp_val)
『数値入力・計算結果』ラベルの更新
「入力値・計算結果の表示」ラベルを更新する処理です。
UI実装時に追加したg_result_labelの”text”の値を変更することで、更新することができます。
# 表示の更新.
def update_display(value):
global g_result_label
global g_calc_param
if g_result_label is None:
return
str_value = make_display_text(value)
# 「入力値・計算結果の表示」ラベルの更新.
g_result_label["text"] = str_value
ボタンを押した後のコマンドで呼び出しましょう。
# ボタンを押したときの処理を返す関数.
def push_btn_command(btn_type):
def callback():
display_value = '0'
# 1つ前に入力したコマンドを保存.
global g_calc_param
g_calc_param[CalcParam.PRE_COMMAND] = btn_type
#「入力値・計算結果の表示」ラベルの更新.
update_display(display_value)
return callback
数値の入力
『数値の入力』の仕様は以下の4点でしたね。
③については既に『数値の整形』で対応しているので、①②④を実装します。
- 数値の入力を保存
- 連続で入力された数値は入力済みの数値の右側に追加
- 0を連続で入力した場合のみ、0のまま
=> make_display_textで実装済み。 - 桁数は10桁まで入力可能
以下は数値入力時の処理で、返り値は『数値入力・計算結果』に表示する数値の文字列とします。
②と④は入力直後に判定したいので、この中で処理を入れます。
# 数値ボタンを押したときの処理.
def push_number(btn_type):
global g_calc_param
display_value = g_calc_param[CalcParam.DISPLAY_VALUE]
if len(display_value) >= 10:
# 桁数は10桁まで入力可能.
return display_value
else:
# 連続で入力された数値は入力済みの数値の右側に追加.
return display_value + get_text(btn_type)
①は表示を変更する処理の中に入れます。
そのほうが入力値と表示される値のズレがなく管理できます。
保存場所はg_calc_param[ParamType.DISPLAY_VALUE]とします。
# 表示の更新.
def update_display(value):
global g_result_label
global g_calc_param
if g_result_label is None:
return
str_value = make_display_text(value)
# 数値の入力を保存.
g_calc_param[CalcParam.DISPLAY_VALUE] = str_value
# 「入力値・計算結果の表示」ラベルの更新.
g_result_label["text"] = str_value
ボタンを押したときの処理にpush_numberを追加します。
# ボタンを押したときの処理を返す関数
def push_btn_command(btn_type):
def callback():
display_value = '0'
if is_number(btn_type): # 数値の入力.
display_value = push_number(btn_type)
global g_calc_param
g_calc_param[CalcParam.PRE_COMMAND] = btn_type
# 「入力値・計算結果の表示」ラベルの更新.
update_display(display_value)
return callback
コードを実行して、数値を入力できるか確認してみましょう。
演算子の入力
演算子の入力の仕様は以下4点です。
- 演算子の入力を保存
- 演算子の入力前後の数値入力を保存
- 演算子を連続で入力すると上書き
- 途中の計算結果の出力・保存
④は計算結果の出力の章で作成する計算処理が必要となるので、
コメントで# TODO:と書いて、後で実装するようにします。
以下は演算子ボタンを押したときの処理です。
# 演算子ボタンを押したときの処理.
def push_operator(btn_type):
global g_calc_param
old_calc_mode = g_calc_param[CalcParam.CALC_MODE]
display_value = g_calc_param[CalcParam.DISPLAY_VALUE]
# 演算子の入力を保存 & 演算子を連続で入力すると上書き.
g_calc_param[CalcParam.CALC_MODE] = get_calc_mode(btn_type)
# 途中の計算結果の出力・保存.
if (not is_operator(g_calc_param[CalcParam.PRE_COMMAND])
and old_calc_mode is not CalcMode.NONE):
calc_result = '0' # TODO: 後で計算処理を実装.
g_calc_param[CalcParam.OLD_INPUT_VALUE] = calc_result
return calc_result
# 演算子の入力前の数値入力を保存.
g_calc_param[CalcParam.OLD_INPUT_VALUE] = display_value
return display_value
ボタンを押したときの処理で呼び出します。
# ボタンを押したときの処理を返す関数.
def push_btn_command(btn_type):
def callback():
display_value = '0'
if is_number(btn_type): # 数値の入力.
display_value = push_number(btn_type)
elif is_operator(btn_type): # 演算子の入力.
display_value = push_operator(btn_type)
global g_calc_param
g_calc_param[CalcParam.PRE_COMMAND] = btn_type
#「入力値・計算結果の表示」ラベルの更新.
update_display(display_value)
return callback
②に含まれる演算子の入力後の値の管理は、数値入力の方にも条件を追加していきます。
前のコマンドが演算子であれば、そのまま入力された数値を表示することで切り替えます。
これで演算子の入力前の数値はCalcParam.OLD_INPUT_VALUE、
入力後の数値はCalcParam.DISPLAY_VALUEで管理されるようになります。
# 数値ボタンを押したときの処理.
def push_number(btn_type):
global g_calc_param
display_value = g_calc_param[CalcParam.DISPLAY_VALUE]
# 演算子入力後は入力値をそのまま表示.
if is_operator(g_calc_param[CalcParam.PRE_COMMAND]):
return get_text(btn_type)
if len(display_value) >= 10:
# 桁数は10桁まで入力可能.
return display_value
else:
# 連続で入力された数値は入力済みの数値の右側に追加.
return display_value + get_text(btn_type)
計算結果の出力
計算結果の出力の仕様は以下の3点です。
- 入力された数値と演算子から計算結果を出力
- 計算処理をできない場合は現状維持
- 0での除算は0とする
演算子と数値2個から計算を行う関数を作成します。
# 計算結果の算出.
def calc_value(calc_mode, param1, param2):
val1 = str_to_value(param1)
val2 = str_to_value(param2)
# 入力された数値と演算子から計算結果を出力.
# 計算処理をできない場合は現状維持.
result = val2
if calc_mode == CalcMode.PLUS: # 足し算.
result = val1 + val2
elif calc_mode == CalcMode.MINUS: # 引き算.
result = val1 - val2
elif calc_mode == CalcMode.MUL: # 掛け算.
result = val1 * val2
elif calc_mode == CalcMode.DIV: # 割り算.
if val2 != 0:
result = val1 / val2
else:
# 0での除算は0とする.
result = 0
if result.is_integer():
result = int(result)
return str(result)
=ボタンを押したときの処理は、上記の計算結果を算出することがメインです。
演算子やコマンド履歴は不要になるので、リセットします。
また=が入力される前に数値が入力されていないと
数値1 =の形式にならず計算できないので表示は変えずに維持します。
# =ボタンを押したときの処理.
def push_equal(btn_type):
global g_calc_param
pre_command = g_calc_param[CalcParam.PRE_COMMAND]
old_calc_mode = g_calc_param[CalcParam.CALC_MODE]
# 演算子やコマンド履歴は不要なので、リセット.
g_calc_param[CalcParam.PRE_COMMAND] = BtnType.BTN_NOT
g_calc_param[CalcParam.CALC_MODE] = CalcMode.NONE
# 計算できないときは現状維持. 数値が2個無いとき.
if not is_number(pre_command):
return g_calc_param[CalcParam.DISPLAY_VALUE]
# 計算結果の出力.
return calc_value(old_calc_mode, g_calc_param[CalcParam.OLD_INPUT_VALUE], g_calc_param[CalcParam.DISPLAY_VALUE])
前章でTODOとした処理も忘れずに置き換えましょう。
# 演算子ボタンを押したときの処理.
def push_operator(btn_type):
global g_calc_param
old_calc_mode = g_calc_param[CalcParam.CALC_MODE]
display_value = g_calc_param[CalcParam.DISPLAY_VALUE]
# 演算子の入力を保存 & 演算子を連続で入力すると上書き.
g_calc_param[CalcParam.CALC_MODE] = get_calc_mode(btn_type)
# 途中の計算結果の出力・保存.
if (not is_operator(g_calc_param[CalcParam.PRE_COMMAND])
and old_calc_mode is not CalcMode.NONE):
# 計算処理.※TODOあった場所.
calc_result = calc_value(old_calc_mode, g_calc_param[CalcParam.OLD_INPUT_VALUE], display_value)
g_calc_param[CalcParam.OLD_INPUT_VALUE] = calc_result
return calc_result
# 演算子の入力前の数値入力を保存.
g_calc_param[CalcParam.OLD_INPUT_VALUE] = display_value
return display_value
ボタンを押したときの処理にもpush_equalを追加します。
# ボタンを押したときの処理を返す関数.
def push_btn_command(btn_type):
def callback():
display_value = '0'
if is_number(btn_type): # 数値の入力.
display_value = push_number(btn_type)
elif is_operator(btn_type): # 演算子の入力.
display_value = push_operator(btn_type)
elif is_equal(btn_type): # =の入力.
display_value = push_equal(btn_type)
global g_calc_param
g_calc_param[CalcParam.PRE_COMMAND] = btn_type
#「入力値・計算結果の表示」ラベルの更新.
update_display(display_value)
return callback
計算機能が追加できたので、動作を確認してみましょう!
入力・出力のクリア
ACボタンを押したときの入力・出力のクリア処理は
以下3点の仕様で構成されます。
- 入力した数値をリセット
- 入力した演算子をリセット
- 計算結果もリセット
コマンド履歴も残す必要はないので、このタイミングで消してしまいましょう。
# ACボタンを押したときの処理.
def push_all_clear():
global g_calc_param
# 入力した数値をリセット.
g_calc_param[CalcParam.OLD_INPUT_VALUE] = '0'
# 入力した演算子をリセット.
g_calc_param[CalcParam.CALC_MODE] = CalcMode.NONE
# 入力した1個前のコマンド履歴も削除.
g_calc_param[CalcParam.PRE_COMMAND] = BtnType.BTN_NOT
# 計算結果もリセット
return '0'
ボタンを押したときの処理にpush_all_clearを追加します。
# ボタンを押したときの処理を返す関数.
def push_btn_command(btn_type):
def callback():
display_value = '0'
if is_number(btn_type): # 数値の入力.
display_value = push_number(btn_type)
elif is_operator(btn_type): # 演算子の入力.
display_value = push_operator(btn_type)
elif is_equal(btn_type): # =の入力.
display_value = push_equal(btn_type)
elif is_all_clear(btn_type): # ACの入力.
display_value = push_all_clear()
global g_calc_param
g_calc_param[CalcParam.PRE_COMMAND] = btn_type
#「入力値・計算結果の表示」ラベルの更新.
update_display(display_value)
return callback
実装はここまでです!お疲れさまでした!
まとめ
今回は電卓作成の機能実装の解説を行いました。
これで電卓アプリの実装は完了となります。お疲れさまでした!
仕様を整えてから実装しても、やはり細部は実装しつつ考えるところが出てきましたね。
今回であれば、変数定義の種類や保存タイミングなどは仕様には含めてない部分でした。
完全な仕様を作るのは不可能に近いので、
足りない部分は柔軟におぎないつつ実装できるようにスキルを上げていきましょう!
長ーい実装でしたが電卓アプリの実装はこれにて終了です。
次回は動作確認をしていきます!
おわり