How to do mouse hovering for group of buttons in python - python-2.7

I am just trying a mouse hovering function for a group of 5 buttons, mouse hovering color
setting is running fine with one button sample but it is not giving proper result
when I tried for multiple buttons, it is affecting only last binded button, can anyone
help me quick on what is wrong with my below code, I tried with bind and bind_all methods!
both are failing to give desired output.
import os
from Tkinter import *
import Tkinter as tk
global btnPlaceY
btnPlaceY=10
class App():
def __init__ (self):
root['width']=400
root['height']=600
root['bg']='blue'
self.btnGroupCreate()
def btnDisplaymessage1(self):
print "Function testing1"
def btnDisplaymessage2(self):
print "Function testing2"
def btnGroupCreate(self):
btnNameNameGroup="btnSample"
self.btnWidget={}
lstBtnTitle=['A','B','C','D','E']
lstBtnCommands=[ lambda:self.btnDisplaymessage1(),
lambda:self.btnDisplaymessage2() ]
for B in range(5):
btnName=btnNameNameGroup+lstBtnTitle[B]
global btnPlaceY
btnPlaceY=btnPlaceY+70
# Button commands
if B==0:
btnMenuCommand=lstBtnCommands[0]
if B==1:
btnMenuCommand=lstBtnCommands[1]
if B==2:
btnMenuCommand=lstBtnCommands[0]
if B==3:
btnMenuCommand=lstBtnCommands[1]
if B==4:
btnMenuCommand=lstBtnCommands[1]
if B==5:
btnMenuCommand=lstBtnCommands[0]
self.btnWidget[btnName]= tk.Button(root, width=4, height=2,text=lstBtnTitle[B],bg="gray",relief=FLAT,command=btnMenuCommand) #activebackground="red"
widget = self.btnWidget[btnName]
widget.bind_all("<Enter>", lambda event: event.widget.configure(bg="red"))
widget.bind_all("<Leave>", lambda event: event.widget.configure(bg="gray"))
self.btnWidget[btnName].pack()
self.btnWidget[btnName].place(x=40, y = btnPlaceY)
def make_callback(self,btnName):
print "make_callback"
root['bg']='green'
widget = self.btnWidget[btnName]
def callback(event):
print "callback"
widget.configure(bg="red")
root['bg']='green'
return callback
root=tk.Tk()
app = App()
root.mainloop()

The problem is that in Python the closures such as your lambda function, close over the names in the surrounding block, not the values. As such, the value of btnName as observed in the lambda depends on when it is executed - as the events can occur only after the loop is exited, the btnName is the last one seen.
There are many ways to avoid this problem, one being the Kevin's default value trick. However, the default value approach is prone to errors - now the function accepts 2 arguments instead of 1 and can be called erroneously in some context.
Thus a better approach is to use a callback maker:
def make_callback(btnName):
def callback(event):
self.btnWidget[btnName].configure(bg="red"))
return callback
self.btnWidget[btnName].bind_all("<Enter>", make_callback(btnName))
However, one notices that the btnWidget is repeated, so it is possible to do this into
widget = self.btnWidget[btnName]
def make_callback(btnName):
def callback(event):
widget.configure(bg="red")
return callback
widget.bind_all("<Enter>", make_callback(widget))
Which could be written with lambdas as:
widget = self.btnWidget[btnName]
widget.bind_all("<Enter>",
(lambda widget: lambda event: widget.configure(bg="red"))(widget))
However, in this case none of this trickery is necessary as the only thing needed in callback is the widget on which the event occurs. And that is readily available in the TkInter events in the attribute widget, so really all you need to do is
widget = self.btnWidget[btnName]
widget.bind_all("<Enter>", lambda event: event.widget.configure(bg="red"))
widget.bind_all("<Leave>", lambda event: event.widget.configure(bg="yellow"))

self.btnWidget[btnName].bind_all("<Enter>", lambda event: self.btnWidget[btnName].configure(bg="red"))
self.btnWidget[btnName].bind_all("<Leave>", lambda event: self.btnWidget[btnName].configure(bg="yellow"))
Inside the lambda expressions on each of these lines, btnName will have the last value that it held during the loop, rather than the value it had when you bound the event. It will be "btnSampleE", even for buttons A through D. Additionally, you should be using bind instead of bind_all for widget-specific events.
The typical way to force "early binding" behavior, so that btnName keeps its current value, is to supply a default argument to the lambda, containing the value you need.
self.btnWidget[btnName].bind("<Enter>", lambda event, x = btnName: self.btnWidget[x].configure(bg="red"))
self.btnWidget[btnName].bind("<Leave>", lambda event, x = btnName: self.btnWidget[x].configure(bg="yellow"))
You can use whatever variable name you want for this argument; you could even call it btnName, as the local value will overshadow the nonlocal one.
self.btnWidget[btnName].bind("<Enter>", lambda event, btnName=btnName: self.btnWidget[btnName].configure(bg="red"))
self.btnWidget[btnName].bind("<Leave>", lambda event, btnName=btnName: self.btnWidget[btnName].configure(bg="yellow"))

