Динамическая инициализация фреймов в папке в Tkinter - PullRequest
2 голосов
/ 20 марта 2020

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

Это моя структура папок:

+-projectfolder
|--bargraphtutor.py
|-- __init__.py (empty)
|--pages
   |--startpage.py
   |-- .
   |-- .
   |--aboutpage.py
   |--__init__.py

__init__.py в папке pages содержит следующий код для упаковки всех файлов .py в этой папке в pages , Это было взято из этого вопроса .

from os.path import dirname, basename, isfile, join
import glob

pages = glob.glob(join(dirname(__file__), "*.py"))
__all__ = [ basename(f)[:-3] for f in pages if isfile(f) and not f.endswith('__init__.py')]
from . import *

И тогда я могу сделать import pages as p. Проблема в том, что каждый кадр является классом в файле, и для инициализации каждого кадра мне нужно знать имя каждого файла и имя класса:

Пример: часть bargraphtutor.py

self.frames = {}
for F in (p.aboutpage.AboutPage, p.startpage.StartPage): # This is where I'd like to make as 
    page_name = F.__name__                               # automated as possible. 
    frame = F(parent=container, controller=self)
    self.frames[page_name] = frame

    # put all of the pages in the same location;
    # the one on the top of the stacking order
    # will be the one that is visible.
    frame.grid(row=0, column=0, sticky="nsew")

Пример: startpage.py:

import tkinter as tk   # python 3
from tkinter import ttk
from tkinter.ttk import Label, Button
from tkinter import font


class StartPage(tk.Frame):   # Each Frame is defined via a class

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        titleLabel= ttk.Label(self, text='This is the Startpage')
        titleLabel.pack()

Итак, как я могу импортировать пакет, а затем перебрать все фреймы в bargraphtutor.py, но не зная всех имен классов? Я дошел до использования p.__all__, который возвращает имена всех файлов в пакете, но я не знаю, как двигаться дальше оттуда.

Редактировать: Если бы я назвал файл так же, как класс, я столкнулся бы с проблемами с пространствами имен?

Ответы [ 2 ]

2 голосов
/ 21 марта 2020

Обзор

Чтобы решить эту проблему, нужно, чтобы все мои страницы наследовали от базового класса, чтобы я мог использовать функцию __ подклассов __ , чтобы получить список всех подклассов за один раз они были импортированы. Для их импорта я бы использовал python glob модуль для поиска файлов и importlib модуль для импорта файлов по их путям.

Пример структуры файла

Например, давайте начнем с этой простой структуры папок:

.
├── main.py
└── pages
    ├── PageOne.py
    ├── PageTwo.py
    ├── __init__.py
    └── basepage.py

__ init __. Py пусто, но позволяет нам рассматривать pages как модуль.

basepage.py определяет класс BasePage, от которого наследуются все другие страницы. Это не должно быть много, так как каждая страница будет отвечать за все внутри страницы. Это может выглядеть примерно так:

import tkinter as tk

class BasePage(tk.Frame):
    def __init__(self, master, controller):
        self.master = master
        self.controller = controller
        super().__init__(master)

PageOne.py и PageTwo.py содержат страницы. У них похожая структура. Например, PageOne.py может выглядеть примерно так:

import tkinter as tk
from .basepage import BasePage

class PageOne(BasePage):
    def __init__(self, parent, controller):
        super().__init__(parent, controller)
        label = tk.Label(self, text="This is page one")
        label.pack(padx=20, pady=20)

Страница два идентична, за исключением того, что в очевидных местах написано «два» вместо «один». Обратите внимание, как эта страница наследуется от BasePage. Это важно, как вы увидите через минуту.

Получение списка файлов для импорта

Вы можете использовать модуль glob, чтобы получить список всех файлов в " страниц "подкаталога. Это будет выглядеть примерно так:

import glob
for filename in glob.glob("./pages/*.py"):
    ...

Если в вашей фактической папке есть файлы, которые являются страницами, а файлы - нет, вы можете использовать соглашение об именах, чтобы импортировать только файлы подкачки. Например, вы можете изменить шаблон на ".pages/Page*.py".

Импорт файла по имени файла

Python importlib Модуль имеет функции, которые позволяют нам импортировать файлы по имени файла.

Например, учитывая имя файла в filename, мы можем импортировать этот файл следующим образом:

import importlib.util
from pathlib import Path

path = Path(filename)
module_name = f"pages.{path.stem}"
spec = importlib.util.spec_from_file_location(module_name, path)
spec.loader.exec_module(module)

Если мы передадим что-то вроде ./pages/PageOne.py, он загрузит модуль под имя pages.PageOne.

Получение классов страниц

После того, как мы импортировали одну или несколько страниц, здесь мы используем класс BasePage. Помните, как каждая страница наследуется от этого класса? Мы можем использовать метод подклассов __ подклассов __ , чтобы получить список всех подклассов.

Собрав все это вместе, вот функция, которая импортирует все файлы, соответствующие "Page * .py" в подпапке "pages", а затем возвращает классы:

def get_pages(self):
    for filename in glob.glob("./pages/*.py"):
        path = Path(filename)
        module_name = f"pages.{path.stem}"
        print(f"module name: {module_name}")
        spec = importlib.util.spec_from_file_location(module_name, path)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)

    return BasePage.__subclasses__()

Создание экземпляров страницы

На данный момент создание экземпляров страницы довольно просто. Ваша основная программа может сделать что-то вроде этого:

    self.frames = {}
    for page_class in self.get_pages():
        page = page_class(parent=page_container, controller=self)
        page_name = page_class.__name__
        self.frames[page_name] = page
        page.grid(row=0, column=0, sticky="nsew")
1 голос
/ 21 марта 2020

Вопрос : Динамическая инициализация кадров в папке в Tkinter

Базовая точка

Получите module из pages.__init__, затем получить страницу class.

cls = getattr(getattr(pages, module_name), page_name)

Ссылка

  • getattr(object, name[, default])
    Вернуть значение именованного атрибут объекта. имя должно быть строкой

Примечание : я не передаю ссылку controller, так как она совпадает с class App.


import tkinter as tk
import pages


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.geometry("200x200")

        menubar = tk.Menu(self, tearoff=0)
        self.configure(menu=menubar)

        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        self.pages = {}
        for filename in pages.__all__:
            module_name, page_name = filename, filename
            cls = getattr(getattr(pages, module_name), page_name)

            self.pages[page_name] = frame = cls(self)

            frame.grid(row=0, column=0, sticky="nsew")
            menubar.add_command(label=page_name, command=frame.tkraise)

        # The following statements are equivalent 
        self.pages['StartPage'].tkraise()

        # From within a page object
        # self.master.pages['StartPage'].tkraise()


if __name__ == "__main__":
    App().mainloop()

Протестировано с Python: 3,5 - 'TclVersion': 8,6 'TkVersion': 8,6

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...