Connect PySide to a signal using QSignalMapper and an instance method - python

I have a small app with a table. This table has some data and a button on each row. These buttons should allow the user to remove corresponding row data. I'm trying to implement it via the clicked button signal, but I need to pass the row number, so I tried using QSignalMapper, as shown in the excerpt below
btnRemoveItem = QPushButton()
btnRemoveItem.clicked.connect(self.removeItem)
self.mapper = QSignalMapper(self)
self.connect(btnRemoveItem, QtCore.SIGNAL("clicked()"), self.mapper,
QtCore.SLOT("map()"))
self.mapper.setMapping(btnRemoveItem, nextRow)
self.connect(self.mapper, QtCore.SIGNAL("mapped(int)"), self.removeItem(),
QtCore.SIGNAL("clicked(int)"))
Problem is, my removeItem(self, index) method is an instance method (because my table belongs to a specific class) and I'm having trouble mapping it in a way I can pass self along with index.
Currently, my code fails with the following error:
TypeError: removeItem() takes exactly 2 arguments (1 given)
Is there a way to make this work correctly? Or is it impossible to map instance methods with QSignalMapper in PySide?

I tried to reproduce your code in PyQt but I'm not fully aware of the differences between Pyside and PyQt so my answer is more of a guess.
Try to remove the second line of your code and replace the last one with:
self.mapper.mapped.connect(self.removeItem)

In the last line of your code, in the connect method, I believe you have a typo in your code
self.connect(self.mapper, QtCore.SIGNAL("mapped(int)"), self.removeItem(),
QtCore.SIGNAL("clicked(int)"))
should be
self.connect(self.mapper, QtCore.SIGNAL("mapped(int)"), self.removeItem,
QtCore.SIGNAL("clicked(int)"))
Having self.removeItem() in the connect method will actually try to call the self.removeItem method rather than providing the subsystem an address to connect the function
As finmor suggests, you should look at new syntax signals and slots as they will dramatically help to clarify your code and make it more Pythonic.

Related

Get valueChanged from qDial to use in method for python 3? [duplicate]

I am trying to learn PyQt from rapid gui programming with python and qt and currently learning Signals and Slots.
Below is a short snippet of my code:
self.connect(self.dial, SIGNAL("valueChanged(int)"),self.spinbox.setValue) #1
self.connect(self.dial, SIGNAL("valueChanged(int)"),self.getValue_dial) #2
self.connect(self.spinbox, SIGNAL("valueChanged(int)"),self.dial.setValue)
self.connect(self.spinbox, SIGNAL("valueChanged(int)"),self.getValue_spinbox)
def getValue_dial(self):
print self.dial.value()
def getValue_spinbox(self):
print self.dial.value()
What I am trying to achieve here is call 2 SLOTS at once that is spinbox.setValue and getValue_dial for dial object as soon as ValueChanged(int) signal is emitted.
The above code executes successfully without any errors and print the appropriate values as they are changed.
Now my question is the above way appropriate to call multiple slots for a single signal.?
Can the above two statements(1 & 2) be combined into a single statement.
Here is the link for my complete code.
The way you're doing it is fine. If you had quite a few things to do you could connect to a new function that handles everything for you.
I notice in your connected getValue functions you're getting the value straight from the object; are you aware that the value is passed as a parameter with the valueChanged(int) signal? If you change your getValue functions to accept an additional parameter there will be no need to get the value directly from the object. Of course you could do away with the getValue function all together and issue your print statement in the helper function.
self.connect(self.dial, SIGNAL("valueChanged(int)"), self.dial_value_changed)
self.connect(self.spinbox, SIGNAL("valueChanged(int)"),self.spinbox_value_changed)
def dial_value_changed(self, value):
self.spinbox.setValue(value)
self.getValue_dial(value)
def spinbox_value_changed(self, value):
self.dial.setValue(value)
self.getValue_spinbox(value)
def getValue_dial(self, value):
print value
def getValue_spinbox(self, value):
print value
Also, and this is down to preference, there is a new style for signals and slots which can make the code a little easier to read. It would change the
self.connect(self.dial, SIGNAL("valueChanged(int)"), self.dial_value_changed)
self.connect(self.spinbox, SIGNAL("valueChanged(int)"),self.spinbox_value_changed)
lines above to
self.dial.valueChanged.connect(self.dial_value_changed)
self.spinbox.valueChanged.connect(self.spinbox_value_changed)
But for the original question and for the two things you're doing I'd just connect the signal twice rather than have a helper function.
You can use a list to connect the two slots/functions in a single statement:
# in Python 2.X
map(self.dial.valueChanged.connect, [self.spinbox.setValue, self.getValue_dial])
# in Python 3.X (map returns an iterator instead of processing the list)
list(map(self.dial.valueChanged.connect, [self.spinbox.setValue, self.getValue_dial]))
# or with any Python version
[self.dial.valueChanged.connect(x) for x in [self.spinbox.setValue, self.getValue_dial]]
This is the appropriate way, yes. You can't combine the two CONNECT statements into one.

