트킨터

From Hidden Wiki
Jump to navigation Jump to search
필독 사항 유닠스 계열 저작물, 성인물, 도박 웹 써버 보안 프로그래밍 그래핔 파싱
필독 사항 고스트BSD 표면 웹 싸이트 제작 리눅스 마스터 파이썬 트킨터 뷰티펄 숲
수학 아이투피 마약, 아청물, 해킹 웹 싸이트 보안 웹 프로그래밍 데이터 분석 게임 제작
통계학 뮤와이어 다크넽 싸이트 제작 정보 보안 기사 쟁고우 팬더즈 파이게임

트킨터 (Tkinter) 패키지는 Tk GUI 툴킷(toolkit)에 대한 파이썬(Python)의 표준 인터페이스이다. Tkinter는 Tk interface의 줄임말로 트킨터 또는 티케이인터라고 읽는다. ttk나 messagebox같은 모듈을 모아놓은 것을 Tkinter package라고 한다.

Tk는 플랫폼 독립적인 GUI 라이브러리이다. 티케이는 많은 프로그래밍 언어에서 그래픽 유저 인터페이스(GUI)를 만들기 위한 GUI 위젯(widget)의 기본 요소들의 라이브러리(library)를 제공하는 오픈 소스 크로스 플랫폼 위젯 툴킷이다.

TclTool Command Language의 약자로 티클 또는 티씨엘이라고 읽는다. Tcl은 스크맆트 언어프로토타이핑, 스크맆트 프로그램, GUI 및 테스팅, CGI, IRC (bot)을 만드는데 사용된다.

TclTk GUI 툴킷을 묶어서 Tcl/Tk라고 부른다.


문서쏠쓰 코드 (source code)는 우분투 19.10, 파이썬 3.7.5에서 테스트되었다.


프로그래밍을 할 때는 모든 것을 직접 만들 필요없이 특정 기능을 하도록 미리 만들어진 소스 코드모듈(module)을 불러와서 여러분의 프로그램에 포함시키는 것으로 프로그래밍에 들이는 시간과 노력을 절약할 수 있다.

파이썬(Python)에서는 아래와 같이

from tkinter import *

import tkinter as tk

같은 Tkinter 모듈을 불러오라는, 단 한 줄의 소스 코드로 간단하게 그래픽 유저 인터페이스(GUI)를 구현할 수 있다. from tkinter import *은 Tkinter 모듈로부터 모든 것을 불러오라는 의미이다. *은 컴퓨터에서 임의의 문자를 의미한다. 모든 것이 아닌 일부만 불러와서 사용하는 것도 가능하다. from functools import partial와 같이 하면 functools 모듈로부터 partial을 불러오라는 뜻이다. import tkinter as tk는 Tkinter 모듈을 tk라는 이름으로 불러오라는 의미이다.


우분투 19.10에는 python3-tk 패키지가 설치되어있지 않으므로 터미널 (terminal)에서

sudo apt install python3-tk

명령어 (command)를 입력하여 관련 패키지를 설치해준다.


소스 코드 편집기로는 VS코디엄 (VSCodium)이나 적당히 아무거나 쓰면 된다.


우분투 19.10의 경우

python3 cal.py

와 같은 방식으로 실행시키면 된다.

pack, grid로 부품 배열

박스 띄우기

Tk()는 트킨터 객체의 생성자이다.

root=Tk()

를 입력하면 빈 창이 뜬다.


(Tk객체).mainloop()를 하면 창에서 입력을 받아들인다.

root.mainloop()

와 같이 해주면 된다.


from tkinter import *
root=Tk()
root.mainloop()

까지 최소 3줄을 쏠쓰 코드 (source code)에 포함해줘야 빈 창이 뜬다.


위젯(widget) 달기

pussy = widget_name(new_Tk_object, ... )
pussy.pack() 

와 같은 방식으로 달아줄 수 있다. pussy는 임의로 붙인 명칭이다.


예를 들어, 레이블(label) 위젯은 이렇게 단다.

root = Tk()

sister_pussy = Label(root, text = "sis pussy")
sister_pussy.pack()

sister_pussy는 임의로 붙인 명칭이다. 그러나 "Label"은 반드시 그대로 써야한다.


위젯 위치 정하기

1. place

2. pack

3. grid

바로 위 예제의 경우 pack을 사용했다. grid는 표와 같은 공간(레이아웃)에서 위치를 정해주는 것이다.


완성된 코드 (code)는 아래와 같다.

from tkinter import *
root = Tk()

sister_pussy = Label(root, text = "sis pussy")
sister_pussy.pack()

root.mainloop()


레이블 1개, 입력 창 1개, 버튼 2개

아래는 grid로 만들어본 상자이다. (기능은 없다.)

from tkinter import *
root = Tk()

title = Label(root,text="Input")
txtbox = Entry(root, width = 15)
btn_1= Button(root, text = "Submit", width=10)
btn_2 = Button(root, text= "Cancel", width=10)

title.grid(row=0, column=0)
txtbox.grid(row=0, column=1)
btn_1.grid(row=1,column=1)
btn_2.grid(row=2, column=1)

root.mainloop()

Label은 레이블(이름표), Entry는 "입력 창", Button은 버튼이다. grid는 각 부품이 배열될 위치를 지정해준다.

grid에서 row는 행렬을, column은 을 의미한다. 표를 세로 방향으로 여러번 잘랐을 때 기둥처럼 세로로 서있는 게 column이다. 가로로 잘랐을 때 ㅡ자 모양으로 차곡차곡 쌓여있는 게 행이다.

2개의 부품을 같은 row와 column에 위치를 지정하면 마지막에 해당 위치에 배정된 것으로 기존의 것이 대체된다. 예를 들어, 다음과 같이 하면 btn_2만 나온다.

btn_1.grid(row=1,column=1)
btn_2.grid(row=1,column=1) 

레이블 2개, 입력 창 2개

Grid라고 하는 geometry manager는 위젯을 2차원의 표에 놓습니다. 마스터위젯은 row와 column에 해당하는 숫자로 나뉘고, 완성된 표에서 각각의 '셀'(표의 한 칸)은 위젯을 잡아둡니다.

언제 그리드 매니저를 쓰는가? 그리드 매니저는 tkinter의 geometry manager중 가장 유연합니다.(융통성 있음) 만약 당신이 세 가지의 모든 지오매트리 매니저를 쓰는 때와 방법을 공부하고 싶지 않으면, 반드시 그리드매니저만이라도 공부하세요. 그리드 매니저는 특히 다이얼로그 박스들들 디자인할 때 사용하기에 편리합니다.

만약 당신이 다이얼로그 박스를 만들기 위해 packer를 쓰고 있다면, packer대신에 그리드를 쓰는게 얼마나 쉬운지 알고는 깜짝 놀랄겁니다.

팩킹을 작동하게 하기위하여 많은 여분의 프레임을 쓰는 대신, 당신은 대부분의 경우에 간단하게 여러 위젯을 하나의 컨테이너 위젯에 담을 수 있고, 그리고 그리도 매니저를 그것들이 모두 당신이 원하는 위치에 놓기 위해 쓸 수 있습니다. (본인은 주로 총 두 개의 컨테이너를 쓰는데, 하나는 다이얼로그 바디를 위해, 또 하나는 아래의 버튼박스를 위해 씁니다.)