Related

Blocking the default listbox scrolling in Python running on Windows

My environment is Python 2.7, running on Windows 7.
When I create a Tkinter window with two scrolling listboxes, the default behavior is that Windows will direct the mousewheel action to whichever of them has the focus. Each increment of the mousewheel moves 4 lines up/down. When the focus leaves the listbox, it stops responding to the mousewheel.
Ultimately I'm looking to completely replace this behavior with my own bindings. I've written the code for that and it yields the new custom response I want, but the problem is that the default behavior still occurs as well, based on the listbox that has the focus. I can't seem to block it no matter what I try.
Here's a simplified code sample to illustrate. I'm expecting that my binding to the <FocusIn> event (with its return 'break' line) would prevent the default mousewheel response, but still occurs. I tried intercepting the <<ListboxSelect>> event as well, but that doesn't block it either. It doesn't even help when I do a bind_all on the <Mousewheel> event; somehow the default response still gets triggered.
import Tkinter as tk
#Root window
root = tk.Tk()
#Widgets
ctrl1 = tk.StringVar()
vsb1 = tk.Scrollbar(root,
orient=tk.VERTICAL)
lst1 = tk.Listbox(root,
width=20,height=10,
listvariable=ctrl1,
activestyle='dotbox',
yscrollcommand=vsb1.set)
vsb1.config(command=lst1.yview)
ctrl2 = tk.StringVar()
vsb2 = tk.Scrollbar(root,
orient=tk.VERTICAL)
lst2 = tk.Listbox(root,
width=20,height=10,
listvariable=ctrl2,
activestyle='dotbox',
yscrollcommand=vsb2.set)
vsb2.config(command=lst2.yview)
#Geometry
lst1.grid(row=0,column=0,sticky=tk.NSEW,padx=(5,0),pady=5)
vsb1.grid(row=0,column=1,sticky=tk.NS,padx=(0,5),pady=5)
lst2.grid(row=0,column=2,sticky=tk.NSEW,padx=(15,0),pady=5)
vsb2.grid(row=0,column=3,sticky=tk.NS,padx=(0,5),pady=5)
#Bindings
def focusIn1(*args):
print 'Focus in 1'
return 'break'
def focusIn2(*args):
print 'Focus in 2'
return 'break'
lst1.bind('<FocusIn>',focusIn1)
lst2.bind('<FocusIn>',focusIn2)
#Dummy listbox content
ctrl1.set('Entry-index-00 Entry-index-01 Entry-index-02 Entry-index-03 '+ \
'Entry-index-04 Entry-index-05 Entry-index-06 Entry-index-07 '+ \
'Entry-index-08 Entry-index-09 Entry-index-10 Entry-index-11 '+ \
'Entry-index-12 Entry-index-13 Entry-index-14 Entry-index-15 '+ \
'Entry-index-16 Entry-index-17 Entry-index-18 Entry-index-19 <end-of-queue>')
ctrl2.set('Entry-index-00 Entry-index-01 Entry-index-02 Entry-index-03 '+ \
'Entry-index-04 Entry-index-05 Entry-index-06 Entry-index-07 '+ \
'Entry-index-08 Entry-index-09 Entry-index-10 Entry-index-11 '+ \
'Entry-index-12 Entry-index-13 Entry-index-14 Entry-index-15 '+ \
'Entry-index-16 Entry-index-17 Entry-index-18 Entry-index-19 <end-of-queue>')
#Begin app
tk.mainloop()
Is there some other event I need to intercept instead? Or is there maybe some different approach I need to use to block the default mousewheel response?
(Just in case it matters... the custom behavior I'm aiming for is to have single-line scrolling of one listbox or the other independently, based solely on whether the mouse pointer is within that listbox. Because of the described issue, if (say) listbox #1 has the focus and the pointer is over listbox #2, they gang-scroll in response to the mousewheel, with #2 scrolling one line at a time while #1 scrolls 4 lines at a time. If I switch and point to #1, it scrolls by itself but it still scrolls 4 lines at a time instead of 1.)
EDIT: Binding that references the <MouseWheel> event, but still fails to block the default scrolling behavior. I put this in place of the <FocusIn> events.
#Bindings
def scrollBlock(*args):
print "Blocking scrolling (except it won't)"
return "break"
root.bind_all('<MouseWheel>',scrollBlock)

Correct parameter to pass a bound event to treeview.identify/identify_region in Tkinter?

I have a couple of treeviews tkinter widgets. For style purposes i need to prevent a manual resizing of the treeview columns width without totally disableing column resize.
Using a possible solution provided here: How to disable manual resizing of Tkinter's Treeview column? i am catching single click events on separators between the columns and stop the event there. For this purpose every treeview binds a handling function.
Example:
self.tree.bind('<Button-1>', lambda event: handle_treeview_single_click(self.tree, event))
def handle_treeview_single_click(tree, event):
if tree.identify_region(event.x, event.y) == 'separator':
return 'break'
Running the code produce the following error:
File "C:\Program Files (x86)\Python27\ArcGIS10.2\lib\lib-tk\ttk.py", line 1277, in identify
return self.tk.call(self._w, "identify", component, x, y)
TclError: bad component "region": must be row or column
this doesn't change regardless the object i click on (Header, cell or separator). For example the relating parameter look like this:
tree = Treeview: .42424440.47830640.47831440.47831800.47831840
x = 464
y = 14
Not sure what im missing here, even more since im fairly new to python.
There is a comment in the ttk source code that explains why this might not be working:
def identify_region(self, x, y):
"""
...
* Availability: Tk 8.6"""
return self.identify("region", x, y)
Since you're using python 2.7, you probably aren't using tk 8.6. You can verify this by printing out the value of Tkinter.TkVersion

Qt. QSplitter. Handle double click when cursor is under splitter

I need to handle double click for QSplitter when cursor is under splitter.
I redefined mouseDoubleClickEvent. But this does not work for this case.
When I do doulble click when cursor is under splitter (ready to move splitter) the method is not calling.
You can use an event filter to filter all events going to the handle of Qsplitter :
bool MyClass::eventFilter(QObject * obj, QEvent * event)
{
if(event->type()==QEvent::MouseButtonDblClick)
{
...
}
return false;
}
Also don't forget to install event filter in the constructor of your class :
MyClass::MyClass(QWidget *parent):QWidget(parent)
{
...
ui->splitter->handle(1)->installEventFilter(this);
...
}
I needed the same in order to be able to evenly space the widgets in the splitter when the user double clicks on the handle (this was my use case). Overriding QSplitter.mouseDoubleClickEvent() does not work because it seems that the handle consumes the double click event itself so it is not propagated to the parent QSplitter. The solution proposed in the accepted answer using eventFilter is quite good but is has the disadvantage that it is not 'dynamic', i.e. the event filter is not installed when the user adds new widgets to the splitter during runtime. So we need to find a way to install the event filter dynamically. There are two options to achieve this:
Override QSplitter.addWidget() and QSplitter.insertWidget():
# inside class MySplitter
def addWidget(self, widget):
super(MySplitter, self).addWidget(widget) # call the base class
self.handle(self.count() - 1).installEventFilter(self)
def insertWidget(self, index, widget):
super(MySplitter, self).insertWidget(index, widget) # call the base class
self.handle(index).installEventFilter(self)
but this is a bit problematic when the user adds widgets by not using these two methods but by setting parent to the child widget, though this is discouraged by the docs - see: http://doc.qt.io/qt-5/qsplitter.html#childEvent
Intercept the childEvent(), which feels a bit hacky but is error proof:
# inside class MySplitter
def childEvent(self, event):
if event.added():
# Note that we cannot test isinstance(event.child(), QSplitterHandle)
# because at this moment the child is of type QWidget,
# it is not fully constructed yet.
# So we assume (hacky but it works) that the splitter
# handle is added always as the second child after
# the true child widget.
if len(self.children()) % 2 == 0:
event.child().installEventFilter(self)
super(MySplitter, self).childEvent(event) # call the base class
I am using b) and it works for me quite well. This has the advantage that you do not need to subclass (I do, however, for teh sake of simplicity), you can install another event filter to intercept the childEvent and install the event filter from outside.
Sorry my code is in PyQt, but I think it is idiomatic enough and easily translated to C++.

