How to use Python Kivy ②-Create a calculator-

Summary

By using the previous "Kv Language Basics", you should have somehow understood the basic usage of Kivy. This time, we will actually create a simple app to deepen our understanding.

What to create

結果_50.jpg

What you create is a calculator app. Enter numbers and operators with the buttons and press the "=" button to display the calculation results. If you press switch again, the design will change.

What to learn anew

The contents to be newly learned are as follows

Reference link

Creating a calculator app in Kivy is often argued in overseas tutorials.

I will give you a reference.

About the program

There are various ways to write code using Kivy. This is just an example. Also, the source code used when writing this article is listed on Github. However, fonts are not placed, so please prepare and place them yourself if necessary.

Verification environment

The verification environment is as follows.

OS: Windows10 64bit Kivy:1.9.1

Python3.4※

Below, I will actually post the code and results.

Execution result

The execution result is as follows.

結果3.jpg

When you start it, the screen will be displayed. The same operation as a normal calculator is possible. * 1 Press the switch button on one bar to switch to the following design * 2

結果4.jpg

code

The program is as follows.

main.py


# -*- coding: utf-8 -*
from kivy.config import Config
Config.set('graphics', 'width', '400')
Config.set('graphics', 'height', '800')
import kivy
kivy.require('1.9.1')

from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.core.text import LabelBase, DEFAULT_FONT
from kivy.core.window import Window
from kivy.properties import BooleanProperty
from kivy.utils import get_color_from_hex
from kivy.resources import resource_add_path

from kivy.factory import Factory

#from kivy.core.window import Window
#Window.size = (450, 600)

#Change the default font
resource_add_path('fonts')
LabelBase.register(DEFAULT_FONT, 'mplus-2c-regular.ttf') #Specify a Japanese font so that Japanese can be used


