이번 포스팅은 파이썬 Kivy 로 타이머를 만들어 본다.

 

 

GUI 환경에서 타이머를 만들어 보는 경험은 큰 도움이 된다. 이벤트 루프(Event Driven Loop)의 작동원리에 대해서 배울 수 있기 때문이다. 대부분의 GUI 프로그램, 게임, 서버 등 Listening 하는 애플리케이션의 구조는 이벤트 루프에서 시작한다.

 

kivy 가 다른 GUI와 다른 점은 kivy 언어를 사용한다는 점인데 이것이 코드를 읽기 어렵게 만들 수도 쉽게 만들 수 있다. 뷰와 콘트롤이 분리된 로직인데 자바스크립트를 이용한 웹페이지 개발에 대하여 알고 있다면 익숙하게 느낄 것이다.

 

최근 유행하는 객체지향 언어의 코드는 위에서 아래로 읽지 않는다. 클래스 별로 분리되어 있기 때문에 먼저 그 클래스의 구조에 대하여 이해를 해야 한다. kivy 같은 경우는 좀 더 생각해봐야 할게 앱이 시작하면 자동으로 이벤트 루프에 들어간다. 사용자가 종료 버튼을 클릭하여 종료될 때 까지 이벤트 루프를 돌고 있다.

 

매 루프마다 처리할 내용을 앱의 update 메소드를 오버라이드해서 사용할 수 있다. update 메소드는 이벤트 루프를 돌면서 거쳐가는 곳 이다. 프레임 세팅을 해주면 1초에 몇번 루프를 실행할 건지 정할 수 있다.

 

먼저 kv언어 파일을 보자. 파일명은 clock.py를 사용한다.

 

CSS 처럼 속성들이 할당되어 있다. kv 구조는 BoxLayout 의 Root 에서 시작된다.

 

< > 꺽쇠 표시 부분은 kv 언어 전체에 적용된다. 동일한 양식을 적용해준다.

 

<RobotoButton@ ... 은 클래스 이름이다. 클래스 이름으로 속성을 적용할 수 있다.

<Label>:
    font_name: 'Roboto'
    font_size: 20
    markup: True

<RobotoButton@Button>:
    font_name: 'Roboto'
    font_size: 25
    bold: True

    border: (2, 2, 2, 2)


BoxLayout:
    time_prop: time
    orientation: 'vertical'

    Label:
        id: time
        text: '[b]00[/b]:00:00'
        font_size: 45

    BoxLayout:
        height: 100
        orientation: 'horizontal'
        padding: 20
        spacing: 20
        size_hint: (1, None)

        RobotoButton:
            id: start_stop
            text: 'Start'
            font_name: 'Roboto'
            on_press: app.start_stop()

        RobotoButton:
            id: reset
            text: 'Reset'
            on_press: app.reset()
    Label:
        id: stopwatch
        text: '00:00.[size=35]00[/size]'
        font_size: 65

id 는 main 파일의 요소와 연결된다.

 

on_press 는 이벤트 핸들링 함수를 호출한다.

 

 

* 다음은 main.py 파일이다.

 

확실히 디자인적 요소인 kivy 언어와 분리되어서 가독성이 좋다. ClockApp 이라는 윈도우 창에서 일어나는 이벤트들이 구현되어 있다.

 

update 메소드는 매 이벤트 루프에 실행된다. 타이밍을 0.016으로 넣으면 60분의1초가 되고 0.01 이면 100분의 1초가 된다. 무슨 코드라도 update에 추가하면 그만큼 실행된다. on_start  와 stop , reset 에는 우리가 아는 타이머의 기능을 구현해두었다. 스타트, 스톱, 리셋 그게 전부다.

 

from time import strftime

from kivy.app import App
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.core.text import LabelBase
from kivy.utils import get_color_from_hex

# 윈도우 설정
Window.size = (550, 350)
Window.clearcolor = get_color_from_hex('#333300')


class ClockApp(App):
    sw_seconds = 0
    started = False

    def update_clock(self, nap):
        self.root.ids.time.text = strftime('[b]%H[/b]:%M:%S')

    def on_start(self):
        Clock.schedule_interval(self.update_clock, 1)
        Clock.schedule_interval(self.update, 0.01)

    def start_stop(self):
        self.root.ids.start_stop.text = ('Start'
                if self.started else 'Stop')
        self.started = not self.started
        print(self.started)
        print(self.sw_seconds)

    def reset(self):
        if self.started:
            self.root.ids.start_stop.text = 'Start'
            self.started = False
        self.sw_seconds = 0

    def update(self, nap):
        if self.started:
            self.sw_seconds += nap
        minutes, seconds = divmod(self.sw_seconds, 60)
        self.root.ids.stopwatch.text = (
            'Timer :  %02d: %02d.[size=40]%02d[/size]'%
            (int(minutes), int(seconds),
             int(seconds * 100 % 100))
        )

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

LabelBase.register(name='Roboto',
                   fn_regular='./font/Roboto-Thin.ttf',
                   fn_bold='./font/Roboto-Medium.ttf')

 

타이머를 만들 때 생각할 점은 1분은 60초로 이루어져 있다는 부분이다. 여기서는 1초 단위로 계산을 하니까 만약 현재 100초라면 100/60 = 1 이 분, 나머지인 40이 초가 된다. 0.01 로 루프가 이루어졌다면 0.01을 100개 더하면 1초가 된다.(update 루프 100번)

 

참고로 여기서는 0.01을 사용하였지만 정밀성을 요하는 분야에 부동소수점을 사용하는 것은 좋지 않다. 그것 말고 정확성을 높이는 한가지 방법은 정수형을 사용하고 나중에 소수로 변환하는 방법이다.

 

0.01 초라면 1로 놓고 계산한 다음 최종 결과물에서 나누기 100을 한다. 1970년1월1일 부터 현재까지의 에픽타임은 밀리세컨드를 정수로 돌려준다. 부동소수점 정확성의 한계가 있다는 것을 알기 때문이다.

 

 

 

공유하기

facebook twitter kakaoTalk kakaostory naver band