Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Class factory with tkinter

I'm trying to build a script for import in my future projects. That Script should create some tk.Frames in a tk.Frame and let me edit the created ones in a main.

I think, the best way to get there is to create a Holder_frame class and put some nested classes in. so I could call them in my main with Holder_frame.F1. I tried a lot of code and I ended up here making me an account. Anyway here is where Im at:

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):
    Names = []
    def __init__(self, master, frames=2):
        tk.Frame.__init__(self, master)
        self.master = master
        frame_names = Holder_frame.Names
        for i in range(0,frames):
            frame_names.append("F"+str(i+1))
        print(frame_names)
        Holder_frame.factory()
    def factory():
        print(Holder_frame.Names)
        print(type(BaseClass))
        for idex,i in enumerate (Holder_frame.Names):
            print(i)
            class NestedClass(BaseClass):
                pass

            NestedClass.__name__ = i
            NestedClass.__qualname__ = i

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

    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)


    root.mainloop()

Everything works fine, till I try to call a Frame. (AttributeError 'Holder_frame' object has no attribute 'F1') I think my problem is the structure but need some help to solve it.

Any suggestions?

like image 686
Thingamabobs Avatar asked May 27 '20 18:05

Thingamabobs


People also ask

Can you use classes with Tkinter?

Working with Classes in Tkinter The way the application works is pretty simple. We first create a root window, on top of which we place a single frame. In order to make it seem like an application with different windows, we'll also create a function that switches from one frame to another.

Can you have nested classes in Python?

Inner or Nested classes are not the most commonly used feature in Python. But, it can be a good feature to implement code. The code is straightforward to organize when you use the inner or nested classes.

What is IntVar () in Tkinter?

_ClassType IntVarConstruct an integer variable. set(self, value) Set the variable to value, converting booleans to integers. get(self) Return the value of the variable as an integer.

Can a Tkinter application have more than one TK instance?

However, a Tkinter application should have only one Tk instance. Therefore, it’s common to inherit from the ttk.Frame class and use the subclass in the root window. To inherit the ttk.Frame class, you use the following syntax:

How do I inherit a Tkinter frame class?

To inherit the ttk.Frame class, you use the following syntax: Since a Frame needs a container, you need to add an argument to its __init__ () method and call the __init__ () method of the ttk.Frame class like this: The following shows the complete MainFrame class that has a label and a button.

What is a nested class in Python?

Let us try to get an overview of the nested class in python. A class defined in another class is known as Nested class. If an object is created using nested class then the object can also be created using the Parent class. Moreover, a parent class can have multiple nested class in it.

What is nested class In JUnit?

Courses Automation Types Of Testing Tutorials Nested Class is Placed inside Another Class and is Arranged in a Hierarchical Structure. Learn about Rules, Template & Examples of JUnit 5 Nested Class: We learned about repeating tests with the same data using the annotation @RepeatedTest in our previous tutorial.


1 Answers

If I'm getting it right I think you mean to have some sort of a Base class that has some configuration which a set of frames have in common like for example you want to have 10 frames of 300x400 geometry and of a brown background in common and later having another set of frames with a different configuration, which can be accessed in an organised way. Then I would say you have an interesting way but I would rather use a list or a dictionary anyway.

Here are some approaches to achieve this goal.

Approach 1

In this approach, I've created a function that returns a dictionary with all the frames created and contained in it like in format ({..., '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'F{n+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'))

Approach 2

Here I've used class and objects. Where I made this class Frames though you can name it anything you want. I also added some important method like cget() and configure(), through these methods once get a value to an option and configure options for all the frames respectively. There are more useful methods like bind() and bind_all() if you need those just modify this class as per your need.

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'F{n+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'])

Approach 3

If you want to have differently configured frames contained in one class, where all those frames have some method in common or some attribute in common.

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'F{n+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__)

Approach 4

This is the approach that OP is trying to achieve.

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()

A lot can be done and can be modified according to one's needs, these are just some approaches that I think will work very well to automate and keep a set of frames in shape and together.

There can be multiple ways to do this or maybe something better and efficient than these, feel free to give suggestions and share something new.

like image 120
Saad Avatar answered Oct 12 '22 13:10

Saad