Please see my edit at the bottom, this issue is now OS specific.
A gif of the problem in action
So I'm having an issue with an instance of ttk.OptionMenu. I've successfully implemented the widget before, however I'm trying to use it here as a dropdown for available files in a pop-up window, and I can't seem to get it to work right.
The issue
The Code
The actual call is made from another file, let's say myproject/main.py
from classes.load_window import *
start_load_menu()
The class for this is stored in a file at myproject/classes/load_window.py
, and it accesses save files stored in myproject/saved/
import tkinter
import tkinter.ttk as ttk
from os import listdir
from os.path import join, isfile
class LoadMenu(object):
def __init__(self):
root = self.root = tkinter.Tk()
root.title("Save Manager")
root.overrideredirect(True)
""" MAIN FRAME """
frm_1 = ttk.Frame(root)
frm_1.pack(ipadx=2, ipady=2)
""" MESSAGE LABEL """
self.msg = str("Would you like to load from a save file?")
message = ttk.Label(frm_1, text=self.msg)
message.pack(padx=8, pady=8)
""" INNER FRAME """
frm_2 = ttk.Frame(frm_1)
frm_2.pack(padx=4, pady=4)
""" TEST IMPLEMENTAITON [DOES NOT WORK] """
mylist = ['1', '2', '3', '4', '5', '6', '7']
test_var = tkinter.StringVar(frm_2)
test_var.set(mylist[3])
test_dropdown = ttk.OptionMenu(frm_2, test_var, *mylist)
test_dropdown.pack(padx=4, pady=4)
print(mylist) # Results in ['1', '2', '3', '4', '5', '6', '7']
""" REAL IMPLEMENTATION [ALSO DOES NOT WORK] """
files = [f for f in listdir('saved') if isfile(join('saved', f))]
file_var = tkinter.StringVar(frm_2)
file_var.set(files[3])
file_dropdown = ttk.OptionMenu(frm_2, file_var, *files)
file_dropdown.pack(padx=4, pady=4)
print(files) # Results in ['DS_Store', 'test1', 'test2', 'test3']
""" BUTTON FUNCTIONALITY """
btn_1 = ttk.Button(frm_2, width=8, text="Load File")
btn_1['command'] = self.b1_action
btn_1.pack(side='left')
btn_2 = ttk.Button(frm_2, width=8, text="Cancel")
btn_2['command'] = self.b2_action
btn_2.pack(side='left')
btn_3 = ttk.Button(frm_2, width=8, text="Create New")
btn_3['command'] = self.b3_action
btn_3.pack(side='left')
btn_2.bind('<KeyPress-Return>', func=self.b3_action)
root.update_idletasks()
""" Position the window """
xp = (root.winfo_screenwidth() // 2) - (root.winfo_width() // 2)
yp = (root.winfo_screenheight() // 2) - (root.winfo_height() // 2)
geom = (root.winfo_width(), root.winfo_height(), xp, yp)
root.geometry('{0}x{1}+{2}+{3}'.format(*geom))
root.protocol("WM_DELETE_WINDOW", self.close_mod)
root.deiconify()
def b1_action(self, event=None):
print("B1")
def b2_action(self, event=None):
self.root.quit()
def b3_action(self, event=None):
print("B3")
def nothing(self):
print("nothing")
def close_mod(self):
pass
def time_out(self):
print ("TIMEOUT")
def to_clip(self, event=None):
self.root.clipboard_clear()
self.root.clipboard_append(self.msg)
def start_load_menu():
menu = LoadMenu()
menu.root.mainloop()
menu.root.destroy()
return menu.returning
Notes
This code is based on a response here for a pop up window that I'm in the process of adapting for a specific purpose (the load menu).
I distilled this code to the minimum to reproduce the issue, but you can probably ignore the function definitions and window geometry.
Everything works fine other than this; the window is displayed center screen, and the button with actual functionality closes the window, it's just this odd quirk with the OptionMenu that I can't seem to find anyone else struggling with, either on here, or other forums.
In case you didn't see the link at the top, you can find a demonstration of the troublesome behavior at this link.
I'm using Python 3.6.4 on OSX 10.12.6
EDIT:
I've since tested this code in a VM running Hydrogen Linux, and it works fine. My question then changes a little:
How can I ensure that this code translates well to OSX? Is there reading available on the discrepancies between running TKinter on different platforms?
I have found this page on the issues regarding Python, TKinter, and OSX, but even when using the recommended TCL packages with the latest stable release of Python, this issue persists.
EDIT 2:
Just to update, I have since found a workaround for the problem. It doesn't answer the question of the odd behavior of the OptionMenu, but I figured I would edit. Honestly, I think Listbox is probably better suited for what I wanted to do anyways. Here it is in action.
Please let me know if I need to make any edits for clarity, or provide additional info. As I'm new to stackoverflow, I don't have much experience sharing issues here. Thank you!
I figured it out!
After digging into this problem further, I distilled the code to the minimum (which I probably should have done before posting here...) and was able to pinpoint root.overrideredirect(True)
as the offending line.
When using overrideredirect(True)
, it's necessary to also use update_idletasks()
before, in order to ensure that the Widget refreshes properly. While it seems Linux can still produce normal behavior without manually updating idle tasks, OS X cannot, so it becomes necessary to preface the code with
root.update_idletasks()
Here's a good excerpt from the documentation that I found on Billal BEGUERADJ's response on an overrideredirect() question.
If you want to force the display to be updated before the application next idles, call the w.update_idletasks() method on any widget.
Some tasks in updating the display, such as resizing and redrawing widgets, are called idle tasks because they are usually deferred until the application has finished handling events and has gone back to the main loop to wait for new events.
If you want to force the display to be updated before the application next idles, call the w.update_idletasks() method on any widget.
While I still don't understand why exactly this widget breaks without update_idletasks()
on OSX, I do understand now why it's good practice to use update_idletasks()
in conjunction with overrideredirect()
to ensure consistent behavior.
Hope this helps anyone else who may get hung up on this.