python - Making a pop-up keyboard in Tkinter with Toplevel -
i have small module pops toplevel
widget when entry
widget gains focus. toplevel window keyboard, expects button clicks trigger method inserts button click entry widget. toplevel should destroyed on 2 conditions: 1) user presses key on actual keyboard, 2) parent of entry widget moved or resized.
everything works, except 1 bug: if user clicks on toplevel, becomes active, , if 1 of destroy events occur, unintended results (like popup coming when entry gets focus again).
my thought if can make entry retain focus throughout process, work, haven't found way make happen.
here's example, it's stripped down can make while retaining structure of 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()
so, there kind of never_get_focus()
method haven't found can apply toplevel widget, or approaching problem wrong way, or what? appreciated.
edit: found band-aid type solution seems work, feel there's still better way handle haven't found yet.
here's i've added frame subclass popup methods:
def createpopup(self, event): try: #when focus moves in entry widget, self.top.destroy() #try destroy toplevel del self.top #and remove reference except attributeerror: #unless doesn't exist, self.top = top(self.entry) #then, create def destroypopup(self, event): try: self.top.destroy() del self.top except attributeerror: pass
i'm adding bounty because want see if there's another, cleaner way of doing this. steps want are:
- focus moves entry widget
- a popup toplevel created (this keyboard)
- the toplevel destroyed when a) key press event occurs actual keyboard, b) focus moves out of entry widget widget or off gui, c) main window moved
- this process repeatable if focus moves entry widget later
you can use state machine handle behavior describe. state machines quite common implement behaviors in graphical user interfaces. here brief example give idea of might like.
first design fsm, here simple 1 perform want (skip configure part sake of brevity).
for implementation, might pick existing library, build own framework, or go old nested if . following quick , dirty implementation.
adapt subscription create state , redirect event 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"))
select combination of state / event want address , put appropriate actions. might discover trapped in state , adapt fsm accordingly.
def fsm(self, event): old_state = self.state #only 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)
Comments
Post a Comment