【问题标题】:Dynamically instantiate pages Tkinter动态实例化页面 Tkinter
【发布时间】:2017-09-10 21:37:09
【问题描述】:

我正在使用最初来自另一个堆栈交换答案的代码构建一个 GUI。我已经修改了它,因为我想将变量传递给后续页面并让它们显示值。作为一种方法,而不是显示在按钮事件上,我正在尝试使用 show() 方法创建页面:

import Tkinter as tk
#import vpnRename12 as Rename
#import vpnRename_pullconfig as pullConfig

"""
Create multiple pages to go through in the same frame. Use lift() to bring
the desired page into view and stack them on top of one another. Define
page-specific methods in each page.
"""

class vpnRenameProgram(tk.Tk):
    """
    Create a page class that will allow you to lift to front the applicable
    page with the proper functions to keep the script running.
    """
    def __init__(self,*args,**kwargs):

        tk.Tk.__init__(self,*args,**kwargs)

        # Create an empty dictionary to contain all of the page names
        self.frames = {}

        self.show("MainView")

    # Instantiate the next page on call, rather than at start to allow 
    flexibility
    def instantiate_page(self, cls):
        """
        Since all of the pages are defined in the same scope/namespace, we 
        can use
        the globals()[] dict to find and instantiate the pages dynamically 
        with the show() method.
        cls is the class argument we are doing a lookup on in the global() 
        dict. 
        """

        try:
            newframe = globals()[cls](container,self)
            page_name = newframe.__name__
        except:
            print("\nError defining inline class %s"%cls)#("Class %s is not 
                defined" %cls)
            newframe = None
            page_name=globals()[cls].__name__

        return newframe, page_name




    # Create lift function to bring desired page to front of view, 
    #instantiate page if
    # it isn't already (check frames dict)
    def show(self, cls):
        if cls not in self.frames.keys():
            container = tk.Frame(self)
            container.pack(side="top", fill="both", expand=True)
            container.grid_rowconfigure(0, weight=1)
            container.grid_columnconfigure(0, weight=1)

            frame, page_name = self.instantiate_page(cls)

            if frame==None:
                frame = globals()[cls](parent=container, controller=self)

            self.frames[page_name] = frame
            frame.grid(row=0, column=0, sticky="news")

        frame = self.frames[cls]
        frame.lift()

    def get_page(self, classname):
        """
        Return instance of page when it's class name is passed in as string
        """
        for page in self.frames.values():
            if str(page.__class__.__name__) == classname:
                return page
            return None

