티스토리 뷰

Python/tkinter

Python,tkinter 입문

hwangyoungjae 2016. 5. 25. 18:48
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Python, tkinter 간단히 사용하기 001

python3.2.2

>> 간단한 창띄우기 <<

1

2

3

4

5

# -*- coding : cp949 -*-

from tkinter import *

 

root=Tk()

root.mainloop()

실행


위에서 생성된 최상위창은 tkinter어플리케이션에서 가장 높은 수준의 GUI구성요소이며,

최상위 창의 이름은 'root'로 하는것이 관례적이다.

 

 

>> packing <<

아래 예제에서 tkinter프로그래밍의 세가지 주요 개념이 나온다.

>GUI객체를 만들어서 그의 부모객체와 연관시키기

>packing

>그릇(containers)와 창부품(widgets)

1

2

3

4

5

6

7

8

9

# -*- coding : cp949 -*-

from tkinter import *

 

root = Tk()

 

F = Frame(root) #root F의 논리적은 부모자식관계 정의

F.pack() #F packing하여 시각적으로 보여지도록 함

 

root.mainloop()

실행

 


tkinter의 최소,최대크기를 지정하지 않으면창부품의 여부에 따라 창크기가 자동으로 조절된다.

6 : root F는 논리적인 부모자식관계가 정의된다.

7 : 논리적인 부모관계에서 시각적인 관계로 설정한다.

어플리케이션에 보여지도록 설정한다.

 

 

>> 창부품 꾸리기 <<

아래 예제에서는 창부품을 만들어서 그것을 F라는 프레임에 집어넣는다.

이번 예제에서 사용될 창부품은 Button이 사용됐으며버튼창부품에는 크기전경색배경색표시텍스트테두리모양 등의 속성이 있는데,

이는 창부품의 지역이름공간에 dict형으로 저장되어 있다버튼창부품외에 다른 창부품역시 속성은 창부품지역이름공간에 dict형으로 저장되어진다.

 

아래 예제는 button1 창부품에 2(표시텍스트배경색)의 속성만 설정하였다.

이전에 설명한것과 마찬가지로 Button을 생성한 후엔 반드시 packing을 해줘야만 어플리케이션에서 보여질수 있다.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

# -*- coding : cp949 -*-

from tkinter import *

 

root = Tk()

 

F = Frame(root)

F.pack() #packing

 

button1=Button(F)

button1['text']="Hello world!"

button1['background']='green'

button1.pack() #packing

 

root.mainloop()

실행


button1버튼이 생성되자 F의 공간이 자동으로 늘어난것을 알수 있다.

위 예제의 부모자식 구조는 root - F - button1 이라 할수 있겠다.

root의 자식은 F이며F의 자식은 button1이 되는것이다.

 

 

>> 클래스 구조 <<

왜 어플리케이션을 클래스로 구성하는가?

프로그램에 클래스 구조를 사용하는 이유는 프로그램을 제어하기가 더 쉽기 때문이다.

큰 프로그램일수록 클래스로 구축되었을때가 그렇지 않은경우에 비해 이해하기가 쉽다.

아래 예제는 위의 예제와 기능적으론 전혀 차이가 없지만코드상으론 클래스형태로 구현된것이다.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

# -*- coding : cp949 -*-

from tkinter import *

 

class MyApp:

    def __init__(self,Parent):

        self.F=Frame(Parent)

        self.F.pack()

       

        self.button1=Button(self.F)

        self.button1["text"]="Hello, World!"

        self.button1["background"]="green"

        self.button1.pack()

root = Tk()

myapp=MyApp(root)

root.mainloop()

실행


 

 

>> 속성 설정하기 <<

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

# -*- coding : cp949 -*-

from tkinter import *

 

class MyApp:

    def __init__(self,Parent):

        self.F=Frame(Parent)

        self.F.pack()

       

        self.button1=Button(self.F)

        self.button1["text"]="Hello, World!"

        self.button1["background"]="green"

        self.button1.pack()

       

        self.button2=Button(self.F)

        self.button2.configure(text="off to join the circus")

        self.button2.configure(background="tan")

        self.button2.pack()

       

        self.button3=Button(self.F,text="Goodbye",background="red")

        self.button3.pack()

 

root = Tk()

myapp=MyApp(root)

root.mainloop()

실행


위의 예제를 버튼을 3개를 만들었다.

버튼의 속성을 설정하는 방법이 약간씩 다른데,

button1버튼은 dict로 된 이름공간을 이용했고,

button2버튼은 configure메서드를 이용했고,

button3버튼은 생성함과 동시에 속성을 설정하였다.

 

