【问题标题】:Making a pop-up keyboard in Tkinter with Toplevel使用 Toplevel 在 Tkinter 中制作弹出式键盘
【发布时间】:2014-04-06 13:52:06
【问题描述】:

我有一个小模块,当 Entry 小部件获得焦点时,它会弹出一个 Toplevel 小部件。 Toplevel 窗口是一个键盘,因此它期望按钮单击会触发一个方法,该方法将该按钮单击插入到 Entry 小部件中。 Toplevel 应该在 2 个条件下被销毁:1)用户在他们的实际键盘上按下一个键,2)Entry 小部件的父级被移动或调整大小。

一切正常,除了一个错误:如果用户单击 Toplevel,它会变为活动状态,如果发生破坏事件之一,我会得到意想不到的结果(例如当条目再次获得焦点时弹出窗口又回来了)。

我的想法是,如果我能让 Entry 在整个过程中保持焦点,一切都会正常进行,但我还没有找到实现这一目标的方法。

这是一个示例,它在保留模块结构的同时尽可能精简。注意:Python 2.7

from Tkinter import *

class Top(Toplevel):
    def __init__(self, attach):
        Toplevel.__init__(self)
        self.attach = attach
        Button(self, text='Button A', command=self.callback).pack()

        self.bind('<Key>', self.destroyPopup)

    def callback(self):
        self.attach.insert(END, 'A')

    def destroyPopup(self, event):
        self.destroy()

class EntryBox(Frame):
    def __init__(self, parent, *args, **kwargs):
        Frame.__init__(self, parent)
        self.parent = parent

        self.entry = Entry(self, *args, **kwargs)
        self.entry.pack()

        self.entry.bind('<FocusIn>', self.createPopup)
        self.entry.bind('<Key>', self.destroyPopup)
        self.parent.bind('<Configure>', self.destroyPopup)

    def createPopup(self, event):
        self.top = Top(self.entry)

    def destroyPopup(self, event):
        try:
            self.top.destroy()
        except AttributeError:
            pass

root = Tk()

e1 = EntryBox(root).pack()

root.mainloop()

那么,是否有某种never_get_focus() 方法我还没有发现我可以应用于Toplevel 小部件,或者我是从错误的方式解决这个问题,还是什么?任何帮助表示赞赏。

编辑:我找到了一种似乎有效的创可贴式解决方案,但我觉得还有更好的方法来处理这个我还没有找到。

这是我添加到 Frame 子类弹出方法中的内容:

def createPopup(self, event):
    try:                           #When focus moves in to the entry widget,
        self.top.destroy()         #try to destroy the Toplevel
        del self.top               #and remove the reference to it
    except AttributeError:         #unless it doesn't exist,
        self.top = Top(self.entry) #then, create it

def destroyPopup(self, event):
    try:
        self.top.destroy()
        del self.top
    except AttributeError:
        pass

我正在添加赏金,因为我想看看是否有另一种更清洁的方式来做到这一点。我想要的步骤是:

  1. 焦点移至条目小部件
  2. 创建了一个弹出 Toplevel(这是一个键盘)
  3. 当 a) 从实际键盘发生按键事件,b) 焦点从 Entry 小部件移到另一个小部件或离开 GUI,c) 移动主窗口时,Toplevel 被破坏
  4. 如果焦点稍后移回 Entry 小部件,则此过程是可重复的

【问题讨论】:

    标签: python tkinter


    【解决方案1】:

    您可以使用状态机来处理您描述的行为。 State machines 在图形用户界面中实现行为非常常见。这是一个简短的示例,可让您了解它的外观。

    首先设计 fsm,这是一个简单的,几乎可以执行您想要的(为简洁起见跳过配置部分)。

    对于实现,你可以选择一个现有的库,构建你自己的框架,或者选择一个好的旧的 nested if 。遵循我快速而肮脏的实施。

    调整订阅以创建状态并将事件重定向到 fsm:

        self.state = "idle"
        self.entry.bind('<FocusIn>', lambda e:self.fsm("focus_entry"))
        self.entry.bind('<FocusOut>', lambda e:self.fsm("focus_out_entry"))
        self.entry.bind('<Key>', lambda e:self.fsm("key_entry"))
        self.parent.bind('<Configure>', lambda e:self.fsm("configure_parent"))
    

    选择您想要处理的状态/事件组合并采取适当的行动。您可能会发现自己陷入某种状态并相应地调整您的 FSM。

    def fsm(self, event):
        old_state = self.state #only for logging
        if self.state == "idle":
            if event == "focus_entry":
                self.createPopup()
                self.state = "virtualkeyboard"
        elif self.state == "virtualkeyboard":
            if event == "focus_entry":
                self.destroyPopup()
                self.state = "typing"
            if event == "focus_out_entry":
                self.destroyPopup()
                self.state = "idle"
            elif event == "key_entry":
                self.destroyPopup()
                self.state = "typing"
        elif self.state == "typing":
            if event == "focus_out_entry":
                self.state = "idle"
        print "{} --{}--> {}".format(old_state, event, self.state)
    

    【讨论】:

    • 非常酷,感谢您对 FSM 的介绍。这行得通;不得不稍微调整一下,但感觉比我之前工作过的所有 try>excepts 都要好得多。谢谢!
    【解决方案2】:

    这可能会对您有所帮助:Making a tkinter widget take focus

    如果您决定只改变焦点,那么您可以将其写入来做到这一点。

    我可能不完全理解你所做的事情的范围,我相信它可能表现得很奇怪的原因是你在顶层的绑定。

    def destroyPopup(self, event):
        self.destroy()
    

    合作:

    self.entry.bind('<FocusIn>', self.createPopup)
    

    不确定这是否有帮助,但可能会考虑添加一些:

    print "____ is triggered"
    

    查看每个方法在您切换焦点时究竟发生了什么,可能有助于确定发生了什么。

    【讨论】:

    • 我可能措辞错误,但我明白为什么会这样。问题来自焦点移回 Entry 小部件,它创建了一个我不想要的新 Toplevel(在创建第一个之后)。我可能以错误的方式接近它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-09
    • 2014-12-01
    相关资源
    最近更新 更多