在Python中使用Tkinter进行子类化

Eri*_*ric 1 python tkinter

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

我终于把一些东西放在一起,让它全部运转起来.但是,我对使用python制作复杂的GUI没有太多帮助.因此使用类来创建小部件等.

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

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

这篇文章的目的是双重的.

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

我的例子有四个源文件.

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

    import main
    
    if __name__ == '__main__':
        title = "Test"
        gui = main.Gui(title)
    
    Run Code Online (Sandbox Code Playgroud)
  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)
    
    Run Code Online (Sandbox Code Playgroud)
  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)
    
    Run Code Online (Sandbox Code Playgroud)
  4. action.py.此模块包含将执行应用程序的实际任务的代码.

    class Adder():
        def __init__(self):
            self.count = 0
    
        def addValue(self, value):
            if value:
                self.count += value
            return self.count
    
    Run Code Online (Sandbox Code Playgroud)

任何改进都非常受欢迎.

tel*_*ist 6

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

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()
Run Code Online (Sandbox Code Playgroud)

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

  • 你现在拥有一个可以触发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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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