Saltar al contenido

Fábrica de clases anidadas con tkinter

Solución:

Si lo estoy haciendo bien, creo que quieres tener una especie de Clase base que tiene alguna configuración que un conjunto de marcos tienen en común, como por ejemplo, desea tener 10 marcos de 300×400 geometría y de un marrón antecedentes en común y luego tener otro conjunto de marcos con una configuración diferente, al que se puede acceder de forma organizada. Entonces diría que tienes una forma interesante, pero prefiero usar una lista o un diccionario de todos modos.

A continuación, se muestran algunos enfoques para lograr este objetivo.

Enfoque 1

En este enfoque, he creado una función que devuelve un diccionario con todos los marcos creados y contenidos en él como en formato (..., 'F20': tkinter.frame, ...)

import tkinter as tk

def get_base_frames(num, master, cnf=, **kw):
    """
    Create list of frames with common configuration options.

    Args:
        num (int): Number of frames to be created.
        master (tk.Misc): Takes tkinter widget or window as a parent for the frames.
        cnf (dict): configuration options for all the frames.
        kw: configuration options for all the frames.

    Return:
        Dictionary of frames (..., 'F20': tkinter.frame, ...).
    """
    return f'Fn+1': tk.Frame(master, cnf=cnf, **kw) for n in range(num)

if __name__ == "__main__":
    root = tk.Tk()
    frame_holder = get_base_frames(10, root, width=50, height=50, bg='brown')

    # Frames can be accessed through their names like so.
    print(frame_holder.get('F1'))

Enfoque 2

Aquí he usado clase y objetos. Donde hice esta clase Frames aunque puedes nombrarlo como quieras. También agregué algún método importante como cget() y configure(), a través de estos métodos, una vez obtenga un valor para una opción y configure las opciones para todos los marcos respectivamente. Hay métodos más útiles como bind() y bind_all() si los necesita, simplemente modifique esta clase según su necesidad.

import tkinter as tk

class Frames(object):
    def __init__(self, master=None, cnf=, **kw):
        super().__init__()
        num = cnf.pop('num', kw.pop('num', 0))
        for n in range(num):
            self.__setattr__(f'Fn+1', tk.Frame(master, cnf=cnf, **kw))

    def configure(self, cnf=, **kw):
        """Configure resources of a widget.

        The values for resources are specified as keyword
        arguments. To get an overview about
        the allowed keyword arguments call the method keys.
        """
        for frame in self.__dict__:
            frame = self.__getattribute__(frame)
            if isinstance(frame, tk.Frame):
                if not cnf and not kw:
                    return frame.configure()
                frame.configure(cnf=cnf, **kw)
    config = configure

    def cget(self, key):
        """Return the resource value for a KEY given as string."""
        for frame in self.__dict__:
            frame = self.__getattribute__(frame)
            if isinstance(frame, tk.Frame):
                return frame.cget(key)
    __getitem__ = cget


if __name__ == "__main__":
    root = tk.Tk()
    frame_holder = Frames(root, num=10, width=10, 
                          bd=2, relief='sunken', bg='yellow')

    # Frames can be accessed through their naems like so.
    print(frame_holder.F4) 
    print(frame_holder['bg'])
    frame_holder.config(bg='blue')
    print(frame_holder['bg'])

Enfoque 3

Si quieres tener tramas configuradas de forma diferente contenidas en una clase, donde todos esos marcos tienen algún método en común o algún atributo en común.

import tkinter as tk

class BaseFrame(tk.Frame):
    def __init__(self, master=None, cnf=, **kw):
        super().__init__(master=master, cnf=, **kw)

    def common_function(self):
        """This function will be common in every 
        frame created through this class."""
        # Do something...

class FrameHolder(object):
    def __init__(self, master=None, cnf=, **kw):
        kw = tk._cnfmerge((cnf, kw))
        num = kw.pop('num', len(kw))

        for n in range(num):
            name = f'Fn+1'
            cnf = kw.get(name)
            self.__setattr__(name, BaseFrame(master, cnf))

if __name__ == "__main__":
    root = tk.Tk()

    holder = FrameHolder(root, 
                    F1=dict(width=30, height=40, bg='black'),
                    F2=dict(width=50, height=10, bg='green'),
                    F3=dict(width=300, height=350, bg='blue'),
                    F4=dict(width=100, height=100, bg='yellow'),
                    )
    print(holder.F1)
    print(holder.__dict__)

Enfoque 4

Este es el enfoque que OP está tratando de lograr.

import tkinter as tk


class BaseClass(tk.Frame):
    def __init__(self, master, cnf=, **kw):
        kw = tk._cnfmerge((cnf, kw))
        cnf = [(i, kw.pop(i, None))
               for i in ('pack', 'grid', 'place') if i in kw]
        tk.Frame.__init__(self, master, **kw)
        self.master = master

        if cnf:
            self.__getattribute__(cnf[-1][0])(cnf=cnf[-1][1])