이 예를 한 번 보세요.(체크 버튼은 2칸, 이미지는 4칸을 차지한다.)

<레이블 1> <입력 1> <이미지> (이미지)
<레이블 2> <입력 2> (이미지) (이미지)
<체크 버튼> (체크 버튼) <버튼 1> <버튼 2>

pack매니저를 사용해서 저 레이아웃(구조)를 짜는 것도 가능하지만, 더 많은 틀 위젯을 필요로 하고 예쁘게 보이기 위해서는 더 많은 노가다를 필요로 합니다. 당신이 pack매니저 대신 그리드 매니저를 사용한다면, 당신은 모든 것들이 적절하게 놓이도록 하기 위해 위젯마다 그저 하나의 call만 필요합니다. (이 레이아웃을 만드는데 필요한 코드를 보려면 다음 섹션을 보세요.) 경고: 같은 마스터 윈도우 내에서 그리드와 팩(pack)을 섞어 쓰지마세요.

그랬다가는 tkinter가 그 둘을 섞는 방법을 찾는데 어마어마한 시간을 써버립니다. 아마 남은 인생을 다 쓸지도.. 그걸 멍하니 기다리기 보다는 프로그램을 종료하고, 당신의 코드를 다시 보세요. 위젯들을 위해 잘못된 parent를 사용하는 것은 흔한 실수입니다.

그리드 매니저를 쓰는 것은 간단합니다. 위젯을 만들고, 위젯들이 놓일 row와 column을 정하기 위해 grid 메소드를 사용하세요. 당신은 미리 그리드의 크기를 정할 수 없습니다. 매니저가 자동적으로 위젯을 보고 크기를 결정합니다.

from tkinter import *
master = Tk()

Label(master, text="보지년").grid(row=0, sticky=W)
Label(master, text="위대하신 자지님").grid(row=1, sticky=W)

e1 = Entry(master)
e2 = Entry(master)

e1.grid(row=0, column=1)
e2.grid(row=1, column=1)

master.mainloop()

비어있는 row와 column은 무시됩니다. 당신이 row를 10과 20으로 놓아도 결과는 같습니다.

Label(master, text="보지년").grid(row=0)
Label(master, text="위대하신 자지님").grid(row=1)

위와 같이 sticky 옵션을 쓰지 않으면 위젯들이 각각의 셀(cell)에서 가운데 정렬된다. sticky 옵션은 N,S,E,W 중에 하나 이상의 값을 필요로 합니다. 레이블(label)들을 왼쪽정렬하기 위해, sticky=W를 사용합니다. N = north 북족, W = west 서쪽, E = east 동쪽, S = south 남쪽.

체크 박스와 그림 추가

from tkinter import *
master = Tk()

#위젯 정의하기
label1 =Label(master, text="여동생 예쁘네")
label2 = Label(master, text="그년 젖탱이 맛있겠다")

entry1 = Entry(master)
entry2 = Entry(master)

checkbutton = Checkbutton(master,text="질싸 체크")

button1 = Button(master, text="로리콘 인증")
button2 = Button(master, text="메오후 인정?")

src_img = PhotoImage(file ='로리.gif').subsample(4)
image = Label(master, image = src_img)

#위젯 배치하기
label1.grid(sticky=E)
label2.grid(sticky=E)

entry1.grid(row=0, column=1)
entry2.grid(row=1, column=1)

checkbutton.grid(columnspan=2, sticky=W)

image.grid(row=0, column=2, columnspan=2, rowspan=2, sticky=N+S+W+E)

button1.grid(row=2, column=2)
button2.grid(row=2, column=3)

master.mainloop()

위젯에 한 셀보다 더 큰 공간을 줄 수도 있습니다. columnspan(칼럼스판)이라는 옵션은 위젯이 한 칼럼 이상의 공간을 차지하도록 하고, rowspan(로우스판)이라는 옵션은 위젯이 한 로우(줄) 이상의 공간을 차지하도록 하는데 쓰입니다. "#위젯 배치하기"의 코드는 레이아웃을 만듭니다.

"#위젯 배치하기"에는 중요한 것이 아주 많습니다.

첫째, label위젯을 위한 위치가 없습니다. 이 경우, 칼럼의 기본값은 0이고, 로우(row)는 그리드에서 사용되지 않은 가장 작은 숫자로 지정됩니다.

다음으로, 엔트리 위젯(텍스트를 적을 수 있는 위젯)은 일반적인 것과 같이 위치되지만, 체크버튼 위젯은 다음의 빈 로우에 위치하고(이 예제의 경우에는 2번 로우), 두 개의 칼럼을 차지합니다.

결과 칸(셀)은 라벨 위젯과 엔트리 위젯의 칼럼을 합한 만큼 자리를 차지합니다.

이미지 위젯은 열(column)과 행(row)의 2칸씩을 차지하도록 동시에 설정됩니다.


마지막으로, 버튼들은 각각 하나의 셀(cell)이 패킹되어있습니다.

src_img = PhotoImage(file ='로리.gif').subsample(4)에서 subsample(4) 안의 숫자가 클 수록 이미지는 축소되어 보인다. subsample이 없으면 원본 크기로 보인다. Tkinter PhotoImage Class는 GIF와 PGM/PPM 파일만 처리할 수 있고 jpg나 png는 쓸 수 없다.

만약 이 파일 이름이 MySisterBozy.py라면 MySisterBozy.py 파일과 같은 폴더에 로리.gif 파일을 놔두면 된다.

자세한 내용은 아래 링크 참조

체크버튼, 콤보박스 넣기

레이블 안에 이미지 넣기

1. Tkinter 위젯

앞 아티클에서 언급했듯이, Tkinter는 제한된(Limited) 핵심 위젯들만을 제공하고 있다. 아래는 Tkinter가 제공하는 주요 위젯들이다.


위젯 / 설명

Button 단순한 버튼

Label 텍스트 혹은 이미지 표시

CheckButton 체크박스

Entry 단순한 한 라인 텍스트 박스

ListBox 리스트 박스

RadioButton 옵션 버튼

Message Label과 비슷하게 텍스트 표시. Label과 달리 자동 래핑 기능이 있다.

Scale 슬라이스 바

Scrollbar 스크롤 바

Text 멀티 라인 텍스트 박스로서 일부 Rich Text 기능 제공

Menu 메뉴 Pane

Menubutton 메뉴 버튼

Toplevel 새 윈도우를 생성할 때 사용. Tk()는 윈도우를 자동으로 생성하지만 추가로 새 윈도우 혹은 다이얼로그를 만들 경우 Toplevel를 사용한다

Frame 컨테이너 위젯. 다른 위젯들을 그룹화할 때 사용

Canvas 그래프와 점들로 그림을 그릴 수 있으며, 커스텀 위젯을 만드는데 사용될 수도 있다


2. 위젯 사용

