본문 바로가기

IT/Python

[Python GUI Programming :: Tkinter] Hello, Again

대규모 프로그램을 작성할 때, 일반적으로 좋은 아이디어는 여러분의 코드를 하나이상의 클래스로 랩핑하는 것입니다.

이번 예제는 Matt Conway의 Hello, World 프로그램입니다.


- Second Tkinter Program

from tkinter import *
    def __init__(self, master):

        frame = Frame(master)
        frame.pack()

        self.button = Button(
            frame, text="QUIT", fg="red", command=frame.quit
            )
        self.button.pack(side=LEFT)

        self.hi_there = Button(frame, text="Hello", command=self.say_hi)
        self.hi_there.pack(side=LEFT)

    def say_hi(self):
        print("hi there, everyone!")

root = Tk()

app = App(root)

root.mainloop()
root.destroy() # optional; see description below


- 실행 결과



해당 프로그램을 실행했을 때, 오른쪽 버튼을 누르면 콘솔창에 "hi there, everyone!"이 출력될 것입니다. 

만약 왼쪽 버튼을 누르면 프로그램은 종료될 것입니다.


Note 

몇몇의 Python 개발 환경은 실행할 때 문제가 있는 경우가 있습니다. 이러한 문제는 일반적으로 어떠한 환경에서 Tkinter를 사용할 때 

일어납니다. 그리고 mainloop 호출과 quit 호출은 환경의 기대와 상호작용합니다. 서로 다른 환경들에서는 만약 여러분이 명시적으로 

destroy 메소드를 호출했을 때, 잘못 동작할 수 있습니다. 만약 예제들이 기대한데로 동작하지 않는다면, Tkinter의 Document를 참고해

서 여러분의 개발환경에 맞게 수정하셔야합니다.


- 코드 설명

위의 예제 어플리케이션은 클래스로 작성되었습니다. 생성자(__init__ 메소드)는 parent 위젯을 호출하고, 여러 child 위젯을 추가합니다.

생성자는 Frame 위젯을 만드는 것으로 시작합니다. 

frame은 간단한 컨테이너이며, 이번 예제의 경우에는 2가지의 위젯을 보유하는데 사용됩니다.


- 생성자 frame 선언부

def __init__(self, master):
        frame = Frame(master)
        frame.pack()


frame 인스턴스는 frame이라고 불리는 지역변수에 쌓입니다. 

위젯이 만들어 진 후에 즉시 pack 메소드를 호출하는 것을 통해서 frame이 보여질 수 있게 만듭니다.

그리고 2개의 버튼 위젯을 frame의 children으로 만듭니다.


- 생성자 button 위젯 선언부

self.button = Button(frame, text="QUIT", fg="red", command=frame.quit)
self.button.pack(side=LEFT)

self.hi_there = Button(frame, text="Hello", command=self.say_hi)
self.hi_there.pack(side=LEFT)


이번 예제에서 우리는 생성자에 여러가지 키워드 옵션을 전달합니다. 

첫 버튼은 "QUIT"라고 label되었고, 글자색은 빨간색으로 만들었습니다.

(fg는 foreground::전경의 약자입니다.) 두번째 버튼은 "Hello"라고 label되었습니다. 두 버튼 모두 command 옵션을 가지고 있습니다.

이러한 옵션은 특정한 함수를 호출하는데, 이번 예제에서는 버튼이 클릭되었을때, 특정한 메세지를 출력하게끔 만들었습니다.


버튼 인스턴스는 인스턴스 속성에 저장됩니다. 이번 예제에서는 side=LEFT라는 인자를 전달했습니다.

해당 인자는 parent frame에서 가능한 최대 왼쪽에 위치하게끔 만드는 인자입니다. 

따라서 첫번째 버튼은 parent의 왼쪽 모서리에 위치하게 됩니다.

그리고 두번째는 바로 첫번재 버튼 오른쪽에 위치하게 됩니다. 기본적으로 위젯은 해당 위젯의 parent에 상대적으로 pack됩니다.

만약 side 옵션이 주어지지 않는다면, 기본옵션은 TOP으로 설정되어있습니다.

"hello" 버튼은 다음과 같이 callback 함수가 주어졌습니다. 해당 콜백함수는 간단하게 메세지를 출력합니다.


- Callback 함수

def say_hi(self):
    print "hi there, everyone!"

마지막으로 우리는 위에서 설명한 App 클래스의 인스턴스를 통해서 어플리케이션을 만드는 script 수준의 코드를 제공합니다.


- Execute Application

root = Tk()

app = App(root)

root.mainloop()
root.destroy()

