用Toplevel在Tkinter制作一个弹出式键盘

时间:2014-04-06 13:52:06

标签: python tkinter

我有一个小模块,当Toplevel小部件获得焦点时会弹出Entry小部件。 Toplevel窗口是一个键盘,因此它需要按钮单击触发一个方法,该方法将该按钮单击插入Entry小部件。应该在两个条件下销毁Toplevel: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. 焦点移动到Entry小部件
  2. 创建了一个弹出式Toplevel(这是一个键盘)
  3. 当a)从实际键盘发生按键事件时,Toplevel被销毁,b)焦点从Entry小部件移出到另一个小部件或从GUI移开,c)主窗口被移动
  4. 如果焦点稍后回到Entry小部件
  5. ,则此过程是可重复的

2 个答案:

答案 0 :(得分:2)

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

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

fsm picture

对于实现,您可以选择现有的库,构建自己的框架,或者在时使用旧的嵌套。遵循我的快速和肮脏的实施。

调整订阅以创建状态并将事件重定向到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)

答案 1 :(得分:0)

这可能有助于您:Making a tkinter widget take focus

如果您决定改变焦点,那么您可以将其写入以实现这一目标。

我可能不完全理解你正在做的事情的范围,我相信它可能是奇怪的行为的原因是由于你对顶级的约束。

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

使用:

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

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

print "____ is triggered"

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