위젯은 객체를 생성하여 필요한 속성들을 지정하여 사용한다. 위젯은 부모 컨테이너와 연관하여 어떤 상대적 위치에 놓이게 되는데, 앞에 설명한 Geometry Manager를 사용하여 각 위젯의 위치를 정하게 된다.

아래 예제는 레이블 안에 이미지를 넣고 화면에 보여주는 코드로서, MyFrame 이라는 클래스를 만들고 생성자에서 필요한 위젯들을 배치하고 있다. 우선 main() 에서는 Tk 객체 root를 만들고 title()을 사용하여 윈도우 제목을 설정하고, geometry()를 사용하여 윈도우의 크기와 좌표를 정해주었다. geometry() 안의 문자열은 윈도우 크기 및 좌표를 "가로x세로+X+Y" 형식으로 표현한다. X는 모니터의 왼쪽으로부터 몇 픽셀이나 떨어졌나, Y는 모니터의 위쪽으로부터의 거리이며, -일 경우 오른쪽과 아래쪽으로부터의 거리이다.

MyFrame 클래스는 Frame으로부터 상속된 파생클래스이고, 생성자에서 Label 하나를 추가하고 있다. Label은 좌표 (0,0)에 위치(place)하게 되고, 레이블 안에는 이미지를 넣고 있다. 이미지는 tkinter의 PhotoImage 클래스를 사용하고 있는데, 이 클래스는 .gif 파일 (혹은 PGM) 만을 읽을 수 있다. 다른 이미지 포맷을 사용하기 위해서는 외부 모듈을 사용해야 한다. PhotoImage() 에 이미지 파일을 적고 리턴된 객체를 레이블에 지정하면 되는데, 특히 가비지 컬렉션으로부터 삭제되는 것을 방지하기 위해 lbl.image = img 처럼 레퍼런스를 증가시켜 준다.

from tkinter import *

class MyFrame(Frame):
    def __init__(self, master):
        img = PhotoImage(file='로리.gif')
        lbl = Label(image=img)
        lbl.image = img  # 레퍼런스 추가
        lbl.place(x=0, y=0)

def main():
    root = Tk()
    root.title('로린이 보지')
    root.geometry('500x400+10+10')
    myframe = MyFrame(root)
    root.mainloop()

if __name__ == '__main__':
    main()

예제로 배우는 파이썬 프로그래밍 - Tkinter 위젯 https://pythonstudy.xyz/python/article/121-Tkinter-%EC%9C%84%EC%A0%AF

체크버튼

checkbutton 위젯 체크버튼

이번에는 윈도우에 체크버튼을 추가해 보겠습니다. 아래 코드를 보세요.

from tkinter import *       
from tkinter import ttk      
from tkinter import messagebox 
win = Tk()                   
def clickMe():                
    messagebox.showinfo('유두 만져졌다', "누군가 만졌다!") 
action = ttk.Checkbutton(win, text='내 유두를 만져줘', command = clickMe) 
action.grid(column=1, row=0) 
win.mainloop()   

앞에서 본 Button 위젯의 예제를 재활용하였습니다. 라인7에서 Button이 Checkbutton으로 변경되었죠? 이 코드를 실행하면 버튼 대신 체크버튼이 윈도우에 추가됩니다. 그외의 부분은 동일한데요. Button위젯과 마찬가지로 체크버튼을 마우스로 클릭하면 clickMe() 함수가 실행되고 그 결과 메시지 박스가 실행됩니다.


Checkbutton 위젯 추가

체크버튼의 경우 GPIO 출력 값을 High, Low 중에 선택할때 사용하기 좋습니다.

아래는 여러 개의 체크버튼 위젯을 생성하고, 그 중 어떤 버튼이 체크되었는지 확인하는 예제입니다. 간단해서 눈으로만 봐도 이해할 수 있습니다.

from tkinter import *
from tkinter import ttk
from tkinter import messagebox
win = Tk()
win.title("나에게 삽입해줘!")
win.geometry('200x100+200+200')
def clickMe():
    str = ''
    if cVar1.get() == 1:
        str = str + '사정 당함, '
    if cVar2.get() == 1:
        str = str + '보지에 사정 당함, '
    if cVar3.get() == 1:
        str = str + '항문에 사정 당함, '
    if str == '':
        str = "nothing was checked"
    messagebox.showinfo("임신 당함", str)
cVar1 = IntVar()
c1 = ttk.Checkbutton(win, text="입", variable = cVar1)
c1.grid(column=0, row=0)
cVar2 = IntVar()
c2 = ttk.Checkbutton(win, text="보지", variable = cVar2)
#c2.deselect()
c2.grid(column=0, row=1)
cVar3 = IntVar()
c3 = ttk.Checkbutton(win, text="항문", variable = cVar3)
#c3.deselect()
c3.grid(column=0, row=2)
action=ttk.Button(win, text="나에게 사정해줘!!", command=clickMe)
action.grid(column=0, row=3)
win.mainloop()

라인18, 라인21, 라인25에서는 체크버튼과 연결해서 사용할 변수를 생성하였습니다. 라인19, 라인22, 라인26 에서 체크버튼 위젯을 생성하였습니다. 체크버튼 위젯을 생성할 때 'variable = 변수명' 처럼 체크버튼의 상태값을 저장할 변수를 설정해 주었습니다. 예를 들어 라인19에 variable = cVar1 이라는 코드에 따라 생성한 체크버튼의 상태값은 cVar1이라는 변수에 연동됩니다. 체크버튼에 연동된 변수들은 해당 체크버튼이 선택되면 1 값으로 선택되지 않으면 0 값으로 설정됩니다. 라인7 ~ 라인17은 clickMe() 함수입니다. 라인9에서는 라인19에서 생성한 체크박스가 선택되었는지 확인합니다. 선택된 경우는 cVar1.get() 이 1값을 가지고, 선택되지 않은 경우는 cVar1.get() 은 0 값을 가집니다. 라인23에 deselect() 라는 함수는 체크박스가 선택되지 않은 상태로 만들어 줍니다. 파이썬에서 # 로 시작하는 라인은 실행되지 않는 주석문입니다.

코드를 실행하면 아래와 같은 윈도우가 나타납니다.


다수의 체크박스 위젯

윈도우가 나타나면 기능을 테스트 해봐야죠. 체크박스 위젯 중 보지항문을 클릭해서 위의 그림처럼 선택합니다. 그리고, "나에게 사정해줘!!"라는 버튼을 클릭합니다.

선택된 체크박스를 보여주는 메시지창

현재 선택된 체크박스를 메시지 박스에서 알려주고 있습니다.

intVar()

위의 예제에서는 intVar() 생성자로 변수를 생성하였습니다. 이렇게 만들어진 변수는 위젯과 연결된 변수라는 의미로 생각하세요. 위젯의 값이 변하면 변수값이 변하고, 변수 값이 변해도 위젯 상태가 변하도록 연결해 줍니다.

TKinter chekcbutton 체크버튼 위젯 intVar https://blog.naver.com/audiendo/220789279239


텍스트박스

textbox 위젯

