首页 文章

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

提问于
浏览
2

我有一个小模块,当 Entry 小部件获得焦点时会弹出一个 Toplevel 小部件 . 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小部件,或者我是从错误的方式处理这个问题,还是什么?任何帮助表示赞赏 .

EDIT :我发现了一种似乎有效的创可贴式解决方案,但我觉得还没有发现's still a better way to handle this that I haven' .

这是我添加到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

我正在添加赏金,因为我想看看是否还有另一种更清洁的方法 . 我想要的步骤是:

  • 焦点移动到Entry小部件

  • 创建了一个弹出式Toplevel(这是一个键盘)

  • 当a)从实际键盘发生按键事件时,Toplevel被破坏,b)焦点移出Entry小部件到另一个小部件或GUI外,c)主窗口被移动

  • 如果焦点稍后移回Entry小部件,则此过程是可重复的

2 回答

  • 0

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

    首先设计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)
    
  • 2

    这可能会帮到你:Making a tkinter widget take focus

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

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

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

    使用:

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

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

    print "____ is triggered"
    

    对每种方法来说,当你切换焦点时,看看究竟发生了什么,可能有助于确定发生了什么 .

相关问题