또 한가지 주목할점은 버튼을 추가한 순서대로 차곡차곡 쌓이며 보여지는것을 알수 있다.

 

 

>> 정렬 <<

packing은 구성요소의 시각적 관계를 제어하는 방법이다.

pack 메서드는 side옵션을 사용하여 버튼의 위치를 정렬을 조절할수 있다.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

# -*- coding : cp949 -*-

from tkinter import *

 

class MyApp:

    def __init__(self,Parent):

        self.F=Frame(Parent)

        self.F.pack()

        

        self.button1=Button(self.F)

        self.button1["text"]="BUTTON 1"

        self.button1["background"]="green"

        self.button1.pack(side=LEFT)

       

        self.button2=Button(self.F)

        self.button2.configure(text="BUTTON 2")

        self.button2.configure(background="tan")

        self.button2.pack(side=LEFT)

       

        self.button3=Button(self.F,text="BUTTON 3",background="red")

        self.button3.pack(side=LEFT)

       

        self.button4=Button(self.F,text="BUTTON 4",background="cyan")

        self.button4.pack(side=LEFT)

 

root = Tk()

myapp=MyApp(root)

root.mainloop()

실행


 

위에서 사용된 LEFT(RIGHT,TOP,BOTTOM) tkinter내에 정의되어 있다.

, tkinter.LEFT라는 뜻이다.

"from tkinter import *" 와 같은 형식으로 tkinter모듈을 임포트 하였으므로,

tkinkter을 붙이지 않고 위처럼 바로 사용할수 있는것이다.

수직적동선에는 TOP BOTTOM이 있으며

수평적동선에는 LEFT RIGHT가 있다.

버튼이 출력되는 순서는 packing한 순서이며,

button1보다 button2를 먼저 packing했다면 button2가 먼저 보여질것이다.

 

그러나 한 그릇안에서 이런식으로 동선을 섞어서 사용하는 것은 좋은 생각이 아니다.

동선을 섞어 쓰게 되면최종적으로 어떻게 보일지 예측하기가 힘들고,

창크기를 조절할때 GUI가 일그러질수도 있기 때문이다.

그래서 좋은 디자인 습관은 같은 그릇안에서 절대로 동선을 섞어 쓰지 않는 것이다.

복잡한 GUI를 다루는 방법으로 여러 동선을 사용하고 싶을땐그릇안에 그릇을 내포시키는 것이다.

 

 

>> 사건 묶기 <<

사건묶기(binding)이란 다음과 같은 객체들 사이의 관계 또는 연결을 정의하는 과정이다.

>창부품

ex) Button

>사건

ex) <Button-1>(마우스왼쪽클릭)

>사건처리자

ex) Button1Click메서드

이제 버튼에게 일을 시킬 시간이다.

아래 예제는 버튼 창부품을 생성하고버튼창부품에 사건(<Button-1>)과 사건처리자(button1Click)를 묶어줌으로써

버튼을 클릭하였을때 지정한 사건처리자가 동작하게끔 한것이다.

※ "<Button-1>" 은 마우스왼쪽 클릭을 의미한다.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

# -*- coding : cp949 -*-

from tkinter import *

 

class MyApp:

    def __init__(self,Parent):

        self.myParent=Parent #부모즉 루트를 기억

        self.F=Frame(Parent)

        self.F.pack()

       

        self.button1=Button(self.F,text="OK",background="green")

        self.button1.pack(side=LEFT)

        self.button1.bind("<Button-1>",self.button1Click)

       

        self.button2=Button(self.F,text="Cancel",background="red")

        self.button2.pack(side=RIGHT)

        self.button2.bind("<Button-1>",self.button2Click)

       

    def button1Click(self,event):

        if self.button1["background"]=="green":

            self.button1["background"]="yellow"

        else:

            self.button1["background"]="green"

   

    def button2Click(self,event):

        self.myParent.destroy()

               

root = Tk()

myapp=MyApp(root)

root.mainloop()

실행


ok버튼을 클릭하면 버튼의 색이 바뀌고, Cancel버튼을 클릭하면 destroy()메서드를 호출하여 창이 닫히게된다.


 

※ 버튼이라는 단어는 완전히 다른 두가지를 지칭하기 위해 사용될수 있다.

첫번째는 버튼창부품이고(화면상에 보이는 버튼),

두번째는 마우스의 버튼이다(마우스에서 손가락으로 누르는 버튼)

따라서 이런 혼란을 피하기 위해 버튼이라고 하지 않고"버튼창부품" 과 "마우스버튼"이라고 구분하여 설명하도록 하겠다.

 

12 : button1버튼창부품에서 일어날 사건을 bind메서드를 이용하여 묶어주었다.

