Discussion:
[Tkinter-discuss] FocusOut not working as expected - tix broken?
mkieverpy
2012-10-07 18:53:21 UTC
Permalink
Hello Michael,

I'm back and tested a bit more.
I updated to standard packages (debian wheezy) python 3.2
tcl/tk 8.5 tix 8.4.3 to avoid any problems with who is using
who. I then expanded your test program to emulate my configuration
and what I want to implement. There I noticed that it's not
a tix problem at all, but standard tcl/tk focus behaves the same.
Here's the program:

-----------------------------------------------
import tkinter

root = tkinter.Tk()
f = tkinter.Frame(root)
f.pack(fill='both', expand=1)
tkinter.Button(f, text='foo').pack()

f2 = tkinter.Frame(root)
f2.pack(fill='both')
tkinter.Button(f2, text='bar').pack()

def on_key(event):
print('key')
def on_focus_out(event):
print('focus out')
f.focus_set()
f.focus_set()
f.bind('<Key>', on_key)
f.bind('<FocusOut>', on_focus_out)

root.mainloop()
-----------------------------------------------

The problem is when I use the 'Tab' key to move
the focus. The first 'Tab' moves the focus
to the 'foo' button. 'f' no longer gets key events
but does not get the focus out event either.
The next 'Tab' moves the focus to the 'bar' button,
'f' gets the focus out event and key events arrive again
after I do 'focus_set'.
So it looks like tk standard behaviour. Can I get what I want?
In my program it's a mouse click that can move
the focus away. So tweaking or disabling 'Tab' is not a solution.

Regards,
Matthias Kievernagel
mkieverpy
2012-10-08 11:09:30 UTC
Permalink
Hello Michael,

I found a workaround:

------------------------------------------------
import tkinter

root = tkinter.Tk()
f = tkinter.Frame(root)
f.pack(fill='both', expand=1)
tkinter.Button(f, text='foo').pack()

f2 = tkinter.Frame(root)
f2.pack(fill='both')
tkinter.Button(f2, text='bar').pack()

f3 = tkinter.Frame(root)
f3.pack(fill='both')

def on_key(event):
print('key')
def on_focus_out(event):
print('focus out')
f3.focus_set()
f3.focus_set()
f3.bind('<Key>', on_key)
f3.bind('<FocusOut>', on_focus_out)

root.mainloop()
------------------------------------------------

The other program does not look wrong though.
I think I'll ask at tcl/tk if they think it's a bug.

Regards,
Matthias Kievernagel
Michael Lange
2012-10-10 09:37:35 UTC
Permalink
Hi,

On Mon, 08 Oct 2012 11:09:30 -0000
mkieverpy at tlink.de wrote:

(...)
Post by mkieverpy
The other program does not look wrong though.
I think I'll ask at tcl/tk if they think it's a bug.
The problem in your first example is the following:

def on_focus_out(event):
print('focus out')
f.focus_set()
f.focus_set()
f.bind('<Key>', on_key)
f.bind('<FocusOut>', on_focus_out)

Now you force the focus when it leaves f immediately back to f - and this
is exactly what Tk does : when you hit "Tab" while the "foo" button is
focussed the <FocusOut> event for f is triggered and so the focus is
moved to f again (but *not* to the button and thus not visibly
highlighted). Then you hit "tab" again and the focus moves to the
"foo"-button again, but does not leave the parent-Frame f and so no
<FocusOut> event is created. I changed your example a little to help see
what's going on:

import tkinter

root = tkinter.Tk()
f = tkinter.Frame(root)
f.pack(fill='both', expand=1)
tkinter.Button(f, text='foo').pack()

f2 = tkinter.Frame(root)
f2.pack(fill='both')
tkinter.Button(f2, text='bar').pack()

def on_key(event):
print('key')
def on_focus_out(event):
print('focus out')
f.focus_set()
f.focus_set()
f.bind('<Key>', on_key)
f.bind('<FocusOut>', on_focus_out)

def on_focus_in(event):
print('focus in\n')
f.bind('<FocusIn>', on_focus_in)

root.mainloop()


On Sun, 07 Oct 2012 18:53:21 -0000
Post by mkieverpy
The problem is when I use the 'Tab' key to move
the focus. The first 'Tab' moves the focus
to the 'foo' button. 'f' no longer gets key events
but does not get the focus out event either.
The next 'Tab' moves the focus to the 'bar' button,
'f' gets the focus out event and key events arrive again
after I do 'focus_set'.
So it looks like tk standard behaviour. Can I get what I want?
With your example from the previous post the focus will never reach the
"bar" button, maybe it was a typo in on_focus_out() and should be
f2.focus.set() ?
Anyway, I am not sure what exactly you want to achieve (maybe I missed
something from your previous posts?), is there a particular reason why not
to do nothing about focus handling and just leave it to the WM?


Regards

Michael


.-.. .. ...- . .-.. --- -. --. .- -. -.. .--. .-. --- ... .--. . .-.

