Search code examples
pythonmacostkinterpyinstallercython

Python/Tkinter/Cython/PyInstaller: menu bar not clickable in MacOS


When I run the following test program using the Python interpreter from the MacOS Terminal, it works as expected. When I Cython it and create an executable using PyInstaller, and run it from the Terminal, the application menu bar becomes unclickable. An equivalent program created using the same process on Windows also works correctly. I'm using Python 3.8.2, Cython 3.0.0, PyInstaller 5.13.0.

This problem happens whether I create a single-file or multiple-files executable; when running as a .app file, the menus aren't even present (that is, I see the Finder menus).

For reference, I used some techniques suggested in the documentation cited in this article: Tkinter Menubars doesn't show up on Mac OS

Any ideas? Thank you.

UPDATE: It appears that if I click away from this app onto another part of the screen, and then click back, the menus become enabled. However I thought they should be enabled from the start. The issue with menus not appearing at all when using a .app file isn't yet resolved.

import tkinter as tk
from tkinter import *
import sys
import tkinter.messagebox as mb
import os.path
    
def show_dialog(e=None, title='Just some dialog'):
    dlog = tk.Toplevel(root)
    dlog.geometry('300x75')
    dlog.title(title)
    dlog.bind('<Escape>', lambda e:dlog.destroy())
    dlog.focus_set()

def showinfo(e=None):
    my_path = os.path.dirname(os.path.abspath(__file__))
    mb.showinfo('Test', my_path)

def create_menus():
    menu_bar = Menu(root)
    appmenu = Menu(menu_bar, name="apple")
    menu_bar.add_cascade(menu=appmenu)
    appmenu.add_command(label="About Me", command=showinfo)
    appmenu.add_separator()
    global file_menu
    global edit_menu
    
    file_menu = Menu(menu_bar, tearoff=0)
    edit_menu = Menu(menu_bar, tearoff=0)
    window_menu = Menu(menu_bar, name="window")
    menu_bar.add_cascade(menu=window_menu, label="Window")
    
    file_menu.add_command(
        label='Open...',
        command=lambda:show_dialog(title='Open'),
        accelerator="Command+O"
    )
    
    file_menu.add_command(
        label='Exit',
        command=sys.exit,
        accelerator="Command+Q"
    )
    
    menu_bar.add_cascade(
        label="File",
        menu=file_menu,
        underline=0
    )
    
    edit_menu.add_command(
        label='Find...',
        command=lambda:show_dialog(title='Find'),
        accelerator="Command+F"
    )
    
    menu_bar.add_cascade(
        label="Edit",
        menu=edit_menu,
        underline=0
    )
    
    root['menu'] = menu_bar

def main():
    global root 
    root = tk.Tk()
    root.title("Simple Editor")
   
    create_menus()
    
    input_text = tk.Text(root, height=10, width=30 )
    input_text.pack(expand=True, fill=tk.BOTH)
   
    root.bind('<Command-o>', lambda e:show_dialog(title='Open'))
    root.bind('<Command-f>', lambda e:show_dialog(title='Find'))
    root.bind('<Command-q>', lambda e:sys.exit)
    root.bind('<Command-i>', showinfo)
    root.mainloop()

if __name__ == "__main__":
    main()

Solution

  • Solved the problem of the menus not appearing when the .app was run. The Info.plist file generated by PyInstaller had the LSBackgroundOnly key set to true; resetting it to false made the menus available.

    The problem with needing to click outside the app to activate the menus was resolved with an upgrade of the Python version.