[원문] http://coreapython.hosting.paran.com/tutor/tutgui.htm

Tkinter GUI 프로그래밍

무엇을 다룰 것인가?
  • 기본 GUI 구축 원리
  • 기본 창부품
  • 간단한 Tkinter 프로그램 구조
  • GUI 그리고 OOP, 그 환상적인 궁합
  • Tkinter의 대안 wxPython

이번 주제에서는 일반적인 관점에서 어떻게 GUI 프로그램이 조립되는지 그리고 어떻게 파이썬 고유의 GUI 툴킷인 Tkinter로 GUI를 구축할 수 있는지 살펴보겠습니다. 이 장의 의도는 완벽한 Tkinter 참조서나 완벽한 자습서에 있지 않습니다. 이미 아주 좋은 자세한 자습서가 파이썬 웹 사이트에 링크되어 있습니다. 이런 자습서가 오히려 여러분을 기본적인 GUI 프로그래밍으로 이끌어 줄 것입니다. 기본적인 GUI 구성요소를 소개해주고 어떻게 사용하는지 가르쳐 드리겠습니다. 어떻게 객체 지향 프로그래밍이 GUI 어플리케이션을 조직하는데 도움을 줄 수 있는지 살펴보겠습니다.

GUI 원리

사실 여기에서 프로그래밍에 관해서 새로 배울 것은 아무것도 없습니다. GUI 프로그래밍은 정확하게 다른 모든 종류의 프로그래밍과 같습니다. 연속열과 회돌이 그리고 분기와 모듈을 예전처럼 똑 같이 사용할 수 있습니다. 차이점이라면 GUI 프로그래밍에서는 보통 툴킷(Toolkit)을 써야 하고 그 툴킷 판매상이 설계한 프로그램 설계의 패턴을 따라야 한다는 것입니다. 새로운 툴킷마다 자신만의 API와 디자인 규칙이 있으며 프로그래머는 이런 것들을 배울 필요가 있습니다. 이 때문에 대부분의 프로그래머는 여러 언어에서 사용가능한 몇 가지 툴킷만으로 표준화를 시도합니다 - 새로운 툴킷을 배우는 일은 새로운 프로그래밍 언어를 배우는 것보다 훨씬 더 어려운 경향이 있습니다!

대부분의 윈도우즈 프로그래밍 언어는 툴킷이 따라옵니다 (보통 아주 원시적인 툴킷이 얇은 껍질에 둘러싸여 윈도우즈 시스템 자체에 내장되어 들어가 있습니다). Visual Basic과 Delphi(Kylix) 그리고 Visual C++/.NET이 이런 예입니다.

자바는 자신만의 그래픽 툴킷을 가진다는 점에서 다릅니다. 이를 Swing이라고 부르는데 자바가 실행되는 곳이면 어디서든 실행됩니다 - 거의 모든 플랫폼에서 실행됩니다!

어느 운영 체제에나 사용할 수 있는 툴킷도 있습니다 (유닉스, 맥, 윈도우즈...). 이런 것들은 보통 포장자가 있어서 여러 언어에서 사용할 수 있습니다. 어떤 것은 상업적이지만 대부분은 무료입니다. 그 예로는 GTK와 Qt 그리고 Tk가 있습니다.

모두 웹 사이트가 있습니다. 몇 가지 예를 살펴봅시다:

  • wxPython은 C++로 작성된 wxWidgets 툴킷의 파이썬 버전이다.
  • PyQt는 Qt 툴킷으로서 대부분의 언어에 "바인딩"이 있다.
  • pyGTK는 GTK+ 툴킷(Gimp 개발의 부산물)으로서 GTK+는 리눅스 공동체에서 널리 사용되는 오픈소스 프로젝트이다.

Qt와 GTK는 대부분의 리눅스 어플리케이션에서 사용되며 상업적이 아니면 무료입니다 (즉, 이익을 목적으로 프로그램을 팔지 말아야 합니다). Qt는 상업적 목적으로 사용하고 싶다면 상업적 라이센스도 제공할 수 있습니다. GTk는 라이센스가 Gnu GPL이어서 특별한 조건이 있습니다.

표준 파이썬 그래픽 툴킷은 (함께 따라오는) Tkinter로서 Tk에 기반하며 좀 오래된 다중-OS 툴킷입니다. 이 툴킷을 아주 자세하게 살펴보겠습니다. Tk는 파이썬뿐만 아니라 Tcl과 Haskell 그리고 Perl에서도 사용가능합니다.

Tk의 원리는 다른 툴킷과 약간 다릅니다. 그래서 또다른 인기있는 파이썬(그리고 C/C++)용 GUI 툴킷을 아주 간략하게 살펴보고 마치겠습니다. 이 툴킷이 접근법이 더 전통적입니다. 그러나 먼저 일반적인 원리를 알아봅시다:

이미 여러 번 언급했듯이 GUI 어플리케이션은 거의 언제나 본성상 사건 주도적입니다. 무슨 뜻인지 기억이 나지 않는다면 다시 돌아가 사건 주도적 프로그래밍 주제를 복습하세요.

이미 여러분은 사용자로서 GUI에 익숙하리라고 가정하겠습니다. 그리고 프로그래머의 관점에서 어떻게 GUI 프로그램이 작동하는지에 초점을 두겠습니다. 여러 개의 창과 MDI 인터페이스 등등이 있는 거대하고 복잡한 GUI를 작성하는 법에는 자세하게 들어가지 않습니다. 약간의 라벨과 버튼 그리고 텍스트 상자와 메시지 상자가 있는 창 한개짜리 어플리케이션을 만들기 위해 필요한 기본지식에 주력하겠습니다.

무엇보다도 어휘를 점검할 필요가 있습니다. GUI 프로그래밍은 독자적인 프로그래밍 용어 집합이 있습니다. 가장 자주 사용되는 용어를 아래 표에 기술하겠습니다:

용어설명
창(Window)어플리케이션이 통제하는 화면의 한 구역. 창은 보통 사각형이지만 어떤 GUI 환경에서는 다른 모양도 허용한다. 창은 다른 창을 포함할 수 있으며 보통 GUI 콘트롤 하나가 창 그 자체로 취급된다.
콘트롤(Control)콘트롤은 어플리케이션을 제어하는데 사용되는 GUI 객체이다. 콘트롤은 특성이 있고 보통 사건을 만들어낸다. 보통 콘트롤은 어플리케이션 수준의 객체에 상응한다. 그리고 사건은 상응하는 객체의 메쏘드에 묶인다. 그런 식으로 사건이 일어나면 객체는 자신의 메쏘드중 하나를 실행한다. GUI 환경은 보통 사건을 메쏘드에 묶는 메커니즘을 제공한다.
창부품(Widget)종종 눈에 보이는 콘트롤로 제한된다. (타이머 같은) 어떤 콘트롤은 주어진 창에 연관될 수 있지만 눈에 보이지 않는다. 창부품은 콘트롤의 하위 세트로서 눈에 보이며 사용자나 프로그래머가 조작할 수 있다. 앞으로 다룰 창부품은 아래와 같다:
  • 프레임(Frame)
  • 라벨(Label)
  • 버튼(Button)
  • 텍스트 엔트리(Text Entry)
  • 메시지 박스(Message boxes)

이 주제에서는 다루지 않지만 다른 곳에서는 사용되는 창부품은 아래와 같다:

  • 텍스트 박스(Text box)
  • 라디오 버튼(Radio Button)
마지막으로, 전혀 다루지 않는 창부품은 아래와 같다:
  • 캔버스(Canvas) - 그림 그리기 용
  • 체크 버튼(Check button) - 다중 선택용
  • 이미지(Image) - BMP, GIF, JPEG 그리고 PNG 이미지 보여주기
  • 리스트박스(Listbox) - 리스트 처리용!
  • 메뉴버튼(Menu/MenuButton) - 메뉴 만들기
  • 스케일/스크롤바(Scale/Scrollbar) - 위치 지정
프레임(Frame)다른 창부품을 함께 무리짓는데 사용되는 창부품. 종종 한 프레임이 창 전체를 나타내는데 사용되고 그 안에 또 프레임이 들어간다.
조감(Layout)콘트롤은 특정 형태의 조감에 맞추어 프레임 안에 배치된다. 레이아웃은 여러 방식으로 지정이 가능하다. 화면 좌표를 이용하여 픽셀 단위로 지정하거나, 다른 구성요소에 대하여 상대 좌표(전후좌우 등등)를 이용하거나, 격자나 테이블 정렬을 이용하기도 한다. 좌표 시스템은 이해하기 쉽지만 창의 크기가 바뀌거나 하면 관리하기가 어렵다. 초보자가 좌표 기반의 조감으로 작업하려면 크기가-바뀌지 않는 창을 사용하기를 권장한다.
자손(Child)GUI 어플리케이션은 계통적인 창부품/콘트롤으로 구성되는 경향이 있다. 어플리케이션 창을 담당하는 최상위 수준의 프레임은 하위프레임을 담고 순서대로 이 하위프레임 안에 또 프레임이나 콘트롤이 담긴다. 이런 콘트롤들은 눈에 보이게 트리 구조로 각 콘트롤마다 하나의 부모가 여러 자손을 가진다. 실제로 이런 구조는 위젯에 의하여 명시적으로 저장되는 것이 보통이다. 그래서 프로그래머, 즉 구이 환경 그 자체는 종종 한 콘트롤과 그의 모든 자손에게 공통적인 조치를 수행하여 통제할 수 있다.

그릇 트리

GUI 프로그래밍을 이해하는 가장 중요한 원칙은 그릇 계통도의 개념을 이해하는 것입니다. 다시 말해 위젯은 트리에 담깁니다. 트리는 최상위 수준의 위젯이 전체 인터페이스를 제어하는 구조입니다. 다양한 자손 위젯이 있습니다. 차례로 각 자손은 자신만의 자손을 가질 수도 있습니다. 사건은 자손 위젯에 도착하고 자손 위젯이 처리할 수 없으면 그 사건은 그의 부모에게 건네지고 등등 최상위 수준으로 건네집니다. 비슷하게 위젯을 그리라는 명령어가 주어지면 그 명령어는 계속해서 그의 자손까지 내려가고, 그리하여 최상위 위젯에 건네어진 그리기 명령어는 전체 어플리케이션을 다시 그릴 것입니다. 반면에 버튼에 건네어진 그리기 명령어는 버튼 자체만 다시 그릴 것입니다.

사건은 트리를 타고 올라가고 명령은 트리를 타고 내려 온다는 이런 개념은 어떻게 GUI가 작동하는지 프로그래머 수준에서 이해하는 열쇠입니다. 위젯을 만들 때 왜 언제나 부모 위젯을 지정할 필요가 있는지 이해할 수 있습니다. 그래야 자신이 그릇 트리에서 어디에 위치하는지 알기 때문입니다. 이 주제에서 만들 간단한 어플리케이션을 위한 그릇 트리를 다음과 같이 보여드릴 수 있습니다:

GUI Containment tree

이를 보면 최상위 수준의 위젯에 Frame이 하나 있어서 가장 바깥쪽 창의 테두리를 표현합니다. 차례로 그 안에 프레임이 두 개 더 있습니다. 한 프레임에는 Text Entry 위젯이 있고 또 하나에는 어플리케이션을 제어하기 위해 두 개의 Button이 사용됩니다. 나중에 GUI를 구축하게 되면 이 다이어 그램을 다시 참조하겠습니다.

일반적인 위젯 여행