class Container(tk.Frame):
    """Container class which can contain tkinter widgets. 
    Geometry (pack, grid, place) configuration of widgets 
    can also be passed as an argument.

    For Example:-

    >>> Container(root, widget=tk.Button,
              B5=dict(width=30, height=40, bg='black',
                      fg='white', pack=(), text='Button1'),
              B6=dict(width=50, height=10, bg='green', text='Button2',
                      place=dict(relx=0.5, rely=1, anchor='s')))
    """
    BaseClass = BaseClass

    def __init__(self, master=None, cnf=, **kw):
        kw = tk._cnfmerge((cnf, kw))
        wid = kw.pop('widget', tk.Frame)
        for name, cnf in kw.items():
            geo = [(i, cnf.pop(i, None))
                   for i in ('pack', 'grid', 'place') if i in cnf]
            setattr(Container, name, wid(master, cnf))
            if geo:
                manager, cnf2 = geo[-1]
                widget = getattr(Container, name)
                getattr(widget, manager)(cnf=cnf2)


if __name__ == "__main__":
    root = tk.Tk()

    Container(root, widget=Container.BaseClass,
              F1=dict(width=30, height=40, bg='black', relief='sunken',
                      pack=dict(ipadx=10, ipady=10, fill='both'), bd=5),
              F2=dict(width=50, height=10, bg='green',
                      pack=dict(ipadx=10, ipady=10, fill='both')),
              )

    Container(root, widget=tk.Button,
              B5=dict(width=30, height=40, bg='black',
                      fg='white', pack=, text='Button1'),
              B6=dict(width=50, height=10, bg='green', text='Button2',
                      place=dict(relx=0.5, rely=1, anchor='s')),
              )

    print(Container.__dict__)
    root.mainloop()

Se puede hacer mucho y se puede modificar según las necesidades de cada uno, estos son solo algunos enfoques que creo que funcionarán muy bien para automatizar y mantener un conjunto de marcos en forma y juntos.

Puede haber varias formas de hacer esto o tal vez algo mejor y eficiente que estas, no dude en dar sugerencias y compartir algo nuevo.

Una solución a este problema, creo, ya que no entiendo completamente su pregunta, pero esta fue mi solución:

import tkinter as tk
from tkinter import Frame,Button

class BaseClass(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.master = master
        self.pack()


class Holder_frame(tk.Frame):
    def __init__(self, master, frames=2):
        tk.Frame.__init__(self, master)
        self.master = master
        self.frame_names = []
        for i in range(frames):
            Holder_frame.create_frames("F"+str(i+1), self)

    @classmethod
    def create_frames(cls, name, master):
        setattr(cls, name, tk.Frame(master))

if __name__ == "__main__":
    root = tk.Tk()
    def raise1():
        print(type(Holder_frame.F1))
    def raise2():
        print(type(Holder_frame.F2))

    holder=Holder_frame(root,frames=2)
    holder.grid(row=1,column=0)
    b1 = tk.Button(root, text='1', command=raise1)
    b1.grid(row=0,column=0)
    b2 = tk.Button(root, text='2', command=raise2)
    b2.grid(row=0,column=1)
    print(Holder_frame.__dict__.items())

    root.mainloop()

El uso de setattr permite agregar variables a la clase, como si escribiera una función en el código. Esto le permite acceder a marcos desde fuera de la clase como una especie de “variable global”.

Usé un archivo para probar si también funciona afuera como un módulo importado:

# main.py
from nested_class import Holder_frame
import tkinter as tk

root = tk.Tk()
holder=Holder_frame(root,frames=1000)
holder.grid(row=1,column=0)
print(Holder_frame.__dict__.items())

root.mainloop()

Espero que esto responda tu pregunta,

Jaime

EDITAR:

Después de pensar que hay, lo que creo, que sea un sistema más limpio para lo que quieres. Con el código de esta publicación, se puede ver que su mi sistema escrito podría ser reemplazado por un ttk.Notebook, y quitando la barra superior usando style.layout('TNotebook.Tab', []), se puede ver que obtendría un widget de marco que podría tener widgets de marco dentro de él:

import tkinter as tk
import tkinter.ttk as ttk

class multiframe_example:
    def __init__(self, master):
        self.master = master

        style = ttk.Style()
        style.layout('TNotebook.Tab', [])   
        notebook = ttk.Notebook(self.master)
        notebook.grid(row=0, column=0)

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

        tab1 = tk.Frame(self.master,  width=500, height=500, background="green")
        tab2 = tk.Frame(self.master,  width=500, height=500)
        tab3 = tk.Frame(self.master,  width=500, height=500)


        notebook.add(tab1)
        notebook.add(tab2)
        notebook.add(tab3)

        notebook.select(0) # select tab 1
        notebook.select(1) # select tab 2
        notebook.select(2) # select tab 3

def main():
    root = tk.Tk()
    root.geometry("500x500")
    multiframe_example(root)
    root.mainloop()

if __name__ == '__main__':
    main()

¡Espero que este código pueda ayudarte y haga lo que quieras!

Sección de Reseñas y Valoraciones

Si te animas, tienes la opción de dejar un tutorial acerca de qué le añadirías a este enunciado.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *