Tkinter TTK OptionMenu не открывается

Просмотрите мои изменения внизу, теперь эта проблема связана с ОС.

GIF-изображение проблемы в действии

Итак, у меня проблема с экземпляром ttk.OptionMenu. Я успешно реализовал этот виджет и раньше, однако я пытаюсь использовать его здесь как раскрывающийся список для доступных файлов во всплывающем окне, и мне кажется, что он не работает правильно.

Проблема

  • Виден только первый вариант в меню, другие варианты недоступны.
  • Он не реагирует на мышь, кроме того, что он затемняется при нажатии
  • Когда на него нажимают, в терминале не отображаются ошибки или вывод, что затрудняет отслеживание

Код

Фактический вызов выполняется из другого файла, скажем myproject/main.py

from classes.load_window import *

start_load_menu()

Класс для этого хранится в файле по адресу 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

Примечания

Этот код основан на ответе здесь для всплывающего окна, которое я адаптирую для конкретной цели (меню загрузки).

Я сократил этот код до минимума, чтобы воспроизвести проблему, но вы, вероятно, можете игнорировать определения функций и геометрию окна.

Все работает отлично, кроме этого; окно отображается в центре экрана, а кнопка с фактическими функциями закрывает окно, это просто странная причуда с OptionMenu, с которой я не могу найти никого, с кем еще борются ни здесь, ни на других форумах.

Если вы не видели ссылку вверху, вы можете найти демонстрацию проблемного поведения по этой ссылке.

Я использую Python 3.6.4 в OSX 10.12.6

ИЗМЕНИТЬ:

С тех пор я протестировал этот код на виртуальной машине под управлением Hydrogen Linux, , и он отлично работает. Тогда мой вопрос немного изменится:

Как я могу убедиться, что этот код хорошо транслируется в OSX? Есть ли сведения о расхождениях между запуском TKinter на разных платформах?

Я нашел эту страницу по вопросам, связанным с Python, TKinter и OSX, но даже при использовании рекомендованных пакетов TCL с последней стабильной версией Python эта проблема сохраняется.

ИЗМЕНИТЬ 2:

Просто чтобы обновить, с тех пор я нашел способ решения проблемы. Это не отвечает на вопрос о странном поведении OptionMenu, но я решил, что буду редактировать. Честно говоря, я думаю, что Listbox, вероятно, лучше подходит для того, чем я хотел заниматься. Вот оно в действии.

Пожалуйста, дайте мне знать, если мне нужно внести какие-либо изменения для ясности или предоставить дополнительную информацию. Поскольку я новичок в stackoverflow, у меня нет особых проблем с обменом опытом. Спасибо!


person jpodolski    schedule 13.03.2018    source источник
comment
Виден только первый вариант в меню, другие варианты недоступны Затем сначала распечатайте файлы, чтобы увидеть, есть ли в нем более одного элемента.   -  person    schedule 13.03.2018
comment
Я сделал это, и список печатается нормально при нормальной печати, хотя и повторяется и печатается как * list. Даже когда есть только одна опция, я обычно все еще могу вызвать меню и увидеть, как появляется эта единственная опция, но даже этого здесь не происходит.   -  person jpodolski    schedule 13.03.2018
comment
оператор files = [f вместо f в listdir ('saved') if isfile (join ('saved', f))] ничего не возвращает в моей реализации Python.   -  person    schedule 13.03.2018
comment
У вас есть каталог с именем saves, в котором хранятся файлы при запуске программы? Я не решаюсь поверить, что это проблема, поскольку список печатает файлы, присутствующие в моем коде, и даже тогда он все равно не работает, когда представлен список чисел. Я отредактировал свой пост, чтобы показать результаты печати списков, а также прикрепить запись проблемы в действии   -  person jpodolski    schedule 13.03.2018
comment
Вам не хватает параметра для ttk.OptionMenu (3-й параметр - значение по умолчанию), но эта ошибка обычно приводит к отсутствию первого параметра, а не к тому, что он является единственным элементом в меню.   -  person jasonharper    schedule 14.03.2018
comment
Я пробовал множество комбинаций параметров, включая добавление значения по умолчанию для списка [0], но проблема все еще сохраняется.   -  person jpodolski    schedule 14.03.2018


Ответы (2)


Если ничего не делать, кроме изменения «файлов» в жестко запрограммированном списке, программа может работать на моем компьютере. Я больше ничем не могу вам помочь.

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
person Community    schedule 13.03.2018
comment
К сожалению, дословный запуск вашего кода приводит к той же проблеме для меня. Может ли проблема быть связана с обработкой моей ОС питон или ТКинтер? На какой платформе вы работаете? Я ценю вашу помощь, спасибо. - person jpodolski; 14.03.2018
comment
Я только что установил виртуальную машину Linux, чтобы проверить это, и вы правы; работает нормально. Знаете ли вы, почему он может работать некорректно в OSX, в отличие от других сред Unix? - person jpodolski; 14.03.2018

Я разобрался!

После дальнейшего изучения этой проблемы я сократил код до минимума (что, вероятно, мне следовало сделать перед тем, как публиковать здесь ...), и смог определить root.overrideredirect(True) как вызывающую нарушение.

При использовании overrideredirect(True) необходимо также использовать update_idletasks() раньше, чтобы обеспечить правильное обновление виджета. Хотя кажется, что Linux по-прежнему может производить нормальное поведение без ручного обновления неактивных задач, OS X не может, поэтому возникает необходимость в предисловии к коду

root.update_idletasks()

Вот хороший отрывок из документации, которую я нашел в ответе Биллала BEGUERADJ на вопрос overrideredirect ().

Если вы хотите принудительно обновить отображение до следующего простоя приложения, вызовите метод w.update_idletasks () для любого виджета.

Некоторые задачи по обновлению отображения, такие как изменение размера и перерисовка виджетов, называются незанятыми задачами, потому что они обычно откладываются до тех пор, пока приложение не закончит обработку событий и не вернется в основной цикл для ожидания новых событий.

Если вы хотите принудительно обновить отображение до следующего простоя приложения, вызовите метод w.update_idletasks () для любого виджета.

Хотя я до сих пор не понимаю, почему именно этот виджет ломается без update_idletasks() в OSX, теперь я понимаю, почему рекомендуется использовать update_idletasks() вместе с overrideredirect() для обеспечения согласованного поведения.

Надеюсь, это поможет всем, кто может на этом зациклиться.

person jpodolski    schedule 15.03.2018