Skip to main content
 首页 » 编程设计

python之使用 Tkinter 进行子类化

2025年05月04日17kenshinobiy

我正在用 python 制作一个应用程序。一切正常。到目前为止,一切都在一个源文件中。你从小处开始,然后一切都在增长。 我到了代码变得很难理解的地步。因此,我决定需要将代码拆分为模块和类。

我终于把一些东西放在一起,让它一切正常工作。但是,我找不到太多关于使用 python 制作复杂 GUI 的信息。因此使用类来创建小部件等等。

我制作了一个小示例应用程序来演示以下内容:

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

这篇文章的目的有两个。

  1. 我希望其他人可以从我为解决这个问题所做的努力中受益。
  2. 也许其他人可以进一步改进这个例子。

我的示例有四个源文件。

  1. 开始.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 
    

非常欢迎任何改进。

请您参考如下方法:

通常,实现 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() 

这种技术的优点有两个:

  • 您现在拥有一个可以触发 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) 

此方法将责任链模式引入到您的应用中,因为您现在可以在链中的任何点控制消息流(即做某事或将其上游传回下游,但通过不同的路径). 注意 不过,好的应用程序设计会尝试将 Model View Controller 模式合并到他们的代码中,如果您在“ View ”代码责任链中的某处引入“控制”代码,这可能会造成困惑,从而引起麻烦。

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

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

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