python - 使用 Toplevel 在 Tkinter 中制作弹出键盘

标签 python tkinter

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

一切正常,除了一个错误:如果用户单击顶层,它就会变为事件状态,如果发生其中一个销毁事件,我会得到意想不到的结果(例如当条目再次获得焦点时弹出窗口会返回)。

我的想法是,如果我能让 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() 方法,或者我是否以错误的方式处理这个问题,或者什么?如有任何帮助,我们将不胜感激。

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

以下是我添加到 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. 焦点移至 Entry 小部件
  2. 创建了一个弹出顶层(这是一个键盘)
  3. 当 a) 从实际键盘发生按键事件,b) 焦点从 Entry 小部件移至另一个小部件或离开 GUI,c) 主窗口移动时,顶层将被销毁
  4. 如果焦点稍后移回 Entry 小部件,此过程是可重复的

最佳答案

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

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

fsm picture

对于实现,您可以选择一个现有的库,构建您自己的框架,或者使用一个好的旧嵌套 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)

关于python - 使用 Toplevel 在 Tkinter 中制作弹出键盘,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22894937/

相关文章:

python - 如何在 Windows 7 上安装适用于 python 2.7 的 tkinter 或 Tcl?

python - 如何摆脱小部件边框?

python - 在 Tkinter 中使用多个框架没有给出预期的结果

python - 使用 virtualenv 创建的 python 实例的外部应用程序? ( Windows )

python - AttributeError - 模块 'django.http.request' 没有属性 'META'

python - 如何从 PySpark Dataframe 中删除重复项并将剩余列值更改为 null

python - 如何让 Treeview 列适合其所在的框架

python - 制作聊天客户端窗口

python - 使用 pytorch 进行多类句子分类(使用 nn.LSTM)

python - 使用 argparse 和 python 接受字典作为参数