PyQt sending parameter to slot when connecting to a signal - python

I have a taskbar menu that when clicked is connected to a slot that gets the trigger event. Now the problem is that I want to know which menu item was clicked, but I don't know how to send that information to the function connected to. Here is the used to connect the action to the function:
QtCore.QObject.connect(menuAction, 'triggered()', menuClickedFunc)
I know that some events return a value, but triggered() doesn't. So how do I make this happen? Do I have to make my own signal?

Use a lambda
Here's an example from the PyQt book:
self.connect(button3, SIGNAL("clicked()"),
lambda who="Three": self.anyButton(who))
By the way, you can also use functools.partial, but I find the lambda method simpler and clearer.

As already mentioned here you can use the lambda function to pass extra arguments to the method you want to execute.
In this example you can pass a string obj to the function AddControl() invoked when the button is pressed.
# Create the build button with its caption
self.build_button = QPushButton('&Build Greeting', self)
# Connect the button's clicked signal to AddControl
self.build_button.clicked.connect(lambda: self.AddControl('fooData'))
def AddControl(self, name):
print name
Source: snip2code - Using Lambda Function To Pass Extra Argument in PyQt4

use functools.partial
otherwise you will find you cannot pass arguments dynamically when script is running, if you use lambda.

I'd also like to add that you can use the sender method if you just need to find out what widget sent the signal. For example:
def menuClickedFunc(self):
# The sender object:
sender = self.sender()
# The sender object's name:
senderName = sender.objectName()
print senderName

In general, you should have each menu item connected to a different slot, and have each slot handle the functionality only for it's own menu item. For example, if you have menu items like "save", "close", "open", you ought to make a separate slot for each, not try to have a single slot with a case statement in it.
If you don't want to do it that way, you could use the QObject::sender() function to get a pointer to the sender (ie: the object that emitted the signal). I'd like to hear a bit more about what you're trying to accomplish, though.

Related

Registering multiple Tkinter listeners for an event

Is it possible to register multiple listeners for an event?
Imagine a click on a Treeview. There might be a standard binding on the <<TreeviewSelect>>, e.g.
tree.bind('<<TreeviewSelect>>', some_function)
resulting in an Entry getting changed. But what if additional Entrys are added later, each to be filled with another part of the data in the Treeview?
As far as I know, there can be only one binding per event-type per widget, i.e. not multiple <<TreeviewSelect>> functions getting bound. The following
tree.bind('<<TreeviewSelect>>', function1)
tree.bind('<<TreeviewSelect>>', function2)
...
would result in the last function being the only one getting called. Is there a way to register multiple function calls?
To run both functions on that event, you could create function3:
def function3(event=None):
function1(event)
function2(event)
and then bind the <<TreeviewSelect>> event to function3.
tree.bind("<<TreeviewSelect>>", function3)
Edit: another way.
You could also add the add=True parameter, like this:
tree.bind("<<TreeviewSelect>>", function1)
tree.bind("<<TreeviewSelect>>", function2, add=True)
This code snippet demonstrates how to use event_add to any widget for multiple access to a selected function.
Once the event is created using the <<name>> tag it can be bound to a function using the bind method.
import tkinter as tk
root = tk.Tk()
tree = ttk.Treeview(root)
tree.event_add("<<TreeViewSelect>>", "<Button-1>", "<Button-3>")
def me(event=None):
print(event.widget)
tree.bind("<<TreeViewSelect>>", me)

How to bind all frame widgets to <'Enter'> event

I the following code I want to bind all frame1 items to <'Enter'> Event, but it does not work. I mean canvas.focus_set() does not take effect. How can I solve my problem?
for w in frame1.winfo_children():
w.bind('<Enter>',canvas1.focus_set())
The comment made by Lafexlos actually sends you in the right direction. When you do
w.bind('<Enter>', canvas1.focus_set())
you call canvas1.focus_set() and use the return value of this function call (which is None) to bind to the event. This isn't what you want, because now every time the event is triggered, None is executed instead of canvas1.focus_set().
What you should do is pass a function reference to the bind function. The reference for calling canvas1.focus_set() is canvas1.focus_set. However, using
w.bind('<Enter>', canvas1.focus_set)
still doesn't work.
This is because the bind function passes an event object to the function it has been given, so it will call canvas1.focus_set(event) instead of canvas1.focus_set(). Because focus_set does not accept any arguments, this fails.
You can fix this in two ways. You could make an extra function, which does accept an event object and then calls canvas1.focus_set() without arguments, and then bind the event to this new function. The other option is to use an anonymous "lambda" function to basically do the same like
w.bind('<Enter>', lambda e: canvas1.focus_set())
This way the lambda function accepts the event object as e, but doesn't pass it to focus_set.
P.S. The <Enter> event is not the event that is triggered when you press the Enter button on your keyboard (that is <Return>). The <Enter> event is triggered whenever you move the mouse onto a widget and is accompanied by the <Leave> event for when you leave the widget with your mouse. This might be what you want, but it often leads to confusion.
by using canvas1.bind_all which is the parent of frame1 I solved my problem. Thanks for all solutions.
If there is any mistake I see you making it is likely you are not calling the write command for the Enter key. Hopefully, if you are attempting to do this on windows, you should rather use Return.
More like:
for w in frame1.winfo_children():
w.bind('<Return>',canvas1.focus_set())

