Bind variable number of PyQt toggles to same number of functions [duplicate] - python-3.x

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

QCheckbox issue [duplicate]

I am struggling to get this working.
I tried to transpose from a c++ post into python with no joy:
QMessageBox with a "Do not show this again" checkbox
my rough code goes like:
from PyQt5 import QtWidgets as qtw
...
mb = qtw.QMessageBox
cb = qtw.QCheckBox
# following 3 lines to get over runtime errors
# trying to pass the types it was asking for
# and surely messing up
mb.setCheckBox(mb(), cb())
cb.setText(cb(), "Don't show this message again")
cb.show(cb())
ret = mb.question(self,
'Close application',
'Do you really want to quit?',
mb.Yes | mb.No )
if ret == mb.No:
return
self.close()
the above executes with no errors but the checkbox ain't showing (the message box does).
consider that I am genetically stupid... and slow, very slow.
so please go easy on my learning curve
When trying to "port" code, it's important to know the basis of the source language and have a deeper knowledge of the target.
For instance, taking the first lines of your code and the referenced question:
QCheckBox *cb = new QCheckBox("Okay I understand");
The line above in C++ means that a new object (cb) of type QCheckBox is being created, and it's assigned the result of QCheckBox(...), which returns an instance of that class. To clarify how objects are declared, here's how a simple integer variable is created:
int mynumber = 10
This is because C++, like many languages, requires the object type for its declaration.
In Python, which is a dynamic typing language, this is not required (but it is possible since Python 3.6), but you still need to create the instance, and this is achieved by using the parentheses on the class (which results in calling it and causes both calling __new__ and then __init__). The first two lines of your code then should be:
mb = qtw.QMessageBox()
cb = qtw.QCheckBox()
Then, the problem is that you're calling the other methods with new instances of the above classes everytime.
An instance method (such as setCheckBox) is implicitly called with the instance as first argument, commonly known as self.
checkboxInstance = QCheckBox()
checkboxInstance.setText('My checkbox')
# is actually the result of:
QCheckBox.setText(checkboxInstance, 'My checkbox')
The last line means, more or less: call the setText function of the class QCheckBox, using the instance and the text as its arguments.
In fact, if QCheckBox was an actual python class, setText() would look like this:
class QCheckBox:
def setText(self, text):
self.text = text
When you did cb = qtw.QCheckBox you only created another reference to the class, and everytime you do cb() you create a new instance; the same happens for mb, since you created another reference to the message box class.
The following line:
mb.setCheckBox(mb(), cb())
is the same as:
QMessageBox.setCheckBox(QMessageBox(), QCheckBox())
Since you're creating new instances every time, the result is absolutely nothing: there's no reference to the new instances, and they will get immediately discarded ("garbage collected", aka, deleted) after that line is processed.
This is how the above should actually be done:
mb = qtw.QMessageBox()
cb = qtw.QCheckBox()
mb.setCheckBox(cb)
cb.setText("Don't show this message again")
Now, there's a fundamental flaw in your code: question() is a static method (actually, for Python, it's more of a class method). Static and class methods are functions that don't act on an instance, but only on/for a class. Static methods of QMessageBox like question or warning create a new instance of QMessageBox using the provided arguments, so everything you've done before on the instance you created is completely ignored.
These methods are convenience functions that allow simple creation of message boxes without the need to write too much code. Since those methods only allow customization based on their arguments (which don't include adding a check box), you obviously cannot use them, and you must code what they do "under the hood" explicitly.
Here is how the final code should look:
# create the dialog with a parent, which will make it *modal*
mb = qtw.QMessageBox(self)
mb.setWindowTitle('Close application')
mb.setText('Do you really want to quit?')
# you can set the text on a checkbox directly from its constructor
cb = qtw.QCheckBox("Don't show this message again")
mb.setCheckBox(cb)
mb.setStandardButtons(mb.Yes | mb.No)
ret = mb.exec_()
# call some function that stores the checkbox state
self.storeCloseWarning(cb.isChecked())
if ret == mb.No:
return
self.close()

Too many positional arguments are given

Hi there smart people,
I have a small program where a combobox should be updated with a new list, depending on a User entry, when a Button is clicked.
Infact I would assume that no arguments need to be given since the called functions "gets" the user entries and then updates the combobox. No additional external Info needed.
Unfortunatley I get the Error:
TypeError:Func_Update_MA() takes 1positional argument but 2 were given.
How can I solve this issue?
To be honest I dont really get the whole "self" thing but I tried pretty much every combination of using self, not using it and combining it with something like args* or kwargs** (another mystery to me)
If you need more code I will provide it off course.
Thanks alot in advance!
class Class_MA_Win():
def __init__(self, Win_MA_Sel, Cockpit_Win):
Btt_Update_MA = Button(self.Mitarbeiter_Selection_Win, text="Liste Updaten")
Btt_Update_MA.bind("<Button-1>",self.Func_Update_MA)
Btt_Update_MA.grid(column=2, row=3, padx=10, pady=10)
def Func_Update_MA(self):
Entry_name = self.Ent_first_name_MA.get()
Entry_lastname = self.Ent_last_name_MA.get()
Entry_ID = self.Ent_ID_MA.get()
Whenever you use widget.bind(...), it will return an event object with a number of attributes describing the event. This is then passed to your func Func_Update_MA which accepts no argument, thus the error.
To solve this, simply accept the event as an arg:
def Func_Update_MA(self,event=None):
...
Also you mentioned about args and kwargs but you seem to wrongly position the asterisks. The correct is *args and **kwargs, like so:
def Func_Update_MA(self, *args, **kwargs):
...

Binding method with multiple arguments to <<ListboxSelect>>

Trying to make a method execute on the selection of an item in a tkinter listbox. I can bind some function to is using .bind("<<ListboxSelect>>", method), but if i try to give any arguments to it, like method(argumentone, argumenttwo), the method no longer executes on the selection of an item from the listbox. It is however executed at startup when Mainloop starts. Furthermore, giving arguments to a method is problematic, as i cant pass the event argument already passed to any bound method, as it is not assigned to any variable beforehand. I'd like to be able to pass the event and one other variable to some method ideally. Any help on achieving this would be very welcome. Some example code of what I'm trying to achieve
def onSelection(event, foo):
widget = event.widget
print(widget.curselection())
print(foo)
sam = "sam"
chars = Listbox(main)
chars.insert(someList)
chars.bind("<<ListboxSelect>>", onSelection(evt, sam))
Note that this code doesn't work. evt isn't defined.
When you specify a function in bind() that ends in parenthesis it just runs the function without bind generating an event. Instead you have to give bind a name. You can accomplish this with lambda or functools.partial. I'll use lambda in my example.
Bind is generating an event when triggered, so lambda will have to take it as input. Then call onSelection() with desired arguments.
from tkinter import *
main = Tk()
def onSelection(event, foo):
widget = event.widget
print(widget.curselection())
print(foo)
someList = ['Stilton', 'Brie', 'Edam', 'Cheddar', 'Ilchester']
chars = Listbox(main)
chars.pack(padx=10, pady=10)
for item in someList:
chars.insert("end", item)
sam = "sam"
chars.bind("<<ListboxSelect>>", lambda event: onSelection(event, sam))
# Take care of event created by bind ----^ ^
# Pass the event as well as argument to callback function ----|
main.mainloop()

Declaring variables in a python tkinter button on click command

i am trying to figure out a problem i'm having with declaring a variable upon a button click. For instance i wish to assign to the variable f the value True. I seem to be having trouble with the syntax or method of using the Button function to declare the variable. Do i initialize the variable beforehand? im not quite sure
here is my code:
import tkinter
from tkinter import *
root=Tk()
b1=Button(root,text="test", command=lambda: f=True)
b1.pack()
root.mainloop()
In Python, assignment is a statement, and thus cannot be done with simple lambdas, which can only contain expressions (function calls, variables, attributes, ... not statements, returns1, breaks, ...). To do what you're wanting to do, you must define a normal function as so:
f = False
def onclick():
global f
f = True
This will access the global namescape's f, (use nonlocal if you're within another function) and use that variable within the function. By assigning to this you will change its value in the outer scope.
Note that you must have f defined in the outer scope before the function can reassign it.
To use it, set the command to onclick as such:
b1=Button(root,text="test", command=onclick)
A lambda isn't necessary as you're passing the function object
Note that the function definition must occur before it is passed as the command argument to the new Button
1: By this I mean returning from an outer function, not returns within the lambda, as the entire lambda expression itself is returned.