윈도우에서 사용자의 키보드 입력을 받기 위해서는 텍스트박스 위젯을 사용합니다. 텍스트박스 위젯의 경우도 위젯과 연결된 변수를 사용해서 입력 문자열을 처리할 수 있습니다.

from tkinter import *
from tkinter import ttk
from tkinter import messagebox
win = Tk ()
win.title("가 가장 좋아하는 체위는?")
win.geometry('500x100+200+200')
def clickMe():
    messagebox.showinfo("에게 실험해봐!", str.get())
str = StringVar()
textbox = ttk.Entry(win, width=50, textvariable=str)
textbox.grid(column = 0 , row = 0)
action=ttk.Button(win, text="보지에 쑤셔죠!", command=clickMe)
action.grid(column=0, row=1)
win.mainloop()

라인9에서 텍스트위젯과 연결할 str 변수를 생성하였습니다. 이번에는 사용한 생성자는 str 타입을 처리할 수 있는 StringVar() 입니다. 라인10에서 ttk.Entry() 메소드를 호출하여 텍스트박스 위젯을 생성하였습니다. "텍스트박스 위젯"은 생성자이름이 Entry() 예요. 이때 width=20 에서 텍스트박스 너비를 20 문자크기로 설정하였습니다. 그리고 textvariable = str 에서 라인9에서 생성한 변수를 텍스트박스 위젯과 연결하였습니다. 이제부터 텍스트박스에 키보드 입력이 들어오면 그래돌 str 변수에 반영됩니다. 물론 그 반대 방향으로도 반영됩니다. 그외에 grid()나 Button() 함수 설명은 이전 포스트에 설명되어 있으므로 생략하겠습니다. 라인12에서 생성된 버튼위젯에 연결된 함수(라인 7~라인8) clickMe()가 실행되면, str.get() 메소드를 이용하여 텍스트박스 위젯에 입력된 문자열을 가져와서 메시지 박스에 보여줍니다.

코드를 실행하면 아래와 같습니다.


Textbox 위젯을 추가한 윈도우

키보드 등으로부터 문자열을 입력받을 수 있는 텍스트박스 위젯이 생성되었습니다. 여기에 1234567 이라고 입력을 하였습니다. 그리고 'Click Me' 버튼을 클릭합니다.


Textbox 입력 문자열 출력

텍스트박스에 입력된 문자열을 정확히 보여주는 메시지 박스가 나타납니다. 이제 텍스트박스 위젯에 입력된 문자열을 구해오는 방법은 아시겠죠?

TKinter textbox 텍스트박스 위젯 https://blog.naver.com/audiendo/220791080634


콤보박스

combobox 위젯

텍스트 박스 위젯은 키보드를 이용해서 문자열을 사용자가 입력할 수 있도록 해줍니다. 하지만, 정해진 메뉴 내에서 선택을 하게 만드는 경우는 마우스만으로 조작이 가능한 콤보박스위젯을 사용하면 편리합니다. 입력할 문자열의 종류가 사전에 정해져 있는 경우 사용해야 합니다. 콤보박스는 메뉴를 가집니다. 메뉴는 리스트로 만들어 지고, 리스트를 콤보박스의 속성에 연결 시켜줌으로서 메뉴 등록을 완료할 수 있습니다.

아래 코드를 볼께요.

from tkinter import *
from tkinter import ttk
from tkinter import messagebox
win = Tk ()
win.title("근친상간")
win.geometry('200x100+200+200')
def clickMe():
    messagebox.showinfo("임신 완료", str.get())
str = StringVar()
combo = ttk.Combobox(win, width=20, textvariable=str)
combo['values'] = ('엄마', '누나', '여동생', '')
combo.grid(column = 0 , row = 0)
combo.current(0)
action=ttk.Button(win, text="강간", command=clickMe)
action.grid(column=0, row=1)
win.mainloop()

라인9에서 콤보위젯과 연동할 변수 str 를 생성하였습니다. 라인10에서 ttk.Combobox() 함수로 콤보박스 위젯을 생성했습니다. 동시에 str 변수와 콤보박스 위젝을 연동하였습니다. 라인 11에서 콤보박스에 리스트를 생성하여 콤보박스 위젯의 values 속성에 할당하였습니다. 이로써 콤보박스에 메뉴가 등록되었습니다. 메뉴는 총 4 항목인데, '엄마', '누나', '여동생', '' 입니다. 라인13에서 콤보박스에 메뉴항목 4개 중에서 0 번째 'apple'이 선택된 상태로 만들어 주었습니다. 만일 윈도우 생성시 'pear'가 선택되어 있도록 만들고 싶으면 combo.current(3)으로 하면 됩니다.

그외에 앞의 포스팅에서 설명한 것들은 생략합니다.

코드 실행결과를 보겠습니다.

combo box 위젯 + button 위젯

콤보박스 위젯 메뉴 중에 '엄마'가 선택되어 있는 것이 보이시죠? 위젯 우측의 아래로 화살표 부분을 클릭하면 나머지 메뉴들을 볼 수 있습니다.

콤보박스에 등록된 문자열들

자~ 이중 '여동생'을 선택하겠습니다. 선택하고 '강간' 버튼을 클릭합니다.

콤보박스에서 선택된 문자열

메시지 창에 '여동생'이 선택되었음을 보여줍니다.


TKinter combobox 콤보박스 위젯 https://blog.naver.com/audiendo/220792145212


버튼 클릭시 새 창 띄우기

from tkinter import *

def nwindow():
    global nwin
    nwin = Toplevel()
    nwin.title("새 성노예")
    btn.config(state = 'disable')

    photo2 = PhotoImage(file = '/home/raper/사진/왕가슴 베이글녀.gif')
    lbl2 = Label(nwin, image = photo2)
    lbl2.pack()

    qbtn = Button(nwin, text = '맛을 봤으니 넌 사형', command = quit)
    qbtn.pack()

    nwin.mainloop()

def quit():
    nwin.destroy()
    btn.config(state = 'normal')

main = Tk()
main.title("옛 성노예")
main.geometry("750x650")

photo = PhotoImage(file = '/home/raper/사진/로린이.gif')
lbl = Label(main, image = photo)
lbl.pack()

btn = Button(main, text = "새로운 노예를 사러 가자", command = nwindow)
btn.pack()

main.mainloop()

from tkinter import *로 Tkinter 패키지를 불러온다.

def nwindow():로 nwindow 함수를 정의한다. () 안에 함수로 입력될 매개변수들을 쉼표(,)로 구분해서 여러개 적어줄 수 있다. def __init__(self, master):와 같은 식으로 맨 앞에 self를 적어준다. 예를 들어 회원 정보를 저장하는 함수의 경우 def member(self, name, phone, address):처럼 이름(name), 폰 번호(phone), 주소(address)의 3개의 변수를 입력받아 저장할 수 있다. 입력값이 없는데 입력값이 없으면 오류가 나는 경우 () 안에 self를 적어준다. _가 2개 연속 있는 경우는 특별한 의미가 있는 것이다. __init__은 초기화하라는 것이다.

함수 안에 있는 pack()이나 변수 앞에 self가 붙어 self.pack()같은 형태인 경우 그 함수 내에서 쓰는 명령이나 변수라는 뜻이다.