<Button-1> "왼쪽마우스클릭"사건이다왼쪽마우스를 클릭하면 사건이 발생하는것이다.

여기에 Button1Click라는 버튼창부품색을 변경해주는 함수를 묶어줌으로써

OK버튼창부품을 클릭하면 OK버튼창부품의 색이 변경된다.

 

24 : Cancel버튼창부품을 왼쪽마우스로 클릭하면 button2Click함수가 동작하고,

button2Click함수는 myapp의 부모창인 root창의 destroy()메서드를 호출하였다.

이렇게 하면 root아래의 모든 자식과 자손이 연달아 종료된다.

, GUI의 모든 부분이 소멸된다.

이런식으로 동작하려면 myapp은 자신의 자손이 누구인지 알아야 한다 그래서 6행에서 myapp이 그의 부모를 기억하도록 한것이다.

 

 

>> 초점 <<

위의 예제에서는 마우스로 클릭하면 버튼에게 일을 시킬수 있었다.

다음 프로그램에서는 마우스뿐만 아니라 키보드에도 반응시키는 방법을 다루도록 하겠다.

먼저, "입력초점(input focus)" 또는 그냥 단순하게 "초점(focus)"이라는 개념을 알필요가 있다.

"초점(focus)" GUI상의 창부품들에게 키보드 사건을 볼수 있도록 해준다.

한 순간에 오직 한 창부품만 초점을 가진다그리고 초점을 가진 창부품만 키보드사건을 보고 반응할수 있다.

창부품에 초점을 설정하는 일은 그 초점을 창부품에 부여하는것이다.

 

예를 들어 다음 프로그램에서는 OK Cancel이라는 2개의 버튼부품창이 있다.

Tab키를 누르면 OK버튼과 Cancel버튼을 번갈아가며 초점이 보여지게 된다.

따라서 button1에 초점을 맞추고 <Return>키를 누르게 되면 사건처리자(button1Click)가 동작하게 된다.

※ <Return> Enter키를 누르는것을 의미한다.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

# -*- coding : cp949 -*-

from tkinter import *

 

class MyApp:

    def __init__(self,parent):

        self.myparent=Frame(parent)

        self.F=Frame(parent)

        self.F.pack()

       

        self.button1=Button(self.F)

        self.button1.configure(text="ok",background="green")

        self.button1.pack(side=LEFT)

        self.button1.bind("<Button-1>",self.button1Click)

        self.button1.bind("<Return>",self.button1Click)

       

        self.button2=Button(self.F)

        self.button2.configure(text="cancel",background="red")

        self.button2.pack(side=RIGHT)

        self.button2.bind("<Button-1>",self.button2Click)

        self.button2.bind("<Return>",self.button2Click)

       

    def button1Click(self,event):

        report_event(event)

        if self.button1["background"]=="green":

            self.button1["background"]="yellow"

        else:

            self.button1["background"]="green"

   

    def button2Click(self,event):

        report_event(event)

        self.myparent.destroy()

 

def report_event(event):

    event_name={"2":"KeyPress","4":"ButtonPress"}

    print("Time:",str(event.time))

    print("EventType="+str(event.type))

    print(event_name[str(event.type)])

    print("EventWidgetID=",event.widget)

    print("EventKeySymbol=",event.keysym)

    print()

   

 

root=Tk()

MyApp(root)

root.mainloop()

실행


 

 

>> 명령어 묶기(Command Binding) <<

앞의 프로그램에서 사건묶기를 소개했다 사건처리자를 창부품에 묶는 방법이 한가지 더 있는데, 바로 명령어묶기(Command Binding)이다.

지난 프로그램에서는 "<Button-1>"을 버튼창부품에 묶었다. "<Button-1>"은 "<ButtonPress>"와 같은 사건을 의미한다.

또한 마우스클릭에 해당하는 사건에는 "<ButtonPress>" "<ButtonRelease>"가 있는데 이 두사건은 큰 차이가 있다.

"<ButtonPress>"는 마우스를 누르는 동작이고,

"<ButtonRelease>"는 마우스를 누른후 떼는 동작이다.

위 두가지를 구별하는 이유는 드래그앱드랍을 지원하기 위해서 이다.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

# -*- coding : cp949 -*-

from tkinter import *

 

class MyApp:

    def __init__(self, parent):

        self.myParent=parent

        self.myContainer1=Frame(parent)

        self.myContainer1.pack()

       

        self.button1=Button(self.myContainer1, command=self.button1Click)

        self.button1.configure(text="OK",background="green")

        self.button1.pack(side=LEFT)

        self.button1.focus_force()

       

        self.button2=Button(self.myContainer1, command=self.button2Click)

        self.button2.configure(text="Cancel",background="red")

        self.button2.pack(side=RIGHT)

       

    def button1Click(self):

        print("Button1Click event handler")

        if self.button1["background"]=="green":

            self.button1["background"]="yellow"

        else:

            self.button1["background"]="green"

           

    def button2Click(self):

        print("Button2Click event handler")

        self.myParent.destroy()

 