이 섹션에서는 파이썬 상호대화 모드를 사용하여 간단한 창과 위젯을 만들어 보겠습니다. IDLE 그 자체가 Tkinter 어플리케이션이므로 IDLE 안에서 Tkinter 어플리케이션을 실행하는 것은 신뢰할 수 없습니다. 물론 IDLE을 편집기로 이용하여 파일을 만들 수 있지만 반드시 실행은 OS 명령어 프롬프트에서 해야 합니다. 파이썬윈(Pythonwin) 사용자는 Tkinter 어플리케이션을 실행할 수 있습니다. 왜냐하면 파이썬윈은 윈도우즈 전용 GUI 툴킷인 MFC를 사용하여 구축되었기 때문입니다. 그렇지만 파이썬윈조차도 Tkinter 어플리케이션의 행위를 예측할 수 없습니다. 결과적으로 운영체제에서 파이썬 프롬프트를 사용하겠습니다.

>>> from Tkinter import *

Tkinter 프로그램이라면 제일 먼저 요구되는 조건입니다 - 위젯의 이름을 반입합니다. 물론 모듈 형태로 반입할 수 있지만 Tkinter를 컴포넌트 이름마다 앞에 타자하는 일은 얼마 못가 귀찮아집니다.

>>> top = Tk()

이는 위젯 계통도에서 최상위 수준 위젯을 만듭니다. 다른 모든 위젯은 이의 자손이 됩니다. 빈 창이 새로 나타났습니다. 빈 타이틀 바에 Tk 로고와 표준 콘트롤 버튼 집합이 있습니다 (최소화, 최대화 등등). 이제 어플리케이션을 구축하면서 컴포넌트를 이 창에 추가하겠습니다.

>>> dir(top)

['_tclCommands', 'children', 'master', 'tk']

dir 함수는 인자에 무슨 이름이 들어 있는지 보여줍니다. 이 함수는 모듈에도 사용할 수 있지만 이 경우 Tk 클래스의 실체인 top 객체의 내부를 알아보는데 사용했습니다. top의 속성중에서 특히 children 속성과 master 속성에 주목하세요. 이 속성들은 위젯 그릇 트리를 가리키는 링크입니다. _tclCommands 속성에 주목하세요. 이 속성은 기억나시겠지만 Tkinter가 Tcl/Tk 위에 구축되었기 때문입니다.

>>> F = Frame(top)

Frame 위젯을 만듭니다. 차례로 그 안에 자손 콘트롤/위젯이 담깁니다. Frametop을 자신의 첫 (이 경우에는 유일한) 매개변수로 지정합니다. 그리하여 Ftop의 자손 위젯이 될 것이라고 알립니다.

>>> F.pack()

Tk 창이 이제 추가된 Frame 위젯의 크기로 줄어 들었습니다 - 현재 비어 있으므로 창은 이제 아주 작습니다! pack() 메쏘드는 레이아웃 관리자를 요청합니다. 이른바 패커 관리자는 간단한 레이아웃에 사용하기 쉽습니다. 그러나 레이아웃이 복잡해지면 약간 어색해 집니다. 지금 당장은 사용하기 쉽기 때문에 패커 관리자에 주력하겠습니다. 어플리케이션에서 위젯은 꾸려 넣지 않으면 (또는 다른 레이아웃 관리자를 이용하지 않으면) 눈에 보이지 않습니다.

>>> lHello = Label(F, text="Hello world")

터미널에서 상호대화적으로 Tkinter를 연습할 때 문자열이 깨져서 알아 볼수 없는 경우가 있다. 그 이유는 터미널 인코딩이 mbcs (euc-kr, cp949)인 반면에 Tkinter는 인코딩이 utf-8로서 서로 다르기 때문이다. 해결 방법은 다음과 같다.

  • 첫 째 유니코드로 넘겨준다: lHello = Label(F, text=unicode("Hello world",'mbcs'))
    터미널 인코딩은 알지만 GUI의 인코딩을 모를 경우 사용한다.
  • 둘 째 직접 변환해서 넘겨준다: lHello = Label(F, text="Hello world".encode('utf-8'))
    GUI의 인코딩은 알지만 터미널 인코딩을 모를 경우 사용한다.

파일에 저장하여 실행할 경우는 반드시 파일 첫 줄이나 두 번째 줄에 다음과 같이 삽입하고 utf-8로 저장한다. 이미 파일에 입력된 문자의 인코딩이 utf-8이므로 위와 같은 변환을 거칠 필요가 없다.

  • # -*- coding: utf-8 -*-

파일에 저장하여 실행하는 경우가 바람직하다. 결론적으로 유니코드는 출력에 목적이 있는 것이 아니라 내부처리에 목적이 있는 것이다. 앞의 방법은 파이썬 내부에서 처리가 가능하여 파이썬 3.0에서는 아예 unicode() 함수가 없다.

여기에서 Label 클래스의 새로운 객체 lHello를 만듭니다. 부모 위젯은 F이고 text 속성은 "Hello world"입니다. Tkinter 객체의 구성자는 매개변수가 많은 경향이 있으므로 (각각 기본 값이 있음) 보통 인자를 Tkinter 객체에 건네는 이름붙은 매개변수 테크닉을 사용합니다. 객체는 아직 꾸려 넣지 않았기 때문에 보이지 않습니다.

마지막 요점으로 이름짓기 관례의 사용에 주목하세요: 라벨에 대하여 Hello 이름 앞에 소문자 l을 두었습니다. 이를 보면 그의 목적이 생각 나기 때문입니다. 대부분의 이름짓기 관례처럼 이는 개인적인 취향이지만, 도움이 될 것이라 생각합니다.

>>> lHello.pack()

이제 볼 수 있습니다. 다음과 같이 보일 것입니다:

Window with Label

객체 구성자에게 매개변수를 건네면 라벨에 예를 들어 폰트와 색상같은 다른 특성을 지정할 수도 있습니다. Tkinter 위젯의 configure 메쏘드를 사용하면 상응하는 특성에 접근할 수 있습니다. 다음과 같이:

>>> lHello.configure(text="Goodbye")

메시지가 바뀌었습니다. 쉽군요, 그렇지 않습니까? configure 메쏘드는 여러 특성을 한 번에 바꾸고 싶을 때 특히 좋은 테크닉입니다. 왜냐하면 특성 모두를 인자로 건넬 수 있기 때문입니다. 그렇지만, 한 번에 하나의 특성만 바꾸고 싶다면, 위에 한 것 같이 그 객체를 사전처럼 취급할 수 있습니다. 다음과 같이:

>>> lHello['text'] = "Hello again"

이 편이 더 짧고 더 이해하기 쉽습니다.

라벨은 아주 무미건조한 위젯입니다. 읽기-전용 텍스트만 화면에 보여줄 수 있을 뿐입니다. 물론 색상과 폰트 그리고 크기는 지정할 수 있습니다. (실제로 간단한 그래픽을 보여주는데에도 사용할 수 있지만 여기에서는 그에 관하여 신경쓰지 않겠습니다).

또다른 객체 유형을 살펴보기 전에 할 일이 한 가지 더 있습니다. 그것은 창의 제목을 설정하는 일입니다. 최상위 수준 위젯인 top의 메쏘드를 사용합니다:

>>> F.master.title("Hello")

top을 직접 사용할 수도 있지만, 나중에 보시듯이 프레임의 마스터 특성을 통하여 접근하는 것이 일반적인 테크닉입니다.

>>> bQuit = Button(F, text="Quit", command=F.quit)

여기에서 새로운 버튼 위젯을 만듭니다. 버튼에는 "Quit" 라벨이 있고 F.quit 명령어와 연관되어 있습니다. 메쏘드 이름을 건넸음을 주목하세요. 메쏘드를 뒤에 괄호를 추가해서 호출하지 않습니다. 이는 파이썬 함수 객체를 건네야 한다는 뜻입니다. 여기에서처럼 Tkinter가 제공하는 내장 객체일 수도 있고 또는 직접 정의한 다른 함수도 될 수 있습니다. 함수나 메쏘드는 인자를 받지 않습니다. quit 메쏘드는 pack 메쏘드처럼 바탕 클래스에 정의되어 있으며 모든 Tkinter 위젯이 상속받습니다. 그러나 보통 어플리케이션의 최상위 수준 창에서 호출됩니다.

>>>bQuit.pack()

또 한 번 pack 메쏘드가 버튼을 보여줍니다.

>>>top.mainloop()

Tkinter 사건 회돌이를 시작합니다. 이제 >>> 프롬프트가 이제 사라졌습니다. 그를 보면 Tkinter가 이제 제어권을 가졌다는 사실을 알 수 있습니다. Quit 버튼을 누르면 프롬프트가 돌아옵니다. 이로서 command 옵션이 작동한다는 것이 증명되었습니다. 창이 닫힐 것이라고 예상하지 마세요. 파이썬 인터프리터는 여전히 실행되고 있으며 오직 주회돌이 기능만 끝냈을 뿐입니다. 파이썬이 종료하면 다양한 위젯이 파괴됩니다 - 실제 프로그램에서 이는 보통 주회돌이가 종료하면 즉시 파괴됩니다!

Pythonwin이나 IDLE에서 이것을 실행하면 약간 결과가 다를 수 있습니다. 그렇다면 지금까지의 명령어를 파이썬 스크립트 안으로 타자해 넣고 OS 명령어 프롬프트에서 실행하세요.

실제로 어쨌든 그를 시험해 볼 좋은 기회입니다. 무엇보다 그것이 바로 대부분의 Tkinter 프로그램이 실제로 작동하는 방식입니다. 지금까지 연구한 바와 같이 핵심 명령어들을 사용하세요:

#!/usr/bin/python# -*- coding: utf-8 -*-# 반드시 파일은 utf-8로 저장할 것 - 중요from Tkinter import *# 창 자체를 설정한다top = Tk()F = Frame(top)F.pack()# 위젯을 추가한다lHello = Label(F, text="안녕하세요")lHello.pack()bQuit = Button(F, text="그만하기", command=F.quit)bQuit.pack()# 회돌이를 실행한다top.mainloop()

top.mainloop 메쏘드를 호출하면 Tkinter 사건 회돌이가 시작되어 사건을 만들어 냅니다. 이 경우 잡고 싶은 유일한 사건은 버튼 눌림 사건이며 이 사건은 F.quit 메쏘드에 연결되어 있습니다. 차례로 F.quit은 어플리케이션을 종료하고 이 번에는 파이썬도 종료하므로 창도 닫힙니다. 시험해 보세요. 다음과 같이 보일 것입니다:

Label with Button

레이아웃 연구

고지: 이제부터는 >>> 프롬프트에서 명령어로 보여드리기 보다 파이썬 스크립트 파일로 예제를 보여드리겠습니다.

이 섹션에서는 창 안에서 Tkinter가 어떻게 위젯의 위치를 정하는지 살펴보고 싶습니다. 이미 Frame과 Label 그리고 Button 위젯을 보셨습니다. 그 모든 것들이 이 섹션에서 필요합니다. 앞 예제에서 위젯의 pack 메쏘드를 사용하여 부모 위젯 안에서 그의 위치를 정합니다. 기술적으로 우리가 하는 일은 Tk의 꾸림자인 레이아웃 관리자를 요청합니다. 조감 관리자의 역할은 프로그래머가 제공하는 힌트에 기반하여 게다가 사용자가 제어하는 창의 크기와 같은 제약에 의거하여 위젯에 대하여 최상의 레이아웃을 결정하는 것입니다. 어떤 레이아웃 관리자는 창 안에서 정확한 위치를 사용합니다. 보통은 픽셀 단위로 지정되며 비주얼 베이직 같은 윈도우즈 환경에서는 흔히 이렇게 지정합니다. Tkinter에는 Placer 레이아웃 관리자가 포함되어 있으며 place 메쏘드를 통하여 위치를 지정할 수 있지만 이 자습서에서는 다루지 않겠습니다. 왜냐하면 더 지능적인 관리자가 보통 더 좋은 선택이기 때문입니다. 창이 크기가 변할 때 무슨 일이 일어나는지 프로그래머가 신경쓰지 않는 편이 더 좋으니까요.

