본문 바로가기

Manual

파이썬 GUI, tkinter를 이용한 시간 관리 프로그램

tkinter, notebook, Entry, Label, Radiobutton, Button, Treeview 등을 사용합니다.

 

다닐 알렉산드로비치가 쓴 《시간을 정복한 남자 류비셰프》를 보면 학술서적 70권, 단행본 100권 분량에 달하는 연구논문과 방대한 분량의 학술자료를 남긴 사람이 나온다. 그의 믿기지 않는 생산력의 비결은 시간 관리에 있었다. 사용하는 모든 시간을 통계 내어 관리하는 것이다. 

그는  일일이 기록하고 하나 하나 계산 했지만 항목별로 사용 시간을 통계 내어주는 프로그램이 있으면 좋겠다 생각하고, 시간 관리 프로그램을 만들어보게 되었다.

 

1. 메뉴탭 설정

2. [입력] 프레임 만들기

     1) 메뉴 탭 만들기     

     2) Label 만들기

     3) radio button 만들기

     4) Entry 만들기

     5) 배치

     6) 입력 값을 저장하는 함수 만들기

     7) 함수를 실행시키는 버튼 만들기

3. [통계] 프레임 만들기

     1) 메뉴 탭 추가하기

     2) 라벨과 엔트리 설정하기

     3) 함수 만들기

  • 데이터프레임(dataframe)을 리스트(list)로 변환
  • 항목별 통계내기
  • 소숫점자리수 정하기
  • 엔트리 창 데이터 지우기

     4) 버튼 만들기

     5) 배치하기

4. [목록] 프레임 만들기

  • Treeview

 


시간 사용 기록이 저장되는 텍스트 파일을 만들고, usedtime.txt로 저장한다.

usedtime.txt
0.00MB

1. 메뉴 탭 설정

필요한 모듈들을 import 한다.

GUI를 위해 tkinter와 tkinter.ttk를

텍스트 파일을 읽어 들이고 저장하기 위해 pandas

데이터프레임을 리스트로 변환해 treeview를 보여주기 위해 numpy가 필요하다.

import tkinter as tk
import tkinter.ttk as ttk
import pandas as pd
import numpy

창 이름 = tk.Tk() 형식으로 창을 만들고

창 이름.title("표시할 창의 제목") 형식으로 창의 제목을 만든다.

시간 관리 프로그램이므로 창의 제목으로  "Time Management"가 표시되게 한다.

시간 사용 데이터를 입력하는 [입력] 창, 사용한 시간을 항목별로 통계내는 [통계]창 그리고 사용한 시간들의 목록을 보여주는 [목록] 창을 만들려고 한다. 메뉴 탭을 만들기 위해 Notebook을 이용한다.

만들려는 노트북 이름.ttk.Notebook(노트북이 표시될 창 이름, width = 창의 넓이, height = 창의 높이) 형식을 사용하고

grid로 배치하고

text file을 읽어 와 데이터프레임 df에 저장한다. 

window = tk.Tk()
window.title("Time Management")
notebook=ttk.Notebook(window, width=400, height=500)
notebook.grid(row=0, column=0)
df= pd.read_csv('usedtime.txt', header = None, names=['date', 'sort', 'used_time','content'])

 

2. [입력] 프레임 만들기

     1) 메뉴 탭 만들기

프레임 이름 = tk.Fame(프레임을 만드는 창 이름) 형식으로 프레임을 만들고

notebook.add(프레임 이름, text = "메뉴 이름") 형식으로 데이터를 입력하기 위한 창을 만든다.

frame1=tk.Frame(window)
notebook.add(frame1, text="입력")

     2) Label 만들기

라벨이름 = tk.Label(프레임 이름, text="나타낼 문장", font=("글꼴 이름", 글꼴 크기), width=라벨넓이, anchor='정렬 방식') 형식으로 라벨을 설정한다. 'w'는 왼쪽 정렬, 'E'는 오른쪽 정렬.

label1=tk.Label(frame1, text="* 시간 사용 내역을 입력하고, 입력 버튼을 클릭하세요.", font=("나눔고딕", 14), width=50, anchor='w' )
label2=tk.Label(frame1, text="구           분", font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )

'입력' 메뉴 탭이 나타났지만, 라벨은 표시되지 않았다. 아직 위치를 정해 배치하지 않았기 때문이다. 

라벨, 라디오버튼, 입력창 등의 배치는 위치를 조정하기 쉽게 한꺼번에 모아서 하려고 한다.

     3) radio button 만들기

