使用 Toplevel 在 Tkinter 中制作弹出式键盘 [英] Making a pop-up keyboard in Tkinter with Toplevel

查看:56
本文介绍了使用 Toplevel 在 Tkinter 中制作弹出式键盘的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a small module that pops up a Toplevel widget when an Entry widget gains focus. The Toplevel window is a keyboard, so it expects button clicks that trigger a method which inserts that button click into the Entry widget. The Toplevel should be destroyed on 2 conditions: 1) the user presses a key on their actual keyboard, 2) the parent of the Entry widget is moved or resized.

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

Everything works, except for one bug: if the user clicks on the Toplevel, it becomes active, and if one of the destroy events occur, I get unintended results (like the popup coming back when the Entry gets focus again).

我的想法是,如果我能让 Entry 在整个过程中保持专注,一切都会好起来的,但我还没有找到实现这一点的方法.

My thought is that if I can make the Entry retain focus throughout the process, everything will work, but I haven't found a way to make that happen.

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

Here's an example, it's about as stripped down as I can make it while retaining the structure of the module. Note: 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 小部件,或者我是从错误的方式解决这个问题,还是什么?任何帮助表示赞赏.

So, is there some kind of never_get_focus() method I haven't found that I can apply to the Toplevel widget, or am I approaching this problem from the wrong way, or what? Any help is appreciated.

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

EDIT: I found a band-aid type solution that seems to work, but I feel like there's still a better way to handle this that I haven't found yet.

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

Here's what I've added to the Frame subclass popup methods:

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

我添加赏金是因为我想看看是否有另一种更简洁的方法来做到这一点.我想要的步骤是:

I'm adding a bounty because I want to see if there's another, cleaner way of doing this. The steps I want are:

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

推荐答案

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

You can use a state machine to handle the behavior you describe. State machines are quite common to implement behaviors in graphical user interfaces. Here is a brief example to give you an idea of what it might look like.

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

First design the fsm, here is a simple one that perform almost what you want (skip the configure part for the sake of brevity).

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

For the implementation, you might pick an existing library, build your own framework, or go for a good old nested if . Following my quick and dirty implementation.

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

Adapt the subscription to create state and redirect event to 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.

Select the combination of state / event that you want to address and put appropriate actions. You might discover that you get trapped in a certain state and adapt your FSM accordingly.

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)

这篇关于使用 Toplevel 在 Tkinter 中制作弹出式键盘的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