Tkinter의 가장 단순한 레이아웃 관리자는 그 동안 사용해 온 꾸림자(packer)입니다. 패커는 기본으로 그냥 위젯을 차곡차곡 쌓아 올립니다. 보통 위젯이라면 별로 원하는 바가 아니겠지만 프레임으로 어플리케이션을 구축하면 프레임을 하나하나 쌓아 올리는 것이 상당히 합리적인 접근법입니다. 그러면 패커 또는 기타 레이아웃 관리자를 이용하여 각 프레임 안에 적절하게 다른 위젯들을 넣을 수 있습니다. 이 예제가 작동하는 모습은 사례 연구 주제에서 볼 수 있습니다.

그렇지만, 간단한 패커조차도 선택사항을 여러가지 제공합니다. 예를 들어 side 인자를 제공하면 위젯들을 수직 말고 수평으로 정렬할 수 있습니다. 다음과 같이:

lHello.pack(side="left")bQuit.pack(side="left")

위젯들이 왼쪽으로 가고 그리하여 첫 위젯 (라벨)은 외쪽 벽에 바짝 붙어 나타나고, 다음에 다음 위젯 (버튼)이 옵니다. 위의 예제에서 줄을 수정하면 다음과 같이 보일 것입니다:

Left packed widgets

"left""right"로 바꾸면 라벨은 오른쪽에 바짝 붙어 나타나고 버튼이 그 왼쪽에 나타납니다. 다음과 같이:

Right packed widgets

위젯이 한데 포개어져 있기 때문에 별로 멋지지 않습니다. 패커는 또 이에 대처하기 위한 매개변수를 제공합니다. 가장 쉽게 사용하는 방법은 Padding이며 수평 패딩(padx)과 수직 패딩(pady)으로 지정됩니다. 이 값들은 픽셀단위로 지정됩니다. 예제에 수평 패딩을 추가해 봅시다:

lHello.pack(side="left", padx=10)bQuit.pack(side='left', padx=10)

다음과 같이 보일 것입니다:

Horizontal padding

창의 크기를 변경하면 위젯이 또다른 위젯에 상대적으로 위치를 유지하지만 창의 중앙에 머무는 것을 볼 수 있습니다. 위젯들을 왼쪽에 꾸려 넣었는데 왜 그런가? 그 대답은 위젯들을 프레임 안에 꾸려 넣었지만 그 프레임은 변이 지정되지 않은 채로 꾸려 넣었졌기 때문입니다. 그래서 패커의 기본 값이 상단 중앙에 위치합니다. 위젯을 창의 올바른 변에 머물게 하고 싶으면 프레임도 적절한 변에 꾸려 넣을 필요가 있습니다:

F.pack(side='left')

창의 수직 크기를 변경해도 위젯들이 중앙에 머무는 것을 주목하세요 - 역시 그것이 패커의 기본 행위입니다.

padx와 pady를 직접 가지고 놀아 보세요. 값을 다르게 주고 조합해 주면서 등등 그 효과를 알아 보세요. 여러분에게 과제로 남깁니다. 패커를 사용하면 sidepadx/pady가 위젯의 위치에 상당히 유연함을 알 수 있습니다. 여러가지 옵션이 있습니다. 각 옵션은 미묘하게 모습을 제어합니다. 자세한 것은 Tkinter 페이지를 참조하세요.

두 가지 레이아웃 관리자가 있습니다. 하나는 grid이고 다른 하나는 placer라고 부릅니다. pack() 대신에 grid 관리자를 사용하려면 grid()를 호출하고 placer 관리자를 사용하려면 place()를 호출하면 됩니다. 각자 자신만의 옵션 집합이 있으며 여기에서는 패커만 다룰 생각이므로 자세한 것은 Tkinter 자습서와 참조서를 보셔야 하겠습니다. 핵심 요점은 grid가 (놀랍게도!) 구성요소들을 창 안에서 격자 안에 정렬한다는 것입니다 - 이는 예를 들어 텍스트 입력 상자가 줄줄이 늘어선 대화상자에 유용할 수 있습니다. placer 사용자는 픽셀 단위로 좌표를 고정하거나 창 안에서 상대적인 좌표를 사용합니다. 후자에서 컴포넌트는 창과 더불어 크기가 바뀌는데 언제나 수직 공간의 75%를 차지합니다. 이는 복잡한 창 디자인에 유용할 수 있습니다. 그러나 미리-심사숙고해서 계획할 필요가 있습니다 - 모눈 종이와 연필 그리고 지우게를 강력하게 추천합니다!

프레임과 패커로 겉모습 통제하기

프레임 위젯은 실제로 몇 가지 유용한 특성이 있습니다. 무엇보다 논리적 프레임 안에 컴포넌트를 모아 두면 아주 좋습니다. 그러나 어떤 경우는 볼 수도 있었으면 하는 경우가 있습니다. 프레임은 라디오 버튼이나 체크 박스 같은 무리지어진 콘트롤에 특히 유용합니다. 프레임은 다른 많은 Tk 위젯처럼 relief 특성을 제공하여 이 문제를 해결합니다. Relief 특성은 여러 값을 가질 수 있습니다: sunken, raised, groove, ridge 또는 flat이 그것입니다. sunken 값을 대화 박스에 사용해 봅시다. 그냥 Frame 생성줄을 다음고 같이 바꾸면:

 F = Frame(top, relief="sunken", border=1) 

고지 1: 테두리도 제공할 필요가 있습니다. 주지 않으면 Frame은 움푹(sunken) 들어가지만 테두리는 보이지 않습니다 - 아무 차이도 보이지 않습니다!

고지 2: 테두리 두께에 인용부호를 붙이지 않았음을 주목하세요. 옵션 주위에 언제 인용부호를 붙일지 언제 생략할지 아는 것은 Tk 프로그래밍의 혼란스러운 측면 중의 하나입니다. 일반적으로 숫자나 한 개짜리 문자라면 인용부호를 생략할 수 있습니다. 숫자와 문자열이 섞여 있으면 인용부호가 필요합니다. 대소문자의 사용도 마찬가지입니다. 불행하게도 쉬운 해결책은 없습니다. 시행 착오를 통해 배워야 합니다. 파이썬은 종종 유효한 옵션 목록을 에러 메지시에 보여줍니다!