Passing StringVar object from Entry to Label within functions in tkinter

Hi I've been struggling to get this to work, each time i change something I receive another error. I've been trying to create an entry box with a function and then get the variable from the entry box into a label, created by a button press. When I tried to do this often this error came up.
TypeError: get() missing 1 required positional argument: 'self'
I then put self in in the method brackets.
command = lambda: x.myFunc(self.my_variable.get(self))
Then another error, which I'm not sure how to sort out.
AttributeError: 'My_Class' object has no attribute '_tk'
Here's the full code, I'm new to classes and self, so any corrections are welcome.
from tkinter import *
import time
class My_Class:
def start(self):
self.root=Tk()
self.my_variable=StringVar
self.entry_box=Entry(self.root, textvariable=self.my_variable)
self.entry_box.pack()
self.button=Button(self.root,text="Pass variable now",
command=lambda:x.myFunc(self.my_variable.get(self)))
self.button.pack()
def myFunc(self,my_variable):
self.lab=Label(self.root,text=self.my_variable)
self.lab.pack()
x=My_Class()
x.start()
This is the correct way to create a StringVar object:
text = StringVar() # note additional ()
Can you explain me what x is in the following statement:
lambda: x.myFunc(self.my_variable.get(self))
x is not visible inside the class, because it's declared outside the class.
myFunc is not indented correctly: you should indent it like the __init__ method.
I really recommend you to watch some tutorials on OOP before proceeding. You are basically trying to guess how OOP works.
If you make myFunc A method if the class (which you might be trying to do; it's hard to know because your indentation is wrong), you don't have to pass anything to myFunc. That function has access to everything in the class, so it can get what it needs, when it needs it. That lets you eliminate the use of lambda, which helps reduce complexity.
Also, you normally don't need a StringVar at all, it's just one more thing to keep track of. However, if you really need the label and entry to show exactly the same data, have them share the same textvariable and the text is updated automatically without you having to call a function, or get the value from the widget, or set the value n the label.
Here's an example without using StringVar:
class My_Class:
def start(self):
...
self.entry_box = Entry(self.root)
self.button = Button(..., command = self.myFunc)
...
def myFunc(self):
s = self.entry_box.get()
self.lab = Label(..., text = s)
...

Resources