Why does gtk.main_quit() not work?

I'm new here. :)
I am having trouble emigrating from GTK+ in C to PyGTK, specifically I do not understand why (in this specific context of a pause/play button) gtk.main_quit() does nothing even though it is called.
In my working example, after you press "Play", press "Pause" and then try to use the "Quit" button. On my end the "Quit" button, even though it calls gtk.main_quit() (and prints stuff after it to prove it does), will not have any effect on the program. Any ideas on what I am doing wrong?
Here it is:
import pygtk
pygtk.require('2.0')
import gtk, gobject, cairo
import time
global paused
paused = False
running = False
def some_stuff(widget, data=None):
global paused
global running
x = 0
if running:
return
running = True
paused = False
while True:
x += 1
print x
while gtk.events_pending():
gtk.main_iteration(False)
while paused:
while gtk.events_pending():
gtk.main_iteration(False)
running = False
paused = False
def pause_stuff(widget, data=None):
global paused
paused = not paused
def force_quit(widget, data=None):
print 'before'
gtk.main_quit() # doesn't work, gtk main loop keeps running
#exit() # obviously works to kill everything
print 'after'
def main():
window = gtk.Window()
window.connect("delete-event", force_quit)
window.show()
vbox = gtk.VBox(True, 3)
vbox.show()
window.add(vbox)
button = gtk.Button("Play")
button.connect("clicked", some_stuff, None)
vbox.add(button)
button.show()
button = gtk.Button("Pause")
button.connect("clicked", pause_stuff, None)
vbox.add(button)
button.show()
button = gtk.Button("Quit")
button.connect("clicked", force_quit, None)
vbox.add(button)
button.show()
window.present()
gtk.main()
return 0
if __name__ =="__main__":
main()
gtk.main_quit() instructs the GTK main loop to terminate. It has no effect in your code because your some_stuff() function implements its own version of the main loop, unaffected by calls to gtk.main_quit(). Since the outer while never exits, once the Play button is clicked, you never return into the real GTK main loop.
A correct GTK program does not reimplement the main loop like some_stuff() does, precisely to avoid such problems, and also to avoid busy-looping while nothing is going on. The while gtk.events_pending(): gtk.main_iteration() idiom should be used very sparingly, only when absolutely necessary (updating a progress bar comes to mind).
Adding this as answer based on the comment to the other answer (since this is a bit long for a comment). Let's assume that one game iteration is fast and you can do one in gtk main thread without hurting the UI.
Make a function that runs one single iteration of your game (to begin with you can just print something). The function should be an "idle handler callback" (see below). It should return True, except if the game is paused or stopped: in that case it should return False (meaning the function should not be called again).
In Play() add an idle handler for the iteration function
In Pause(), update the paused variable, and if unpausing also add an idle handler like in Play()
This way the iteration function keeps getting called after Play is called (but the UI is also updated). after Pause the iteration function is still called once, but it will return False and will not be called again.

