методы программирования tkinter и GUI

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

Итак, я начал программировать GUI с помощью tkinter и коротко рассказываю, что мой код становится довольно уродливым довольно быстро. Я пытаюсь создать графический редактор на основе плитки для видеоигры. Мои главные проблемы:

  1. неспособность обратных вызовов возвращать значения.
  2. невозможность легко передавать данные между окнами.

Я предполагаю, что причина, по которой я рассматриваю эти проблемы, связана с тем, что я использую функции намного больше, чем я использую классы. Например, мое окно загрузки «загрузка» полностью обрабатывается: нажатие опции меню в главном окне вызывает функцию, загружающую новое окно. Внутри этого окна я создаю диалог открытого файла при поиске изображения и изменяю изображение, отображающее изображение, когда я нажимаю клавишу ввода (чтобы он рисовал соответствующую сетку по изображению). функция функции функции.

Мне кажется, что для меня очень плохой практикой является включение дополнительных аргументов для компенсации. Например, когда я создаю плиточный набор, экземпляр созданного класса TileSet должен быть отправлен обратно в главное окно, где может отображаться соответствующая информация. У меня есть список загруженных наборов данных как глобальная переменная (еще более плохая практика: все, что связано с моим корневым окном, находится в глобальной области! Yay!), И поскольку функции обратного вызова не возвращают значения, я передаю этот список в качестве аргумента к моей функции «load setset window», которая затем передает аргумент функции create setset (называемой при нажатии соответствующей кнопки в окне), где это действительно необходимо, чтобы я мог добавить свой недавно созданный набор элементов в список. Передача аргументов через функцию «иерархия» вроде бы кажется ужасной идеей. Это запутывает, это ужасно для написания модульного кода, и просто вообще кажется ненужным.

Моя попытка исправить проблему состояла бы в том, чтобы написать класс, представляющий весь графический интерфейс пользователя, и пользовательские классы окон (которые класс GUI может создавать и ссылаться), которые могут фактически хранить соответствующие данные. Это должно заботиться о проблемах с передачей данных между окнами. Надеюсь, это сократит мое бесплатное использование лямбда-функций в обратных вызовах. Но мне интересно: это лучший способ? Или, по крайней мере, близко? Я бы предпочел не начинать переписывать, а затем заканчивать другой системой, которая просто неаккуратная и запутанная по-другому. Я знаю, что мои методы плохие, но я не знаю, какой будет лучший подход. Я получаю много советов о том, как делать конкретные вещи, но не о том, как структурировать программу в целом. Любая помощь будет принята с благодарностью.

    Похоже, вы пытаетесь создать графический интерфейс, который действует процедурно, и это не сработает. Графические интерфейсы не являются процедурными, их код не работает линейно, когда функции вызывают обратные вызовы, возвращающие значения. То, что вы просите, не уникально для tkinter. Это характер программирования GUI на основе событий – обратные вызовы не могут вернуть ничего, потому что вызывающий объект является событием, а не функцией.

    Грубо говоря, вы должны использовать какой-либо глобальный объект для хранения ваших данных. Обычно это называется «Модель». Это может быть глобальная переменная, или это может быть база данных, или она может быть объектом какого-то рода. В любом случае он должен существовать «глобально»; то есть он должен быть доступен для всего GUI.

    Часто этот доступ обеспечивается третьим компонентом, называемым «Контроллер». Это интерфейс между графическим интерфейсом («Вид») и данными («Модель»). Эти три компонента составляют так называемый шаблон модели-представления-контроллера или MVC.

    Модели, виду и контроллеру не обязательно должны быть три разных объекта. Часто GUI и контроллер являются одним и тем же объектом. Для небольших программ это работает достаточно хорошо – компоненты графического интерфейса напрямую связаны с вашей моделью данных.

    Например, у вас может быть класс, представляющий окно, которое наследуется от Tkinter.Toplevel. Он может иметь атрибут, который представляет редактируемые данные. Когда пользователь выбирает «Новое» из главного окна, он делает что-то вроде self.tileset = TileSet(filename) . То есть, он устанавливает атрибут named tileset объекта GUI с именем self который является экземпляром класса TileSet специфичным для данного имени файла. Более поздние функции, которые манипулируют данными, используют self.tileset для доступа к объекту. Для функций, которые живут вне основного окна (например, функция «сохранить все» из главного окна), вы можете передать этот объект в качестве аргумента или использовать объект окна в качестве контроллера, попросив его что-то сделать для своего Tileset.

    Вот краткий пример:

     import Tkinter as tk import tkFileDialog import datetime class SampleApp(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) self.windows = [] menubar = tk.Menu(self) self.configure(menu=menubar) fileMenu = tk.Menu(self) fileMenu.add_command(label="New...", command=self.new_window) fileMenu.add_command(label="Save All", command=self.save_all) menubar.add_cascade(label="Window", menu=fileMenu) label = tk.Label(self, text="Select 'New' from the window menu") label.pack(padx=20, pady=40) def save_all(self): # ask each window object, which is acting both as # the view and controller, to save it's data for window in self.windows: window.save() def new_window(self): filename = tkFileDialog.askopenfilename() if filename is not None: self.windows.append(TileWindow(self, filename)) class TileWindow(tk.Toplevel): def __init__(self, master, filename): tk.Toplevel.__init__(self, master) self.title("%s - Tile Editor" % filename) self.filename = filename # create an instance of a TileSet; all other # methods in this class can reference this # tile set self.tileset = TileSet(filename) label = tk.Label(self, text="My filename is %s" % filename) label.pack(padx=20, pady=40) self.status = tk.Label(self, text="", anchor="w") self.status.pack(side="bottom", fill="x") def save(self): # this method acts as a controller for the data, # allowing other objects to request that the # data be saved now = datetime.datetime.now() self.status.configure(text="saved %s" % str(now)) class TileSet(object): def __init__(self, filename): self.data = "..." if __name__ == "__main__": app = SampleApp() app.mainloop()