root=Tk()

MyApp(root)

root.mainloop()

실행결과를 보면 이전 예제와 크게 다르지 않지만

행위는 다르다.

 

위의 예제는 마우스버튼을 누르기만 하면 동작을 한다, ButtonPress상태에서 메시지가 출력되었다.

또한버튼에 초점을 맞춘후 Enter키를 눌렀을경우, <Return>액션에 대한 메시지가 출력되었다.

 

하지만 이번 예제는 버튼자체에 명령어를 묶음으로서

클릭(ButtonRelease)와 스페이스에 대해 함꼐 동작을 하는걸 확인할수 있다.

버튼자체에 명령어를 묶어주는것을 명령어 묶기 라고 할수 있다.

 

 

>> 사건묶기와 명령어묶기의 차이 <<

바로 위 예제에서는 Tab키를 이용하여 초점을 "OK"버튼창부품에 두고스페이스바를 눌러서 버튼색이 바뀌도록 할수는 있지만엔터키를 투르면 아무 효과도 없다.

그 이유는 버튼창부품에 대하여 "command"옵션에 마우스사건 인지뿐만 아니라 키보드사건에 대한 인지가 제공되기 때문이다.

창 부품이 기다리는 키보드사건은 "<Return>"키가 아니라 스페이스바이다.

명령어묶기로 사건처리자를 묶었다면 스페이스바를 누르면 동작을 하겠지만엔터키는 아무 효과가 없다.

 

따라서 명령어묶기를 사용할 생각이라면 정확하게 무엇에 묶고 싶은지 잘 이해하는것이 좋다.

다시 말해무슨 키보드/마우스 사건이 명령어를 호출하는지 정확하게 이해하는 것이 좋다는것이다.

 

또한 모든 창부품이 "command"옵션을 제공하는것은 아니다다양한 버튼창부품들(라디오버튼,체크버튼 등) "command"옵션이 제공된다.

그외 다른것들은 비슷한옵션(예를들어 "scrollcommand")를 제공하기도 한다.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

# -*- coding : cp949 -*-

from tkinter import *

 

class MyApp:

    def __init__(self, parent):

        self.myParent=parent

        self.myContainer1=Frame(parent)

        self.myContainer1.pack()

       

        self.button1=Button(self.myContainer1, command=self.button1Click)

        self.button1.bind("<Return>",self.button1Click_a)

        self.button1.configure(text="OK", background="green")

        self.button1.pack(side=LEFT)

        self.button1.focus_force() #실행시 기본 초점을 button1에 맞춤

       

        self.button2=Button(self.myContainer1, command=self.button2Click)

        self.button2.bind("<Return>"self.button2Click_a)

        self.button2.configure(text="Cancel", background="red")

        self.button2.pack(side=RIGHT)

       

    def button1Click(self):

        print("button1Click event handler")

        if self.button1["background"]=="green":

            self.button1.configure(background="yellow")

        else:

            self.button1.configure(background="green")

       

    def button2Click(self):

        print("button2Click event handler")

        self.myParent.destroy()

        

    def button1Click_a(self,event):

        print("button1Click_a event handler (a wrapper)")

        self.button1Click()

       

    def button2Click_a(self,event):

        print("button2Click_a event handler (a wrapper)")

        self.button2Click()

   

root=Tk()

MyApp(root)

root.mainloop()

위 예제는 button1버튼창부품에 button1Click메서드를 명령어묶기로 묶은 후

<Return>키에 button1Click_a메서드를 사건묶기로 묶어 주었다.

따라서 클릭,엔터,스페이스는 모두 같은동작을 하지만엔터는 button1Click_a메서드를 통하여 button1Click메서드를 호출하는 형태이다.

 

위 예제를 통해 명령어묶기와 사건묶기의 차이를 알수있다.

명령어묶기는 버튼창부품에 미리 정해져있는 사건(클릭과 스페이스)에 사건처리자를 지정해주는것이고,

사건묶기는 원하는 사건에 원하는 사건처리자를 연결하여 창부품에 지정하는것이라고 할수 있다.

 

 

>> 정보 공유하기 <<

지난 예제들에서는 사건처리자에게 실제로 일을 시키는 방법들을 알아보았다.

아래 예제를 통하여 사건처리자 사이에 정보를 공유하는 법에 대하여 알아보도록 하겠다.

 