사용한 시간을 몇 가지 항목으로 나누어 통계내려 한다. 나의 시간이 어느 한쪽에 편중되지 않도록 관리하기 위해서이다. 항목을 미리 정해 두고 라디오버튼으로 선택하게 하면 편리하겠다.

라디오버튼을 설정하기 전에 라디오버튼에서 받아올 변수를 설정한다.

문자를 받아오기 위해 변수명 = tk.StringVar() 형식을 사용한다.

라디오버튼은 라디오버튼 이름 = tk.Radiobutton(프레임이름, text='표시할 글자', value='선택 시 가져올 문자', font=("글꼴이름", 글자 크기), variable=변수명, width=넓이, anchor='정렬방식') 형식으로 설정한다.

sort=tk.StringVar()

rad1 = tk.Radiobutton(frame1, text='그림', value='drawing',font=("나눔고딕", 12), variable=sort, width=10, anchor='w' )
rad2 = tk.Radiobutton(frame1, text='건강', value='health',font=("나눔고딕", 12), variable=sort,width=10,anchor='w' )
rad3 = tk.Radiobutton(frame1, text='뉴스', value='news',font=("나눔고딕", 12), variable=sort,width=10,anchor='w' )
rad4 = tk.Radiobutton(frame1, text='쓰기', value='writing',font=("나눔고딕", 12), variable=sort,width=10,anchor='w' )
rad5 = tk.Radiobutton(frame1, text='독서', value='reading',font=("나눔고딕", 12), variable=sort,width=10,anchor='w')
rad6 = tk.Radiobutton(frame1, text='가사', value='housework',font=("나눔고딕", 12), variable=sort,width=10,anchor='w')
rad7 = tk.Radiobutton(frame1, text='신앙', value='spiritual',font=("나눔고딕", 12), variable=sort,width=10,anchor='w')
rad8 = tk.Radiobutton(frame1, text='오락', value='joy',font=("나눔고딕", 12), variable=sort,width=10,anchor='w')

     4) Entry 만들기

날짜, 사용 시간, 내용을 입력하는 입력 창을 만들기 위해 라벨과 엔트리를 이용한다. 

엔트리는 엔트리 이름 =tk.Entry(프레임 이름, width=넓이) 형식으로 민든다.

label3=tk.Label(frame1, text="날짜(YYMMDD)", font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label4=tk.Label(frame1, text="사용 시간(분)", font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label5=tk.Label(frame1, text="내           용", font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )

date =tk.Entry(frame1, width=12)
used_time =tk.Entry(frame1, width=12)
content =tk.Entry(frame1, width=26)

     5) 배치

지금까지 만든 항목들을 정교하게 배치하기 위해 항목이름. place(x=좌표값, y=좌표값) 형식을 이용한다.

label1.place(x=20, y=10) 
label2.place(x=20, y=50) 
rad1.place(x=130, y=80)  
rad2.place(x=200, y=80)
rad3.place(x=270, y=80)
rad4.place(x=340, y=80)
rad5.place(x=130, y=50)
rad6.place(x=200, y=50)
rad7.place(x=270, y=50)
rad8.place(x=340, y=50)
label3.place(x=20, y=120) 
date.place(x=130, y=120) 
label4.place(x=20, y=160) 
used_time.place(x=130, y=160) 
label5.place(x=20, y=200) 
content.place(x=130, y=200) 

각 라벨과 입력 창 등의 크기나 위치를 마음에 들게 수정고 마무리 한다.

     6) 입력 값을 저장하는 함수 만들기

입력한 값을 받아 와 데이터프레임에 새로운 열로 추가하고, 

다음 입력을 위해 입력 창을 지우는 함수를 get_value라는 이름으로 만들려고 한다. 

 

입력받은 값들은 항목이름.get() 형식으로 가져온다.

먼저, 날짜와 사용 시간 Entry에 입력된 값을 정수로 바꿔 변수에 저장한다. 

radiobutton으로 입력받은 값과 내용으로 입력 받은 Entry 값은 문자로 저장한다.

Def get_save():
    ndate=int(date.get())
    nsort=sort.get()
    nused_time=int(used_time.get()) 
    ncontent=content.get()

추가하는 새로운 row의 인덱스는 현재 데이터프레임의 마지막 인덱스 다음 숫자가 되어야 한다. 인덱스는 0부터 시작하므로, 현재 데이터프레임의 row 개수를 구하면 그 숫자가 바로 새 row의 인덱스가 된다.