nwindow 함수로 새 창을 띄운다. global nwin 하여 nwin을 전역 변수(global variable)로 설정하여 다른 함수에서도 사용할 수 있게 한다. global을 선언하지 않으면 같은 클래스(class) 안에 있더라도 다른 함수(def)에 있으면 사용 못 하는 지역 변수(local variable)이다. 전역 변수는 그 프로그램 전체에서 사용할 수 있다. global you, rape, sister, mother, daughter와 같은 식으로 쉼표(,)로 구분하여 여러개의 변수를 동시에 선언할 수 있다.

다른 클래스에 있는 함수는 다른 클래스의 이름이 classname이고 함수의 이름이 defname일 경우 classname.defname을 하면 사용할 수 있다.

Tkinter와 같이 모듈(외부의 py 파일)에 있는 클래스, 함수, 변수를 불러올 경우 우선 import로 그 모듈을 불러와야 한다. 그리고 모듈 파일의 이름이 modulename.py이고, 모듈 이름이 modulename, 클래스 이름이 classname, 함수 이름이 defname, 변수 이름이 variablename일 경우, modulename.classname, modulename.defname, modulename.variablename처럼 앞에 modulename.을 붙여주면 그 모듈의 클래스, 함수, 변수를 불러올 수 있다.

class MyFrame(Frame):와 같은 경우 MyFrame 클래스가 Frame 클래스를 상속했다는 의미이며 부모의 함수를 상속받았으므로 부모 클래스인 Frame의 함수를 마음대로 가져다가 쓸 수 있다. 그러나 부모인 Frame은 자식 클래스인 MyFrame의 함수를 가져다가 쓸 수 없다.

nwin = Toplevel()을 해서 새 창으로 띄운다. nwin = Tk()를 해도 새 창으로 뜨지만 빈 창만 뜬다.

nwin.title("새 성노예")에 창의 맨 위에 뜨는 창의 제목을 적어준다.

btn.config(state = 'disable')를 해주면 새 창을 닫지 않는 이상 새 창을 띄우는 버튼이 불활성화 되어 다시 클릭할 수 없다. 다시 클릭하려면 새 창을 닫아야 한다. 이 config를 해주지 않으면 새 창을 계속 띄울 수 있다.

리눅스의 경우 자신의 사용자 계정 이름이 raper일 경우 photo2 = PhotoImage(file = '/home/raper/사진/왕가슴 베이글녀.gif')와 같이 적어주면 된다. 윈도우즈의 경우 photo2 = PhotoImage(file = 'D:\사진\왕가슴 베이글녀.gif')와 같이 적어주면 된다. 이미지 파일이 이 파이썬 프로그램과 같은 폴더에 있을 경우 photo2 = PhotoImage(file = '왕가슴 베이글녀.gif')만 적어줘도 된다.

/는 슬래시(slash) 또는 빗금(solidus)이라고 부르며 유닉스리눅스에서 디렉터리를 구분할 때 사용한다. \는 역슬래시(backslash) 또는 역사선(reverse solidus)이라고 불리며 윈도우즈에서 디렉터리를 구분할 때 사용한다. 한국어 윈도우즈에서는 역슬래시 대신 원화 기호 ₩를 사용한다. 대부분의 남한 키보드에는 역슬래시 대신에 원화 기호가 인쇄되어 있다.

qbtn = Button(nwin, text = '맛을 봤으니 넌 사형', command = quit)에 새 창의 닫기 버튼에 들어갈 말을 적어주면 된다.

btn.config(state = 'normal')이면 새 창을 닫으면 원래 있던 창의 새 창 띄우는 버튼이 다시 활성화되지만 이게 btn.config(state = 'disable')와 같으면 새 창을 닫아도 새 창을 띄우는 버튼이 불활성화된 상태 그대로 남는다.

main.geometry("750x650")면 가로 750, 세로 650 크기의 창이 열린다. main.geometry("750x650+350+200")이면 모니터의 왼쪽으로부터 350, 위로부터 200 떨어진 위치에 창이 열리고, main.geometry("750x650-130-180")이면 모니터의 오른쪽에서 130, 아래에서 180 떨어진 위치에 창이 열린다.


(Tkinter) Image won't show up in new window https://stackoverflow.com/questions/35924690/tkinter-image-wont-show-up-in-new-window


프레임 여러개 만들기

아래 예제는 고객 데이타를 입력 받는 간단한 윈도우 샘플이다. 메인 Frame 안에 4개의 자식 Frame을 사용하였고, 각각의 자식 Frame 안에 레이블, 텍스트, 버튼 등의 위젯들을 추가하였다.

from tkinter import *
from tkinter.ttk import *

class MyFrame(Frame):
    def __init__(self, master):
        Frame.__init__(self, master)
 
        self.master = master
        self.master.title("오피걸 고객 입력")
        self.pack(fill=BOTH, expand=True)
 
        # 성명
        frame1 = Frame(self)
        frame1.pack(fill=X)
 
        lblName = Label(frame1, text="성명", width=10)
        lblName.pack(side=LEFT, padx=10, pady=10)
 
        entryName = Entry(frame1)
        entryName.pack(fill=X, padx=10, expand=True)
 
        # 회사
        frame2 = Frame(self)
        frame2.pack(fill=X)
 
        lblComp = Label(frame2, text="회사명", width=10)
        lblComp.pack(side=LEFT, padx=10, pady=10)
 
        entryComp = Entry(frame2)
        entryComp.pack(fill=X, padx=10, expand=True)
 
        # 특징
        frame3 = Frame(self)
        frame3.pack(fill=BOTH, expand=True)
 
        lblComment = Label(frame3, text="특징", width=10)
        lblComment.pack(side=LEFT, anchor=N, padx=10, pady=10)
 
        txtComment = Text(frame3)
        txtComment.pack(fill=X, pady=10, padx=10)
 
        # 저장
        frame4 = Frame(self)
        frame4.pack(fill=X)
        btnSave = Button(frame4, text="저장")
        btnSave.pack(side=LEFT, padx=10, pady=10)
 
 
def main():
    root = Tk()
    root.geometry("600x550+100+100")
    app = MyFrame(root)
    root.mainloop()


if __name__ == '__main__':
    main()

예제로 배우는 파이썬 프로그래밍 - Tkinter 위젯 https://pythonstudy.xyz/python/article/121-Tkinter-%EC%9C%84%EC%A0%AF


체크버튼과 입력값으로 if문 만들기

from tkinter import *
from tkinter import ttk, messagebox
 
