I'm connecting multiple signal/slots using a for loop in PyQt. The code is bellow:
# Connect Scan Callbacks
for button in ['phase', 'etalon', 'mirror', 'gain']:
getattr(self.ui, '{}_scan_button' .format(button)).clicked.connect(
lambda: self.scan_callback(button))
What I expect:
Connect button phase_scan_button clicked signal to the scan_callback slot and send the string phase as a parameter to the slot. The same for etalon, mirror and gain.
What I'm getting:
For some reason my functions is always passing the string gain as parameter for all the buttons. Not sure if I'm being stupid (likely) or it is a bug.
For reference, the slot method:
def scan_callback(self, scan):
print(scan) # Here I always get 'gain'
if self.scanner.isWorking:
self.scanner.isWorking = False
self.scan_thread.terminate()
self.scan_thread.wait()
else:
self.scanner.isWorking = True
self.scan_thread.start()
getattr(self.ui, '{}_scan_button' .format(
scan)).setText('Stop Scan')
getattr(self, '_signal{}Scan' .format(scan)).emit()
My preferred way of iterating over several widgets in pyqt is storing them as objects in lists.
myButtons = [self.ui.phase_scan_button, self.ui.etalon_scan_button,
self.ui.mirror_scan_button, self.ui.gain_scan_button]
for button in myButtons:
button.clicked.connect(lambda _, b=button: self.scan_callback(scan=b))
If you need the strings "phase", "etalon", "mirror" and "gain" separately, you can either store them in another list, or create a dictionary like
myButtons_dict = {"phase": self.ui.phase_scan_button,
"etalon": self.ui.etalon_scan_button,
"mirror": self.ui.mirror_scan_button,
"gain": self.ui.gain_scan_button}
for button in myButtons_dict:
myButtons_dict[button].clicked.connect(lambda: _, b=button self.scan_callback(scan=b))
Note, how I use the lambda expression with solid variables that are then passed into the function self.scan_callback. This way, the value of button is stored for good.
Your lambdas do not store the value of button when it is defined. The code describing the lambda function is parsed and compiled but not executed until you actually call the lambda.
Whenever any of the buttons is clicked, the current value of variable button is used. At the end of the loop, button contains "gain" and this causes the behaviour you see.
Try this:
funcs = []
for button in ['phase', 'etalon', 'mirror', 'gain']:
funcs.append( lambda : print(button))
for fn in funcs:
fn()
The output is:
gain
gain
gain
gain
Extending the example, as a proof that the lambdas don't store the value of button note that if button stops existing, you'll have an error:
del button
for fn in funcs:
fn()
which has output
funcs.append( lambda : print(button))
NameError: name 'button' is not defined
As noted here : Connecting slots and signals in PyQt4 in a loop
Using functools.partial is a nice workaround for this problem.
Have been struggling with same problem as OP for a day.
Related
i have a question when using the Button widget in tkinter. I am new to this.
I noticed that when we use the command in the Button widget, sometimes we call a simple function just like that and sometimes we use lambda function and then we call it. What is the difference?
For example: tk.Button(window, text = "Click Me!", command = myfunction)
tk.Button(win,text="Result",command=lambda: result(en1.get())
Cant we just use it without lambda?
THank you.
Use of lambda:
The parentheses are the main reason that the function gets executed when given as command to a Button without lambda. If the function(which you are passing to the Button as a command) has no parameters(to be passed to itself), then you can simply pass it as a command avoiding the parentheses(). And hence you don't need to use lambda in this case. Like in this Example:command=func.
So using lambda is only necessary when the function has its own parameters(to be passed to itself).Like in this Example:command=lambda:func(a,b,c)
What lambda Does:
When you have to pass arguments to the function itself you have cannot avoid parentheses().
So in the case of buttons, lambda basically delays the execution of the function until the user clicks the button, by creating another function on the spot, which does not get called until the button is actually clicked. Hence the function does not get executed, where it is given as command to the Button.
Any Questions will be answered.
books = []
result = db.answer_query(query)
for item in result:
book = Label(search_page,text=item,font="none 12 bold",pady=2)
book.grid(row=row_count, column=0, sticky = W)
books.append(book)
row_count += 1
for book in books:
book.bind("<Button-1>", lambda e: BookInfoPage(book.cget("text")))
book.bind("<Enter>", lambda event, h=book: h.configure(fg="#638cbb"))
book.bind("<Leave>", lambda event, h=book: h.configure(fg="#000000"))
This is the current code which loads multiple labels and places them on successive rows of each index in a list returned from db.answer_query(query). This function returns a list of string values.
The current issue that I have is that for the "<Button-1>" event, I pass the parameter book.cget("text") into the function BookInfoPage() which is a function that provides information about the clicked book name (provided from the labels). I want to pass text from a specified tkinter label on mouse click into the function BookInfoPage() but currently it passes the text from the last label only, no matter which label is clicked.
This is because book variable is recognised as the last entity in the list from the for loop. Is there any way around this issue?
I want to be able to pass text from a specified tkinter label on mouse click into the function BookInfoPage().
Here is a video (gif) of the current working application: https://imgur.com/a/9sjynxm
See that even though I click on "harry potter" it gives me the result for "the great gasby", the last entry in the list.
The event object passed to the callback can tell you which widget you clicked on. Instead of throwing away the event object, use it:
book.bind("<Button-1>", lambda event: BookInfoPage(event.widget.cget("text")))
An arguably better solution is to call a proper function, and use the event object to get the widget that was clicked on. Using a function is easier to write, easier to read, and easier to debug.
def show_book(event):
book = event.widget
BookInfoPage(book.cget("text"))
book.bind("<Button-1>", show_book)
I have a bound key combination :
self.parent.bind_all('<Control-n>', self.next_marked)
It is supposed to take me to the next tag in a text widget whose parent is a frame.
def next_marked(self, skip=False):
print (len(self.text.tag_ranges('definition')))
print('next_marked()')
self.text.focus_set()
print (self.text.index(INSERT))
next_tag = str(self.text.tag_nextrange('definition', 'insert+1c')[0])
print (self.text.index(INSERT))
spl = next_tag.split('.')
line = int(spl[0])
col = int(spl[1])
self.text.mark_set('insert', '%d.%d' % ( line, col ))
It does this when I do not use the hotkey, however when I do use the hotkey, it always moves the position of the cursor down one line and then performs the function. Is this my operating system at work? (Windows 7) Any recommendation on how to handle this?
I am using Python 2.7 and Tkinter 8.5
The problem seems to be that <Control-n> is already bound to "go to next line" on the Text class, and if there are multiple bindings, they will all be executed, in a specific order:
Tkinter first calls the best binding on the instance level, then the best binding on the toplevel window level, then the best binding on the class level (which is often a standard binding), and finally the best available binding on the application level.
So you could either overwrite the existing class-level binding of <Control-n> for all the Text widgets:
self.parent.bind_class("Text", '<Control-n>', lambda e: None)
Or bind your function to the instance (so it is scheduled before the class-level binding) and make it return "break" to cancel all subsequent bindings:
def next_marked(self, skip=False):
...
return "break"
self.text.bind('<Control-n>', self.next_marked)
Also, note that when used as a callback to bind, the first parameter (after self), i.e. skip in your case, will always be the Event.
Making PushButton widgets for a program. The intent was to create each PushButton, connect it to a function which compared two string values, ap.parse_answer(), then add the PushButton to the appropriate cell of a QGridLayout:
answers = ["this", "that", "the other", "one more"]
correct_answer = "this"
for grid_pos in [(i,j) for i in range(0,2) for j in range(0,2)]:
answer_disp = AnswerDisplay()
current_answer = answers.pop()
answer_disp.setText(current_answer)
answer_disp.clicked.connect(
lambda: self.ap.parse_answer(current_answer, answer))
answer_grid.addWidget(answer_disp, *grid_pos)
Here is the AnswerDisplay class:
class AnswerDisplay(QtGui.QPushButton):
def __init__(self):
super(AnswerDisplay, self).__init__()
answer_font = QtGui.QFont()
answer_font.setWeight(24)
answer_font.setPixelSize(20)
self.setFont(answer_font)
Unfortunately, what happens is the same function gets connected to each button. The last function generated end up on all the buttons, so it seems the connect is being reapplied to previous created buttons. But how do I address this? My approach can't be completely invalid because the setText() function correctly sets the text for each button without overwriting the previous assignments.
I tried to address the issue making an single AnswerDisplay and then copying it with deepcopy():
for grid_pos in [(i,j) for i in range(0,2) for j in range(0,2)]:
disp = AnswerDisplay()
answer_disp = deepcopy(disp)
super(AnswerDisplay, answer_disp).__init__()
...
but it produced the same undesired result.
I've done some searching, but all I've found are questions from people trying to get the kind of result I'm trying not to get. Any help would be appreciated.
Your issue is that you're not capturing the values in the lambda function. Because of the way Python's scoping rules work, you're using the same value (the last one) each time.
Change the lambda line to this to capture the variables you want:
answer_disp.clicked.connect(
lambda ca=current_answer, a=answer: self.ap.parse_answer(ca, a))
There are other related questions/answers that may give you more of an explanation about this (like here)
I initialize a wx.ListBox like this :
mylistbox = wx.ListBox(self, style=wx.LB_SINGLE)
mylistbox.Bind(wx.EVT_LISTBOX, self.OnEventListBox)
# some other things (append some items to the list)
mylistbox.SetSelection(5)
I also have :
def OnEventListBox(self, event):
print 'hello'
# plus lots of other things
How to make that the command mylistbox.SetSelection(5) in the initialization is immediately followed by the call of OnEventListBox?
Remark : It seems that SetSelection() doesn't generate a wx.EVT_LISTBOX automatically.
From the documentation:
Note that [SetSelection] does not cause any command events to be emitted...
This is on purpose, so that the events don't all trigger while you're trying to set up the UI. You could just manually call OnEventListBox for the desired functionality.
Better yet, if you don't need the event for what you're doing on init, you could extract the initialisation into a separate function, then call that on init and in OnEventListBox.