[python] GUI programming tkInter (menu)
A A

목차

    728x90

     

    가장 중요한 메뉴의 특징에 대해 요약해보자.

    • 클래식한 메뉴는 실제로 어플리 케이션 창의 맨 위에 위치한 수평 막대이다.
    • 막대에는 항목이라하는 수평으로 배치된 여러 옵션들을 포함한다.
    • 이러한 옵션에는 사용자가 마우스를 사용하지 않고도 작업에 액세스 할 수 있도록 키보드 단축키(핫키. hot-keys) 가 있을 수 있다.
    • 메뉴의 옵션을 단축키던 마우스던 선택하면 두가지 효과 중 하나가 발생한다.
      • 옵션에 바인딩된 콜백을 싲가한다.
      • 새로운 메뉴(하위 메뉴)가 펼쳐진다.
    • tkinter 애플리케이션에서 메뉴를 만들고 싶다면 다음을 수행해야한다.
      • 최상위 메뉴 객체를 생성한다.
      • 창안에 삽입한다.
      • 여러개의 필수 하위메뉴를 바인딩(cascade)하거나 단일 콜백을 연결한다.

     

    실습 1 )  메인 창에 메뉴 만들기

    import tkinter as tk
    from tkinter import messagebox
    
    def help_app():
      messagebox.showinfo("App","The application\nthat does nothing")
    
    root = tk.Tk()
    
    # 메인 메뉴 생성 및 창에 넣기
    main_menu = tk.Menu(root)
    root.config(menu=main_menu)
    
    #첫번째 메뉴 추가
    file_menu = tk.Menu(main_menu)
    main_menu.add_cascade(label="File", menu=file_menu)
    
    #두번째 메뉴 추가 : 콜백 추가
    help_menu = tk.Menu(main_menu)
    main_menu.add_command(label="Help", command=help_app)
    
    root.mainloop()

    이 메뉴는 현재 하위 메뉴가 존재하지 않고 상단에 File , Help의 상위 메뉴들로 만 구성되어 있다.

    그리고, 마우스없이 사용하기 어렵다는 점이 있다.

    다른 기능들과 세부적인 메뉴가 필요하다.

     

    실습 2 ) 단축키 추가

    import tkinter as tk
    from tkinter import messagebox
    
    def help_app():
      messagebox.showinfo("App","The application\nthat does nothing")
    
    root = tk.Tk()
    
    main_menu = tk.Menu(root)
    root.config(menu=main_menu)
    
    file_menu = tk.Menu(main_menu)
    main_menu.add_cascade(label="File", menu=file_menu, underline=0) # 단축키 추가 
    
    help_menu = tk.Menu(main_menu)
    main_menu.add_command(label="Help", command=help_app, underline=1) # 단축키 추가
    
    root.mainloop()

    underline=0 은 Alt + F 와 같고 underline=1은 Alt +B와 같다.

    모든 단축키가 고유해야하고, 겹치는 요소들이 없어야 한다.

     

    실습 3 ) 하위 메뉴 구성

    import tkinter as tk
    from tkinter import messagebox
    
    def help_app():
      messagebox.showinfo("App","The application\nthat does nothing")
    
    def are_you_sure(): #나가기 콜백 함수 
      if messagebox.askyesno("Quit", "Are you sure you want to quit?"):
        root.destroy()
    
    root = tk.Tk()
    
    main_menu = tk.Menu(root)
    root.config(menu=main_menu)
    
    file_menu = tk.Menu(main_menu)
    main_menu.add_cascade(label="File", menu=file_menu, underline=0) 
    file_menu.add_commnad(label="Quit", underline=0, command=are_you_sure) # command 콜백 함수 추가 
    
    help_menu = tk.Menu(main_menu)
    main_menu.add_command(label="Help", command=help_app, underline=1) 
    
    root.mainloop()

     

    하위 메뉴 상단에 이상한 점섬을 확인했을 것이다.

    이 점선은 티어오프(tearoff)라고 하는데, 아주 오래전 GUI에서 사용되던 고풍스런 스타일로, 현재는 선호되지 않는다.

    이에 대해 tearoff=0을 추가하여 차이점을 확인해보자.

    import tkinter as tk
    from tkinter import messagebox
    
    def help_app():
      messagebox.showinfo("App","The application\nthat does nothing")
    
    def are_you_sure():
      if messagebox.askyesno("Quit", "Are you sure you want to quit?"):
        root.destroy()
    
    root = tk.Tk()
    
    main_menu = tk.Menu(root)
    root.config(menu=main_menu)
    
    file_menu = tk.Menu(main_menu, tearoff=0) # tearoff 삭제 
    main_menu.add_cascade(label="File", menu=file_menu, underline=0) 
    file_menu.add_commnad(label="Quit", underline=0, command=are_you_sure) 
    
    help_menu = tk.Menu(main_menu)
    main_menu.add_command(label="Help", command=help_app, underline=1) 
    
    root.mainloop()

     

    그리고, 새로운 기능을 하나 더 추가하여 Open이라는 메뉴를 file아래로 추가해보자.

    import tkinter as tk
    from tkinter import messagebox
    
    def help_app():
      messagebox.showinfo("App","The application\nthat does nothing")
    
    def are_you_sure():
      if messagebox.askyesno("Quit", "Are you sure you want to quit?"):
        root.destroy()
      
    def open_file(): # 콜백 함수 추가 
      massagebox.showinfo("Open doc", "Opening file...")
    
    root = tk.Tk()
    
    main_menu = tk.Menu(root)
    root.config(menu=main_menu)
    
    file_menu = tk.Menu(main_menu, tearoff=0) 
    main_menu.add_cascade(label="File", menu=file_menu, underline=0) 
    file_menu.add_commnad(label="Open..", underline=0, command=open_file)  # 하위 메뉴 추가 및 콜백 함수 적용 
    file_menu.add_commnad(label="Quit", underline=0, command=are_you_sure) 
    
    help_menu = tk.Menu(main_menu)
    main_menu.add_command(label="Help", command=help_app, underline=1) 
    
    root.mainloop()

     

    Open..과 Quit 사이의 선이 있었으면 좋겠다면, add_separator()를 적용해보자.

    import tkinter as tk
    from tkinter import messagebox
    
    def help_app():
      messagebox.showinfo("App","The application\nthat does nothing")
    
    def are_you_sure():
      if messagebox.askyesno("Quit", "Are you sure you want to quit?"):
        root.destroy()
      
    def open_file(): 
      massagebox.showinfo("Open doc", "Opening file...")
    
    root = tk.Tk()
    
    main_menu = tk.Menu(root)
    root.config(menu=main_menu)
    
    file_menu = tk.Menu(main_menu, tearoff=0) 
    main_menu.add_cascade(label="File", menu=file_menu, underline=0) 
    file_menu.add_commnad(label="Open..", underline=0, command=open_file) 
    file_menu.add_separator()   # 구분선 추가
    file_menu.add_commnad(label="Quit", underline=0, command=are_you_sure) 
    
    help_menu = tk.Menu(main_menu)
    main_menu.add_command(label="Help", command=help_app, underline=1) 
    
    root.mainloop()

     

    실습 4 ) 하위 메뉴 구성하기

    하위 메뉴를 계단식으로 펼쳐야 하는 경우 add_cascade()를 활용하여 추가할 수 있다.

    import tkinter as tk
    from tkinter import messagebox
    
    def help_app():
      messagebox.showinfo("App","The application\nthat does nothing")
    
    def are_you_sure():
      if messagebox.askyesno("Quit", "Are you sure you want to quit?"):
        root.destroy()
      
    def open_file(): 
      massagebox.showinfo("Open doc", "Opening file...")
    
    root = tk.Tk()
    
    main_menu = tk.Menu(root)
    root.config(menu=main_menu)
    
    file_menu = tk.Menu(main_menu, tearoff=0) 
    main_menu.add_cascade(label="File", menu=file_menu, underline=0) 
    file_menu.add_commnad(label="Open..", underline=0, command=open_file) 
    
    sub_file_menu = tk.Menu(file_menu, tearoff=0) #하위 메뉴 구성 
    file_menu.add_cascade(label="Sub File", menu=sub_file_menu, underline=5) # 해당하는 메뉴에 add_cascade를 통해 하위 메뉴 참조 
    
    file_menu.add_separator()    
    file_menu.add_commnad(label="Quit", underline=0, command=are_you_sure) 
    
    help_menu = tk.Menu(main_menu)
    main_menu.add_command(label="Help", command=help_app, underline=1) 
    
    root.mainloop()

    물론 현재 sub_file_menu에는 add 된 부분이 없어, 화살표만 생성되어있음을 볼 수 있다.

     

    그렇기 때문에 하위 sub_file_menu에 add_command를 통해 label을 추가해보자.

    import tkinter as tk
    from tkinter import messagebox
    
    def help_app():
      messagebox.showinfo("App","The application\nthat does nothing")
    
    def are_you_sure():
      if messagebox.askyesno("Quit", "Are you sure you want to quit?"):
        root.destroy()
      
    def open_file(): 
      massagebox.showinfo("Open doc", "Opening file...")
    
    root = tk.Tk()
    
    main_menu = tk.Menu(root)
    root.config(menu=main_menu)
    
    file_menu = tk.Menu(main_menu, tearoff=0) 
    main_menu.add_cascade(label="File", menu=file_menu, underline=0) 
    file_menu.add_commnad(label="Open..", underline=0, command=open_file) 
    
    sub_file_menu = tk.Menu(file_menu, tearoff=0) 
    file_menu.add_cascade(label="Sub File", menu=sub_file_menu, underline=5)
    
    for i in range(1, 8):
      sub_file_menu.add_command(label=f"{str(i)}. Sub File.txt", underline=0) # for 문을 통한 1.Sub File.txt 의 형식으로 생성
    
    file_menu.add_separator()    
    file_menu.add_commnad(label="Quit", underline=0, command=are_you_sure) 
    
    help_menu = tk.Menu(main_menu)
    main_menu.add_command(label="Help", command=help_app, underline=1) 
    
    root.mainloop()

     

    실습 5 ) Hot - keys 적용

    일부 메뉴에 전용 단축키를 통해 접근할 수 있도록 하기 위해 두 단계를 수행해야한다.

    1. hot-keys의 이름을 지정하는 문자열을 accelerator 속성에 설정한다.
      항목 내부에 오른쪽에 정렬된 문자열을 표시하는 것 외 다른 효과는 없고 콜백은 설정되지 않는다.
    2. 핫키와 적절한 콜백을 연결하는 global binding을 한다.
    import tkinter as tk
    from tkinter import messagebox
    
    def help_app():
      messagebox.showinfo("App","The application\nthat does nothing")
    
    def are_you_sure(event=None):  #bind() 하기 위한 인자 설정 
      if messagebox.askyesno("Quit", "Are you sure you want to quit?"):
        root.destroy()
      
    def open_file(): 
      massagebox.showinfo("Open doc", "Opening file...")
    
    root = tk.Tk()
    
    main_menu = tk.Menu(root)
    root.config(menu=main_menu)
    
    file_menu = tk.Menu(main_menu, tearoff=0) 
    main_menu.add_cascade(label="File", menu=file_menu, underline=0) 
    file_menu.add_commnad(label="Open..", underline=0, command=open_file) 
    
    sub_file_menu = tk.Menu(file_menu, tearoff=0) 
    file_menu.add_cascade(label="Sub File", menu=sub_file_menu, underline=5)
    
    for i in range(1, 8):
      sub_file_menu.add_command(label=f"{str(i)}. Sub File.txt", underline=0)
    
    file_menu.add_separator()    
    file_menu.add_commnad(label="Quit", underline=0, command=are_you_sure, accelerator="Ctrl-Q") # accelerator 문구 지정 
    
    help_menu = tk.Menu(main_menu)
    main_menu.add_command(label="Help", command=help_app, underline=1) 
    
    root.bind_all("<Control-q>", are_you_sure)  # are_you_sure 에 대한 콜백 함수를 <Control-q> 이벤트로 바인드.
    root.mainloop()

     

    참고 사항

     하위 메뉴 항목을 config()를 통해 수정할 수 없다.

    tkinter의 관점에서 해당 항목은 위젯이 아니라 특정 위젯의 구성요소 일 뿐이기 때문이다.

    메뉴 항목을 조작하기 위해서는 전용 메서드를 사용해야한다.

    menu_object.entryconfigure(i, prop=value)
    • i : 수정된 항목의 정수 인덱스이다.
    • prop = value : 수정된 속성을 가르키는 키워드 인수이다.
    import tkinter as tk
    
    def on_off():
      global accessible
      if accessible == tk.DISABLED:
        accessible = tk.ACTIVE
      else:
        accessible = tk.DISABLED
      sub_menu.entryconfigure(1, state=accessible)
    
    accessible = tk.DISABLED
    root = tk.Tk()
    menu = tk.Menu(root)
    root.config(menu=menu)
    
    sub_menu = tk.Menu(menu, tearoff=0)
    menu.add_cascade(label="Menu", menu=sub_menu)
    
    sub_menu.add_command(label="On/Off", command=on_off)
    sub_menu.add_command(lable="Switch", state=tk.DISABLED)
    
    root.mainloop()

    해당 entryconfigure()함수를 통해 인덱스 1 번에 해당하는 switch의 요소가 선택되어,

    On/Off를 선택할때마다 Switch의 버튼의 활성화 상태값이 변화하는 것을 볼 수 있다.

    Copyright 2024. GRAVITY all rights reserved