mainloop는 Tk event loop를 호출합니다. 해당 어플리케이션은 quit method가 호출될 때 까지 main loop에서 대기하게됩니다. 

어플리케이션 종료는 quit버튼을 클릭하거나 윈도우창을 닫으면 됩니다.

destroy는 확실한 개발 환경아래에서 예제가 실행되고있을 때만 요구됩니다. 

destroy는 event loop가 종료되었을 때, main window를 파괴할때 명시적으로 사용됩니다.

몇몇의 개발환경은 Python process가 완료되기 전까지 종료되지 않습니다. 


- more on widget references


 두번째 예제에서 frame 위젯은 frame이라는 지역변수로 선언됩니다. 그리고 2개의 button 위젯을 child로 찾습니다. 

여기에는 심각한 문제 하나가 있습니다.

만약 __init__함수가 종료되고, 변수 frame이 scope에서 벗어나게되면 어떻게 될까요?

다행이도 위젯 인스턴스를 계속해서 살려둘 필요는 없습니다. Tkinter는 자동으로 위젯 트리를 유지합니다. 

그래서 위젯 변수가 scope에서 사라지더라도 창이 사라지진 않습니다. 

Tkinter가 자동으로 위젯 트리를 유지하기때문에 우리는 명시적으로 destroy 메소드를 사용하여 제거해야합니다. 

만약에 여러분들이 위젯이 생성된 이후에 무엇인가를 하고싶다면, 해당 위젯변수를 scope에 살려두는것이 좋습니다.


여러분들이 위젯변수를 scope에 유지할 생각이 없다면, 코드를 다음과 같이 한줄로 작성할 수 도 있습니다.

Button(frame, text="Hello, command=self.hello").pack(size=LEFT)


해당 코드를 작성하실 때, 결과값은 None이기 때문에 받으실 필요가 없습니다.(pack 메소드의 return값은 None입니다.)

해당 코드를 안전하게 사용하고 싶다면, 다음과 같이 코드를 2줄로 나누는 것이 좋습니다.

w = Button(frame, text="Hello, command=self.hello")
w.pack()


- more on widget names

Tk 프로그래밍 경험이 있는 사람이 Tcl을 사용할 때, Tkinter의 위젯 이름에 대해서 겪는 혼란이 있습니다.

Tcl에서 여러분들은 개별의 위젯 이름을 사용했어야했을 겁니다. 

예를들어 Tcl 명령어 중 dialog를 parent로 하는 Button을 만들고, 이름을 ok라고 짓는다면 다음과 같이 코드를 작성할 수 있습니다.

#
button .dialog.ok
#


Tkinter에서는 다음과 같이 작성합니다.

#
ok = Button(dialog)
#

여기서 ok와 dialog는 위젯의 실제 이름이 아니라 위젯 인스턴스의 참조(reference)입니다. 

Tkinter 자체에서는 위젯의 고유이름이 필요하기 때문에, 자동으로 고유한 이름을 위젯에 할당합니다.

위의 경우에 dialog name은 아마도 다음과 같은 형태일 겁니다.

".1428748"


그리고 버튼의 이름 형태는 다음과 같을 겁니다.

"1428748.1432920"


만약에 여러분이 특정 Tkinter 위젯의 전체 이름을 알고싶다면, 위젯 인스턴스에 str() 메소드를 사용하여 출력하면

위젯 이름을 얻을 수 있습니다.

#
print str(ok)
>>> .1428748.1432920
#

만약에 여러분이 무언가를 출력하고자 할때, Python은 자동적으로 str() 메소드를 사용하여 출력합니다. 

하지만 name=ok 같은 연산은 그렇지 않으므로 명시적으로 str() 메소드를 사용하는게 좋습니다.

만약에 여러분들이 정말로 임의의 위젯이름이 필요하다면 위젯을 생성할 때, name 옵션 통해서 생성할 수 있습니다. 

이렇게 만든 위젯이름은 Tcl로 작성된 코드와 interface로 연결될 때 사용됩니다.


아까 따라한 예제와 같이 위젯 이름이 .dialog.ok (혹은 dialog 이름을 잊었다면 다음과 같이 생성될 수 있습니다. 1428748.ok)

#
ok = Button(dialog, name="ok")
#


위젯 이름을 작성하실 때는 숫자로만 되어있는 이름은 사용하지 마시길 권해드립니다.

Tkinter가 생성해내는 위젯 이름과 충돌할 가능성이 있습니다.


추가적으로 더 말씀드리면 위젯 이름은 생성할때만 줄 수 있으며, 위젯 생성 후에는 이름을 변경할 수 없습니다.


[Reference]

[1]. An Introduction to Tkinter(Work in Progress)