获取对象之间tkinter中事件的访问权限

时间:2017-09-08 15:17:34

标签: python events tkinter callback

在这个示例python / tkinter脚本中,我有3个类。我的第一个类SandBox设置了一个带有两个选项卡的笔记本,每个选项卡都是一个单独的类对象。

这个脚本不是我正在处理的真实世界脚本,它只是尝试在这里添加一些简单的东西来帮助解释我的问题。

我正在尝试确定如何通过PageOne对象通知PageTwo对象的更改。例如,当数据被添加到PageOne中的列表框时,我希望PageTwo能够收到有关更改的通知,并且可以访问添加的数据。任何指导都表示赞赏。

from tkinter import *
from tkinter import ttk


class PageOne:
    def __init__(self):
        pass

    def init_ui(self, page):
        self.lb_test_one = Listbox(page, selectmode=SINGLE)
        self.lb_test_one.grid(row=0, column=0, columnspan=10, sticky=W + E)
        self.lb_test_one.configure(exportselection=False)
        self.lb_test_one.bind('<<ListboxSelect>>', self.on_test_one_selected)

        # UI Row 3
        self.b_add_data = Button(page, text="Add Data", command=self.on_add_data)
        self.b_add_data.grid(row=1, column=0, columnspan=10, sticky=N + S + E + W)
        return page

    counter = 0
    def on_add_data(self):
        self.lb_test_one.insert(self.counter, 'Test Data: {}'.format(self.counter))
        self.counter += 1

    def on_test_one_selected(self, evt):
        event_data = evt.widget
        if len(event_data.curselection()) > 0:
            index = int(event_data.curselection()[0])
            value = event_data.get(index)


class PageTwo:
    def __init__(self):
        pass

    def init_ui(self, page):
        self.lb_test_two = Listbox(page, selectmode=SINGLE)
        self.lb_test_two.grid(row=0, column=0, columnspan=10, sticky=W + E)
        self.lb_test_two.configure(exportselection=False)

        return page


class SandBox:
    root_tk = None
    def __init__(self):
        self.root_tk = Tk()
        self.root_tk.geometry("250x350")
        nb = ttk.Notebook(self.root_tk)
        nb.add(PageOne().init_ui(ttk.Frame(nb)), text='Tab One')
        nb.add(PageTwo().init_ui(ttk.Frame(nb)), text='Tab Two')
        nb.pack(expand=1, fill="both")
        self.root_tk.mainloop()

if __name__ == "__main__":
    SandBox()

2 个答案:

答案 0 :(得分:1)

使用多个类,您可以将框架或对象传递给应该与另一个进行交互的类。

您甚至可以使用变量名来处理类外部的类属性

这是一个示例代码,展示了如何与一个类到另一个类以及从外部类到主类实例的数据进行交互。

import tkinter as tk


class LeftFrame(tk.Frame):

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.l_text = tk.Text(self.parent, width = 40, height = 10)
        self.l_text.grid(row=0, column=0)
        self.l_text.bind("<Key>", self.return_data)

    def return_data(self, event):
        data = self.l_text.get(1.0, tk.END)
        # uses the variable name "app" assigned to the main window class.
        # then calls a method inside that class to append the data from another class text box
        app.add_to_right_frame(data) 


class RightFrame(tk.Frame):

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.r_text = tk.Text(self.parent, width = 40, height = 10)
        self.r_text.grid(row=0, column=1)
        self.r_text.bind("<Key>", self.return_data)

    def return_data(self, event):
        data = self.r_text.get(1.0, tk.END)
        # uses the variable name "app" assigned to the main window class.
        # then calls a method inside that class to append the data from another class text box
        app.add_to_left_frame(data)


class MainWindow(tk.Frame):

    def __init__(self, root):
        tk.Frame.__init__(self, root)
        self.master = root
        self.main_frame = tk.Frame(self.master)
        self.main_frame.grid(row=0, column=0, sticky="nsew")

        #makes a class attribute of each frame so it can be manipulated later            
        self.f1 = LeftFrame(self.main_frame)
        self.f2 = RightFrame(self.main_frame)

    def add_to_left_frame(self, data):
        self.f1.l_text.delete(1.0, tk.END)
        self.f1.l_text.insert(1.0, data)

    def add_to_right_frame(self, data):
        self.f2.r_text.delete(1.0, tk.END)
        self.f2.r_text.insert(1.0, data)


if __name__ == "__main__":
    root = tk.Tk()
    app = MainWindow(root)
    root.mainloop()

结果是一个窗口,其中包含2个从外部类创建的文本框,能够键入一个框并让它显示在另一个框中,反之亦然。

答案 1 :(得分:1)

一种解决方案是将SandBox设置为控制器,您可以使用该控制器实现发布/订阅系统。每个页面都可以调用控制器的notify方法,然后可以调用所有订阅者的notify方法。

首先为每个页面提供对控制器的引用:

class Sandbox:
    def __init__(self):
        ...
        page1 = PageOne(controller=self)
        page2 = PageTwo(controller=self)

然后,每个页面都可以调用控制器上的方法来发送信息:

class PageOne:
    def __init__(self, controller):
        self.controller = controller
        ...
    def on_add_data(self):
        ...
        self.controller.notify("some data")

SandBox可以遍历所有已知页面(通过保存对列表中每个页面的引用),也可以提供一种方法,以便其他页面可以要求添加到列表中(即:{{{ 1}}方法)

对于前者的一个例子,它就像

一样简单
subscribe

然后,您需要为每个页面实现class SandBox: def __init__(self): ... page1 = PageOne(controller=self) page2 = PageTwo(controller=self) self.subscribers=[page1, page2] ... def notify(self, data): for page in self.subscribers: page.notify(data) ,以便在接收数据时执行您希望它执行的任何操作。

这只是基本概念。 pub / sub系统必须允许您订阅某些类型的事件,以便所有数据不会一直发送给每个订户。例如。 PageOne可能并不关心PageOne何时发生变化。

您可以通过在控制器中引入notify方法来实现此目的,在该控制器中传递您要订阅的信息类型,以及调用该类型消息的函数。在这种情况下,“类型”非常类似于Tkinter中的事件。它可以实现为一个简单的字符串,它不必是任何花哨的东西。

在PageTwo:

subscribe

在PageOne中:

def __init__(...):
    ...
    # tell the controller that when a notification with a type of
    # "list_changed" is received it should call self.on_list_changed
    self.controller.subscribe("list_changed", self.on_list_changed)
    ...

def on_list_changed(self, data):
    print("data received:", data)

在SandBox中:

# send a "list_changed" message with some data
self.controller.notify("list_changed", "some data...")