프레임은 창을 채우지 않음을 주목하세요. 당연히 fill이라고 부르는 패커 옵션으로 고칠 수 있습니다. 프레임을 꾸릴 때 그리하여 다음과 같이 합니다:

F.pack(fill="x")

이는 수평으로 채웁니다. 프레임이 전체 창을 채우도록 하고 싶다면 또 fill='y'를 사용하면 됩니다. 이는 아주 흔히 요구되므로 특별한 채우기 옵션으로 BOTH가 있으므로 다음과 같이 타자할 수 있습니다:

F.pack(fill="both")

스크립트를 실행한 최종 결과는 이제 다음과 같이 보입니다:

Sunken Frame

위젯 더 추가하기

이제 텍스트 Entry 위젯을 살펴봅시다. 이는 친숙한 한 줄짜리 텍스트 입력 박스입니다. 더 섬세한 Text 위젯의 메쏘드와 많은 부분을 공유합니다. 텍스트 위젯은 여기에서 다루지 않습니다. 엔트리 위젯을 사용하여 단순히 사용자가 타자하는 것들을 나포하고 요구에 따라 그 텍스트를 지우겠습니다.

다시 "Hello World" 프로그램으로 돌아가 텍스트 엔트리 위젯을 프레임 안에 추가하고 그 안에 타자한 텍스트를 지울 수 있는 버튼을 추가하겠습니다. 이는 엔트리 위젯을 만들고 사용하는 법 뿐만 아니라 사건 처리 함수를 정의하고 그것을 위젯에 연결하는 법도 보여줄 것입니다.

#!/usr/bin/python# -*- coding: utf-8 -*-# 반드시 파일은 utf-8로 저장할 것 - 중요from Tkinter import *# 먼저 사건 처리자를 만든다.def evClear():  eHello.delete(0,END)# 최상위 수준 창/프레임을 만든다.top = Tk()F = Frame(top)F.pack(expand="true")# 이제 텍스트 엔트리가 있는 프레임을 만든다.fEntry = Frame(F, border=1)eHello = Entry(fEntry)fEntry.pack(side="top", expand="true")eHello.pack(side="left", expand="true")# 마지막으로 버튼이 있는 프레임을 만든다. # 강조하기 위해 이를 움푹 들여 넣는다.fButtons = Frame(F, relief="sunken", border=1)bClear = Button(fButtons, text="텍스트 지우기", command=evClear)bClear.pack(side="left", padx=5, pady=2)bQuit = Button(fButtons, text="그만하기", command=F.quit)bQuit.pack(side="left", padx=5, pady=2)fButtons.pack(side="top", expand="true")# 이제 사건 회돌이를 실행한다.F.mainloop()

bClear 버튼에 command 인자로 사건 처리자의 이름(evClear)을 괄호없이 한 번 더 건넸습니다. 이름짓기 관례의 사용에 주목하세요. evXXX는 사건 처리자를 상응하는 위젯에 연결합니다.

프로그램을 실행하면 결과는 다음과 같습니다:

Entry and button controls

텍스트 엔트리 박스 안에 무엇인가 타자하고 "텍스트 지우기" 버튼을 치면 내용이 다시 삭제됩니다.

사건 묶기 - 위젯을 코드에 묶는 방법

지금까지 파이썬 함수를 GUI 사건에 연관시키기 위해 버튼의 명령어 특성을 사용하였습니다. 가끔 더 명시적으로 제어하고 싶은 경우가 있습니다. 예를 들어 특정 키 조합을 잡고 싶으면 bind 함수를 사용하여 사건과 파이썬 함수를 함께 명시적으로 함께 묶거나 (엮습니다).

이제 핫 키를 정의하겠습니다 - CTRL-c - 위의 예제에 있는 텍스트를 지우기 위해서 말입니다. 그렇게 하려면 CTRL-C 키 조합을 Clear 버튼과 같은 사건 처리자에 묶을 필요가 있습니다. 불행하게도 예상치 못한 단점이 있습니다. 명령어 옵션을 사용할 때 지정된 함수는 인자를 받지 못합니다. bind 함수를 사용하여 같은 일을 하려면 묶이 함수는 반드시 인자를 하나 받아야 합니다. 이 때문에 evClear를 호출하는 매개변수를 하나 가진 새로운 함수를 만들 필요가 있습니다. 다음을 evClear 정의 뒤에 추가하세요:

def evHotKey(event):    evClear()

그리고 다음 줄을 Entry 위젯의 정의 뒤에 추가하세요:

eHello.bind("<Control-c>",evHotKey) # 키 정의는 대소문자에 민감하다 

프로그램을 다시 실행하면 이제 버튼을 누르거나 Ctrl-c를 타자해서 텍스트를 지울 수 있습니다. bind를 사용하면 마우스 클릭 같은 것들을 잡을 수 있습니다. 즉 초점(Focus)을 잡거나 놓을 수 있으며 심지어 창을 보이게 만들 수도 있습니다. 이에 관한 더 자세한 정보는 Tkinter 문서를 참조하세요. 보통 가장 어려운 부분은 사건 기술의 형식을 알아내는 것입니다!

짧은 메시지

MessageBox를 이용하여 사용자에게 짧은 메시지를 보고할 수 있습니다. 이는 Tk에서 아주 쉽습니다. 아래에 보여주는 바와 같이 tkMessageBox 모듈을 기능을 사용하여 완성됩니다:

import tkMessageBoxtkMessageBox.showinfo("Window Text", "A short message") 

에러도 있습니다. 다양한 showXXX 함수를 통하여 경고, Yes/No 그리고 OK/Cancel 상자로 얻을 수 있습니다. 에러는 다양한 아이콘과 버튼으로 구별됩니다. 뒤의 두 상자는 showXXX 대신 askXXX를 사용하며 어느 버튼을 사용자가 눌렀는지 값을 돌려줍니다. 다음과 같이:

res = tMessageBox.askokcancel("Which?", "Ready to stop?")print res

다음은 Tkinter 메시지 상자들입니다:

Info box Error box Yes/No box

어플리케이션을 객체로 감싸기

GUI를 프로그래밍할 때 전체 어플리케이션을 클래스로 포장하는 것이 일반적입니다. 이는 질문을 불러 일으킵니다. 어떻게 Tkinter 어플리케이션의 위젯을 이 클래스 구조에 맞추는가? 두 가지 선택이 있습니다. 어플리케이션 자체를 Tkinter Frame의 하위 클래스로 만들거나 멤버 필드에 최상위 창을 가리키는 참조점을 저장합니다. 후자의 접근법이 다른 툴킷에서 가장 많이 사용됩니다. 그래서 여기에서 이 접근법을 사용하겠습니다. 전자의 접근법이 작동하는 모습을 보고 싶으면 뒤로 돌아가 사건 주도적 프로그래밍 주제에 있는 예제를 살펴보세요. (그 예제도 놀랍도록 다재다능한 Tkinter Text 위젯의 기본 사용법을 보여줍니다.)

위의 예제를 OO 구조로 변환하겠습니다. 엔트리 필드와 Clear 버튼 그리고 Quit 버튼을 이용하겠습니다. 먼저 Application 클래스를 만들고 구성자 안에서 GUI 시각적 부품들을 조립합니다.

결과로 나온 Frame을 self.mainWindow에 할당하고, 그리하여 클래스의 다른 메쏘드들은 최상위 수준의 Frame에 접근할 수 있습니다. 접근할 필요가 있는 (Entry 필드 같은) 다른 위젯들도 마찬가지로 Frame의 멤버 변수에 할당됩니다. 이 테크닉을 이용하면 사건 처리자는 어플리케이션 클래스의 메소드가 되며 모두 (물론 이경우는 아무것도 없지만) self 참조를 통하여 어플리케이션의 다른 데이터 멤버에 접근할 수 있습니다. 이렇게 하면 GUI와 밑에 깔린 어플리케이션 객체가 흠없이 통합됩니다:

#!/usr/bin/python# -*- coding: utf-8 -*-# 반드시 파일은 utf-8로 저장할 것 - 중요from Tkinter import *     class ClearApp:   def __init__(self, parent=0):      self.mainWindow = Frame(parent)      # 엔트리 위젯을 만든다.      self.entry = Entry(self.mainWindow)      self.entry.insert(0,"파이썬 만세!")      self.entry.pack(fill=X)            # 이제 버튼 2 개를 추가하고, grooved 효과를 준다.      fButtons = Frame(self.mainWindow, border=2, relief="groove")      bClear = Button(fButtons, text="지우기",                       width=8, height=1, command=self.clearText)      bQuit = Button(fButtons, text="이제 그만",                       width=8, height=1, command=self.mainWindow.quit)      bClear.pack(side="left", padx=15, pady=1)      bQuit.pack(side="right", padx=15, pady=1)      fButtons.pack(fill=X)      self.mainWindow.pack()      # 제목을 설정한다.      self.mainWindow.master.title("파이썬")         def clearText(self):      self.entry.delete(0,END)      app = ClearApp()app.mainWindow.mainloop()

다음은 그 결과입니다:

OOP version

결과적으로 모습이 앞의 구현과 놀랍도록 비슷합니다. 물론 아래 프레임을 조작해서 깔끔하게 홈이 파이도록 마감했고 버튼에 너비를 주어서 아래의 wxPython 예제와 더 비슷하게 보이게 만들었습니다.

물론 메인 어플리케이션만 객체로 쌀 수 있는 것은 아닙니다. Frame을 기반으로 클래스를 만들어 그 안에 버튼의 표준 집합을 담고 그 클래스를 대화상자 창을 구축하는데 재사용할 수 있습니다. 심지어 전체 대화상자를 만들어서 그를 여러 프로젝트에 사용할 수도 있습니다. 또는 표준 위젯을 상속받아 그 능력을 확장할 수 있습니다 - 아마도 상태에 따라 색상을 바꾸는 버튼을 만들수 있을 것입니다. 이 방식대로 파이썬 메가 위젯(PMW)이 구축되었습니다. PMW는 Tkinter 확장으로서 내려 받을 수 있습니다.

대안 - wxPython

다른 GUI 툴킷이 많지만 가장 인기 있는 툴킷은 wxPython입니다. 이 툴킷은 이번에는 C++ 툴킷인 wxWidgets를 포장한 것입니다. wxPython는 일반적으로 GUI 툴킷중에서 Tkinter보다 훨씬 더 모범적입니다. Tk보다 "바로 사용할 수 있는" 표준적인 기능을 더 많이 제공합니다 - 예를 들어, 풍선 도움말과 상태 바 등등은 Tkinter에서 손수 처리해 주어야 합니다. wxPython을 사용하여 위의 간단한 "Hello World" 예제를 다시 만들어 보겠습니다.

wxPython은 자세하게 들어가지 않겠습니다. 어떻게 wxPython이 작동하는지 더 알고 싶다면 wxPython 웹사이트에서 패키지를 내려 받으세요.

일반적 용어로 wxPython에 작업틀이 정의되어 있기 때문에 창을 만들고 그 창을 콘트롤로 채우며 그리고 그 콘트롤에 메쏘드를 묶을 수 있습니다. 완전히 객체 지향적이므로 함수가 아니라 메쏘드를 사용해야 합니다. 예제는 다음과 같이 보입니다:

import wx# --- 맞춤 프레임을 정의한다. 이는 메인 창이 된다 ---class HelloFrame(wx.Frame):   def __init__(self, parent, id, title, pos, size):        wx.Frame.__init__(self, parent, id, title, pos, size)        # 올바른 배경을 얻으려면 패널이 필요하다.        panel = wx.Panel(self)        # 이제 텍스트와 버튼 위젯을 만든다.        self.tHello = wx.TextCtrl(panel, -1, "Hello world", pos=(3,3), size=(185,22))        bClear = wx.Button(panel, -1, "Clear", pos=(15, 32))        self.Bind(wx.EVT_BUTTON, self.OnClear, bClear)        bQuit = wx.Button(panel, -1, "Quit", pos=(100, 32))        self.Bind(wx.EVT_BUTTON, self.OnQuit, bQuit)       # 다음은 사건 처리자이다.   def OnClear(self, event):       self.tHello.Clear()          def OnQuit(self, event):       self.Destroy()# --- 어플리케이션을 정의한다 ---# 모든 wxPython 프로그램은 어플리케이션 클래스를# 반드시 wx.App으로부터 상속받아 정의해야 한다.class HelloApp(wx.App):   def OnInit(self):       frame = HelloFrame(None, -1, "Hello", (200,50), (200,90) )       frame.Show(True)       self.SetTopWindow(frame)       return True# 실체를 만들고 사건 회돌이를 시작한다HelloApp().MainLoop()

다음과 같이 보입니다:

wxPython Hello program

작업틀에 의하여 호출되는 메쏘드에 사용된 이름짓기 관례에 주목하세요 - OnXXXX. 또 사건을 위젯에 묶는데 사용된 EVT_XXX 상수에 주목하세요 - 이렇게 이름 지어진 상수들이 한 가득 있습니다. wxPython은 방대한 위젯이 있습니다. Tkinter보다 훨씬 더 많이 있으며 아주 섬세한 GUI를 구축할 수 있습니다. 불행하게도 사용되는 좌표가 위치 배정 체계에 기반하는데 이는 잠시만 지나면 아주 귀찮아집니다. Tkinter의 패커와 아주 비슷한 체계를 사용하는 것도 가능하지만 문서화가 잘 되어 있지 않습니다.

말이 나온 김에 흥미로운 사실을 지적하고 싶습니다. 이 예제와 위의 아주 비슷한 Tkinter 예제가 모두 실행 코드의 줄 개수가 대략 같습니다 - Tkinter: 19줄, wxPython: 21줄.

결론적으로 텍스트 기반의 도구에 재빠르게 GUI 전방 모듈을 만들고 싶다면 Tkinter가 최소의 노력으로 만족시켜 줄 수 있습니다. 완전히 특징을 갖춘 크로스 플랫폼 GUI 어플리케이션을 구축하고 싶다면 wxPython을 더 자세하게 살펴보세요.

다른 툴킷에는 MFC와 .NET이 있습니다. 물론 좀 허술한 curses도 있습니다. 이는 일종의 텍스트 기반 GUI입니다! Tkinter에서 배운 많은 지식이 이런 모든 툴킷에 적용되지만 각자 나름의 개성이 있고 약점이 있습니다. 하나를 골라서 익히고 GUI 디자인의 멋진 세계를 즐겨 보세요. 마지막으로 많은 툴킷에 그래픽 GUI 구축 도구가 있다는 사실을 언급하고 싶습니다. 예를 들어 Qt는 Blackadder가 있고 GTK는 Glade가 있습니다. wxPython은 Python Card가 있습니다. 파이썬 카드는 전체적인 wxPython GUI 구축 과정을 간소화시켜 줍니다. 또한 무료 GUI 빌더인 Boa Constructor도 얻을 수 있습니다. 물론 여전히 알파 배포 상태이긴 하지만 말입니다. 심지어 Tkinter용 GUI 구축기도 있습니다. 이른바 SpecTix 로서 이전의 Tcl 도구에 기반하여 Tk 인터페이스를 구축하지만, 파이썬을 포함하여 여러 언어로 코드를 만들어 낼 수 있습니다. 또한 Tkinter용으로 개선된 위젯 집합이 있습니다. 이른바 Tix라고 하는데 최근에 표준 파이썬 라이브러리에 추가되어 (그리고 또다른 인기있는 애드-인은 파이썬 메가 위젯(PMW)입니다) 기본적인 Tkinter 집합과 wxPython 등등에 의하여 제공되는 집합 사이의 틈새를 메꾸어 주고 있습니다.

이 정도로 마치겠습니다. 이 장은 Tkinter 참조 페이지가 목적이 아니며 여러분을 시작시키는 정도로 충분합니다. 파이썬 웹 페이지의 Tkinter 섹션에서 다른 Tkinter 자원으로 가능 링크들을 살펴보세요.

또한 Tcl/Tk 사용법에 관하여 여러 책이 있습니다. 적어도 하나는 Tkinter에 관한 책입니다. 그렇지만 사례 연구에서 Tkinter에 다시 돌아오겠습니다. 사례 연구에서는 배치 모드 프로그램을 GUI에 싸 넣어서 사용성을 개선하는 방법을 보여줍니다.

기억해야 할 것
  • GUI 콘트롤을 창부품(widgets)이라고 한다.
  • 창부품은 그릇 계통도 안에서 조립된다.
  • GUI 툴킷마다 다양한 창부품 집합을 제공한다. 물론 어느 툴킷에나 있을 것이라고 예상되는 기본 집합은 있다.
  • 프레임으로 관련 창부품을 무리짓고 재사용가능한 GUI 구성요소의 토대를 형성할 수 있다.
  • 사건 처리 함수나 메쏘드는 그 이름을 창부품의 command 특성에 연결하여 창부품과 연관시킨다.
  • OOP는 창부품 그룹에 상응하는 객체를 만들고 사건에 상응하는 메쏘드를 만들어서 GUI 프로그램을 상당히 단순화 시켜 줄 수 있다.

앞으로HOME다음으로

이 페이지에 관하여 질문이나 제안이 있다면 저에게 이메일을 보내 주세요: alan.gauld@btinternet.com

'파이썬 프로그래밍' 카테고리의 다른 글

tkinter 소개  (0) 2011.09.04
Tkinter로 생각하기  (0) 2011.04.24
TKINTER 요약  (0) 2011.04.24
UltraEdit Python 설정  (0) 2009.06.28
파이썬 데몬 만들기  (0) 2009.04.07

+ Recent posts