현재 row의 수를 len(데이터프레임 이름.index)를 이용해 구하여 변수 i에 저장한다.

입력받은 값들로 리스트를 만들어 데이터프레임 이름.loc(인덱스번호) = [추가하는 row 데이터 리스트] 형식으로 추가한다.

    i=len(df.index)
    df.loc[i]=[ndate, nsort, nused_time, ncontent]

row가 추가된 데이터 프레임은 데이터프레임 이름.to_csv('텍스트파일 이름.txt', index=False, header=False) 형식으로 텍스트 파일로 저장한다.

    df.to_csv('TimeRecord.txt',index=False, header=False)

이제 입력 창들을 지우기 위해  엔트리이름. delete(0, tk.END)를 이용한다.

    date.delete(0,tk.END)
    used_time.delete(0,tk.END)
    content.delete(0,tk.END)

     7) 함수를 실행시킬 버튼 만들기

마지막으로 입력한 데이터를 추가하고 저장한 다음 입력 창을 지우는 함수를 실행시키는 버튼을 만들어 배치하면 프레임1의 작업이 끝난다.

버튼은 버튼이름 = tk.Button(프레임이름, text="나타낼글",font=("글꼴이름", 크기), width=넓이, fg="글자색", highlightthickness=1, command=실행할 함수이름) 형식으로 만든다.

프레임에 배치하는 방법은 다른 위젯들과 같이 place를 이용한다.

button_save = tk.Button(frame1, text="입  력",font=("나눔고딕", 14), width=10,
                       fg="red", highlightthickness=1, command=get_save)
button_save.place(x=300, y=250)

 

 

3. [통계] 프레임 만들기

     1) 메뉴 탭 추가하기

'frame2'라는 이름으로 두 번째 프레임을 만들고, [통계] 메뉴 탭이 나타나게 한다.

데이터프레임을 날짜순으로 정렬한다. 

### frame2 ###
frame2=tk.Frame(window)
notebook.add(frame2, text="통계")

df = df.sort_values(by = ['date'], ascending = [True])

< [통계] 탭이 추가 되었다 >

     2) Label과 Entry 설정하기

통계 시작일과 끝날을 입력받기 위해 안내글을 쓰고, 항목별로 통계 결과가 표시되는 라벨을 설정한다. 통계 결과는 단순히 사용한 시간을 합친 값과 전체 사용 시간에 대한 비율을 백분율(%)로 표시하려고 한다,