class Calculator1(BoxLayout):

    clear_bool = BooleanProperty(False)

    def print_number(self, number):
        '''Enter the entered value'''
        if self.clear_bool:
            self.clear_display()

        text = "{}{}".format(self.display.text, number) #The character string entered so far and the value entered are displayed.
        self.display.text = text

        print("Number "{0}Was pressed".format(number))

    def print_operator(self, operator):
        if self.clear_bool:
            self.clear_bool = False

        text = "{} {} ".format(self.display.text, operator)
        self.display.text = text

        print("operator"{0}Was pressed".format(operator))

    def print_point(self, operator):
        # 「.Processing when "" is pressed

        print("(Unimplemented) Operator "{0}Was pressed".format(operator))

    def clear_display(self):
        self.display.text = ""
        self.clear_bool = False

        print(""C" was pressed")
    def del_char(self):
        ''' "<x"Display the calculation result when is pressed'''

        self.display.text = self.display.text[:-1]

        print("「<x "was pressed")

    def calculate(self):
        ''' "="Display the calculation result when is pressed'''
        try:
            self.display.text = str(eval(self.display.text)) #Evaluate a single expression Example: eval("5 + 10")Becomes 15
            self.clear_bool = True

            print('Calculation completed')
        except:
            #Without entering numbers'=Error countermeasures such as when pressing ’
            print('error Typing error')


#class Calculator2(BoxLayout):
#    def __init__(self, **kwargs):
#        super(Calculator2, self).__init__(**kwargs)




class CalculatorRoot(BoxLayout):
    def __init__(self, **kwargs):
        super(CalculatorRoot, self).__init__(**kwargs)

    def change_calc(self):   
        self.clear_widgets()
        self.add_widget(Calculator1())

    def change_calc2(self):   
        self.clear_widgets()
        calc2 = Factory.Calculator2()
        #calc2 = Calculator2()

        self.add_widget(calc2)



class CalculatorApp(App): 
    def __init__(self, **kwargs):
        super(CalculatorApp, self).__init__(**kwargs)

        self.title = 'calculator'
    pass


if __name__ == "__main__":
    Window.clearcolor = get_color_from_hex('#FFFFFF')
    CalculatorApp().run()

Kv file

The Kv file is as follows.

calculator.kv


#: import get_color_from_hex kivy.utils.get_color_from_hex

CalculatorRoot



<CalculatorRoot>
    Calculator1
        id: calculator1

<calculator1>
#Layout of the entire screen
    orientation: "vertical" #Place objects horizontally
    display: display_input

    ActionBar:

        ActionView:
            ActionPrevious:
                title: 'calculator'
                with_previous: False #If true, the logo on the head becomes a button?

            ActionButton:
                text: 'switching'
                on_press: print("push ");app.root.change_calc2()

#            ActionButton:
#                text: 'sound'
#                icon: 'atlas://data/images/defaulttheme/audio-volume-high'

#            ActionGroup:    #Group buttons
#                text: 'type'
#                ActionButton:
#                    text: 'Scientific calculator'
#                ActionButton:
#                    text: 'programmer'


    TextInput:  #Number display part
        id: display_input
        size_hint_y: 1  #Vertical size is 1 for the whole/5.Display at a rate of 5
        font_size: 60
        hint_text: '0'

    Keyboard:   #Display part of the taen key
        size_hint_y: 3.5    #Vertical size of the whole 3.5/4.Display at a rate of 5


<Keyboard@GridLayout>:  # class Keyboard(GridLayout):Same meaning as

    #Create a button with 4 columns x 5 rows
    cols: 4
    rows: 5

    spacing: 2
    padding: 4

    #1st row
    ClearButton:    #Button type
        text: "C"   #Button display name
    CalcButton:
        text: "%"
    DelButton:
        text: "<x"
    OperatorButton:
        text: "/"

    #2nd row
    NumberButton:
        text: "7"
    NumberButton:
        text: "8"
    NumberButton:
        text: "9"
    OperatorButton:
        text: "*"

    #3rd row
    NumberButton:
        text: "4"
    NumberButton:
        text: "5"
    NumberButton:
        text: "6"
    OperatorButton:
        text: "-"

    #4th row
    NumberButton:
        text: "1"
    NumberButton:
        text: "2"
    NumberButton:
        text: "3"
    OperatorButton:
        text: "+"

    #5th row
    CalcButton:
        text: "+/-"
    NumberButton:
        text: "0"
    CalcButton:
        text: "."
    EqualButton:
        text: "="

<ButtonFormat@Button>:
    font_size: '30dp'
    background_normal: ''
    background_down: ''
    background_color: get_color_from_hex("#83481F")
    on_press: self.background_color = get_color_from_hex("#825534")
    on_release: self.background_color = get_color_from_hex("#823600")


<NumberButton@ButtonFormat>:
    #font: "Roboto"
    font_size: '30dp'
    bold: True
    on_press:  app.root.ids['calculator1'].print_number(self.text)   #When the button is pressed main.py's Calculator class print_number()Is executed

<OperatorButton@ButtonFormat>:
    on_press:  app.root.ids['calculator1'].print_operator(self.text)

<ClearButton@ButtonFormat>:
    on_press:  app.root.ids['calculator1'].clear_display()

<DelButton@ButtonFormat>:
    on_press:  app.root.ids['calculator1'].del_char()

<EqualButton@ButtonFormat>:
    on_press:  app.root.ids['calculator1'].calculate()

<CalcButton@ButtonFormat>:  #Will display decimal point but not implemented
    on_press:  app.root.ids['calculator1'].print_point(self.text)


<Calculator2@BoxLayout>
    id: calculator2

    orientation: "vertical" #Place objects horizontally
    display: display_input

    ActionBar:
        ActionView:
            ActionPrevious:
                title: 'Calculator 2'
                with_previous: False #If true, the logo on the head becomes a button?
            ActionOverflow:

            ActionButton:
                text: 'switching'
                on_press: print("push2 ");app.root.change_calc()


    TextInput:  #Number display part
        id: display_input
        size_hint_y: 1  #Vertical size is 1 for the whole/4.Display at a rate of 5
        font_size: 60
    Keyboard2:   #Display part of the taen key
        size_hint_y: 3.5    #Vertical size of the whole 3.5/4.Display at a rate of 5


<Keyboard2@GridLayout>:  # class Keyboard(GridLayout):Same meaning as

    #Create a button with 4 columns x 5 rows
    cols: 4
    rows: 5

    spacing: 2
    padding: 4

    #1st row
    ClearButton2:    #Button type
        text: "Delete"   #Button display name

    CalcButton2:
        text: "Surplus"
    DelButton2:
        text: "<x"
    OperatorButton2:
        text: "quotient"

    #2nd row
    NumberButton2:
        text: "Seven"
        calc_val: '7'
    NumberButton2:
        text: "Eight"
        calc_val: '8'
    NumberButton2:
        text: "Nine"
        calc_val: '9'

    OperatorButton2:
        text: "product"

    #3rd row
    NumberButton2:
        text: "four"
    NumberButton2:
        text: "Five"
    NumberButton2:
        text: "Six"
    OperatorButton2:
        text: "difference"

    #4th row
    NumberButton2:
        text: "one"
    NumberButton2:
        text: "two"
    NumberButton2:
        text: "three"
    OperatorButton2:
        text: "sum"

    #5th row
    CalcButton2:
        text: "+/-"
    NumberButton2:
        text: "zero"
    CalcButton2:
        text: "."
    EqualButton2:
        text: "="

<ButtonFormat2@Button>:
    font_size: '30dp'
    background_normal: ''
    background_down: ''
    background_color: get_color_from_hex("#398133")
    on_press: self.background_color = get_color_from_hex("#73FF66")
    on_release: self.background_color = get_color_from_hex("#52824E")


<NumberButton2@ButtonFormat2>:
    bold: True

<OperatorButton2@ButtonFormat2>:

<ClearButton2@ButtonFormat2>:

<DelButton2@ButtonFormat2>:

<EqualButton2@ButtonFormat2>:

<CalcButton2@ButtonFormat2>:

Commentary

The basic explanation of Kv Language was given last time, so I will mainly explain the parts that could not be explained last time. Let's start with the Kv file.

About layout at startup

At startup, the Calculator Root of the rootWidget is set to calculator. Among them, calculator1 has the following code.

<calculator1>
#Layout of the entire screen
    orientation: "vertical" #Place objects horizontally
    display: display_input

    ActionBar:

        ActionView:
            ActionPrevious:
                title: 'calculator'
                with_previous: False #If true, the logo on the head becomes a button?
            ActionOverflow:

            ActionButton:
                text: 'switching'
                on_press: print("push ");app.root.change_calc2()
    TextInput:  #Number display part
        id: display_input
        size_hint_y: 1  #Vertical size is 1 for the whole/4.Display at a rate of 5
        font_size: 60
        hint_text: '0'

    Keyboard:   #Display part of the taen key
        size_hint_y: 3.5    #Vertical size of the whole 3.5/4.Display at a rate of 5

calculator1 is the main layout, of which TextInput is the display area and Keyboard is the keyboard part, which is the main layout. The value of "size_hint_y" of TextInput is "1" and the value of "size_hint_y" of Keyboard is "3.5", and the vertical display area of Keyboard is arranged according to this ratio with TextInput.

説明.jpg

About Text Input

TextInput is a wighet that allows you to enter characters. This time we are using the property hint_text: '0'. hint_text: can set the display of characters when not entered.

reference

Kewbord is a custom widget created based on GridLayout.

<Keyboard@GridLayout>:

About Grid Layout

Reprinted from the official website gridlayout.gif

GirdLayout is a widget that allows you to place widgets on a grid.

<Keyboard@GridLayout>:  # class Keyboard(GridLayout):Same meaning as

    #Create a button with 4 columns x 5 rows
    cols: 4
    rows: 5

As a basic usage, "cols" sets the number of cells in the horizontal direction, and "rows" sets the number of cells in the vertical direction. It is not necessary to set both, it is possible to set only "cols". This time I used it to create a keyboard. Also, although not used this time, you can use "size_hint" for each cell, so you can adjust the size of the grid individually. The same layout can be achieved by using BoxLayout in combination, so it depends on your personal preference whether to use GildLayout or BoxLayout.

reference

About Action Bar

ActoonBar can display titles and icons, and add buttons, just like Android's Action Bar. Mainly set at the top and bottom of the screen

説明2.jpg

    ActionBar:

        ActionView:
            ActionPrevious:
                title: 'calculator'
                with_previous: False #Back button if true
            ActionOverflow:

            ActionButton:
                text: 'switching'
                on_press: print("push ");app.root.change_calc2()

            ActionButton:
                text: 'sound'
                icon: 'atlas://data/images/defaulttheme/audio-volume-high'

            ActionGroup:    #Group buttons
                text: 'type'
                ActionButton:
                    text: 'Scientific calculator'
                ActionButton:
                    text: 'programmer'

ActionView is a class based on BoxLayout, and uses it to arrange buttons and so on. ActionPrevious sets the title of the app and the back button. If you set the name to the "title" property and the property of "with_previous" to "True", you can return to the previous page by clicking the kivy icon. However, to use the back function, you need to create multiple screens with slide etc. The icon image can be changed by specifying the "app_icon" parameter. Use "Action Button" to add a button. You can change the image with the "icon" parameter. * "Atlas" is used to specify the icon image. "Atlas" is explained in the next section.

Use Action Group to group buttons. The execution result is as follows.

説明3.jpg

reference

About atlas

Atlas is a collection of multiple images into a single image. It is a technology often used in games and CG, and is used to save memory and shorten loading. Detailed explanation can be found by searching for "Unity Atlas" etc.

kivy also uses atlas, and the default buttons and on / off switches all use this mechanism. Kivy's atlas is realized by a file in atlas format (extension .atlas) and a png file specified inside it. The atlas file looks like this in json format:

python


{
    "<basename>-<index>.png ": {
        "id1": [ <x coordinate>, <y coordinate>, <width>, <height> ],
        "id2": [ <x coordinate>, <y coordinate>, <width>, <height> ],
        # ...
    },
    # ...
}

The actual file is as follows.

kivy/data/images/defaulttheme.atlas

defaulttheme.atlas


{
    "defaulttheme-0.png ": {
        "action_bar": [
            158, 
            133, 
            36, 
            36
        ], 
        "action_group": [
            452, 
            171, 
            33, 
            48
        ], 
        "action_group_disabled": [
            2, 
            121, 
            33, 
            48
        ], 
~ Omitted ~
        "audio-volume-high": [
            464, 
            406, 
            48, 
            48
        ], 
~ Omitted ~
    }
}

defaulttheme-0.png

defaulttheme-0.jpg

Although it is atlas, I think that it is not often prepared and used by myself when creating a desktop application as a personal impression. The reason is that it has the advantage of faster memory and loading, but on the other hand it is troublesome to manage files, and even from the standard specifications of current PCs, it reads from individual image files without using atlas. I think that it is enough for actual operation even if it is used in. Unless you want to load a small image for an RPG map in tiles on the entire screen or display a large number of images like a barrage of a shooting game, you do not need to use it.

reference

About multi-line execution in Kv file

ActionButton:
    text: 'switching'
    on_press: print("push ");app.root.change_calc2()

Looking at the command at the time of on_press, there is a ";" after the print command. You can execute multiple commands by writing ";". You can also align the lines with a line break + space after the ";".

on_press: 
    app.ans = eval(input.text);
    input.text = str(app.ans)

About getting color by get_color_from_hex ()

    #: import get_color_from_hex kivy.utils.get_color_from_hex

    background_color: get_color_from_hex("#83481F")
    on_press: self.background_color = get_color_from_hex("#825534")
    on_release: self.background_color = get_color_from_hex("#823600")

You can get the color from hexadecimal notation by using get_color_from_hex (). I import from get_color_from_hex () kivy.utils, but not on the Python side.

#: import get_color_from_hex kivy.utils.get_color_from_hex

By writing, you can import on the Kv file side.

reference

Explanation 2

Next, I will explain the Python file side. The Python file side can be almost explained with the contents of the previous basic explanation. There is only one new element. It's a widget switch.

Switch widget

The following is an excerpt of the applicable code below.

from kivy.factory import Factory

class Calculator1(BoxLayout):
    clear_bool = BooleanProperty(False)
    
    def print_number(self, number):
~ Omitted ~
       
#class Calculator2(BoxLayout):
#    def __init__(self, **kwargs):
#        super(Calculator2, self).__init__(**kwargs)

class CalculatorRoot(BoxLayout):

 def change_calc(self):   
        self.clear_widgets()
        self.add_widget(Calculator1())

    def change_calc2(self):   
        self.clear_widgets()
        calc2 = Factory.Calculator2()
        #calc2 = Calculator2()

        self.add_widget(calc2)

The kv file is as follows

<Calculator2@BoxLayout>
~ Omitted ~

It is a function in change_calc (), clear_widgets (), which removes all applicable child widgets. Use remove_widget ('widget name') to specify and remove only a specific widget. Conversely, you can add a widget by specifying add_widget ('widget name'). change_calc () once deletes all widgets belonging to CalculatorRoot (clears the screen) and adds a new widget of Calculator1 class.

Next is change_calc2 (). Similarly, the screen is cleared and a new widget for Calculator2 is added, but there is a big difference from change_calc (). That is ** the Calculator1 class is defined in the Python file, but the Calculator2 class is not defined on the Python side. It is defined on the kv file side **.

self.add_widget(Calculator2())

If you execute Calculator2 () directly like Calculator1 (), an error will occur at startup.

from kivy.factory import Factory

calc2 = Factory.Calculator2()

Therefore, we will use the Factory class this time. The Factory class allows you to ** automatically register any class or module and instantiate a class anywhere in your project. Therefore, you can use the widget defined in the kv file. ** **

Using this method, if you want to use only the screen and do not need to implement the function, you do not need to write the widget in the Python file, so you can write the program concisely. In this case, if you do not use the Factory class, you need to declare the Calculator2 () class in the Python file and declare initialization etc. as commented out.

class Calculator2(BoxLayout):
    def __init__(self, **kwargs):
        super(Calculator2, self).__init__(**kwargs)

reference

In addition, although it is a method of switching screens, there is a method of using Carousel or Screen Manager after that. As for Carousel, some people have actually done it, so I will list it for reference. I will explain how to use ScreenManager next time.

reference

Summary

I think that this time it will be possible to create an application that ends with a simple single screen. Next time, I will explain how to use WebAPI in Kivy and screen transition using ScreenManager.

Continuation of this content

New content has been added.

How to use Python Kivy ③-Linkage with WebAPI (from sending and receiving requests to displaying results)-

Recommended Posts

How to use Python Kivy ②-Create a calculator-
How to create a Kivy 1-line input box
How to create a multi-platform app with kivy
[Python] How to use checkio
[Python] How to use input ()
How to use Python lambda
[Python] How to use virtualenv
How to use Python bytes
[Python] How to create a 2D histogram with Matplotlib
[Python] How to use list 3 Added
How to use OpenPose's Python API
How to use ChemSpider in Python
How to create a Dockerfile (basic)
Python: How to use pydub (playback)
How to use PubChem in Python
5 Ways to Create a Python Chatbot
How to use python zip function
[Python] How to use Typetalk API
How to create a config file
[Python Kivy] How to create an exe file with pyinstaller
How to use NUITKA-Utilities hinted-compilation to easily create an executable file from a Python script
python: How to use locals () and globals ()
How to use __slots__ in Python class
How to create a repository from media
How to use "deque" for Python data
How to use Python zip and enumerate
[Python] Understand how to use recursive functions
Summary of how to use Python list
How to use regular expressions in Python
[Python2.7] Summary of how to use subprocess
How to use is and == in Python
How to run a Maya Python script
[Question] How to use plot_surface of python
Use click to create a sub-sub command --netsted sub-sub command -
Steps to create a Twitter bot with python
[Algorithm x Python] How to use the list
How to use iptables
How to notify a Discord channel in Python
How to use numpy
How to use tkinter with python in pyenv
How to use TokyoTechFes2015
How to use venv
How to use hmmlearn, a Python library that realizes hidden Markov models
[Note] How to create a Ruby development environment
How to use dictionary {}
[Python] How to use hash function and tuple.
How to use Pyenv
How to use list []
How to use python-kabusapi
[Python] How to draw a histogram in Matplotlib
Create a Python environment
How to use OptParse
How to use Ruby's PyCall to enable pyenv Python
How to create a Rest Api in Django
How to use return
How to use pyenv-virtualenv
[Note] How to create a Mac development environment
How to use imutils
3. Natural language processing with Python 1-2. How to create a corpus: Aozora Bunko
[Python] How to create a local web server environment with SimpleHTTPServer and CGIHTTPServer
How to make a Python package using VS Code