I was wondering if some advanced menu features were available in PySimpleGui. Mainly, could you have check boxes next to items on the menu to show the state of that item? The screen shot below show menu checks in action in Visual Studio Code.
PySimpleGui has the ability to create interfaces like those in tkinter, Qt, WxPython and Remi. It uses a nested list to layout menus, which makes like a whole lot simpler.
Check menus are supported by tkinter
I did not see the option for check menus when browsing through the PySimpleGui documentation. So, the first question is whether that feature is available in tkinter. So, looking at tkinter code, there is the ability to create menus that have checks or radio selections.
So, I built a simple test application to demonstrate the menu feature. There are a couple of things that tkinter does to implement this feature.
First, it uses a variable to keep track of the value of the check or radio menu item.
Second, it uses an
add_radiobutton() call to add those items to the menu instead of the normal
check1 = StringVar() optmenu.add_checkbutton(label="Option 1", variable=check1)
Here is what the application looks like when running on Linux Mint. One thing I didn't like was that there was no space between the check mark and the text for the menu item.
Here is the complete python 3 code for the tkinter test menu program. You do not need have PySimpleGui installed to run the tkinter version.
from tkinter import * def do_nothing(): filewin = Toplevel(root) button = Button(filewin, text="Do nothing button") button.pack() root = Tk() menubar = Menu(root) filemenu = Menu(menubar, tearoff=0) filemenu.add_command(label="New", command=do_nothing) filemenu.add_command(label="Open", command=do_nothing) filemenu.add_command(label="Save", command=do_nothing) filemenu.add_command(label="Save as...", command=do_nothing) filemenu.add_command(label="Close", command=do_nothing) filemenu.add_separator() filemenu.add_command(label="Exit", command=root.quit) menubar.add_cascade(label="File", menu=filemenu) editmenu = Menu(menubar, tearoff=0) editmenu.add_command(label="Undo", command=do_nothing) editmenu.add_separator() editmenu.add_command(label="Cut", command=do_nothing) editmenu.add_command(label="Copy", command=do_nothing) editmenu.add_command(label="Paste", command=do_nothing) editmenu.add_command(label="Delete", command=do_nothing) editmenu.add_command(label="Select All", command=do_nothing) menubar.add_cascade(label="Edit", menu=editmenu) optmenu = Menu(menubar, tearoff=0) check1 = StringVar() optmenu.add_checkbutton(label="Option 1", variable=check1) check2 = StringVar() optmenu.add_checkbutton(label="Option 2", variable=check2) optmenu.add_separator() radio = StringVar() optmenu.add_radiobutton(label="Radio One", variable=radio, value=1) optmenu.add_radiobutton(label="Radio Two", variable=radio, value=2) optmenu.add_radiobutton(label="Radio Three", variable=radio, value=3) menubar.add_cascade(label="Preferences", menu=optmenu) helpmenu = Menu(menubar, tearoff=0) helpmenu.add_command(label="Help Index", command=do_nothing) helpmenu.add_command(label="About...", command=do_nothing) menubar.add_cascade(label="Help", menu=helpmenu) root.config(menu=menubar) root.mainloop()
Peeking into PySimpleGui Source code
I took a look at the PySimpleGui source code, to see whether adding support for the add_checkbox function would be a simple matter. I learned a lot by reading the code, but that isn't something that I want to tackle. It would be one thing if I just wanted to tweak the code for myself, but to do it right I would need to contact the developer, have to fix the code for Qt, WxPython, and don't even know if Remi supports that, do a pull request, and on and on.
I quickly concluded that it wasn't worth the effort and think it violates the "SIMPLE" part of PySimpleGui. It would just make it more complicated.
Writing My Own Code to Support Check Menus
I did play around with some code to handle check menus on my own. PySimpleGui does allow you to use a ! character at the beginning of a menu item to have the item be disabled.
So, I thought maybe use [ ] and [x] to indicate the check menu. It looked pretty good in the menu. Wrote a recursive function to go through the menu and find the item that was being clicked. To make changes to menu items, you do have to maintain the entire menu and update it all at once.
I went further down this path enough to convince myself it is possible to create and manage check menus in this manner. Handling radio menus would be a different matter. But still, it seemed like code that would need to be maintained that returned very little bang for the effort.
A PySimpleGui take on the problem
So, after all of that...
- PySimpleGui doesn't seems to support check or radio menus out of the box.
- Modifying PySimpleGui isn't really an option for me.
- Writing my own code to handle the menu is an option, but I would have to REALLY want that feature to bother with it.
I came to the conclusion that the best solution is to just build a dialog for handling preferences. That is a simple task in PySimpleGui.
- A dialog let's you put all the user configuration options is one place, not just those that could be set from a check menu.
- Which, in turn, makes it easier to persist the items to a config file or a database.
- If I want more user visibility for some settings, they can be included in a status bar instead of the menu.
Here is the code for an equivalent menu in PySimpleGui with a Preferences choice on the File menu. It looks better and the code is cleaner that the tkinter version above.
import PySimpleGUI as sg def do_nothing(): sg.popup("Do nothing") def show_preferences(): sg.popup("Preferences", "Build a dialog for your setting preferences.") menu_def = [ [ "File", [ "New", "Open", "Save", "Save As...", "Close", "---", "Preferences...", "---", "Exit", ], ], ["Edit", ["Undo", "---", "Cut", "Copy", "Paste", "Delete", "Select All",]], ["Help", ["Help Index", "About..."]], ] layout = [[sg.Menu(menu_def, tearoff=False)]] window = sg.Window("psg", layout, size=(250, 200)) while True: event, values = window.read() if event in ("Exit", None): break elif event == "Preferences...": show_preferences() else: do_nothing() window.close()