class cframe:

    def __init__(self, fr):
        self.defframe = fr

        self.fr0 = Frame(fr)
        self.fr0.pack()

        global virgin, bitch, price, rent
        virgin = IntVar()
        bitch = IntVar()
        price = IntVar()
        rent = IntVar()

        self.fr1 = Frame(self.fr0)
        self.fr1.pack(fill=X)

        imgplace = PhotoImage(file = '로리공주.gif')
        self.la1 = Label(self.fr1, image = imgplace)
        self.la1.image = imgplace
        self.la1.pack()

        self.fr2 = Frame(self.fr0)
        self.fr2.pack(fill = X)
        self.la2 = Label(self.fr2, text = "처녀막이 있다고 생각함?")
        self.la2.pack(side = LEFT)
	 			
        self.cb2_2 = Checkbutton(self.fr2, text = '걸레', variable = bitch)
        self.cb2_2.pack(side = RIGHT)
		 
        self.cb2_1 = Checkbutton(self.fr2, text = '처녀', variable = virgin)
        self.cb2_1.pack(side = RIGHT)
		
        self.fr3 = Frame(self.fr0)
        self.fr3.pack(fill=X)
        self.la3 = Label(self.fr3, text = "산다면 얼마까지 낼 생각?(가격은 백만원 단위로)")
        self.la3.pack(side = LEFT)
        self.en3 = Entry(self.fr3, textvariable = price)
        self.en3.pack(side = RIGHT)

        self.fr4 = Frame(self.fr0)
        self.fr4.pack(fill=X)
        self.la4 = Label(self.fr4, text = "남한테 빌려준다면 얼마에 빌려줄래?(가격은 백만원 단위로)")
        self.la4.pack(side = LEFT)
        self.en4 = Entry(self.fr4, textvariable = rent)
        self.en4.pack(side = RIGHT)

        self.fr5 = Frame(self.fr0)
        self.fr5.pack(fill=X)
        self.bu5_1 = ttk.Button(self.fr5, text = "입찰", command=cframe.selection)
        self.bu5_1.pack(side = LEFT)
        self.bu5_2 = ttk.Button(self.fr5, text = "포기", command=cframe.cbutton)
        self.bu5_2.pack(side = RIGHT)
	 
    def selection():
        stringv = ''
		 
        if virgin.get() == 1 and bitch.get() == 1:
            stringv = "어떻게 처녀면서 걸레지?"

        elif virgin.get() == 1:
            if price.get() < 100:
                stringv = "처녀에 이정도 미인을 1억원도 안 내고 사려고?"
                if rent.get() > 10:
                    stringv = stringv + " 1억도 안 주고 산 걸 천만원 넘게 받고 빌려주다니 개새끼네."
                else:
                    stringv = stringv + " 1억도 투자 안 했으니까 싸게라도 많이 굴리면 본전은 뽑겠지!"
            elif 100 <= price.get() <= 1000:
                stringv = "이렇게 예쁜데다 처녀 공주인데 몇억은 내야지!"
                if rent.get() < 10:
                    stringv = stringv + " 몇억 주고 산 처녀를 천만원도 안 되는 돈에 빌려주다니 제정신?"
                else:
                    stringv = stringv + ' 몇억짜리 처녀인데 천만원은 넘게 받아야 빌려주지.'
            else:
                stringv = "아무리 처녀라도 10억원 이상을 내다니 미쳤네."

        elif bitch.get() == 1:
            if price.get() > 100:
                stringv = "걸레한테 1억원을 넘게 내다니 미쳤구나!"
                if rent.get() < 1:
                    stringv = stringv + ' 아무리 걸레라도 원래 공주였는데 고작 100만원도 안 받고 빌려준다니 완전 너덜너덜해질 때까지 굴릴 생각?'
                else:
                    stringv = stringv + ' 원래 공주였는데 이정도는 받아야 빌려주지.'
            elif 100 >= price.get() >= 10:
                stringv = '걸레 몸값으로는 이게 적정가지.'
                if rent.get() < 1:
                    stringv = stringv + ' 걸레인데 창녀로 굴리면서 화대로 100만원씩이나 받을 수는 없지.'
                else:
                    stringv = stringv + ' 명색이 공주인데 화대로 백만원은 받아야 하지 않겠어?'
            else:
                stringv = "예뻐도 비처녀라면 좀..."
                if rent.get() < 0.1:
                    stringv = stringv + ' 아무리 천만원도 안 주고 산 여자라고 해도 1회 십만원도 안 받고 빌려줄수가 있냐?' 
                else:
                    stringv = stringv + ' 비싼 보지가 왔어요! 한 번 쑤시는데 10만원 넘게 듭니다!'

        else:
            stringv = "처녀가 아니면서 걸레도 아니라고?"

        messagebox.showinfo("경매 결과", stringv)

    def cbutton():
        mwin.destroy()

mwin = Tk()
mwin.title('노예 시장에 매물로 나온 공주')
mwin.geometry("620x420+290+160")
cfview = cframe(mwin)
mwin.mainloop()


체크버튼과 엔트리(Entry)로 값을 입력 받은 후 그 값을 if, elif, else문에 넣어 각각의 조건에 해당하는 문장을 새 창에 출력한다. 하나의 조건에 하나의 문장을 배당하고, 2개의 값의 조합에 2개의 문장이 조합되어 출력된다.


로리공주.gif 파일을 이 파이썬 프로그램과 같은 폴더에 넣어놓거나, 이미지 파일의 위치를 절대 경로나 상대 경로로 적어주어야 한다. 파일의 이름은 바뀌어도 되나 반드시 gif 파일이어야 하며 jpg나 png 파일은 쓸 수 없다.


절대 경로는 /home/username/사진/로리공주.gif 같은 것이나 D:\사진\로리공주.gif 같은 것이고, 상대 경로는 ./사진/로리공주.gif 나 .\사진\로리공주.gif 같은 것이다. 유닉스리눅스에서는 디렉터리 구분을 슬래시(/)로 하고, 윈도우즈에서는 역슬래시(\)나 원화 기호(₩)로 한다. 리눅스나 윈도우즈 모두 현재 폴더는 .으로 나타내며 상위 폴더는 ..으로 나타낸다. username 폴더에 해당하는 사용자의 홈 디렉터리는 ~로 표시한다. 루트(/)나 드라이브 문자(D:)처럼 최상위 경로부터 적는 것을 절대 경로라고 하고, 중간을 생략하고 현재 디렉터리 기준으로 적는 것을 상대 경로라고 한다.


즉, 이미지 파일이 이 파이썬 프로그램이 있는 폴더의 하위 폴더인 "사진" 폴더에 있을 경우 imgplace = PhotoImage(file = './사진/로리공주.gif') 와 같은 식으로 적어주면 된다. 다른 곳에서도 이 프로그램을 수정 없이 쓰기 위해서는 절대 경로로 적는 것은 피하고, 되도록이면 상대 경로로 적어줘야 한다. 이미지 파일이 1,000개쯤 된다고 생각해봐라. 이걸 다 수정하는 노가다를 하느니 처음부터 상대 경로로 적어주는 것이 좋다. 이미지 파일 뿐만 아니라 다른 파일의 경우도 마찬가지이다. 특히 웹 싸이트 만들 때 로컬 컴퓨터에서 개발할 때랑 가상 전용 써버(VPS)에 업로드 할 때랑 절대 경로가 다르므로 문제가 생기는 경우가 많다. 개발할 때부터 상대 경로만 쓰면 VPS에 그대로 업로드해도 잘 작동한다.


객체 지향 프로그래밍(클래스 사용)

