在Python中使用Tkinter进行子类化

时间:2014-04-01 12:30:39

标签: python tkinter

我正在用python创建一个应用程序。一切正常。到目前为止,一切都在一个源文件中。你从小开始,然后一切都在增长。 我到了一个代码很难理解的地方。所以我决定我需要在模块和类中拆分代码。

我终于得到了一些东西,让它全部运转起来。但是,我找不到用python制作复杂的GUI的太多东西。因此使用类来创建小部件等。

我做了一个小示例应用程序,演示了以下内容:

  1. 拆分GUI代码和操作代码。在我的例子中,动作代码由一个单独的类处理,这也可能只是一个单独的模块。
  2. 通过子类化容器创建自定义小部件,在我的示例中为Tkinter.LabelFrame。
  3. 使用传播的虚拟/自定义事件来触发主代码中的操作。
  4. 与子类/小部件交换数据
  5. 这篇文章的目的有两个。

    1. 我希望其他人可以从我必须解决的斗争中受益。
    2. 也许其他人可以进一步改进这个例子。
    3. 我的示例有四个源文件。

      1. start.py。该模块仅启动应用程序,创建Gui类的对象。

        import main
        
        if __name__ == '__main__':
            title = "Test"
            gui = main.Gui(title)
        
      2. main.py。该模块包含Gui类,并保存GUI的根元素。

        import Tkinter
        import action
        import widget
        
        class Gui():
            def __init__(self, title):
                self.root = Tkinter.Tk()
                self.root.protocol("WM_DELETE_WINDOW", self.applicationExit)
                self.root.title(title)
        
                #create the action object
                self.process = action.Adder()
        
                #create the input frame
                self.frameIn = widget.Input(self.root)
                self.frameIn.grid(row=0, column=0, padx = 5, pady =5, ipadx = 5, ipady = 5, sticky = Tkinter.N)
        
                #create the output frame
                self.frameOut = widget.Output(self.root)
                self.frameOut.grid(row=1, column=0, padx = 5, pady =5, ipadx = 5, ipady = 5, sticky = Tkinter.N)
        
                #bind events
                self.root.bind("<<input_submit>>", self.__submit)
        
                self.root.mainloop()
        
            def applicationExit(self):
                self.root.destroy()
        
            def __submit(self, event = None):
                value = self.frameIn.getValue()
                result = self.process.addValue(value)
                self.frameOut.outputText.set(result)
        
      3. widget.py。该模块包含两个自定义小部件,用于GUI。

        import Tkinter
        
        class Input(Tkinter.LabelFrame):
            def __init__(self, master):
                Tkinter.LabelFrame.__init__(self, master, text = "Input")
                self.inputText = Tkinter.StringVar()
        
                #create entry box
                self.entInput = Tkinter.Entry(self, textvariable = self.inputText, width = 20,)
                self.entInput.grid(row = 0, column = 0, padx = 5, pady = 2, sticky = Tkinter.N)
        
                #create submite button
                self.btnSubmit = Tkinter.Button(self, text = "Add", width = 10,
                    command = self.__handlerSubmitButton)
                self.btnSubmit.grid(row = 1, column = 0, padx = 5, pady = 2, sticky = Tkinter.N)
        
            def getValue(self):
                value = self.inputText.get()
                if value.isdigit():
                    return int(value)
                else:
                    None
        
            def __handlerSubmitButton(self, event = None):
                self.btnSubmit.event_generate("<<input_submit>>")
        
        class Output(Tkinter.LabelFrame):
            def __init__(self, master):
                Tkinter.LabelFrame.__init__(self, master, text = "Output")
                self.outputText = Tkinter.StringVar()
        
                #create out put label box
                self.lblOutput = Tkinter.Label(self, textvariable = self.outputText, width = 20,
                    anchor = Tkinter.E)
                self.lblOutput.grid(row = 0, column = 0, padx = 5, pady = 2, sticky = Tkinter.N)
        
            def setValue(self, value):
                self.outputText.set(value)
        
      4. action.py。该模块包含将执行应用程序实际任务的代码。

        class Adder():
            def __init__(self):
                self.count = 0
        
            def addValue(self, value):
                if value:
                    self.count += value
                return self.count
        
      5. 非常欢迎任何改进。

1 个答案:

答案 0 :(得分:6)

通常,实现Tkinter应用程序的标准模式是使用一些名为Application的根对象或扩展Tkinter.Frame的东西,然后继续创建定义接口的所有小部件: / p>

import Tkinter as tk

class Application(tk.Frame):

    def __init__(self, root, *args, **kwargs):
        tk.Frame.__init__(self, root, *args, **kwargs)
        ... #do other initialisation
        self.grid() #or pack()

... 

if __name__ == '__main__':
    root = tk.Tk()
    app = Application(root)
    root.mainloop()

这种技术的优点有两个方面:

  • 您现在拥有一个可以触发Tkinter事件和行为的对象(因为Tkinter有自己的小部件层次结构),并且还可以使用普通的类习惯用法拦截这些行为,例如方法
  • 您的根类可以传递您自己的消息处理方案(用于处理要求4),该方案可以与您构建接口时将形成的自然层次结构保持一致和协调。

作为后一点的一个例子:

class Message(object):
    def __init__(self, kind, data):
        self.kind = kind
        self.data = data

class Application(tk.Frame):
    def __init__(self, root, *args, **kwargs):
        self.widgets = []
        ... #do widget declarations

    def message_downstream(self, message):
        for widget in self.widgets:
            widget.receive_message(message)

    def message_upstream(self, message):
        #do some logic based on the message
        ...

class Widget(tk.Button):
    def __init__(self, master, name, *args, **kwargs):
        tk.Button.__init__(self, master, *args, **kwargs)
        self.master = master
        #perhaps set command event to send a message
        self['command'] = lambda: self.message_upstream(Message(self.name, "I Got Clicked"))

    def message_downstream(self, message):
        #similar to above
        pass

    def message_upstream(self, message):
        self.master.message_upstream(self, message)

此方法将责任链模式引入您的应用程序,因为您现在可以在链中的任何位置控制消息流(即执行某些操作或将其向上游传递回下游,但通过不同的路径)。 谨防但是,良好的应用程序设计试图将模型视图控制器模式合并到他们的代码中,如果您在“视图”代码链中的某个位置引入“控制”代码,这可能会让人头疼。

在Tkinter层次结构中使用责任链的最佳方法是将代码限制为仅涉及接口问题,并将其他所有内容(即修改数据的代码)传递给某些适当的控制器,例如你提到的动作类。

那你为什么要使用上面这样的模式呢?当您的界面以复杂的方式与自身交互时。一个示例可能是某些子菜单中的控件更改了某些其他帧中可见的内容。该行为并不真正关注或依赖于模型,因此如上所述实现它将起作用。

我曾经为Python编写了一个代码编辑器,它在你输入的时候自动编译并在另一个窗口中运行代码(实际上很烦人),显示代码输出或发生了哪些异常。我使用一系列职责从编辑器小部件中收集代码,并将程序输出发送到输出窗口。我还使用它同时对两个窗口应用语法高亮更改。