【问题标题】:communication between tkinter toplevelstkinter 顶层之间的通信
【发布时间】:2020-07-20 04:21:39
【问题描述】:

我正在做一个小项目,并制作了一个小屏幕键盘作为 tkinter Toplevel

我的应用程序是这样构建的:

  • 根窗口(Tk 小部件)
    • 输入 1(入口小部件)
    • 输入 2(入口小部件)
    • 输入 3(文本小部件)
    • on_screen-keyboard (Toplevel-Widget)

Toplevel-Widget 包含按钮,以及应在条目中输入文本的回调(就像键盘按钮一样)

我想要的是键盘/键盘的子级与最后一个活动输入小部件之间的通信。我的问题是,我不知道如何说键盘,它应该向哪个输入小部件发送消息。

import tkinter as tk
from tkinter import ttk

class MainWindow(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.active_input = tk.Variable(value=None)
        ttk.Button(self, text="show Keyboard", command=lambda: Keyboard(self)).pack()
        self.text = tk.StringVar(value="")
        self.input1 = ttk.Entry(self)
        self.input1.bind("<FocusIn>", lambda e: self.active_input.set(self.input1))
        self.input2 = ttk.Entry(self)
        self.input2.bind("<FocusIn>", lambda e: self.active_input.set(self.input2))
        self.input3 = tk.Text(self, height=3, width=15)
        self.input3.bind("<FocusIn>", lambda e: self.active_input.set(self.input3))
        self.input1.pack()
        self.input3.pack()
        self.input2.pack()

class Keyboard(tk.Toplevel):

    OPENED = False
    NAME = "- Keyboard -"
    NUM = [{"text":"1", "width":1},
           {"text":"2", "width":1},
           {"text":"3", "width":2}]
    CHAR= [{"text":"A", "width":1},
           {"text":"B", "width":1},
           {"text":"C", "width":2}]

    def __init__(self, master):
        if not Keyboard.OPENED:
            Keyboard.OPENED = True
            print("keyboard opened!")
            self.master = master
            tk.Toplevel.__init__(self, master)
            self.title(self.NAME)
            self.protocol("WM_DELETE_WINDOW", self.close)

            self.keyb_nb = ttk.Notebook(self)
            self.keyb_nb.pack()

            self.num_tab = ttk.Frame(self.keyb_nb)
            self.createPad(self.num_tab, Keyboard.NUM,2)
            self.keyb_nb.add(self.num_tab, text="123")

            self.char_tab = ttk.Frame(self.keyb_nb)
            self.createPad(self.char_tab, Keyboard.CHAR, 2)
            self.keyb_nb.add(self.char_tab, text="ABC")

    def createPad(self, master, pad:list, max_col):
        self.co_count = 0
        self.ro = 1
        for button in pad:
            button["id"] = ttk.Button(master, width=6*button["width"], text=button["text"], command=self.bclicked(button))
            if self.co_count >= max_col:
                self.ro = self.ro + 1
                self.co_count = 0
            button["id"].grid(row=self.ro, columnspan=button["width"], column=self.co_count)
            self.co_count = self.co_count+button["width"]

    def bclicked(self, button:dict):
        """
        reciver = self.master.active_input #I think the Problem here is, that  the variable contains a string, not a widget
        reciever.focus_force()
        reciever.insert(index=tk.INSERT, string=button["text"])
        """
        pass

    def close(self):
        Keyboard.OPENED = False
        self.destroy()
        print("keyboard closed!")


root = MainWindow()
root.mainloop()

这里 Mainwindow 的 init 和 Keyboard 类的 bclicked 很重要...

代码已准备好调试

我希望有一个解决方案,类似于互联网中的通信(发送者=按钮、接收者 ID、消息),但非常欢迎每个可行的解决方案

顺便说一句:我也在寻找一个解决方案,我如何不必强制输入聚焦并且顶层保持在屏幕的最高层(如果我聚焦 Tk-Widget/其中一个输入/按钮,键盘将留在它的前面)

摘要:当键盘顶层已经获得焦点时,我如何找出 3 个输入小部件中的哪一个最后处于活动状态?

【问题讨论】:

标签: python tkinter communication


【解决方案1】:

我可能做了比需要更多的更改,但主要关注方法keyboard_triger()pass_key_to_master(),这两个使用变量master实现的思想,可以访问超出范围的调用方法。 另外,set_focused_object() 方法存储了对最后一个被聚焦的对象的引用,请注意,它存储的是小部件而不是事件,这比每次搜索对象都容易

import tkinter as tk
from tkinter import ttk

class MainWindow(tk.Tk):

    def keyboard_triger(self, key):
        # to identify wath object is just use
        # isinstance(self.active_input, ttk.Entry)
        self.active_input.insert(tk.END, key)

    def new_keyboard(self):
        Keyboard(self)

    def set_focused_object(self, event):
        self.active_input = event.widget

    def __init__(self):
        tk.Tk.__init__(self)
        self.active_input = None

        ttk.Button(self, text="Show Keyboard", command=self.new_keyboard).pack()

        self.input1 = ttk.Entry(self)
        self.input1.bind("<FocusIn>", self.set_focused_object)
        self.input1.pack()

        self.input2 = ttk.Entry(self)
        self.input2.bind("<FocusIn>", self.set_focused_object)
        self.input2.pack()

        self.input3 = tk.Text(self, height=3, width=15)
        self.input3.bind("<FocusIn>", self.set_focused_object)
        self.input3.pack()

class Keyboard(tk.Toplevel):

    def pass_key_to_master(self, key):
        self.master.keyboard_triger(key)

    def __init__(self, master):
        tk.Toplevel.__init__(self, master)

        self.master = master
        self.title('Keyboard')

        # this way of agruping keys stores the kwags
        # of the drawing method
        keys = {
            'A': {'x': 0, 'y': 0},
            'B': {'x': 20, 'y': 20},
            'C': {'x': 50, 'y': 50}
        }

        # expected structure
        # {string key: reference to the button}
        self.buttons = {}
        for i in keys:
            self.buttons[i] = tk.Button(  # i=i is required to make a instance
                self, text=i, command=lambda i=i: self.pass_key_to_master(i)
            )
            self.buttons[i].place(**keys[i])


if __name__ == '__main__':
    root = MainWindow()
    root.mainloop()

【讨论】:

    【解决方案2】:

    你的代码可能有更好的结构。(但我没有修改你的代码结构。)

    在你的代码之后,我使用了一个全局变量。并修复了你代码中的一些错误。它可以在我的电脑上正常工作。

    import tkinter as tk
    from tkinter import ttk
    
    class MainWindow(tk.Tk):
        def __init__(self, *args, **kwargs):
            tk.Tk.__init__(self, *args, **kwargs)
            self.active_input = tk.Variable(value=None)
            ttk.Button(self, text="show Keyboard", command=lambda: Keyboard(self)).pack()
            global focusedWidget
            focusedWidget = None
            self.text = tk.StringVar(value="")
            self.input1 = ttk.Entry(self)
            self.input1.bind("<FocusIn>", self.getFocusWidget)
            self.input2 = ttk.Entry(self)
            self.input2.bind("<FocusIn>", self.getFocusWidget)
            self.input3 = tk.Text(self, height=3, width=15)
            self.input3.bind("<FocusIn>", self.getFocusWidget)
            self.input1.pack()
            self.input3.pack()
            self.input2.pack()
    
        def getFocusWidget(self,event): # this function could be a static function
            global focusedWidget
            focusedWidget = event.widget
    
    class Keyboard(tk.Toplevel):
    
        OPENED = False
        NAME = "- Keyboard -"
        NUM = [{"text":"1", "width":1},
               {"text":"2", "width":1},
               {"text":"3", "width":2}]
        CHAR= [{"text":"A", "width":1},
               {"text":"B", "width":1},
               {"text":"C", "width":2}]
    
        def __init__(self, master):
            if not Keyboard.OPENED:
                Keyboard.OPENED = True
                print("keyboard opened!")
                self.master = master
                tk.Toplevel.__init__(self, master)
                self.title(self.NAME)
                self.protocol("WM_DELETE_WINDOW", self.close)
    
                self.keyb_nb = ttk.Notebook(self)
                self.keyb_nb.pack()
    
                self.num_tab = ttk.Frame(self.keyb_nb)
                self.createPad(self.num_tab, Keyboard.NUM,2)
                self.keyb_nb.add(self.num_tab, text="123")
    
                self.char_tab = ttk.Frame(self.keyb_nb)
                self.createPad(self.char_tab, Keyboard.CHAR, 2)
                self.keyb_nb.add(self.char_tab, text="ABC")
    
        def createPad(self, master, pad:list, max_col):
            self.co_count = 0
            self.ro = 1
            for button in pad:
                button["id"] = ttk.Button(master, width=6*button["width"], text=button["text"], command=lambda button=button:self.bclicked(button)) # this lambda expression has some errors.
                if self.co_count >= max_col:
                    self.ro = self.ro + 1
                    self.co_count = 0
                button["id"].grid(row=self.ro, columnspan=button["width"], column=self.co_count)
                self.co_count = self.co_count+button["width"]
    
        def bclicked(self, button:dict):
            global focusedWidget
    
            """
            reciver = self.master.active_input #I think the Problem here is, that  the variable contains a string, not a widget
            reciever.focus_force()
            reciever.insert(index=tk.INSERT, string=button["text"])
            """
            if not focusedWidget: # If user hasn't click a entry or text widget.
                print("Please select a entry or text")
                return
            if focusedWidget.widgetName=='ttk::entry': # use if statement to check the type of selected entry.
                focusedWidget.insert(index=tk.INSERT,string=button["text"])
            else:
                focusedWidget.insert("end",button["text"])
    
    
        def close(self):
            Keyboard.OPENED = False
            self.destroy()
            print("keyboard closed!")
    
    
    root = MainWindow()
    root.mainloop()
    

    【讨论】:

    • 谢谢。我发现了另一个错误:在按钮命令中使用 partial() 而不是 lambda 会更好。因此你必须从 functools 导入部分
    • @silvapuer 是的,这是您的邮政编码中的错误。使用 lambda 是可以的。您可以在official document 阅读它
    • 真的很棒!谢谢
    • 你认为没有全局变量可以解决这个问题吗?
    • @silvapuer 是的,alex 的回答没有使用全局变量。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-06
    • 1970-01-01
    • 1970-01-01
    • 2018-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多