파이썬에서는 클래스(class)를 사용하여 객체 지향 프로그래밍(object-oriented programming, OOP)을 할 수 있다.


아무 것도 없는 창

from tkinter import *

root=Tk()
root.mainloop()

위에서 생성된 최상위창은 tkinter 애플리케이션에서 가장 높은 수준의 GUI구성요소이며, 최상위 창의 이름은 'root'로 하는것이 관례적이다.

packing 하기

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

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

>packing

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

from tkinter import *

root = Tk()

F = Frame(root) #root와 F의 논리적인 부모자식관계 정의
F.pack() #F를 packing하여 시각적으로 보여지도록 함

root.mainloop()

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

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

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

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

창 부품(위젯) 꾸리기

창 가운데 녹색 버튼 띄우기.

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

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

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

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

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

from tkinter import *

root = Tk()

F = Frame(root)
F.pack() #packing

button1=Button(F)
button1['text']="로리 망꼬 다이스키!"
button1['background']='green'
button1.pack() #packing

root.mainloop()

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

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

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

클래스 구조

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

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

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

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

from tkinter import *

class MyApp:
    def __init__(self,Parent):
        self.F=Frame(Parent)
        self.F.pack()

        self.button1=Button(self.F)
        self.button1["text"]="로리 망꼬 다이스키!"
        self.button1["background"]="green"
        self.button1.pack()
root = Tk()
myapp=MyApp(root)
root.mainloop()

중앙에 닫기 버튼이 있는 창

