【问题标题】:Nested Class factory with tkinter带有 tkinter 的嵌套类工厂
【发布时间】:2020-09-14 22:18:57
【问题描述】:

我正在尝试构建一个脚本以在我未来的项目中导入。 该脚本应该在 tk.Frame 中创建一些 tk.Frames,然后让我在 ma​​in 中编辑创建的那些。

我认为,最好的方法是创建一个 Holder_frame 类并放入一些嵌套类。 所以我可以用 Holder_frame.F1 在我的 ma​​in 中调用它们。 我尝试了很多代码,最终我在这里注册了一个帐户。 无论如何,这是我所在的地方:

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

一切正常,直到我尝试调用 Frame。 (AttributeError 'Holder_frame' 对象没有属性 'F1') 我认为我的问题是结构,但需要一些帮助来解决它。

有什么建议吗?

【问题讨论】:

  • 您应该提供一个最小且可重现的示例。
  • 你的意思是要执行一些代码?我可以添加它。
  • 不仅是一些代码,而且是最少且可重现的代码,因此我们可以复制并粘贴您的代码并查看发生了什么错误。
  • 好的,把它改成完整的例子。
  • 我不知道你到底想要什么,为什么?但是 F1 和 F2 主要是未知的。你认为你在哪里创建了这些对象,你认为你在哪里存储了它们?

标签: python tkinter factory inner-classes


【解决方案1】:

如果我说得对,我认为您的意思是拥有某种 基类,该类具有某些配置,一组框架具有共同点,例如您希望拥有 10 个框架300x400 几何形状和 棕色 背景共同,后来有另一组 具有不同配置的框架,可以以有组织的方式访问大大地。那我会说你有一个有趣的方式,但无论如何我宁愿使用列表或字典。

这里有一些实现这一目标的方法。

方法一

在这种方法中,我创建了一个函数,该函数返回一个字典,其中包含创建并包含在其中的所有帧,格式为 ({..., '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'))

方法2

这里我使用了类和对象。我在哪里开设了这门课Frames,尽管你可以随意命名。我还添加了一些重要的方法,如cget()configure(),通过这些方法一次获取一个选项的值并分别为所有框架配置选项。 还有更多有用的方法,如bind()bind_all(),如果您需要这些方法,只需根据需要修改此类即可。

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'])

方法 3

如果您希望在一个类中包含不同配置的框架,所有这些框架都有一些共同的方法或一些共同的属性。

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

方法 4

这是 OP 试图实现的方法。

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

可以做很多事情,并且可以根据自己的需要进行修改,这些只是我认为可以很好地自动化并保持一组框架形状和在一起的一些方法。

有多种方法可以做到这一点,或者比这些更好、更有效的方法,请随时提出建议并分享新的东西。

【讨论】:

  • 方法 3 似乎非常接近,如果不完全是我尝试构建的。我会尝试构建并让您知道是否缺少某些内容。我不知道“tk._cnfmerge”、“kw.pop/get”,它似乎是强大的工具。非常感谢!
  • @Atlas435:在你的代码中,你想用NestedClass(BaseClass)做什么,请详细说明。
  • 这是我知道的从我的 main 中取出特定 Frame 的唯一方法。对于Exampel,如果您将BaseClass 放在Holder_frame 的命名空间中,您将可以通过Holder_frame.BaseClass 调用它并解析简单的参数。旁注:我 6 个月前开始使用 python3 进行编码。
  • 是否可以将 FrameHolder 设为 tk.Frame 而不是对象?
  • @Atlas435:现在我对你想要达到的目标有了更多的了解,看到有无数种方法可以做这样的事情,但这真的取决于你想怎么做。现在我有了一种新方法,我将添加到我的答案中,但我不确定这是否是正确的方法。
【解决方案2】:

我认为这个问题的一个解决方案,因为我不完全理解你的问题,但这是我的解决方案:

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

setattr 的使用允许向类添加变量,就像在代码中键入函数一样。这允许您从类外部访问框架,作为某种“全局变量”

我使用了一个文件来测试它是否也可以作为导入模块在外部工作:

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

我希望这能回答你的问题,

詹姆斯

编辑:

在我认为有一个更清洁的系统来满足您的需求之后。使用this post 的代码,可以看到您的我的书面系统可以替换为ttk.Notebook,并且通过使用style.layout('TNotebook.Tab', []) 删除顶部栏,可以看到您将获得一个可以具有框架的框架小部件里面的小部件:

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

希望这段代码可以支持你并做你想做的事!

【讨论】:

  • 这个答案达到了我的目标,这就是我投票的原因。我遇到的一个小问题是配置它们似乎“困难”,甚至更难在其上放置小部件。但这是向前迈出的一大步,谢谢你到这里。
  • @Atlas435:这可以在更少的 LOC(代码行)中实现,如果这是您希望创建框架的方式,那么我很想知道您为什么采用这种方法。
  • 很少 LOC 是什么意思?如果你考虑直接调用 tk.Frame,那就对了。但是这个模块应该可以设置像具有多个框架的显示这样的东西,稍后我会寻找将它放在一些模式中。这是我一遍又一遍地不断写的东西,我很高兴能更快地编写代码。我也考虑线程这就是我想要上课的原因。
  • @jimbob88 你的编辑给了我 TclError: Slave index 3 out of bounds
  • @Atlas435 抱歉,现在应该修复了
猜你喜欢
  • 1970-01-01
  • 2016-04-21
  • 1970-01-01
  • 1970-01-01
  • 2015-07-26
  • 1970-01-01
  • 1970-01-01
  • 2015-05-28
  • 2017-02-08
相关资源
最近更新 更多