【问题标题】:Calling a widget from another class to change its properties in tKinter从另一个类调用小部件以更改其在 tKinter 中的属性
【发布时间】:2017-11-28 12:32:36
【问题描述】:

我在我的 tKinter 应用程序的主构造函数中创建了一个函数,它更新了小部件的某些属性,例如他们的文本跨越多个框架。我想要做的是在控制器框架中同时更改多个框架中的小部件。

def update_widgets(self, frame_list, widget_name, criteria, output):
    for i in frame_list:

        i.widget_name.config(criteria=output)

# update_widgets(self, [Main, AnalysisSection], text_label, text, "foo")
# would result in Main.text_label_config(text="foo") and 
# AnalysisSection.text_label_config(text="foo") ideally.

但是,使用此代码时,我遇到了两个问题。首先,我收到一个属性错误,指出两个框架都没有属性 widget_name。其次,当我尝试使用 self 前缀引用小部件名称时,两个框架都说它们没有属性 self。有没有办法解决这个问题?

完整程序如下:

import tkinter as tk

class Root(tk.Tk):

def __init__(self, *args, **kwargs):


    tk.Tk.__init__(self, *args, **kwargs)
    self.frames = {}

    container = tk.Frame(self)
    container.pack(side="bottom", expand=True)#fill="both", expand=True)

    container.grid_rowconfigure(0, weight=1)
    container.grid_columnconfigure(0, weight=1)

    for X in (A, B):
        frame=X(container, self)
        self.frames[X]=frame
        frame.grid(row=0, column=0, sticky="nsew")

    self.show_frame(A)

def show_frame(self, page):
    frame = self.frames[page]
    frame.tkraise()

def update_widgets(self, frame_list, widget_name, criteria, output):
    for i in frame_list:
        frame = self.frames[i]
        widget = getattr(frame, widget_name)
        widget[criteria] = output



class A(tk.Frame):

     def __init__(self, parent, controller):

         tk.Frame.__init__(self, parent)

         self.controller = controller

         self.text = 'hello'

         self.classLabel = tk.Label(self, text="Frame A")

         self.classLabel.pack(side=tk.TOP)

         # trying to change this widget
         self.wordLabel = tk.Label(self, text="None")
         self.wordLabel.pack(side=tk.TOP)

         self.changeTextLabel = tk.Label(self, text="Change text above across both frames").pack(side=tk.TOP)
         self.changeTextEntry = tk.Entry(self, bg='pink')
         self.changeTextEntry.pack(side=tk.TOP)

         self.changeFrameButton = tk.Button(text="Change to Frame B", command=lambda: self.controller.show_frame(B))
         self.changeFrameButton.pack(side=tk.TOP, fill=tk.X)

         self.changeTextEntryButton = tk.Button(self, text="ENTER", width=5, command=lambda: self.controller.update_widgets([A, B], 'self.wordLabel', 'text', self.changeTextEntry.get()))
         self.changeTextEntryButton.pack(side=tk.TOP, fill=tk.X)


         ### calling this function outside of the button; this is already
         ### called within a function in my project.
         x = self.controller.update_widgets([A, B], 'wordLabel', 'text', '*initial change*')



class B(tk.Frame):

     def __init__(self, parent, controller):

       tk.Frame.__init__(self, parent)

       self.controller = controller

       self.text = 'hello'

       self.classLabel = tk.Label(self, text="Frame B")

       self.classLabel.pack(side=tk.TOP)

       # trying to change this widget
       self.wordLabel = tk.Label(self, text="None")
       self.wordLabel.pack(side=tk.TOP)

       self.changeTextLabel = tk.Label(self, text="Change text above across both frames").pack(side=tk.TOP)
       self.changeTextEntry = tk.Entry(self, bg='light yellow').pack(side=tk.TOP)

       self.changeFrameButton = tk.Button(text="Change to Frame A", command=lambda: self.controller.show_frame(A))
       self.changeFrameButton.pack(side=tk.TOP, fill=tk.X)

       self.changeTextEntryButton = tk.Button(self, text="ENTER", width=5, command=lambda: self.controller.update_widgets([A, B], 'self.wordLabel', 'text', self.changeTextEntry.get()))
       self.changeTextEntryButton.pack(side=tk.TOP, fill=tk.X)

if __name__ == '__main__':
   app = Root()

【问题讨论】:

标签: python tkinter widget


【解决方案1】:

您的代码中的问题是您试图获取一个 class 的属性,而不是一个类的 instance。您需要将i 转换为该类的实际实例。你还有一个额外的问题是你传递'self.wordLabel' 而不仅仅是'wordLabel'

一个简单的解决方法是在self.frames 中查找实例

def update_widgets(self, frame_list, widget_name, criteria, output):
    for i in frame_list:
        frame = self.frames[i]
        label = getattr(frame, widget_name)
        label[criteria] = output

您还需要将按钮命令更改为如下所示:

self.changeTextEntryButton = tk.Button(... command=lambda: self.controller.update_widgets([A,B], 'wordLabel', 'text', self.changeTextEntry.get()))

如果您打算让update_widgets 始终更新所有页面类,则没有理由传入框架类列表。相反,您可以迭代已知类:

def update_widgets(self, widget_name, criteria, output):
    for frame in self.frames.values():
        label = getattr(frame, 'classLabel')
        label[criteria] = output

然后您需要修改按钮以删除框架类列表:

self.changeTextEntryButton = tk.Button(..., command=lambda: self.controller.update_widgets('wordLabel', 'text', self.changeTextEntry.get()))

【讨论】:

  • frame_list 是我的应用程序中的框架列表;我正在尝试在两个框架(Main 和 AnalysisSection)中引用特定小部件并更改所选小部件的属性之一。
  • @Azidic:我很抱歉;我没有足够仔细地阅读您的原始问题。我已经重写了我的答案。
  • 感谢您的输入,但由于某种原因,getattr 不适用于引用构造函数中的对象(自身)的类属性。 (AttributeError:类型对象'A'没有属性'self.classLabel')
  • @Azidic:它不起作用的原因是,尽管您暗示 frame_list 是帧列表,但在您更新的问题中,我发现它不是。它是框架的列表。两者有很大的不同。
  • 我还有一个问题:如何在构造函数中调用update_widgets 而不使用按钮?当我在 A 类(已更新)x = self.controller.update_widgets([A, B], 'wordLabel', 'text', '*initial change*') 中运行这行代码时,它返回了这个错误:KeyError: <class '__main__.A'>。在这个函数调用中使用 lambda 没有任何区别,但修复了关键错误。
猜你喜欢
  • 1970-01-01
  • 2015-02-06
  • 2019-08-04
  • 2014-09-01
  • 2019-12-17
  • 2021-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多