Is it possible to retrieve the content of Qt Signals with a lambda function?

I'm working on a PySide based App in which I continously get values and want to put them onto the GUI.
When I receive a value (I receive them via a CAN device using the PCANBasic library) I convert him to an int and emit him via the .emit() attributte of PySide.QtCore.Signal
Signal = PySide.QtCore.Signal(int)
# as soon as a new value is received and processed
Signal.emit(new_value)
Now I try to display my new_value on a PySide.QtGui.QSlider, thats what I do at the moment:
my_slider = PySide.QtGui.QSlider()
Signal.connect(change_slider_value)
# with a simple helper function
def change_slider_value(value):
my_slider.setValue(value)
What I wanna do is:
Signal.connect(lambda value = data : my_slider.setValue(value))
With data being the what I emited with Signal (I'd love to somehow mark it, but the formating disappeaered on me and its my first post -.-)
When I test this I get the following Traceback:
self.calibrate.bar_val_signal.connect(lambda value = data: self.UI.calibrate.ctrl.Bar.setValue(value)) # self.change_bar_value)
NameError: global name 'data' is not defined
(You see the program is probably somewhat more complicated)
Translated to our pseudo code it would probably look like this:
Signal.connect(lambda value = data: my_slider.setValue(value))
NameError: global name 'data' is not defined
In my opinion the issue is that the lambda function can't get the value out of the signal.
Has anybody a idea if there's a possibility to work without the need for a helper function.
Thanks in advance
You don't need to use a lambda. Since your change_slider_value function only takes the argument that your signal would emit, you can just connect the signal to that.
Signal.connect(change_slider_value)
But as for why your lambda wasn't working, think of data as the parameter of a function. data will contain whatever the lambda is called with, therefore you could just do this, omitting 'value':
Signal.connect(lambda data: my_slider.setValue(data))
But I would suggest using the first solution, unless your parameters for chang_slider_value change.

Custom view with InstanceEditor

I'm working on a dynamic Traits UI where I can select the class to use for certain instances. I've got it working nicely using an InstanceEditor with a "values" argument containing InstanceFactoryChoice instances.
My problem appears when I want to specify a view to use for the selected instance. Using the "view" argument works if I omit the "values" argument, but with it I get the default view instead. The relevant part of the view looks like this:
Item('item',
show_label=False,
editor=InstanceEditor(
view=item_view,
values=[InstanceFactoryChoice(klass=k) for k in classes],
editable=True),
style='custom')
What's more confusing to me is that it also works as expected (i.e. uses the "item_view" view to display the instance) when I use the "simple" style instead of "custom". However, then the view appears in a new window, I want it to be inline.
Am I missing something here? I'm on TraitsUI 4.3.
OK, after some source-diving I found that adding the "view" argument to the InstanceFactoryChoice call instead seems to do what I want. Still, it seems there's an inconsistency in there somewhere...
InstanceFactoryChoice(klass=k, view=item_view)

PyQt TypeError connect()

I'm very new to Python, so I'm sorry ahead of time if this is a simple mistake.
class TaskTabs(QtGui.QTabWidget):
...(some init stuff here)....
def remove(self):
self.removeTab(0)
self.addTab(Tabs.General(self.nao, self.parent), 'General')
In another class:
self.taskTabs = TaskTabs(self.nao, mainWidget)
....(Some other stuff here)....
loadEmpathy = QtGui.QAction(QtGui.QIcon(), '&Load Empathy', self)
loadEmpathy.setShortcut('Ctrl+E')
loadEmpathy.triggered.connect(self.taskTabs.remove())
There error that I am getting is:
TypeError: connect() slot argument should be a callable or a signal, not 'NoneType'
What I am trying to do is to remove a tab in my GUI and add in various ones (which I'll implement later, just testing this now) from a menu. My menu code works perfectly, and now I want to set an action for what happens when it's clicked. I created this remove method in my TaskedTabs file, the remove function works great in my init function, but I want to separate it (for purposes later on). Can anyone explain what is wrong with my code?
As the error message says, connect() needs a callable method. But what you are giving it is the result of a method, because you're calling it. remove() returns None, which is then used as the argument for connect(), which doesn't work. Solve this by removing the brackets after remove.
loadEmpathy.triggered.connect(self.taskTabs.remove)

A sorted and filtered treemodel in Python Gtk+3..?

I am trying to get a treemodel (a liststore in fact) that can be filtered and also sorted. I have the following piece of code
self.modelfilter = self.liststore.filter_new()
self.modelfilter.set_visible_func(\
self._visible_filter_function)
self.treeview.set_model(self.modelfilter)
where self.liststore and self.treeview are standard Gtk.ListStore and Gtk.TreeView objects that I get from a glade file, and self._visible_filter_function is a filtering function.
The problem is that self.modelfilter does not seem to be sortable. When I click on the column headers (of the columns in self.treeview) to sort them, I get
Gtk-CRITICAL **: gtk_tree_sortable_get_sort_column_id: assertion `GTK_IS_TREE_SORTABLE (sortable)' failed
saying that the treemodel is not sortable.
This problem seems to be surmountable in PyGtk as suggested here. The idea is to stack a ListStore, a TreeModelFilter and a TreeSortFilter one inside the other and feed the last one as the model for the treeview.
However this trick does not seem to be working in Python Gtk+3. When I try
self.modelfilter = self.liststore.filter_new()
self.modelfilter.set_visible_func(\
self._visible_filter_function)
self.sorted_and_filtered_model = \
Gtk.TreeModelSort(self.modelfilter)
self.treeview.set_model(self.sorted_and_filtered_model)
it complains
Gtk.TreeModelSort(self.modelfilter)
TypeError: GObject.__init__() takes exactly 0 arguments (1 given)
Now I tried to get a instance of Gtk.TreeModelSort with no arguments. But this instance does not have any set_model method.
I am lost here.
Is there another way to set the model for Gtk.TreeModelSort? Or is there a totally different way to get a filtered and sortable treemodel object that can be displayed in a treeview?
>>> from gi.repository import Gtk
>>> mymodel = Gtk.ListStore()
>>> Gtk.TreeModelSort(model=mymodel)
<TreeModelSort object at 0x1d4d190 (GtkTreeModelSort at 0x1f0d3f0)>
In my opinion PyGObject is not ready yet. It has no browsable documentation, some things are not instrospected yet and in particular this:
Sometimes a widget work with Gtk.MyWidget(attr=foo), like this one.
Sometimes with Gtk.MyWidget.new_with_label('Foo'), like buttons. Yes, Gtk.MyWidget(label='Foo') doesn't work.
Kind regards

Categories

Resources