사건처리자에게 어떠한 일을 시키고 그 결과를 다른 사건처리자와 공유하고 싶은 다양한 상황이 있다.

일반적인 패턴은 어플리케이션에 두 세트의 창부품이 있다는것이다한 세트의 창부품은 일정한 정보를 만들고 선택하고,

다른 세트의 창부품은 그 정보를 가지고 일을 한다.

 

예를 들어 한 창부품에서 사용자는 파일리스트로부터 파일을 선택하고다른창부품들은 고른 그 파일에 대하여 다양한 연산을 할수 있다.

파일열기삭제복사이름바꾸기 등등

또는 한 세트의 창부품은 어플리케이션에 다양한 환경구성을 설정하고또다른 세트는 (Save Cancel옵션을 제공하는 버튼들)디스크에 그런 설정을 저장하거나 또는 저장하지 않고 버릴수 있게끔 하거나

한 세트의 창부품은 실행하고자 하는 프로그램에 대하여 매개변수들을 설정하고 또다른 창부품은(Run이나 Execute같은 이름을 가진 버튼들)그런 매개변수를 가지고 프로그램을 시작시킬수 있다.

또는 나중에 같은 함수를 호출할떄 정보를 건네기 위하여 사건처리자 함수를 요청할 필요가 있을수 있다.

그냥 두가지 다른 값으로 변수를 이리저리 바꾸는 사건처리자를 생각해보자 변수에 새로 값을 할당하려면사건처리자는 지난번 실행될때 그 변수에 어떤 값이 할당되었는지를 기억해야 한다.

 

여기에서 문제는 각 사건처리자가 별도의 함수라는 것이다각 사건처리자는 자신만의 지역변수가 있고이 변수들은 다른 사건처리자함수와 공유하지 않으며심지어 나중에 호출되는 자신과도 공유하지 않는다그래서 문제는 자신의 지역변수를 공유할수 없다면어떻게 사건처리자 함수가 다른 사건처리자와 데이터를 공유할수 있겠는가이다.

이해대한 해결책은 2가지가 있다.

 

해결책 첫번째전역변수 사용하기

첫번째 방법으로 공유하고자 하는 변수를 전역변수로 사용하는 방법이다.

예를들면각처리자에서 myValue1 myValue2를 바꾸거나 볼 필요가 있다면 아래와 같은 행을 배치하면 된다.

global myValue1, myValue2

그러나 전역변수를 사용하는 것은 잠재적으로 위험요소가 있다.

또한 일반적으로 지저분한 프로그래밍이라고 눈총을 받는다.

 

해결책 두번째실체변수 사용하기

좀더 깔끔한 방법으로 "실체변수"(self변수)를 사용하여 사건처리자 사이에 공유할 정보를 유지하는 것이다.

물론 위방법을 사용하려면 어플리케이션이 클래스로 구현되어야 한다.

 

아래 예제에서 아주 단순한 정보를 기억하고 공유해보도록 하겠다.

요청된 마지막버튼의 이름을 self.myLastButtonInvoked라는 실체변수에 저장하도록 하겠다.

 

아래 예제는 버튼을 3개 보여준다이 프로그램을 실행하고버튼을 아무거나 클릭하면그 이름과 클릭되었던 앞의 버튼 이름이 화면에 나타난다.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

 

26

27

28

29

 

30

31

32

33

 

34

35

36

37

38

39

40

41

# -*- coding : cp949 -*-

from tkinter import *

 

class MyApp:

    def __init__(self, parent):

        self.myLastButtonInvoked=None #어떠한 버튼의 이름도 들어있지 않다.

       

        self.myParent=parent

        self.myContainer1=Frame(parent)

        self.myContainer1.pack()

       

        self.yellowButton=Button(self.myContainer1, command=self.yellowButtonClick)

        self.yellowButton.configure(text="YELLOW", background="yellow")

        self.yellowButton.pack(side=LEFT)

       

        self.redButton=Button(self.myContainer1, command=self.redButtonClick)

        self.redButton.configure(text="RED", background="red")

        self.redButton.pack(side=LEFT)

       

        self.whiteButton=Button(self.myContainer1, command=self.whiteButtonClick)

        self.whiteButton.configure(text="WHITE", background="white")

        self.whiteButton.pack(side=LEFT)

       

    def yellowButtonClick(self):

        print("YELLOW button clicked. Previous button invoked was"self.myLastButtonInvoked)

        self.myLastButtonInvoked="YELLOW"

   

    def redButtonClick(self):

        print("RED button clicked. Previous button invoked was"self.myLastButtonInvoked)

        self.myLastButtonInvoked="RED"

 

    def whiteButtonClick(self):

        print("white button clicked. Previous button invoked was"self.myLastButtonInvoked)

        self.myLastButtonInvoked="WHITE"

 