How do I wait for a certain number of buttons to be clicked in Tkinter/Python?

I'm trying to write a simple 'Simon' game, but I have hit a road block here, and honestly have no idea how to get around it.
So here, I made a class for the four buttons in the GUI:
class button:
def buttonclicked(self):
self.butclicked= True
def checkIfClicked(self):
if self.butclicked== True:
global pressed
pressed.append(self.color)
self.butclicked= False
def __init__(self, color1):
self.color= color1
self.button= tk.Button(root, text=' '*10, bg= self.color, command= self.buttonclicked)
self.button.pack(side='left')
self.butclicked=False
I then created four instances of this class in blue, red, yellow, and green as bb, rb, yb, and gb.
Once everything is packed into the Tk() module, it enters a while loop that appends a random color to a list activecolors. I try to use the following loop to wait until the list pressed is at least as long as the list activecolors before comparing the two to see if the user was correct:
while len(pressed)<len(activecolors):
sleep(.25)
print('In the check loop')
bb.checkIfClicked()
rb.checkIfClicked()
yb.checkIfClicked()
gb.checkIfClicked()
However, since it is stuck inside the while loop, the program can't tell that the button has been clicked. I thought adding the sleep method into the loop would allow the code to have time to do other things (such as process button clicks), but this is not the case. Any help is appreciated.
Here is the link to the full code, if you would like to see it. A warning though: it's not pretty.
Edit:
I ended up just changing the code to check the list only after a new button was clicked, telling the computer the code was ready. I've updated the Google Document if you'd like to see it.
You are making it too complicated. This program uses partial from functiontools to allow a variable to be passed to the function so one function handles all clicks (Python 2.7).
from Tkinter import *
from functools import partial
class ButtonsTest:
def __init__(self):
self.top = Tk()
self.top.title('Buttons Test')
self.top_frame = Frame(self.top, width =400, height=400)
self.colors = ("red", "green", "blue", "yellow")
self.colors_selected = []
self.num_clicks = 0
self.wait_for_number = 5
self.buttons()
self.top_frame.grid(row=0, column=1)
Button(self.top_frame, text='Exit',
command=self.top.quit).grid(row=2,column=1, columnspan=5)
self.top.mainloop()
##-------------------------------------------------------------------
def buttons(self):
for but_num in range(4):
b = Button(self.top_frame, text = str(but_num+1),
command=partial(self.cb_handler, but_num))
b.grid(row=1, column=but_num+1)
##----------------------------------------------------------------
def cb_handler( self, cb_number ):
print "\ncb_handler", cb_number
self.num_clicks += 1
this_color = self.colors[cb_number]
if (self.num_clicks > self.wait_for_number) \
and (this_color in self.colors_selected):
print "%s already selected" % (this_color)
self.colors_selected.append(this_color)
##===================================================================
BT=ButtonsTest()