0
1

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.

Kivy と Python で電卓を作る3:基本部分の作成

Last updated at Posted at 2023-01-11

目次

Kivy と Python で電卓を作る:概要
Kivy と Python で電卓を作る:方針
Kivy と Python で電卓を作る:基本部分の作成←イマココ
Kivy と Python で電卓を作る:電卓機能の追加

3. ボタンと表示部分の実装

今回から実装.

  • 必要なボタン類の作成
  • 数字や記号の入力ができる
  • 表示が更新される

今回, 作成するのはここまで.
image_1.png

完成したらこんなふうになります.

3-1. ファイル構成

Kivy_Calculator  
|- Documents:原稿入れ
|- Layout:レイアウト関連のファイル
|  |- Calculator.kv
|  `- CalculatorMain.py
|- Params:数値の設定ファイル
|  `- CalcParams.py
|- Button.py
`- CalculatorApp.py:電卓メイン App

レイアウトと数値の設定は別ファイルに分ける.
数値を直書き(マジックナンバーね)しないようにすると後々の修正が楽にできる.

3-3. CalculatorApp.py

電卓アプリ起動に使うファイル.
CalculatorMain.py と分けたのは, 初期起動時に色々設定したい! となったときに分けた方がシンプルになるため.
つまり, 今回は分ける意味はそこまででもない.

from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder

from Layout.CalculatorMain import CalculatorMain

Window.size = (500, 900)
Builder.load_file('Layout/Calculator.kv')

class CalculatorApp(App):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def build(self):
        return CalculatorMain()

if __name__ == '__main__':
    CalculatorApp().run()

3-3. Calculator.kv

レイアウトを決定する kivy ファイル.
数字を表示する DisplayArea と ButtonArea を分けてみた.
分けた方がコードの見た目は良くなるのだが, 別 class となるため実装が少し面倒になる.

#:kivy 1.2.0
#:import MainParam Params.CalcParams.MainParam
#:import ButtonAreaParams Params.CalcParams.ButtonAreaParams
#:import NumberButton Button.NumberButton
#:import Operator Params.CalcParams.Operator

<CalculatorMain>:
    canvas.before:
        Color:
            rgba: MainParam.background_color
        Rectangle:
            pos: self.pos
            size: self.size
    CalculatorLayout:
        orientation: 'vertical'
        size_hint: MainParam.calc_size_hint
        DisplayArea:
            id: display_area
            size_hint: MainParam.display_area_size_hint
            Label:
                id: display_num
                text: self.parent.display_num
        ButtonArea:
            id: button_area
            size_hint: MainParam.button_area_size_hint
                

<ButtonArea>:
    padding: ButtonAreaParams.outer_padding
    GridLayout:
        size_hint: ButtonAreaParams.button_upper_area_size_hint
        cols: 4
        rows: 5
        ACButton:
            text: 'AC'
            on_press: root.reset_number()
        PlusMinusButton:
            text: '±'
            on_press: root.reverse_number()
        DeleteButton:
            text: 'del'
            on_press: root.delete_number()
        OperatorButton:
            text: '+'
            on_press: root.set_operator(Operator.PLUS)
        NumberButton:
            text: '7'
            on_press: root.add_number(self.text)
        NumberButton:
            text: '8'
            on_press: root.add_number(self.text)
        NumberButton:
            text: '9'
            on_press: root.add_number(self.text)
        OperatorButton:
            text: '-'
            on_press: root.set_operator(Operator.MINUS)
        NumberButton:
            text: '4'
            on_press: root.add_number(self.text)
        NumberButton:
            text: '5'
            on_press: root.add_number(self.text)
        NumberButton:
            text: '6'
            on_press: root.add_number(self.text)
        OperatorButton:
            text: '×'
            on_press: root.set_operator(Operator.MULTIPLICATION)
        NumberButton:
            text: '1'
            on_press: root.add_number(self.text)
        NumberButton:
            text: '2'
            on_press: root.add_number(self.text)
        NumberButton:
            text: '3'
            on_press: root.add_number(self.text)
        OperatorButton:
            text: '÷'
            on_press: root.set_operator(Operator.DIVISION)
        NumberButton:
            text: '0'
            on_press: root.add_number(self.text)
        DecimalSeparatorButton:
            text: '.'
            on_press: root.add_decimal_separator()
        OperatorButton:
            text: '='

3-4. CalculatorMain.py

レイアウト関連は, Calculator.kv に記載し, 処理はこっちに書く.

from kivy.logger import Logger
from kivy.properties import StringProperty
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout

from Params.CalcParams import MainParam


class CalculatorMain(Screen):

    pass


class CalculatorLayout(BoxLayout):
    """電卓のメイン部分.
    """
    pass


class DisplayArea(BoxLayout):
    """電卓の表示部分.
    """
    first_num = 0
    second_num = 0
    display_num = StringProperty('')
    is_number_decimal = False
    is_plus = True

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def add_number(self, num):
        """数値を入力する処理.

        Args:
            num (str): 入力された数値
        """
        # 桁数を制限する処理
        if len(self.display_num) < MainParam.max_digits:
            # display_num が 0 で 0 が入力された場合への対処
            if self.display_num == '0' and num != 0:
                self.display_num = num
            else:
                self.display_num += num

    def add_decimal_separator(self):
        """小数点を入力する処理.
        """
        # 小数点が複数入力されないようにする処理
        if not self.is_number_decimal:
            self.display_num += '.'
            self.is_number_decimal = True

    def delete_number(self):
        """数値を削除する処理.
        """
        # 数値が全て削除された時に 0 を表示する処理
        if len(self.display_num) == 2 and self.display_num[0] == '-':
            self.display_num = '0'
        elif len(self.display_num) != 1:
            self.display_num = self.display_num[:-1]
        else:
            self.display_num = '0'

    def reset_number(self):
        """数値を 0 にリセットする処理.
        """
        self.display_num = '0'

    def reverse_number(self):
        """数値のプラスとマイナスを反転する処理.
        """
        if self.is_plus:
            self.is_plus = False
            self.display_num = '-' + self.display_num
        else:
            self.is_plus = True
            self.display_num = self.display_num[1:]

    def set_operator(self, operator):
        """演算子を設定する処理.

        Args:
            operator (Enum): 演算子の種類を示す.
        """
        Logger.info("Operator {} Pressed".format(operator))


class ButtonArea(BoxLayout):
    """電卓の入力・計算ボタン部分.
    """

    def add_number(self, text):
        """数字の入力処理.
        """
        Logger.info("Number Button {} Pressed".format(text))
        self.parent.children[1].add_number(text)

    def add_decimal_separator(self):
        """小数点の追加処理.
        """
        Logger.info("Decimal Separator Button Pressed")
        self.parent.children[1].add_decimal_separator()

    def delete_number(self):
        """削除ボタン.
        """
        Logger.info("Delete Button Pressed")
        self.parent.children[1].delete_number()

    def reset_number(self):
        """数字のリセット処理.
        """
        Logger.info("AC Button Pressed")
        self.parent.children[1].reset_number()

    def reverse_number(self):
        """プラス・マイナスの反転処理.
        """
        Logger.info("Reverse Button Pressed")
        self.parent.children[1].reverse_number()

    def set_operator(self, operator):
        """演算子の設定処理.

        Args:
            operator (Enum): 演算子
        """
        Logger.info("Operator Button Pressed")
        self.parent.children[1].set_operator(operator)
self.parent.children[1]

は, self (ButtonArea) の親 (CalculatorLayout) の2番目の子供 (DisplayArea) という意味.
この辺りの関係は, 素人がpython(kivy)でブロック崩しを作ってみた話②を参考にした.
Button.py に各処理を記載しても良いのだが, 入れ子構造が増えるので, self.parent.parent.parent と繋がってダサくなってしまう.
上手い書き方をご存じの方がいましたらコメントお願いします.

3-5. Button.py

Button クラスを継承して数字を入力する NumberButton と 小数点を入力する DecimalSeparatorButton を作る.

from kivy.uix.button import Button


class NumberButton(Button):
    """数字の入力ボタン."""
    pass


class DecimalSeparatorButton(Button):
    """小数点の入力ボタン."""
    pass


class DeleteButton(Button):
    """削除ボタン."""
    pass


class ACButton(Button):
    """電源ボタン."""
    pass


class PlusMinusButton(Button):
    """プラスマイナス反転ボタン."""
    pass


class OperatorButton(Button):
    """演算子を設定するボタン."""
    pass

3-6. CalcParams.py

設定値を記載しているだけのファイル. フォントサイズや色をまとめて書いておくと, 後々変更する時に一括で変更できて楽.

from kivy.metrics import dp

from enum import Enum


class MainParam:
    # 電卓の全体に適用される設定
    calc_size_hint = (1., 1.)
    display_area_size_hint = (1., 280. / 900)
    button_area_size_hint = (1., 620. / 900)
    background_color = (58. / 255, 64. / 255, 71./ 255, 1.)
    max_digits = 10


class ButtonAreaParams:
    # 電卓のボタンに関する設定
    outer_padding = (dp(20), dp(20), dp(20), dp(20))
    button_upper_area_size_hint = (1., 500. / 620.)
    button_lower_area_size_hint = (1., 120. / 620.)


class Operator(Enum):

    NONE = 0
    PLUS = 1
    MINUS = 2
    MULTIPLICATION = 3
    DIVISION = 4
outer_padding = (dp(20), dp(20), dp(20), dp(20))

としているのは「20px padding してね!」と書いてもディスプレイの解像度によって padding が 20px にならないことがあるため.

4. 実行結果

実行結果はこんな感じ.
各種数値の入力はいい感じですね.

ログもイイ感じに出ています.
image_2.png

5. 次回予告

次回はいよいよ, 計算処理と状態遷移を追加してゆきます.
前回:Kivy と Python で電卓を作る2:設計
次回:

0
1
1

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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?