print("\n"*100) #화면정리

print("Start...")

root=Tk()

MyApp(root)

root.mainloop()

print("Complete...")

실행 


 

 

 

>> 명령어묶기 더 자세히 <<

명령어묶기에 대하여 좀더 고급특징을 알아보도록 하겠다.

이전 예제에서 명령어묶기는 "command"옵션을 사용하여 사건처리자를 창부품에 묶었다.

self.button1=Button(self.myContainer1, command=self.button1Click)

, button1창부품을 클릭하거나스페이스바로 누르면 command옵션으로 지정한 button1Click이라는 사건처리자가 동작하는것이다.

button2창부품 또한 같은 방식으로 button2Click이라는 사건처리자가 동작할것이다.

 

하지만 만약 위와 상황이 다르다고 가정해보자,

버튼이 여러개이고그 모든 버튼은 본질적으로 같은유형의 동작을 한다고 생각해보자,

각각의 버튼마다 사건처리자를 만들어 주는것보단

단 하나의 사건처리자를 지정하고버튼이 서로다른 인자를 건네주게끔 동작을 한다면 코드가 훨씬 간결해질것이다.

 

아래 예제는 명령어묶기를 할때 사건처리자에 인자를 지정해주었다.

01

02

03

04

05

06

07

08

09

10

11

 

12

13

14

15

16

17

 

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

# -*- coding : cp949 -*-

from tkinter import *

 

class MyApp:

    def __init__(self, parent):

        self.myParent=parent

        self.myContainer1=Frame(parent)

        self.myContainer1.pack()

       

        button_name="OK"

        self.button1=Button(self.myContainer1, command=self.buttonHandler(button_name, 1, "Good stuff!"))

        self.button1.configure(text=button_name, background="green")

        self.button1.pack(side=LEFT)

        self.button1.focus_force #실행시 기본초점 맞추기

       

        button_name="Cancel"

        self.button2=Button(self.myContainer1, command=self.buttonHandler(button_name, 2, "Bad stuff!"))

        self.button2.configure(text=button_name,background="red")

        self.button2.pack(side=LEFT)

       

    def buttonHandler(self, arg1, arg2, arg3):

        print("buttonHandler routine received arguments : ",arg1.ljust(8), arg2, arg3)

       

    def buttonHandler_a(self, event, arg1, arg2, arg3):

        print("buttonHandler_a received event", event)

        self.buttonHandler(arg1,arg2,arg3)

 

print("\n"*100) #화면 정리

print("Start...")

root=Tk()

myapp=MyApp(root)

print("어플리케이션 실행 준비")

root.mainloop()

print("어플리케이션 실행 완료")

위 예제를 실행해보면

11행과 17행에서 명령어묶기로 지정한 buttonHandler사건처리자가

어플리케이션이 실행되기도 전에 먼저 수행되어 화면에 문자열을 출력하는것을 알수 있다.

일단 위 예제를 실행하여 명령어묶기에 함수전달방법이 달라지는것에 따라 어떤 문제점이 생겼는지만 알아보고,

위 예제의 문제의 해결방법을 아래예제를 통해 알아보도록 하겠다.

 

 

>> 역호출함수 <<

위 예제의 문제점을 살펴보면함수가 어플리케이션이 실행되기도 전에 ButtonHandler사건처리자가 실행된다는것이다.

그 이유는 바로 명령어묶기를 했던 방법때문이다.

self.button1=Button(self.myContainer1, command=self.buttonHandler(button_name, 1, "Good stuff!"))

의도한 바는 아니지만 역호출함수로 사용할 것이 무엇인지 요구하지 않고, buttonHandler함수를 직접 호출하고 있기 때문이다.

 

명령어묶기에서 함수를 지정한 방법의 차이를 설명해보면

>command=self.buttonHandler #함수객체

>command=self.buttnHandler() #함수호출

위는 명령어묶기에 함수객체를 지정해주는 것이고,

아래는 명령어묶기에 함수를 호출하여 함수의 Return값을 지정한것이다.

따라서 아래방법에 대하여 Return값이 None이 될경우,

command옵션은 None값에 묶이게 된다는것이다.

때문에 위 예제에서 버튼을 클릭하여도 아무런 동작을 하지 않게 되는것이다.

 

자 이제 이 문제에 대한 해결방법을 알아보자

일반적으로 해결방법은 두가지이다.

첫번째는 파이썬에 내장된 람다(lambda)함수를 사용하는 것이다.

다른 하나는 함수내포기법(currying)이라고 부른다.

 