label6=tk.Label(frame2, text="언제부터",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label7=tk.Label(frame2, text="언제까지",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label8=tk.Label(frame2, text="흘러간 시간(분)",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label9=tk.Label(frame2, text="사용한 시간(분)",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label10=tk.Label(frame2, text="읽기(분/%)",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label11=tk.Label(frame2, text="쓰기(분/%)",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label12=tk.Label(frame2, text="뉴스(분/%)",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label13=tk.Label(frame2, text="건강(분/%)",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label14=tk.Label(frame2, text="신앙(분/%)",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label15=tk.Label(frame2, text="그림(분/%)",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label16=tk.Label(frame2, text="오락(분/%)",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )
label17=tk.Label(frame2, text="가사(분/%)",font=("나눔고딕", 12), bg='lightgray', width=12, anchor='center' )

통계 기간을 입력받을 칸과 통계 결과가 표시될 칸을 만든다. 

입력 받을 Entry는 entry1,2로 하고

통계 결과가 표시되는 Entry는 text_' ' 형식으로 구분하였다.

entry1=tk.Entry(frame2, width=12)
entry2=tk.Entry(frame2, width=12)

text_pt=tk.Entry(frame2, width=6)
text_ut=tk.Entry(frame2, width=6)
text_rt=tk.Entry(frame2, width=6)
text_wt=tk.Entry(frame2, width=6)
text_nt=tk.Entry(frame2, width=6)
text_ht=tk.Entry(frame2, width=6 )
text_st=tk.Entry(frame2, width=6 )
text_dt=tk.Entry(frame2, width=6)
text_jt=tk.Entry(frame2, width=6 )
text_hwt=tk.Entry(frame2, width=6)
text_ut2=tk.Entry(frame2, width=6)  
text_rt2=tk.Entry(frame2, width=6)
text_wt2=tk.Entry(frame2, width=6)
text_nt2=tk.Entry(frame2, width=6)
text_ht2=tk.Entry(frame2, width=6 )
text_st2=tk.Entry(frame2, width=6 )
text_dt2=tk.Entry(frame2, width=6)
text_jt2=tk.Entry(frame2, width=6 )
text_hwt2=tk.Entry(frame2, width=6)

     3) 함수 만들기

          (1) count_pt()

통계 시작일과 끝날을 읽어 들여 정수로 변환해 변수에 저장하고

def count_pt():
    e_date=int(entry2.get())
    s_date=int(entry1.get())

입력받은 끝날과 시작날로 부터 전체 시간을 분 단위로 계산

    global total_min
    total_min = (e_date - s_date+1)*24*60

text_pt에 입력한다.

    text_pt.insert(0, total_min)

통계 기간 동안의 데이터만 ndf라는 데이터프레임에 저장하고

    global ndf
    ndf=df[(df.date >= s_date) & (df.date <= e_date)]

가가 항목별로 통계를 내는 sum_time() 함수를 호출한다.

    sum_time()

          (2) count_pt()

각 항목별로 사용한 시간을 계산하여 입력할 변수를 선언한다.

def sum_time():
    rt=hwt=nt=dt=ht=wt=st=jt=0

통계기간 데이터가 저장된 데이터프레임의 열들을 리스트로 변환해 df_rows에 저장한다.

변수명 = 데이터프레임명.to_numpy().tolist() 형식을 사용한다.

    df_rows=ndf.to_numpy().tolist()

df_rows의 각 row에 대해

항목값이 들어있는 1번 항의 값을 비교하여 각 항목별 사용 시간(2번 항 값)이 더해지도록 

for ~in ~와 if, elif를 이용한다.

    for row in df_rows:
        if row[1] == "reading":
            rt = rt + row[2]
        elif row[1] == "health":
            ht = ht + row[2]
        elif row[1] == "housework":
            hwt = hwt + row[2]
        elif row[1] == "news":
            nt = nt + row[2]
        elif row[1] == "joy":
            jt = jt + row[2]
        elif row[1] == "drawing":
            dt = dt + row[2]
        elif row[1] == "writing":
            wt = wt + row[2]
        elif row[1] == "spiritual":
            st = st + row[2]

전체 사용 시간을 계산하기 위해 각 항목별로 계산한 시간 값을 모두 더해 ut에 저장한다.

    ut = rt+hwt+nt+dt+ht+wt+st+jt

통계 기간 동안 실제로 사용한 시간은 얼마나 되는지 백분율로 계산하여 ut2에 저장한다.

소수점 첫째 자리까지 계산하도록 

round(계산식, 소수점 자릿수) 형식을 사용한다.

    ut2 = round((ut/total_min)*100, 1)

각 항목별로 사용한 시간에서 차지하는 비율을 백분율로 계산하여 변수에 저장한다.

    ht2 = round((ht/ut)*100, 1)
    nt2 = round((nt/ut)*100,1)
    jt2 = round((jt/ut)*100,1)
    dt2 = round((dt/ut)*100,1)
    wt2 = round((wt/ut)*100,1)
    st2 = round((st/ut)*100,1)
    hwt2 = round((hwt/ut)*100,1)
    rt2 = round((rt/ut)*100,1)

계산한 값들이 해당하는 엔트리에 입력되게 한다.

    text_ut.insert(0, ut)
    text_ht.insert(0, ht)
    text_nt.insert(0, nt)
    text_jt.insert(0, jt)
    text_dt.insert(0, dt)
    text_wt.insert(0, wt)
    text_st.insert(0, st)
    text_hwt.insert(0, hwt)
    text_rt.insert(0, rt)

    text_ut2.insert(0, ut2) 
    text_ht2.insert(0, ht2)
    text_nt2.insert(0, nt2)
    text_jt2.insert(0, jt2)
    text_dt2.insert(0, dt2)
    text_wt2.insert(0, wt2)
    text_st2.insert(0, st2)
    text_hwt2.insert(0, hwt2)
    text_rt2.insert(0, rt2)

          (3) del_data()

다른 기간에 대한 통계를 낼 수 있도록 가가 엔트리에 입력한 값이나 입력된 값들을 삭제하는 함수를 만든다.

엔트리명.delete(처음위치, 끝위치) 형식을 사용한다.

각 엔트리에 입력된 값을 처음부터 끝까지 다 지우기 위해 0과 tk.END를 입력한다.

def del_data():
    entry1.delete(0, tk.END)
    entry2.delete(0, tk.END)
    text_pt.delete(0, tk.END)
    text_nt.delete(0, tk.END)
    text_ut.delete(0, tk.END)
    text_ht.delete(0, tk.END)
    text_hwt.delete(0, tk.END)
    text_jt.delete(0, tk.END)
    text_wt.delete(0, tk.END)
    text_st.delete(0, tk.END)
    text_dt.delete(0, tk.END)
    text_rt.delete(0, tk.END)
    text_ut2.delete(0, tk.END)
    text_nt2.delete(0, tk.END)
    text_ut2.delete(0, tk.END)
    text_ht2.delete(0, tk.END)
    text_hwt2.delete(0, tk.END)
    text_jt2.delete(0, tk.END)
    text_wt2.delete(0, tk.END)
    text_st2.delete(0, tk.END)
    text_dt2.delete(0, tk.END)
    text_rt2.delete(0, tk.END)

     4) 버튼 만들기

함수들을 실행시킬 버튼을 만든다. 계산하여 통계를 구하는 버튼과 입력된 데이터를 삭제하는 버튼을 만들었다.

button_count = tk.Button(frame2, text="계산하기",font=("나눔고딕", 14), width=5,
                       fg="red", highlightthickness=1, command=count_pt)
button_del = tk.Button(frame2, text="지우기",font=("나눔고딕", 14), width=5,
                       fg="red", highlightthickness=1, command=del_data)

     5) 배치하기

각 항목들을 적당한 위치에 배치하기 위해 항목.place(x=좌표값, y=좌표값) 형식을 사용한다.

label6.place(x=20, y=10)
label7.place(x=20, y=40)
label8.place(x=20, y=90)
label9.place(x=20, y=120)
label10.place(x=20, y=170)
label11.place(x=20, y=200)
label12.place(x=20, y=230)
label13.place(x=20, y=260)
label14.place(x=20, y=290)
label15.place(x=20, y=320)
label16.place(x=20, y=350)
label17.place(x=20, y=380)

entry1.place(x=130, y=10)
entry2.place(x=130, y=40)

text_pt.place(x=130, y=90)
text_ut.place(x=130, y=120)
text_rt.place(x=130, y=170)
text_wt.place(x=130, y=200)
text_nt.place(x=130, y=230)
text_ht.place(x=130, y=260)
text_st.place(x=130, y=290)
text_dt.place(x=130, y=320)
text_jt.place(x=130, y=350)
text_hwt.place(x=130, y=380)

text_ut2.place(x=200, y=120) 
text_rt2.place(x=200, y=170)
text_wt2.place(x=200, y=200)
text_nt2.place(x=200, y=230)
text_ht2.place(x=200, y=260)
text_st2.place(x=200, y=290)
text_dt2.place(x=200, y=320)
text_jt2.place(x=200, y=350)
text_hwt2.place(x=200, y=380)

button_count.place(x=300, y=40)
button_del.place(x=300, y=420)

 

3. [목록] 프레임 만들기

     1) 메뉴 탭 추가하기

frame3=tk.Frame(window)
notebook.add(frame3, text="목록")

     2) 목록 표시하기

Treeview를 이용하여 입력한 시간 사용 내역을 볼 수 있게 한다.

트리뷰명 = tk.ttk.Treeview(프레임명, column=["칼럼명1", "칼럼명2", ... ], displaycolumns=[표시 할 순서대로 칼럼명 나열"칼럼명", "칼럼명", ,,,] 형식을 사용한다.

treeview = tk.ttk.Treeview(frame3, column=["date", "sort", "used_time", "content"],
                           displaycolumns=["date", "sort", "content", "used_time"])

treeview.pack()

칼럼별로 크기와 정렬 방식 등을 설정하고,

트리뷰가 표시되게 한다.

treeview.column("date", width=70, anchor="center")
treeview.heading("date", text="날짜", anchor="center")

treeview.column("sort", width=100, anchor="center")
treeview.heading("sort", text="항목", anchor="center")

treeview.column("content", width=150, anchor="w")
treeview.heading("content", text="내용", anchor="center")

treeview.column("used_time", width=50, anchor="center")
treeview.heading("used_time", text="시간(분)", anchor="center")

treeview["show"] = "headings"

통계기간이 아닌 전체 입력 데이터를 볼 수 있도록 처음 데이터프레임(df)으로 리스트를 만들어 row_list에 저장한다.

row_list의 각 열(row)들을 treeview에 입력하기 위해 for i in range()를 이용한다. 

열 수는 len(리스트명)으로 구한다.

row_list = df.values.tolist()

for i in range(len(row_list)):
    treeview.insert("", "end", text="", values=row_list[i], iid=i)

 

 

728x90