We have found all life forms in the galaxy are capable of superior
development.
-- Kirk, "The Gamesters of Triskelion", stardate 3211.7
mkieverpy
2012-10-11 16:20:10 UTC
Permalink
Hi,
Post by Michael Lange
(...)
Post by mkieverpy
The problem is when I use the 'Tab' key to move
the focus. The first 'Tab' moves the focus
to the 'foo' button. 'f' no longer gets key events
but does not get the focus out event either.
The next 'Tab' moves the focus to the 'bar' button,
'f' gets the focus out event and key events arrive again
after I do 'focus_set'.
So it looks like tk standard behaviour. Can I get what I want?
With your example from the previous post the focus will never reach the
"bar" button, maybe it was a typo in on_focus_out() and should be
f2.focus.set() ?
Anyway, I am not sure what exactly you want to achieve (maybe I missed
something from your previous posts?), is there a particular reason why not
to do nothing about focus handling and just leave it to the WM?
Sorry, to have been unclear here.
My main interest is capturing all keyboard input
with these lines:
------------------------------
def on_key(event):
print('key')

f.bind('<Key>', on_key)
------------------------------
I want to use this to implement keyboard control for some features.
In the case where 'f' has children which can get focus
I may loose keyboard input because 'f' won't get notified
about FocusOut (lines 2-4 in my above description).
In the workaround I use a frame without children.
In this case I always get the FocusOut event and
can reclaim focus with 'f.focus_set()'.

Regards,
Matthias Kievernagel
Michael Lange
2012-10-11 17:19:52 UTC
Permalink
Hi,

On Thu, 11 Oct 2012 18:20:10 +0200
Post by mkieverpy
My main interest is capturing all keyboard input
------------------------------
print('key')
f.bind('<Key>', on_key)
------------------------------
I want to use this to implement keyboard control for some features.
In the case where 'f' has children which can get focus
I may loose keyboard input because 'f' won't get notified
about FocusOut (lines 2-4 in my above description).
In the workaround I use a frame without children.
In this case I always get the FocusOut event and
can reclaim focus with 'f.focus_set()'.
I am not sure if I understand correctly what you are trying to achieve;
in one my programs there is a window with a couple of keyboard bindings
for this and that, there I simply put all the bindings into one method
like this:

def apply_default_bindings(self, widget):
for key in ('KP_Home', 'KP_End', 'KP_Insert', 'KP_Up', 'KP_Left',
'KP_Right', 'KP_Down', 'KP_Next', 'KP_Prior',
'KP_Begin', 'z'):
widget.bind('<Control-%s>' % key, self.zoom_in_by_key)
for key in ('KP_0', 'KP_1', 'KP_2', 'KP_3', 'KP_4', 'KP_5',
'KP_6', 'KP_7', 'KP_8', 'KP_9', 'Z'):
widget.bind('<Control-Shift-%s>' %key, self.zoom_out_by_key)
for key in ('Left', 'Right', 'Prior', 'Next', 'Home', 'End'):
widget.bind('<%s>' % key, self.scroll_canvas)
widget.bind('<Control-o>', self.openfile)
widget.bind('<Control-s>', self.stop)
widget.bind('<Control-p>', self.play)
widget.bind('<Control-i>', self.pause)
widget.bind('<Control-Down>', self.set_volume)
widget.bind('<Control-Up>', self.set_volume)
widget.bind('<Control-n>', self.setmark)
widget.bind('<Control-d>', self.clearmarks)
widget.bind('<Control-t>', self.see_next_mark)
widget.bind('<Control-Shift-T>', self.see_prev_mark)
widget.bind('<Control-c>', self.see_cursor)
widget.bind('<Control-Shift-C>', self.center_cursor)
widget.bind('<Control-Left>', self.move_cursor)
widget.bind('<Control-Right>', self.move_cursor)
widget.bind('<Control-Prior>', self.move_cursor)
widget.bind('<Control-Next>', self.move_cursor)

and call this once every widget in the window, which
costs a few extra lines, but otoh saves quite some headaches about which
widget is focussed.

Regards

Michael


.-.. .. ...- . .-.. --- -. --. .- -. -.. .--. .-. --- ... .--. . .-.

