Tkinter unbinding

R

Roger

I've done a lot of googling for this topic and I fear that it's not
possible. I have a widget that is overloaded with several bindings.
I want to be able to unbind one method form the same Event without
destroying all the other bindings to the same event that's associated
to the same widget.

For example:

import Tkinter

def test():
print 'test'

def test2():
print 'test2'

root = Tkinter.Tk()
funcid1 = root.bind("<1>", lambda e: test())
funcid2 = root.bind("<1>", lambda e: test2(), add='+')
root.unbind("<1>", funcid2)
root.mainloop()

When run neither <1> binding will exist against the root because the
unbind will unbind all the functions associated with that event.
However, in this example, I only want to unbind test2 not test1.

Any help is greatly appreciated. Thanks!
Roger.
 
R

r

Yea, my answer was really not a helping answer(sorry) just showing
exactly why this will not work with w.unbind(). Why do you need two
separate functions to bind the same event?? You cannot combine the
two??
 
R

Roger

Yea, my answer was really not a helping answer(sorry) just showing
exactly why this will not work with w.unbind(). Why do you need two
separate functions to bind the same event?? You cannot combine the
two??

I can't combine the two in my app unfortunately. The binding is to a
custom widget that upon it being destroyed the binding is no longer
valid. I can work around this by being hacky but I prefer to delete
the one binding itself which would make things cleaner.
 
R

r

Maybe someone will chime in with an answer, sorry i could not help.
ponder this, i must...
 
B

Bad Mutha Hubbard

Roger said:
I've done a lot of googling for this topic and I fear that it's not
possible. I have a widget that is overloaded with several bindings.
I want to be able to unbind one method form the same Event without
destroying all the other bindings to the same event that's associated
to the same widget.

For example:

import Tkinter

def test():
print 'test'

def test2():
print 'test2'

root = Tkinter.Tk()
funcid1 = root.bind("<1>", lambda e: test())
funcid2 = root.bind("<1>", lambda e: test2(), add='+')
root.unbind("<1>", funcid2)
root.mainloop()

When run neither <1> binding will exist against the root because the
unbind will unbind all the functions associated with that event.
However, in this example, I only want to unbind test2 not test1.

Any help is greatly appreciated. Thanks!
Roger.

I believe you've discovered a bug. Aside from recommending trying
wxWidgets, here's the source of the unbind function in Tkinter.py:

def unbind(self, sequence, funcid=None):
"""Unbind for this widget for event SEQUENCE the
function identified with FUNCID."""
self.tk.call('bind', self._w, sequence, '')
if funcid:
self.deletecommand(funcid)

-------------------------------------------
First, it replaces all bindings for the sequence with the empty string,
i.e., it deletes all bindings for that event unconditionally. THEN it
calls deletecommand() with the funcid, who knows what that does. My Tcl
is not so sharp.
I have an idea for a workaround, let me see if it works...
-Chuckk
 
B

Bad Mutha Hubbard

Bad said:
I believe you've discovered a bug. Aside from recommending trying
wxWidgets, here's the source of the unbind function in Tkinter.py:

def unbind(self, sequence, funcid=None):
"""Unbind for this widget for event SEQUENCE the
function identified with FUNCID."""
self.tk.call('bind', self._w, sequence, '')
if funcid:
self.deletecommand(funcid)

-------------------------------------------
First, it replaces all bindings for the sequence with the empty string,
i.e., it deletes all bindings for that event unconditionally. THEN it
calls deletecommand() with the funcid, who knows what that does. My Tcl
is not so sharp.
I have an idea for a workaround, let me see if it works...
-Chuckk

Alas, my workaround doesn't work either. Tkinter.py also states that
calling bind with only an event sequence will return all bindings for
that sequence; I was thinking I could then remove the function in
question from that list and call bind again with each of the
functions in the remaning list as argument. I had
high hopes. The return value of calling bind with no target function
is just about Tcl nonsense:

#!/usr/bin/env python

import Tkinter

def test(event):
print 'test'

def test2(event):
print 'test2'

root = Tkinter.Tk()
funcid1 = root.bind("<1>", test)
funcid2 = root.bind("<1>", test2, add='+')
print funcid1, funcid2

bound = root.bind('<Button-1>')
print "bound:", bound
#root.unbind("<1>", funcid=funcid2)
root.mainloop()

---------------------------
Note that I took out the lambdas and gave event arguments to the
functions; if you did that on purpose, because you need to call the same
functions without events, then just ignore that...
SO, the other workaround, which I've used, is to bind the event to a
generic function, and have that generic function conditionally call
the functions you want. I'll go back and try to make an example from
your example. -Chuckk
 
R

Roger

Note that I took out the lambdas and gave event arguments to the
functions; if you did that on purpose, because you need to call the same
functions without events, then just ignore that...
SO, the other workaround, which I've used, is to bind the event to a
generic function, and have that generic function conditionally call
the functions you want. I'll go back and try to make an example from
your example. -Chuckk

