Tkinter TTK OptionMenu 无法打开

Tkinter TTK OptionMenu won't open

请在底部查看我的编辑,此问题现在 OS 具体。

A gif of the problem in action

所以我遇到了 ttk.OptionMenu 实例的问题。我之前已经成功实现了这个小部件,但是我试图在这里将它用作弹出窗口中可用文件的下拉列表 window,但我似乎无法让它正常工作。

问题

代码

实际调用是从另一个文件进行的,比方说myproject/main.py

from classes.load_window import *

start_load_menu()

此 class 存储在 myproject/classes/load_window.py 的文件中,它访问存储在 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

备注

此代码基于 a response here 弹出窗口 window,我正在为特定目的(加载菜单)进行调整。

我将此代码提炼到最低限度以重现该问题,但您可以忽略函数定义和 window 几何图形。

除此之外一切正常; window 显示在中央屏幕上,具有实际功能的按钮关闭 window,这只是 OptionMenu 的这个奇怪的怪癖,我似乎找不到其他人在这里挣扎,或其他论坛。

如果您没有看到顶部的 link,您可以找到麻烦行为的演示 at this link.

我在 OSX 10.12.6[=21 上使用 Python 3.6.4 =]

编辑:

我已经在 VM 运行 Hydrogen Linux、and it works fine 中测试了这段代码。然后我的问题有点变化:

如何确保此代码能够很好地转换为 OSX? 是否有关于 运行 TKinter 在不同平台上的差异的阅读材料?

我发现 this page 关于 Python、TKinter 和 OSX 的问题,但即使使用推荐的 TCL 软件包和最新稳定版本 [=100] =],此问题仍然存在。

编辑 2:

只是为了更新,我已经找到了解决该问题的方法。它没有回答 OptionMenu 的奇怪行为的问题,但我想我会编辑。老实说,我认为 Listbox 可能更适合我想做的事情。 Here it is in action.

如果我需要为清楚起见进行任何编辑或提供其他信息,请告诉我。由于我是 Whosebug 的新手,所以我没有太多在这里分享问题的经验。谢谢!

除了将 "files" 更改为 hard-coded 列表之外什么都不做,程序就可以在我的机器上 运行。我帮不了你了。

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))]
        files=['a', 'b', 'c', 'd', 'e', '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

我明白了!

在进一步研究这个问题之后,我将代码提炼到最低限度(我可能应该在发帖之前完成...)并且能够将 root.overrideredirect(True) 确定为有问题的行。

在使用overrideredirect(True)时,必须先使用update_idletasks(),以保证Widget正常刷新。虽然看起来 Linux 仍然可以在不手动更新空闲任务的情况下产生正常行为,但 OS X 不能,因此有必要在代码前加上

root.update_idletasks()

这是我在 Billal BEGUERADJ's response on

上找到的文档的一个很好的摘录

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.

虽然我仍然不明白为什么这个小部件在 OSX 上没有 update_idletasks() 时会崩溃,但我现在明白为什么最好将 update_idletasks()overrideredirect() 以确保一致的行为。

希望这对其他可能挂断电话的人有所帮助。