아래 예제를 통해 람다(lambda)함수를 통한 해결방법을 보도록 하겠다.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

 

15

16

17

 

18

19

20

21

22

23

24

25

26

27

 

28

29

30

 

31

32

33

34

35

36

 

37

38

39

40

41

42

43

44

45

46

47

# -*- coding : cp949 -*-

from tkinter import *

 

class MyApp:

    def __init__(self, parent):

        self.myParent=parent

        self.myContainer1=Frame(parent)

        self.myContainer1.pack()

       

        #----- BUTTON #1 -----

        button_name="OK"

       

        #명령어 묶기

        self.button1=Button(self.myContainer1, command=lambda arg1=button_name, arg2=1, arg3="Good stuff!" : self.buttonHandler(arg1, arg2, arg3))

       

        #사건 묶기 -- 사건을 인자로 건넴

        self.button1.bind("<Return>", lambda event, arg1=button_name, arg2=1, arg3="Good stuff!" : self.buttonHandler_a(event, arg1, arg2, arg3))

       

        self.button1.configure(text=button_name, background="green")

        self.button1.pack(side=LEFT)

        self.button1.focus_force() #실행시 기본초점 맞추기

       

        #----- BUTTON #2 -----

        button_name="Cancel"

       

        #명령어 묶기

        self.button2=Button(self.myContainer1, command=lambda arg1=button_name,arg2=2,arg3="Bad stuff!" : self.buttonHandler(arg1, arg2, arg3))

       

        #사건 묶기 -- 사건을 인자로 건네지 않음

        self.button2.bind("<Return>", lambda event, arg1=button_name, arg2=2, arg3="Bad stuff!" : self.buttonHandler(arg1, arg2, arg3))

       

        self.button2.configure(text=button_name, background="red")

        self.button2.pack(side=LEFT)

       

    def buttonHandler(self, argument1, argument2, argument3):

        print("buttonHandler routine recived arguments:", argument1.ljust(8),argument2, argument3)

       

    def buttonHandler_a(self, event, argument1, argument2, argument3):

        print("buttonHandler_a received event", event)

        self.buttonHandler(argument1, argument2, argument3)

 

print("\n"*100) #화면정리

root=Tk()

myapp=MyApp(root)

print("어플리케이션 실행 준비")

root.mainloop()

print("어플리케이션 실행 완료")

실행


OK버튼창부품 클릭

buttonHandler routine recived arguments: OK       1 Good stuff!

 

OK버튼창부품 스페이스키

buttonHandler routine recived arguments: OK       1 Good stuff!

 

OK버튼창부품 엔터키

buttonHandler_a received event <tkinter.Event object at 0x00D56BD0>

buttonHandler routine recived arguments: OK       1 Good stuff!

 

Cancel버튼창부품 클릭

buttonHandler routine recived arguments: Cancel   2 Bad stuff!

 

Cancel버튼창부품 스페이스키

buttonHandler routine recived arguments: Cancel   2 Bad stuff!

 

Cancel버튼창부품 엔터키

buttonHandler routine recived arguments: Cancel   2 Bad stuff!

 

위에서 볼수 있듯이 명령어묶기와 사건묶기에서사건처리자에 인자를 전달하는 방법을

람다(lambda)를 이용하여 인자를 전달함으로써 함수호출이 아닌 함수객체를 전달하였다.

 

 

>> 함수내포기법(currying) <<

앞의 예제에서 인자를 사건처리자(함수)에 건네기 위해 람다를 사용한 방법을 알아보았다.

이번 예제에서는 함수내포기법(currying)을 사용한 방법을 알아보도록 하겠다.

 

함수내포기법(Curry)이란?

가장 단순한 의미에서함수내포기법은 함수를 사용하여 다른 함수를 구성하는 방법이다.

자세한 내용은 아래URL에서 알수 있다.

http://aspn.activestate.com/ASPN/Python/Cookbook/

http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549

 

curry를 사용하는 방법은 "curry"클래스를 프로그램에 포함시키거나파이썬파일에서 import하는것이다.

아래 예제에서는 curry코드를 직접 프로그램에 포함시키도록 하겠다.

아래는 예제에서 사용했던 명령어묶기 방법이다.

self.button1=Button(self.myContainer1, command=self.buttonHandler(button_name, 1, "Good stuff!"))

이걸 curry를 사용하여아래와 같이 다시 작성하였다.

self.button1=Button(self.myContainer1, command=curry(self.buttonHandler, button_name, 1, "Good stuff!"))

curry클래스에 사건처리자와 인자를 전달하여 생성된 함수를 command옵션에 전달한것이다.

 

