[Enhancement] Reusing Context Menu
Type of Issue (Enhancement, Error, Bug, Question)
Enhancement
Environment
Operating System
Linux version ('glibc', '2.35')
PySimpleGUI Port (tkinter, Qt, Wx, Web)
tkinter
Versions
Python version (sg.sys.version)
3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]
PySimpleGUI Version (sg.__version__)
5.0.4.15
GUI Version (tkinter (sg.tclversion_detailed), PySide2, WxPython, Remi)
8.6.12
Your Experience In Months or Years (optional)
Years Python programming experience Years Programming experience overall No Have used another Python GUI Framework? (tkinter, Qt, etc) (yes/no is fine)
Troubleshooting
These items may solve your problem. Please check those you've done by changing - [ ] to - [X]
- [X] Searched main docs for your problem https://Docs.PySimpleGUI.com
- [ ] Looked for Demo Programs that are similar to your goal. It is recommend you use the Demo Browser! https://Demos.PySimpleGUI.com
- [ ] If not tkinter - looked for Demo Programs for specific port
- [ ] For non tkinter - Looked at readme for your specific port if not PySimpleGUI (Qt, WX, Remi)
- [ ] None of your GUI code was generated by an AI algorithm like GPT
- [ ] Run your program outside of your debugger (from a command line)
- [ ] Searched through Issues (open and closed) to see if already reported https://Issues.PySimpleGUI.com
- [ ] Have upgraded to the latest release of PySimpleGUI on PyPI (latest official version) https://Upgrading.PySimpleGUI.com
- [ ] Tried running the Maintenance Release. Use Home Window to access Maint Releases
- [ ] For licensing questions please email mailto:[email protected]
Detailed Description
Unless I'm missing something I think the context menu in general should return the underlying element in the same way as a context menu does on a button. As it stands you cannot reuse the same context menu on different fields. You can of course create a separate menu for each with different keys attached but this seems an inefficient way of doing things.
From the code below I have no easy way to determine if the user is on the size or color field and just right clicks on the field. If the user tabs through so that the field gets the focus I can use window.find_element_with_focus().key to do that but with a mouse it just doesn't work. Binding <Enter> & <Leave> to the Combo key and then <Button-3> to '+MNU' sort of works; however if the user right clicks on size and, without clearing that then right clicks on the color field the color context menu is activated but mouse entry/exit events are not flagged so you can't pick up the relevant field.
Code To Duplicate
import PySimpleGUI as sg
size_vals = ['Small', 'Medium', 'Large']
color_vals = ['Red', 'Blue', 'Green']
combo_mnu = ['', ['Clear', '---', 'Add', 'Delete']]
layout = [
[sg.Text('Size:', size=10), sg.Combo(size_vals, size=15,
default_value='Medium', key='-SIZE', enable_events=True,
right_click_menu=combo_mnu)],
[sg.Text('Color:', size=10), sg.Combo(color_vals, size=15,
default_value='Red',key='-COLOR', enable_events=True, right_click_menu=combo_mnu)],
[sg.OK(), sg.Cancel()]
]
win = sg.Window('test', layout=layout, finalize=True, return_keyboard_events=True)
win['-SIZE'].bind('<Enter>', '+ENTER')
win['-COLOR'].bind('<Leave>', '+EXIT')
win['-SIZE'].bind('<Button-3>', '+MNU')
win['-COLOR'].bind('<Button-3>', '+MNU')
while True:
event, values = win.read()
if event in (sg.WIN_CLOSED, 'Cancel', '-CANCEL', 'CANCELB'):
break
if event is None:
continue
try:
focused = win.find_element_with_focus().key
except AttributeError:
continue
print(event, values, focused)
Screenshot, Sketch, or Drawing
Somewhat experimental, but let's give it a try.... I may not be understanding what you're after, but this is a solution I came up with given a few minutes this morning.
Added in 5.0.4.16 a new window member variable right_click_menu_element which will be None if no element can be determined that was right clicked... or it'll have the element object that was right clicked (assuming a right click menu was chosen.
import PySimpleGUI as sg
layout = [ [sg.Text('Right click element test')],
[sg.Input(key='-IN-')],
[sg.Text(size=(12,1), key='-OUT-')],
[sg.Button('Go'), sg.Button('Exit')] ]
window = sg.Window('Window Title', layout, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, print_event_values=True)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED or event == 'Exit':
break
if event == 'Go':
window['-OUT-'].update(values['-IN-'])
if window.right_click_menu_element:
key = window.right_click_menu_element.key
else:
key = 'do not know'
print(f'Right click elem key = {key}')
window.close()
You can of course create a separate menu for each with different keys attached but this seems an inefficient way of doing things.
This was the intended direction for users to take. It's one of the reasons Menu items have keys that you can use.
A menu is a list. Making a copy and add keys is not difficult nor particularly inefficient since it's a few bytes and 1 line of code.
Here's an example of individual menus for a couple of the elements.
import PySimpleGUI as sg
import copy
orig_menu = ['', ['Edit Me', 'Version', 'Exit']]
menu1 = copy.deepcopy(orig_menu)
menu2 = copy.deepcopy(orig_menu)
menu1[1] = [f'{m}::-IN-' for m in menu1[1]]
menu2[1] = [f'{m}::-OUT-' for m in menu2[1]]
layout = [ [sg.Text('Right click element test')],
[sg.Input(key='-IN-', right_click_menu=menu1)],
[sg.Text('Text element for output', key='-OUT-', right_click_menu=menu2)],
[sg.Button('Go'), sg.Button('Exit')] ]
window = sg.Window('Window Title', layout, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, print_event_values=True)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED or event == 'Exit':
break
if '::' in event:
print(f'key from menu is {event.split("::")[1]}')
window.close()
This one may be even better as you don't need to do a copy.
import PySimpleGUI as sg
orig_menu = ['', ['Edit Me', 'Version', 'Exit']]
menu1 = ['', [f'{m}::-IN-' for m in orig_menu[1]]]
menu2 = ['', [f'{m}::-OUT-' for m in orig_menu[1]]]
layout = [ [sg.Text('Right click element test')],
[sg.Input(key='-IN-', right_click_menu=menu1)],
[sg.Text('Text element for output', key='-OUT-', right_click_menu=menu2)],
[sg.Button('Go'), sg.Button('Exit')] ]
window = sg.Window('Window Title', layout, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, print_event_values=True)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED or event == 'Exit':
break
if '::' in event:
print(f'key from menu is {event.split("::")[1]}')
window.close()
Fair enough; when I first started programming back in the late 70s (yes, pre-dating PCs & DOS) we couldn't afford to duplicate code & it's stuck with me ever since. That does seem to be the best option in this case though, particularly your last suggestion so I'll roll with that. Thanks.
when I first started programming back in the late 70s we couldn't afford to duplicate code
You and me both.... 4K of RAM in my first computer. CPM machines and 6502 processors didn't have many resources to begin with.
I've got 128 GB of RAM now now.
It took a while when I got back into programming with Python to redefine my definitions of large, inefficient, etc.
Generally speaking, if a tradeoff is between simplicity and resource use, simplicity often wins, especially when the resources are so small.
Some of the concepts we learned back then, like stability, memory leaks, race conditions, etc, haven't changed in their importance.
Thank you for the "thanks" . Seeing it never fails to make me smile. And thanks for the comment on the code solution. I hadn't done anything quite like it so your question got me to try solving a new problem. Good stuff.... keep building! Sounds like you're making an excellent interface.
Aah, the good old days. It was all so new, fun & exciting then. The first computer I owned was a Sinclair ZX81 with 1k of memory, plugged into the TV & a cassette player for storage. Amazing what you could do with it though. I subsequently paid a small fortune for an expansion pack which took it to an enormous 16K. I remember how proud I was when I eventually wrote a 'word-processor' (more like a text editor these days) with fonts, justification etc within that memory & being able to save & load it from cassette. Took an age to read & write but it all worked within that minuscule amount of memory. Cheers
This change, with the new member variable was released to PyPI this morning in the 5.0.5 release.
It is not in the call reference guide yet as I didn't make it a property. I made a property in the maint release 5.0.5.1 and will regenerate the call reference shortly. It doesn't change how the feature works. Since it's released to PyPI, closing this issue.
Enjoy!