Thanks Chuckk! You've done exactly what I did so far. I went through
the source in Tkinter.py and had an inkling of what the unbind
function was doing. I believe, and please correct me if I'm wrong,
in: self.tk.call('bind', self._w, sequence, '') , the '' is unbinding
all methods and I believe the self.deletecommand(funcid) is a
workaround for a memory leak otherwise. Perhaps self.tk.call('bind',
self._w, sequence, funcid) would work but that's a pure guess. I
would have liked to investigate the tcl source directly to see if I
could develop a workaround through a tk.call() but that was hitting a
wall in terms of any documentation I could research. I'm interested
in any workaround you may have however!

You now, I really considered going over to wxwidgets, and I definitely
want to try it after the current app I'm developing is complete, but
I've so much experience with Tkinter now after banging my head against
a wall for months (productive months mind you) on various projects,
it's like an old persnickety friend you just can't give up. =)

Thanks a ton!
Roger.
 
P

Peter Otten

Roger said:
Thanks Chuckk! You've done exactly what I did so far. I went through
the source in Tkinter.py and had an inkling of what the unbind
function was doing. I believe, and please correct me if I'm wrong,
in: self.tk.call('bind', self._w, sequence, '') , the '' is unbinding
all methods and I believe the self.deletecommand(funcid) is a
workaround for a memory leak otherwise. Perhaps self.tk.call('bind',
self._w, sequence, funcid) would work but that's a pure guess. I
would have liked to investigate the tcl source directly to see if I
could develop a workaround through a tk.call() but that was hitting a
wall in terms of any documentation I could research. I'm interested
in any workaround you may have however!

The documentation for bind in tcl is here

http://www.tcl.tk/man/tcl8.4/TkCmd/bind.htm

and as far as I understand it doesnt support unbinding selected callbacks,
either. I'd suggest a plain-python workaround along the lines of

import Tkinter

def test(event):
print 'test'

def test2(event):
print 'test2'

root = Tkinter.Tk()
root.geometry("200x100+100+100")

class Multiplexer:
def __init__(self):
self.funcs = []
def __call__(self, event):
for f in self.funcs:
f(event)
def add(self, f):
self.funcs.append(f)
return f
def remove(self, f):
self.funcs.remove(f)

m = Multiplexer()
m.add(test)
m.add(test2)
root.bind("<1>", m)

def unbind():
print "unbind"
m.remove(test2)

button = Tkinter.Button(root, text="unbind test2", command=unbind)
button.pack()

root.mainloop()

Peter
 
R

Roger

either. I'd suggest a plain-python workaround along the lines of

Wow. You just blew my mind. I'm going to play with this. Thanks a
lot, I've really learned a lot in just that small bit. I don't have
much experience in playing with a lot of the 'private' calls such as
__call__. I need to do some more reading.

Roger.
 
P

Peter Otten

Roger said:
Wow. You just blew my mind. I'm going to play with this. Thanks a
lot, I've really learned a lot in just that small bit. I don't have
much experience in playing with a lot of the 'private' calls such as
__call__. I need to do some more reading.

Here's a non-OO variant:

funcs = []
def call(event):
for f in list(funcs): # *
f(event)
root.bind("<1>", call)

funcs.append(test)
funcs.append(test2)
root.bind("<1>", call)

def unbind():
print "unbind"
funcs.remove(test2)

I does the same, but is a bit harder to manage if you have more than one
event/widget to deal with.

(*) iterating over a copy of the list of functions is slightly more robust
as it will not accidentally skip callbacks when the original list is
modified during iteration. I suggest that you change the OO version
accordingly.

Peter
 
Joined
Dec 4, 2011
Messages
1
Reaction score
0
my way to handle tcl nonsense is just to copy it...

Here is a workaround bypassing the bug in Tkinter and calling tk directly.

Code:
#!/usr/bin/env python                                                                                          

import Tkinter

def unbind(widget, sequence, funcid):
  '''unbind funcid callback for sequence on widget                                                             
                                                                                                               
  preserve other binding in case of multiple bindings.                                                         
  '''
  #construct a string containing existing binding minus funcid one                                             
  remain_bindings = "\n".join([e for e in widget.bind(sequence).split("\n") if e and e.find(funcid) == -1 ])
  widget.tk.call('bind', widget, sequence,remain_bindings)

def test(event):
  print 'test'

def test2(event):
  print 'test2'

root = Tkinter.Tk()
funcid1 = root.bind("<1>", test)
funcid2 = root.bind("<1>", test2, add='+')
print funcid1, funcid2

#replace                                                                                                       
#root.unbind("<1>", funcid=funcid2)                                                                            
#by                                                                                                            
unbind(root,"<1>", funcid=funcid2)

root.mainloop()
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top