아래 예제에서 명령어묶기를 할때 여러가지 방법이 사용되었다.

일단 예제를 본후 설명하도록 하겠다.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

 

36

37

38

 

39

40

41

42

43

44

45

46

47

48

 

49

50

51

52

53

54

55

56

57

58

 

59

60

61

62

63

64

65

66

67

68

69

# -*- coding : cp949 -*-

from tkinter import *

 

# ----- code for class : curry(begin) -----

class curry:

    def __init__(self, fun, *args, **kwargs):

        self.fun=fun

        self.pending=args[:]

        self.kwargs=kwargs.copy()

       

    def __call__(self, *args, **kwargs):

        if kwargs and self.kwargs:

            kw=self.kwargs.copy()

            kw.update(kwargs)

        else:

            kw=kwargs or self.kwargs

        return self.fun(*(self.pending+args), **kw)

# ----- code for class : curry(end) -----

 

# ----- code for function : event_lambda(begin) -----

def event_lambda(f, *args, **kwds):

    return lambda event, f=f, args=args, kwds=kwds : f(*args, **kwds)

# ----- code for function : event_lamda(end) -----

 

class MyApp:

    def __init__(self, parent):

        self.myParent=parent

        self.myContainer1=Frame(parent)

        self.myContainer1.pack()

       

        # ----- BUTTON #1 -----

        button_name="OK"

       

        #명령어묶기 -- 함수내포기법 사용

        self.button1=Button(self.myContainer1, command=curry(self.buttonHandler, button_name, 1, "Good stuff!"))

       

        #사건묶기 -- event_lambda 함수 사용

        self.button1.bind("<Return>", event_lambda(self.buttonHandler, button_name, 1, "Good stuff!"))

       

        self.button1.configure(text=button_name, background="green")

        self.button1.pack(side=LEFT)

        self.button1.focus_force() #실행시 기본초점 맞추기

       

        # ----- BUTTON #2 -----

        button_name="Cancel"

       

        #명령어묶기 -- 함수내포기법 사용

        self.button2=Button(self.myContainer1, command=curry(self.buttonHandler, button_name, 2, "Bad stuff!"))

       

        #사건묶기 -- event_lambda 함수를 두 단계로 사용

        event_handler=event_lambda(self.buttonHandler, button_name, 2, "Bad stuff!")

        self.button2.bind("<Return>", event_handler)

       

        self.button2.configure(text=button_name, background="red")

        self.button2.pack(side=LEFT)

       

    def buttonHandler(self, argument1, argument2, argument3):

        print("buttonHandler routine received arguments:", argument1.ljust(8), argument2, argument3)

       

    def buttonHandler_a(self, event, argument1, argument2, argument3):

        print("buttonHandler_a received event", event)

        self.buttonHandler(argument1, argument2, argument3)

 

print("\n"*100) #화면정리

root=Tk()

myapp=MyApp(root)

print("어플리케이션 실행 준비")

root.mainloop()

print("어플리케이션 실행 완료")

위 예제를 실행해보면 각 OK버튼과 Cancel버튼에

클릭,엔터키,스페이스키에 대하여 같은 동작을 하는것을 알수 있다.

하지만 코드상으론 서로 다르게 buttonHandler함수를 호출하였다.

 

35행은 curry클래스를 이용하여 사건처리자객체를 생성한방법이고,

38행은 lambda객체를 생성하여 return해주는 event_lambda함수를 이용하여 사건묶기를 해주었고,

48행 역시 curry클래스를 이용하였고,

51행은 lambda객체를 이용하여 return해주는 값을 다시한번 event_handler이라는 변수에 집어넣은후

사용한 방법이다.

 

curry클래스와, event_lambda함수, buttonHandler함수, buttonHandler_a함수가 어떻게 동작하는지는 코드를 보면 알수 있으므로 따로 설명하지 않도록 하겠다.

 

lambda curry 그리고 event_lambda -- 어느것을 사용해야 하는가?

>curry event_lambda를 요청하는 코드는 상대적으로 직관적이며 짧고 간단하다.

단점은 이것들을 사용하기 위해서는 프로그램에 코드를 삽입해주어야 한다는것이다.

 

>대조적으로 lambda는 파이썬에 내장되어 있다반입하기 위해 특별히 해야 할것이 없다.

단점은 lambda를 사용하면 코드의 가독성이 떨어진다는 것이다.

 

자 선택은 사용자의 몫이다자기가 사용하기 편하고가장 친숙한것을 사용하자 그리고 작업에 가장 적당하다고 여겨 지는것을 사용하자

 

 

참조 : http://coreapython.hosting.paran.com/GUI/Thinking%20in%20Tkinter.htm

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함