class MainView(tk.Frame):

    def __init__(self,parent, controller,**kwargs):
        tk.Frame.__init__(self,parent)
        self.controller = controller
        self.edit_directory="edit_dir"
        self.complete_directory ="comp_dir"

        TitleLabel = tk.Label(self, text="VPN Script Editor")
        TitleLabel.pack({"side":"top"})

        EditLabel = tk.Label(self, text="Edit File Directory: 
            %s"%self.edit_directory)
        EditLabel.pack()

        CompLabel = tk.Label(self, text="Completed File Directory: 
            %s"%self.complete_directory)
        CompLabel.pack()

        Next = tk.Button(self, text="Next", command=lambda: 
            controller.show("listVPN"))
        Next.pack()


class listVPN(tk.Frame):
    """
    This is the second page, it contains a text box where you will list the 
    names of the vpn's that you want to edit. It will also display the 
    directories
    obtained by the pullconfig script.
    """

    def read_list(self):
        vpn_list=str(self.var_vpn_list.get()).upper()
        return vpn_list

    def __init__(self, parent, controller, **kwargs):
        self.controller = controller
        tk.Frame.__init__(self, parent)

        self.var_vpn_list = tk.StringVar()


        label=tk.Label(self, text="Please list the VPNs desired to edit")
        label.pack()

        #Create text box to submit a list of vpns back to the main program
        vpnLabel = tk.Label(self, text="VPN Names").pack()
        self.TextBox = tk.Entry(self, textvariable=self.var_vpn_list)
        self.TextBox.pack()

        vpnListSubmit = tk.Button(self, text="Enter", command= lambda: 
        self.read_list() and self.controller.show("pickFiles"))
        vpnListSubmit.pack()


class pickFiles(tk.Frame):
    """
    Second page that allows you to select your desired files from the 
    edit directory specified in the config file. Check all desired files, 
    list will be returned to the program.
    """

    def get_vpn_list(self):
        list = self.controller.get_page("listVPN").var_vpn_list.get()
        self.vpn_list = str(list).upper()

        self.vpn_label.configure(text="VPN List: %s"%self.vpn_list)

        return self.vpn_list

    def __init__(self, parent, controller,**kwargs):

        # Inherits from the tk.Frame class 
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.vpn_list = tk.StringVar()

        list = self.controller.get_page("listVPN").var_vpn_list.get()
        self.vpn_list = str(list).upper()



        show_vpn = tk.Button(self, text="Show vpnlist", command = 
            self.get_vpn_list)
        show_vpn.pack()

        self.vpn_label = tk.Label(self, text="VPN List: %s" %self.vpn_list)
        self.vpn_label.pack()

        # todo: get external module function to run with variable input
        #file_list = Rename.searchFile(vpnlist)

# Execute program on calling the parent class       
if __name__=="__main__":
    app = vpnRenameProgram()
    app.mainloop()

编辑: 以上是我导入的自定义脚本的整个代码注释掉了。我的主要问题是关于布局。我希望框架相互堆叠,但事实并非如此。为什么要这样做?什么能让我走上获得我想要的布局的轨道?

【问题讨论】:

  • 我很困惑你想要什么。您能否提供一个我们可以运行的完整示例来说明您遇到的问题?
  • “我已经修改了它,因为我想将变量传递给后续页面” - 你为什么这样做?您从中复制的代码使得在页面之间共享公共数据块或让每个页面从任何其他页面获取它想要的数据变得非常简单。创建页面时无需向页面传递数据。
  • 这句话很难理解:“我希望每个页面只使用一个容器实例,因为这会导致每个页面单独分层。” 每个页面正在使用容器的一个实例。你不需要改变任何东西。另外,您写了 “我不希望容器成为全局变量”,但 container 不是全局变量。它是传递给每个页面的局部变量。
  • 您是否阅读了以下问题和答案? stackoverflow.com/questions/32212408/…stackoverflow.com/questions/33646605/…?您能解释一下为什么这些页面都不能解决您的问题吗?
  • 这似乎是XY problem。也许与其试图让我们修复您损坏的代码,不如解释一下您要解决的真正问题。

标签: python-2.7 oop user-interface tkinter


【解决方案1】:

您的代码的主要问题是您要创建多个容器。用作程序基础的代码专门设计为在容器中包含多个框架的单个容器。

第一步是创建容器,并保存一个引用以便以后使用:

class vpnRenameProgram(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        self.container = tk.Frame(self)
        self.container.pack(side="top", fill="both", expand=True)
        self.container.grid_rowconfigure(0, weight=1)
        self.container.grid_columnconfigure(0, weight=1)

        self.frames = {}
        self.show_frame("MainView")

我还将建议您传递实际类而不是类名。这样您就不必深入研究globals() 来尝试根据名称找到正确的类。

将上面的最后一行更改为如下所示:

    self.show_frame(MainView)

您还需要更改get_page,但现在是一个简单的查找:

def get_page(self, page_class):
    return self.frames.get(page_class, None)

最后一步是重新定义show 以按需创建框架。您创建了一个名为 instantiate_page 的方法,但我认为没有理由不将其全部放在一个函数中,因为它只是几行额外的代码:

def show(self, page_class):
    if page_class in self.frames:
        frame = self.frames[page_class]
    else
        frame = page_class(parent=self.container, controller=self)
        frame.grid(row=0, column=0, sticky="nsew")
        self.frames[page_class] = frame

    frame.tkraise()

仅此而已。您只需要记住在调用showget_page 时传递 而不是类的名称(例如:controller.show(listVPN)、controller.get_page(pickFiles)` 等)

【讨论】:

  • 非常感谢,这几乎正好击中了我认为我遇到问题的地方。现在它正在创建一个我想要的容器。
猜你喜欢
  • 2012-01-19
  • 1970-01-01
  • 2012-07-02
  • 2014-01-26
  • 2019-11-08
  • 1970-01-01
  • 2022-01-03
  • 1970-01-01
相关资源
最近更新 更多