import tkinter as tk
class Application(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.grid()  
        self.createWidgets()
    def createWidgets(self):
        self.quitButton = tk.Button(self, text='보지질싸', command=self.quit)
        self.quitButton.grid()
app = Application()
app.master.title('니 애미 보지')
app.mainloop()

창이 작게 뜨기 때문에 창 맨 위에 뜨는 제목(title)인 "니 애미 보지"가 보이지 않는다. 마우스로 창을 키우면 창 이름인 "니 애미 보지"가 보인다.

속성 설정하기

from tkinter import *

class MyApp:
    def __init__(self,Parent):
        self.F=Frame(Parent)
        self.F.pack()

        self.button1=Button(self.F)
        self.button1["text"]="로린이 따먹자!"
        self.button1["background"]="green"
        self.button1.pack()

        self.button2=Button(self.F)
        self.button2.configure(text="오토코노코도 따먹자!")
        self.button2.configure(background="tan")
        self.button2.pack()

        self.button3=Button(self.F,text="그러다 너도 흑형한테 따먹힘.",background="red")
        self.button3.pack()

root = Tk()
myapp=MyApp(root)
root.mainloop()

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

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

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

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

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


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

정렬

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

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

from tkinter import *

class MyApp:
    def __init__(self,Parent):
        self.F=Frame(Parent)
        self.F.pack()

        self.button1=Button(self.F)
        self.button1["text"]="유두"
        self.button1["background"]="green"
        self.button1.pack(side=LEFT)

        self.button2=Button(self.F)
        self.button2.configure(text="요도")
        self.button2.configure(background="tan")
        self.button2.pack(side=LEFT)

        self.button3=Button(self.F,text="보지",background="red")
        self.button3.pack(side=LEFT)

        self.button4=Button(self.F,text="항문",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>" 은 마우스왼쪽 클릭을 의미한다.

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="하지원 팬티색",background="green")
        self.button1.pack(side=LEFT)
        self.button1.bind("<Button-1>",self.button1Click)

        self.button2=Button(self.F,text="자궁색",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()

"하지원 팬티색" 버튼을 클릭하면 팬티의 색이 바뀌고, "자궁색" 버튼을 클릭하면 destroy() 메서드를 호출하여 창이 닫히게된다.

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

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

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

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


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

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

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

'하지원 팬티색' 버튼창부품을 클릭하면 '하지원 팬티색' 버튼창부품의 색이 변경된다.


23행 : '자궁색' 버튼창부품을 왼쪽마우스로 클릭하면 button2Click함수가 동작하고,

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

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

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

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

초점(focus)

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

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

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

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

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

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


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

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

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

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

from tkinter import *

class MyApp:
    def __init__(self,parent):
        self.F=Frame(parent)
        self.F.pack()

        self.button1=Button(self.F)
        self.button1.configure(text="하지원 팬티색",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="자궁색",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)
        root.destroy()

def report_event(event):
    print('time', event.time, 'type', event.type, 'widget', event.widget, "keysym", event.keysym, "num", event.num, "char", event.char, "x", event.x, 'y', event.y, 'x_root', event.x_root, 'y_root', event.y_root, )
    event_name={"2":"KeyPress","4":"ButtonPress"}
    print()
    print("시간:",str(event.time))
    print("이벤트 종류 = "+str(event.type))
    print(event_name[str(event.type)])
    print("이벤트 위젯 아이디 =",event.widget)
    print("이벤트 키 심벌 =",event.keysym)
    print()

root=Tk()
MyApp(root)
root.mainloop()

윈도우즈에서는 report_event에서 시간과 이벤트 종류는 화면에 뜨지만 다른 부분들은 오류가 발생하여 뜨지 않는다. 리눅스에서는 정상적으로 작동한다.


이벤트명

bind() 메서드의 첫번째 파라미터로 사용하는 이벤트명은 문자열로서 <이벤트명> 과 같이 앵글 브래킷으로 묶여 있다. 다음은 자주 사용되는 이벤트명을 예시한 것이다.

<Button-1> 마우스 왼쪽 버튼 클릭

<Button-2> 마우스 중간 버튼 클릭

<Button-3> 마우스 오른쪽 버튼 클릭

<Double-Button-1> 왼쪽 버튼 더블클릭

<Return> Enter 키 눌려짐

<Key> 키가 눌려짐


Event Object

이벤트 핸들러(이벤트 콜백)는 event라는 하나의 파라미터를 갖는데, 이는 Tkinter Event Object 로서 다음과 같은 속성(attribute)들을 갖는다. 위의 #2 예제를 보면, click() 함수에서 event 파라미터를 받아들이고, 이 event의 x, y 좌표를 사용하고 있음을 알 수 있다.

char 키보트 이벤트에서 발생하는 문자 하나

keysym 키보트 이벤트에서 발생하는 키의 심볼명

num 마우스 이벤트의 버튼 번호. 왼쪽부터 1, 2, 3

x, y 위젯의 죄상단으로부터의 상대적 마우스 위치

x_root, y_root 화면 죄상단으로부터의 상대적 마우스 위치

Key 이벤트가 발생한 위젯

키보드 입력을 보여주기

아래 예제는 Key 이벤트에 대해 keyPressed() 함수를 바인딩하고, 전달된 event.char 를 써서 눌려진 키를 프린트하는 코드이다.

from tkinter import *
 
def keyPressed(event):
    # 키보드 문자 하나 출력
    print(event.char)
 
root = Tk()
 
frame = Frame(root, width=100, height=100)
# Key 이벤트 바인딩
frame.bind('<Key>', keyPressed) 
frame.place(x=0, y=0)
 
# 키보드 포커스를 갖게 한다
frame.focus_set()
 
root.mainloop()

예제로 배우는 파이썬 프로그래밍 - Tkinter 이벤트 https://pythonstudy.xyz/python/article/122-Tkinter-%EC%9D%B4%EB%B2%A4%ED%8A%B8

명령어 묶기

명령어 묶기(Command Binding)

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

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

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

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

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

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

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")를 제공하기도 한다.

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개 보여준다. 이 프로그램을 실행하고, 버튼을 아무거나 클릭하면, 그 이름과 클릭되었던 앞의 버튼 이름이 화면에 나타난다.

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이라는 사건처리자가 동작할것이다.


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

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

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

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


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

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("어플리케이션 실행 완료")

위 예제를 실행해보면

10행과 16행에서 명령어묶기로 지정한 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)함수를 통한 해결방법을 보도록 하겠다.

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에서 알수 있다.

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

https://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옵션에 전달한것이다.


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

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

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함수를 호출하였다.


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

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

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

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

사용한 방법이다.


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


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

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

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


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

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


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

GUI 계산기 만들기

Tkinter를 이용한 그래피컬 사용자 인터페이스 (GUI) 계산기의 파이썬 (Python) 쏘쓰 코드 (source code)이다.


''' cal.py
A updated tiny calculator using the Tkinter GUI toolkit
you can type in functions contained in module math
for instance type in  tan(pi/180)  then click  =
tested with Python 3.7.5 on Ubuntu 19.10
'''
from math import *
from functools import partial
import tkinter as tk
class MyApp(tk.Tk):
    def __init__(self):
        # the root will be self
        tk.Tk.__init__(self)
        self.title("Darknet TK Calculator")
        # use width x height + x_offset + y_offset (no spaces!)
        #self.geometry("300x150+150+50")
        # or set x, y position only
        self.geometry("+150+50")
        self.memory = 0
        self.create_widgets()
    def create_widgets(self):
        # this also shows the calculator's button layout
        btn_list = [
        '7',  '8',  '9',  '*',  'C',
        '4',  '5',  '6',  '/',  'M->',
        '1',  '2',  '3',  '-',  '->M',
        '0',  '.',  '=',  '+',  'neg' ]
        rel = 'ridge'
        # create all buttons with a loop
        r = 1
        c = 0
        for b in btn_list:
            # partial takes care of function and argument
            cmd = partial(self.calculate, b)
            tk.Button(self, text=b, width=5, relief=rel, command=cmd).grid(row=r, column=c)
            c += 1
            if c > 4:
                c = 0
                r += 1
        # use an Entry widget for an editable display
        self.entry = tk.Entry(self, width=33, bg="yellow")
        self.entry.grid(row=0, column=0, columnspan=5)
    def calculate(self, key):
        if key == '=':
            # guard against the bad guys abusing eval()
            if '_' in self.entry.get():
                self.entry.insert(tk.END, " not accepted!")
            # here comes the calculation part
            try:
                result = eval(self.entry.get())
                self.entry.insert(tk.END, " = " + str(result))
            except:
                self.entry.insert(tk.END, "--> Error!")
        elif key == 'C':
            self.entry.delete(0, tk.END)  # clear entry
        elif key == '->M':
            self.memory = self.entry.get()
            # extract the result
            if '=' in self.memory:
                ix = self.memory.find('=')
                self.memory = self.memory[ix+2:]
            self.title('M=' + self.memory)
        elif key == 'M->':
            if self.memory:
               self.entry.insert(tk.END, self.memory)
        elif key == 'neg':
            if '=' in self.entry.get():
                self.entry.delete(0, tk.END)
            try:
                if self.entry.get()[0] == '-':
                    self.entry.delete(0)
                else:
                    self.entry.insert(0, '-')
            except IndexError:
                pass
        else:
            # previous calculation has been done, clear entry
            if '=' in self.entry.get():
                self.entry.delete(0, tk.END)
            self.entry.insert(tk.END, key)
app = MyApp()
app.mainloop()


사칙연산이 가능한 GUI 계산기이다. 키보드로 직접 입력하면 tan(pi/180) 등 사칙 연산 외의 계산도 가능하다.


우분투 19.10 기준으로, 파일 이름이 cal.py일 경우 파이썬 3.7.5에서는 터미널에서

sudo apt install python3-tk
python3 cal.py 

명령어를 입력하면 실행된다.


http://uoxqi4lrfqztugili7zzgygibs4xstehf5hohtkpyqcoyryweypzkwid.onion/?img=361615491111.png

http://hostxvivwx3lzvfdnof2muv7q5fkcovkfa3nexlnl5zrelif2mawxkad.onion/image.php?di=631T


http://pdogfxf7k6lyqe7uhmrokpc74nk2td75m4al5t6uvfhdvvxvng3nazid.onion\/tnibabrS30.jpg

http://3b6clio4syptsnvvtzyxifqvtizyazgyowpp3v5f7dj3mzmfhyoy4iyd.onion/images/8f98719adf96e79c9647c790631c1c2e.png


쏘쓰 코드 설명

아래 링크는 쏘쓰 코드 (source code) 출처이다.


  • Updated Tiny Tkinter Calculator (Python)

Nov 13th, 2013 9:14 pm

https://www.daniweb.com/programming/software-development/code/467452/updated-tiny-tkinter-calculator-python


쏘스 코드에서 ' 나 " 3개로 싸인 부분은 주석 (comment)이다. 한 줄의 맨 앞에 #를 써놔도 주석이다. 자세한 설명은 파이썬 문서를 참조하라.


# avoid integer division by Python2
from __future__ import division

try:
    # Python2
    import Tkinter as tk
except ImportError:
    # Python3
    import tkinter as tk

파이썬 2를 위한 소스 코드는 삭제하거나 수정하였다. 왜냐하면 요즘에는 대부분의 사람들이 파이썬 3를 쓰기 때문이다.


  • Python Language - Integer Division | python Tutorial

https://riptutorial.com/python/example/2797/integer-division


  • Difference between tkinter and Tkinter

2013-07-24

https://stackoverflow.com/questions/17843596/difference-between-tkinter-and-tkinter


from math import *
from functools import partial
import tkinter as tk

functools에서 partial을 불러오라는 의미이다. *은 전부 불러오라는 의미이다. tkinter를 tk라는 이름으로 불러오라는 의미이다.


MyApp 클래스 (class) 아래에 def로 정의 (definition)된 3개의 함수 (function)가 있다. 참고로 선언 (declaration)은 정의와는 약간 다르다.


  • C 언어 코딩 도장: 60.2 함수 선언과 정의 분리하기

https://dojang.io/mod/page/view.php?id=522


더 자새한 설명은 Tkinter 문서 참조.

트킨터로 게임 만들기

  • [파이썬 게임 프로그래밍 공부] 1. tkinter 모듈 시작하기

2017-09-01

https://alegruz.imweb.me/blog/?idx=221667&bmode=view


  • [파이썬 게임 프로그래밍 공부] 2. 위젯에 간단한 오브젝트 생성하기

2017-09-01

https://alegruz.imweb.me/blog/?idx=221692&bmode=view

함께 보기