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

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

EDIT:

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

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

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

РЕДАКТИРОВАТЬ 2:

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

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

0
jpodolski 13 Мар 2018 в 03:36

2 ответа

Лучший ответ

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

После дальнейшего изучения этой проблемы я сократил код до минимума (что, вероятно, мне следовало сделать перед публикацией здесь ...) и смог определить 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() для обеспечения согласованного поведения.

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

0
jpodolski 16 Мар 2018 в 15:27

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

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
0
user4171906user4171906 13 Мар 2018 в 22:24