Unable to change widget inside StackedWidget

I am trying to replace a widget for another. I am using a StackedWidget. I have the following.
First, I register add some widgets to the StackedWidget:
self.stackedWidget.addWidget(w1)
self.stackedWidget.addWidget(w2)
self.stackedWidget.addWidget(w3)
The I bind the click of a button of w1:
QObject.connect(w1.pushButton,SIGNAL("clicked()"),self.stackedWidget,SLOT(self.stackedWidget.setCurrentWidget(w2)))
For Slot I have also tried "setCurrentIndex". I checked if the signal is being received, and it is ok.
Finally, I show w1.
self.stackedWidget.setCurrentWidget(w1)
Although the "clicked()" signal is received when the button of w1 is pressed, the widget w2 never appears in the StackedWidget.
UPDATE:
I am doing
QObject.connect(w1.pushButton,SIGNAL("clicked()"),self.stackedWidget,SLOT('w1Clicked()'))
as suggested; however, I get
Object::connect: No such slot QStackedWidget::w1Clicked()
Object::connect: (sender name: 'pushButton')
Object::connect: (receiver name: 'stackedWidget')
I guess I have to create the slot "w1Clicked" somehow, but I am using the designer I can't figure it out.
I have one Main Window with the StackedWidget and separate forms with the buttons, so I don't see how to make the connection or create slots.
Also, I discovered that the problem with the way I was doing:
QObject.connect(w1.pushButton,SIGNAL("clicked()"),self.stackedWidget,SLOT(self.stackedWidget.setCurrentWidget(w2)))
is that "self.stackedWidget.setCurrentWidget(w2)" gets executed immediately, it does not wait for the signal! That's why w2 was never shown.
Still I have no idea.
Your connect call is wrong. You can't define what values are going to be passed to your slot when you make the connection. Instead you need to create your own slot and handle the signal as you desire (apologies if my python syntax is off):
QObject.connect(w1.pushButton,SIGNAL("clicked()"),self,SLOT("w1Clicked()"))
def w1Clicked(self):
self.stackedWidget.setCurrentWidget(w2)
Finally, it worked this way:
w1.pushButton.clicked.connect(self.w1Clicked)
At least the signal is received and the method is called correctly.

More information in signal/slot with QPushButton

When I implements the function that is executed when a button is clicked, the code is like this:
self.connect(btnBrowse, SIGNAL("clicked()"), self.browseFile)
and I implement the function browseFile
def browseFile(self):
But when i am inside the method browseFile, I don't have information about the button being clicked, because I want to implement just one function browseFile for many buttons. how can I do to have more information the slot, for example to have my function looks like this:
def browseFile(self, option):
Thanks
Connect to a lambda (or use functools.partial).
Also note the use of new style syntax, which is much more readable and pythonic.
self.btnBrowse.clicked.connect(lambda: self.browseFile(option))
Sender() provides a pointer to the button that sent the event, you can then read the button text (or other associated data) to determine which button was pressed

PyQt context menu

I'm adding a contextmenu to a QTableWidget dynamically:
playlistContenxt = QAction("Add to %s" % (currentItem.text()), self.musicTable)
playlistContenxt.setData(currentData)
self.connect(playlistContenxt, SIGNAL("triggered()"), self.addToPlaylistAction)
self.musicTable.addAction(playlistContenxt)
currentItem.text() is a playlist name thats being fetched from db, as you can see only one function (addToPlaylistAction) receives all triggers from different actions. On my addToPlaylistAction function, how do I determine which menu has been clicked?
The correct way is to use signal mapper: You can assign data to each of the senders and get a signal with that data.
You can use QAction.setData to set some data, so that the slot knows which playlist to add to. Then from the slot you can call self.sender() to get the action that triggered the signal, and use action.data() to get the data back.

Categories