Leave bigotry in your quarters; there's no room for it on the bridge.
-- Kirk, "Balance of Terror", stardate 1709.2
Bryan Oakley
2012-10-11 17:39:35 UTC
Permalink
Post by Michael Lange
I am not sure if I understand correctly what you are trying to achieve;
in one my programs there is a window with a couple of keyboard bindings
for this and that, there I simply put all the bindings into one method
...
and call this once every widget in the window, which
costs a few extra lines, but otoh saves quite some headaches about which
widget is focussed.
That's more complicated than it needs to be. I'll give you the same
advice as I just gave someone else: are you aware of "bind_all" and
Tkinter's bindtags? That is a much cleaner way to implement global
bindings IMO. The advantage to "bind_all" is that individual widgets
can override the default bindings if they want (eg: if you want
control-o to mean something totally different only in one widget, you
can).
Michael Lange
2012-10-11 18:23:24 UTC
Permalink
On Thu, 11 Oct 2012 12:39:35 -0500
Post by Bryan Oakley
That's more complicated than it needs to be. I'll give you the same
advice as I just gave someone else: are you aware of "bind_all" and
Tkinter's bindtags? That is a much cleaner way to implement global
bindings IMO. The advantage to "bind_all" is that individual widgets
can override the default bindings if they want (eg: if you want
control-o to mean something totally different only in one widget, you
can).
It is not certain if we are talking about global bindings however, that
depends on the the OP's application. In the case of my example the
bindings just apply to the widgets inside one particular container, not
globally to all widgets. Then it comes in handy to simply do:

for widget in (button1, button2, button3, entry1, spinbox1, spinbox2):
apply_default_bindings(widget)

That's just three extra lines including the function definition to apply
quite a bunch of bindings to all relevant widgets, I don't think this is
so awfully complicated ;)

There is of course bind_all, as well as bind_class and bindtags or the
simple possibility of a custom widget class with extra default bindings.
Which one is the best of course depends on what exactly you want to
achieve and how the application window is designed.

I think global bindings are usually fine for simple window layouts, but
for more complex apps with several windows or several containers in one
window (e.g. in Tab-like interfaces), custom dialogs etc. you may have to
be careful to avoid unwanted effects.

Regards

Michael

.-.. .. ...- . .-.. --- -. --. .- -. -.. .--. .-. --- ... .--. . .-.

"Life and death are seldom logical."
"But attaining a desired goal always is."
-- McCoy and Spock, "The Galileo Seven", stardate 2821.7
Bryan Oakley
2012-10-11 17:35:23 UTC
Permalink
Post by mkieverpy
Sorry, to have been unclear here.
My main interest is capturing all keyboard input
------------------------------
print('key')
f.bind('<Key>', on_key)
There are better ways to capture all keyboard input if that's your
real goal. Are you aware of the "bind_all" method, which associates a
binding with all widgets? Typically this is used for shortcuts, but it
can be used any time you want to capture certain events no matter
where they occur.

Tkinter has the notion of "bind tags" (or bindtags). To this day, no
other GUI toolkit has as powerful of a mechanism for managing events;
this is truly one of Tkinter's hidden gems. The short version is, you
can associate a custom tag to any (or every) widget, and then bind to
that tag. Then, no matter what widget has focus, your binding will
fire. The advantage this has over "bind_all" is that with the bindtag
you can control when the event is processed -- before the actual
event, after the actual event, etc. With "bind_all", the "all"
bindings by default are processed after event and class level
bindings. The practical implication of that is that the widget or
class bindings have the option to prevent the event from propagating.
With bind tags you can guarantee your bindings fire before any others.



I
mkieverpy
2012-10-16 12:42:25 UTC
Permalink
Hello,

thanks for the new hints/information Michael and Bryan.
I don't know if your hints would work in my setup, though.
Except bind_all, which should work, I guess.

My program structure:
I have a Frame, which is nearly the complete toplevel window
(only a title label and quit button outside;
this is the Frame I originally tried to let capture all the keys).
The left half is taken by a tix Tree and the right half
contains one of a few editor classes (subclassed from Frame)
which get exchanged based on the selected Tree node.
One type of nodes opens a Canvas in the right half
where I want the key shortcuts to work.

The problem I see with both your hints (bind keys to all widgets,
tag widgets and bind keys to tag) is I doubt they will work with tix Tree.
I don't think tix Tree hands down any bind or tag to
its subwidgets (or does it?).

Thanks again,
Matthias Kievernagel
Michael Lange
2012-10-16 18:03:40 UTC
Permalink
Hi,

On Tue, 16 Oct 2012 12:42:25 -0000
Post by mkieverpy
The problem I see with both your hints (bind keys to all widgets,
tag widgets and bind keys to tag) is I doubt they will work with tix
Tree. I don't think tix Tree hands down any bind or tag to
its subwidgets (or does it?).
Why not simply try it:


import tkinter
from tkinter import tix

root = tix.Tk()

t = tix.Tree(root)
t.pack(side='left')
l = tkinter.Listbox(root)
l.pack(side='right')

def on_key(event):
print(str(event.widget) + ' received key event: ' + event.keysym)

root.bind_all('<Any-Key>', on_key, add=True)

root.mainloop()

Here this works as expected. You can even use a DirTree instead of a Tree
and you will see that keyboard navigation through the tree still works
fine.

Regards

Michael


.-.. .. ...- . .-.. --- -. --. .- -. -.. .--. .-. --- ... .--. . .-.

Vulcans do not approve of violence.
-- Spock, "Journey to Babel", stardate 3842.4

Loading...