Differentiating between signal sources in PySide - python

Is there trivial or elegant way to differentiate between many same-type signal sources in PySide/PyQt?
I am learning PySide. I have written simple application, which multiplies two numbers from two different QLineEdit() objects. Result is displayed in third QLineEdit.
Multiplier and multiplicand QLineEdit.textChanged() signals are connected to one method (TxtChanged). In this method i have to differentiate between signal sources. After some trials I figured out some workaround based upon placeholder text (4 lines below "is there another way?" comment in my code)
code:
import sys
from PySide import QtGui, QtCore
class myGUI(QtGui.QWidget):
def __init__(self, *args, **kwargs):
QtGui.QWidget.__init__(self, *args, **kwargs)
self.multiplier = 0
self.multiplicand = 0
self.myGUIInit()
def myGUIInit(self):
# input forms
a1_label = QtGui.QLabel("a1")
a1_edit = QtGui.QLineEdit()
a1_edit.setPlaceholderText("a1")
a2_label = QtGui.QLabel("a2")
a2_edit = QtGui.QLineEdit()
a2_edit.setPlaceholderText("a2")
# output form
a1a2_label = QtGui.QLabel("a1*a2")
self.a1a2_edit = QtGui.QLineEdit()
self.a1a2_edit.setReadOnly(True)
# forms events
a1_edit.textChanged.connect(self.TxtChanged)
a2_edit.textChanged.connect(self.TxtChanged)
# grid
grid = QtGui.QGridLayout()
grid.setSpacing(10)
grid.addWidget(a1_label,1,0)
grid.addWidget(a1_edit,1,1)
grid.addWidget(a2_label,2,0)
grid.addWidget(a2_edit,2,1)
grid.addWidget(a1a2_label,3,0)
grid.addWidget(self.a1a2_edit,3,1)
self.setLayout(grid)
self.setGeometry(100,100,200,200)
self.setWindowTitle("a*b")
self.show()
def TxtChanged(self,text):
sender = self.sender()
sender_text = sender.text()
if sender_text == '': sender_text = '0'
# is there another way?
if sender.placeholderText() == 'a1':
self.multiplicand = sender_text
else:
self.multiplier = sender_text
product = int(self.multiplier) * int(self.multiplicand)
print(self.multiplier,self.multiplicand,product)
self.a1a2_edit.setText(str(product))
def main():
app = QtGui.QApplication(sys.argv)
mainWindow = myGUI()
sys.exit(app.exec_())
main()
best regards,
ostrzysz

You can use the functools.partial function - and therefore connect your signals to straight to your method/function but rather to a python object which will automatically call your function with some extra data you pass it:
from functools import partial
...
....
a1_edit.textChanged.connect(partial(self.TxtChanged, a1_edit))
a2_edit.textChanged.connect(partial(self.TxtChanged, a2_edit))
...
def TxtChanged(self,sender, text):
# and here you have the "sender" parameter as it was filled in the call to "partial"
...
partials is part of the stdlib, and is very readable, but one can always use lambda instead of partial for the same effect -
a1_edit.textChanged.connect(lambda text: self.TxtChanged(a1_edit, text))
In this way the object yielded by the lambda expression will be a temporary function that will use the values for "self" and "a1_edit" from the current local variables (at the time the button is clicked), and the variable named "text" will be supplied by Pyside's callback.

One thing that bugs me most in your code is that you are using placeholderText to differentiate. QObjects has another property called objectName that is more suitable for your task. And, you don't need to use sender.text() to get the text of QLineEdit. textChanged already sends it, so you will have it in your text parameter.
Also, using a dictionary instead of two separate variables (multiplier and multiplicand) will simplify your code further.
Here is the changed code:
class myGUI(QtGui.QWidget):
def __init__(self, *args, **kwargs):
QtGui.QWidget.__init__(self, *args, **kwargs)
self.data = {"multiplier": 0,
"multiplicand": 0}
self.myGUIInit()
def myGUIInit(self):
a1_label = QtGui.QLabel("a1")
a1_edit = QtGui.QLineEdit()
a1_edit.setObjectName("multiplicand")
a2_label = QtGui.QLabel("a2")
a2_edit = QtGui.QLineEdit()
a2_edit.setObjectName("multiplier")
# skipped the rest because same
def TxtChanged(self, text):
sender = self.sender()
# casting to int while assigning seems logical.
self.data[sender.objectName()] = int(text)
product = self.data["multiplier"] * self.data["multiplicand"]
print(self.data["multiplier"], self.data["multiplicand"], product)
self.a1a2_edit.setText(str(product))

Although #jsbueno and #Avaris answered your direct question about signal sources, I wouldn't relay on this sources in your concrete case. You can make instance members a1_edit and a2_edit:
...
self.a1_edit = QtGui.QLineEdit()
...
self.a2_edit = QtGui.QLineEdit()
...
It will simplify your TxtChanged function:
def TxtChanged(self,text):
try:
multiplier = int(self.a1_edit.text())
multiplicand = int(self.a2_edit.text())
except ValueError:
self.a1a2_edit.setText('Enter two numbers')
return
product = multiplier * multiplicand
print(multiplier, multiplicand, product)
self.a1a2_edit.setText(str(product))
Also, instead of handling ValueError exception, you can use QIntValidator for input controls:
self.int_validator = QtGui.QIntValidator()
self.a1_edit.setValidator(self.int_validator)
self.a2_edit.setValidator(self.int_validator)

Related

Updated: How to reduce QWidget nesting in PyQt code?

Updated Question
I think my original quandary might be a result of the structure of my PyQt app. The way I've approached creating a GUI is to divide the larger widget into smaller pieces, each given their own class until the parts are simple enough. Because of this, I end up with a ton of nesting, as a large widget holds instances of smaller widgets, and those hold their own even smaller widgets. It makes it hard to navigate data around the app.
How should a PyQt app be structured so that it is simple to understand in code and yet has a structure containing very little nesting? I haven't found many examples of this around, so I'm sort of stuck. The code example in my original question shows a pretty good example of the structure I'm currently using, which has a large amount of nesting.
Info on program
The GUI is used to create a set of parameters for running a test. The options in each setting should correspond to a binary number, and all of the binary numbers indicated by each set of options are collected, formed into a single sequence of binary numbers, and passed on. Changes to settings do not have to be carried over between sessions, as each new session will most likely correspond to a new test (and thus a new set of choices for settings).
The basic flow of the app should be that upon opening it, all available settings (about 20 total) are set to their default values. A user can go through and change whatever settings they would like, and once they're done they can press a "Generate" button to gather all of the binary numbers corresponding to the settings and create the command. It would be very helpful to have a live preview of individual bits that updates as settings are changed, which is why updates must be immediate.
Some settings are dependent on other; for instance, Setting A has 4 options, and if option 3 is selected, Setting B should be made visible, otherwise it is invisible.
Original Question
I'm definitely a beginner to PyQt, so I don't quite know if I've worded my question correctly, but here goes. I've got a GUI wherein I'm attempting to take a bunch of different settings, keep track of what number was selected from each setting, and then pass the number up to an object that keeps track of all of the numbers from all of the settings. The trouble is that I don't know the best way to get all the individual settings values up my tree of classes, so to speak. Here's the structure of my GUI so far:
Bottom: individual custom QWidgets, each responsible for a single setting. Each has a signal that fires whenever the value it returns changes.
Middle: a QWidget containing ~7-10 individual settings each. These collect settings into related groups.
Top: a QTabWidget that places each instance of a setting group into an individual tab. This widget also contains an object that should ideally collect all of the settings from individual groups into it.
My question is how do I get the values from the bottom layer signals to the top layer widget? My only idea is to connect all of the signals from those small setting widgets to a signal in the middle layer, and connect the middle layer signal to something in the top layer. This sort of chaining seems crazy, though.
I'm running PyQt5 and Python 3.7.
Here's some stripped down code which hopefully shows what I want to do.
class TabWindow(QTabWidget):
def __init__(self):
super().__init__()
self.tabs = [SettingsGroup1, SettingsGroup2, SettingsGroup3]
self.setting_storage = { # dictionary is where I'd like to store all settings values
# 'setting name': setting value
}
for tab in self.tabs:
self.addTab(tab, 'Example')
class SettingsGroup(QWidget):
def __init__(self):
super().__init__()
# not shown: layout created for widget
self.settings = []
def add_to_group(self, new_setting):
self.settings.append(new_setting)
# not shown: add setting to the layout
class SettingsGroup1(SettingsGroup):
def __init__(self):
super().__init__()
self.add_to_group([Setting1, Setting2, Setting3])
class SettingsGroup2(SettingsGroup):...
class SettingsGroup3(SettingsGroup):...
class Setting(QWidget):
val_signal = pyqtSignal([int], name='valChanged')
def __init__(self, name):
self.val = None
self.name = name
def set_val(self, new_val):
self.val = new_val
self.val_signal.emit(self.val) # <-- the signal I want to pass up
class Setting1(Setting):
def __init__(self, name):
super().__init__(name)
# not shown: create custom setting layout/interface
class Setting2(Setting):...
class Setting3(Setting):...
I use a lot of inheritance (SettingsGroup -> SettingsGroup1, 2, 3) because each subclass will have its own functions and internal dependencies that are unique to it. For each Setting subclass, for instance, there is a different user interface.
Thanks for any help provided!
EDIT: The question has been updated in the meantime, I've added a solution that's more specific at the bottom of this answer.
I feel like this question is slightly "opinion based", but since I've had my share of similar situations I'd like to propose my suggestions. In these situations it's important to understand that there's not one good way to do things, but many ways to do it wrong.
Original answer
An idea could be to create a common signal interface for every "level", which will get that signal and send it back to its parent by adding its own name to keep track of the setting "path"; the topmost widget will then evaluate the changes accordingly.
In this example every tab "group" has its own valueChanged signal, which includes the group name, setting name and value; the source signal is fired from the "source" (a spinbox, in this case), then it follows its parents which, in turn "add" their name in turn.
Keep in mind that you can also just use a generalized pyqtSignal(object) for every parent and connect it with widget.valueChanged.connect(self.valueChanged), and then track its group and setting by walking by self.sender() parents backwards.
As a final notice, if you are using these values for application settings, remember that Qt already provides the QSettings API, which can be used as a common and OS-transparent interface for every configuration you need to set (and remember between sessions) in your application. I implemented it in the example, but I suggest you to read its documentation to better understand how it works.
import sys
from PyQt5 import QtCore, QtWidgets
class SettingWidget(QtWidgets.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self, name):
super().__init__()
self.settings = QtCore.QSettings()
self.val = 0
self.name = name
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
layout.addWidget(QtWidgets.QLabel(self.name))
self.spinBox = QtWidgets.QSpinBox()
layout.addWidget(self.spinBox)
self.spinBox.valueChanged.connect(self.set_val)
def set_val(self, new_val):
if self.val != new_val:
self.val = new_val
self.valueChanged.emit(self.val)
# enter a setting group, ensuring that same name settings won't
# be mismatched; this allows a single sub level setting only
self.settings.beginGroup(self.parent().name)
self.settings.setValue(self.name, new_val)
# leave the setting group. THIS IS IMPORTANT!!!
self.settings.endGroup()
class SettingWidget1(SettingWidget):
def __init__(self):
super().__init__('Setting1')
class SettingWidget2(SettingWidget):
def __init__(self):
super().__init__('Setting2')
class SettingWidget3(SettingWidget):
def __init__(self):
super().__init__('Setting3')
class SettingsGroup(QtWidgets.QWidget):
# create two signal signatures, the first sends the full "path",
# while the last will just send the value
valueChanged = QtCore.pyqtSignal([str, str, int], [int])
def __init__(self, name):
super().__init__()
self.name = name
layout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
def add_to_group(self, new_setting):
widget = new_setting()
# emit both signal signatures
widget.valueChanged.connect(
lambda value, name=widget.name: self.valueChanged.emit(
self.name, name, value))
widget.valueChanged.connect(self.valueChanged[int])
self.layout().addWidget(widget)
class SettingsGroup1(SettingsGroup):
def __init__(self):
super().__init__('Group1')
self.add_to_group(SettingWidget1)
self.add_to_group(SettingWidget2)
class SettingsGroup2(SettingsGroup):
def __init__(self):
super().__init__('Group2')
self.add_to_group(SettingWidget3)
class TabWidget(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.settings = QtCore.QSettings()
self.tabs = [SettingsGroup1, SettingsGroup2]
self.settingsDict = {}
for tab in self.tabs:
widget = tab()
self.addTab(widget, widget.__class__.__name__)
widget.valueChanged[str, str, int].connect(self.valueChangedFullPath)
widget.valueChanged[int].connect(self.valueChangedOnly)
def valueChangedFullPath(self, group, setting, value):
# update the settings dict; if the group key doesn't exist, create it
try:
self.settingsDict[group][setting] = value
except:
self.settingsDict[group] = {setting: value}
settingsData = [group, setting, value]
print('Full path result: {}'.format(settingsData))
# Apply setting from here, instead of using the SettingWidget
# settings.setValue() option; this allows a single sub level only
# self.applySetting(data)
def valueChangedOnly(self, value):
parent = sender = self.sender()
# sender() returns the last signal sender, so we need to track down its
# source; keep in mind that this is *not* a suggested approach, as
# tracking the source might result in recursion if the sender's sender
# is not one of its children; this system also has issues if you're
# using a Qt.DirectConnection from a thread different from the one that
# emitted it
while parent.sender() in sender.children():
parent = sender.sender()
widgetPath = []
while parent not in self.children():
widgetPath.insert(0, parent)
parent = parent.parent()
settingsData = [w.name for w in widgetPath] + [value]
print('Single value result: {}'.format(settingsData))
# similar to valueChangedFullPath(), but with this implementation more
# nested "levels" can be used instead
# self.applySetting(settingsData)
def applySetting(self, settingsData):
# walk up to the next to last of settingsData levels, assuming they are
# all parent group section names
for count, group in enumerate(settingsData[:-2], 1):
self.settings.beginGroup(group)
# set the setting name settingsData[-2] to its value settingsData[-1]
self.settings.setValue(*settingsData[-2:])
for g in range(count):
self.settings.endGroup()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
# set both Organization and Application name to make settings persistent
app.setOrganizationName('StackOverflow')
app.setApplicationName('Example')
w = TabWidget()
w.show()
sys.exit(app.exec_())
Alternate solution, based on updated answer
Since the answer has become more specific in its update, I'm adding another suggestion.
As far as we can understand now, you don't need that level of "nested" classes, but more specifically designed code that can be reused according to your purposes. Also, since you're using binary based data, it makes things a bit (pun intended) easier, as long as you know how bit operation works (which I assume you do) and the setting "widgets" don't require specific GUI customization.
In this example I created just one "setting" class and one "group" class, and their instancies are created only according to their names and default values.
import sys
from PyQt5 import QtCore, QtWidgets
defaultValues = '0010101', '1001010', '000111'
# set bit lengths for each setting; be careful in ensuring that each
# setting group has the full default value bit length!
groups = [
['Group 1', [1, 3, 2, 1]],
['Group 2', [1, 2, 2, 1, 1]],
['Group 1', [2, 1, 2, 1]],
]
class BinaryWidget(QtWidgets.QFrame):
changed = QtCore.pyqtSignal()
def __init__(self, name, index, defaults='0'):
QtWidgets.QFrame.__init__(self)
self.setFrameShape(self.StyledPanel|self.Sunken)
layout = QtWidgets.QGridLayout()
self.setLayout(layout)
self.index = index
self.defaults = defaults
self.buttons = []
# use the "defaults" length to create buttons
for i in range(len(defaults)):
value = int(defaults[i], 2) & 1
# I used QToolButtons as they're usually smaller than QPushButtons
btn = QtWidgets.QToolButton()
btn.setText(str(value))
layout.addWidget(btn, 1, i)
btn.setCheckable(True)
btn.setChecked(value)
btn.toggled.connect(self.changed)
# show the binary value on change, just for conveniency
btn.toggled.connect(lambda v, btn=btn: btn.setText(str(int(v))))
self.buttons.append(btn)
layout.addWidget(QtWidgets.QLabel(name), 0, 0, 1, layout.columnCount())
def value(self):
# return the correct value of all widget's buttons; they're reversed
# because of how bit shifting works
v = 0
for i, btn in enumerate(reversed(self.buttons)):
v += btn.isChecked() << i
# bit shift again, according to the actual "setting" bit index
return v << self.index
def resetValues(self):
oldValue = self.value()
self.blockSignals(True)
for i, value in enumerate(self.defaults):
self.buttons[i].setChecked(int(self.defaults[i], 2) & 1)
self.blockSignals(False)
newValue = self.value()
# emit the changed signal only once, and only if values actually changed
if oldValue != newValue:
self.changed.emit()
class Group(QtWidgets.QWidget):
changed = QtCore.pyqtSignal()
def __init__(self, name, defaults=None, lenghts=None):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
self.name = name
self.bitLength = 0
self.widgets = []
if defaults is not None:
self.addOptions(defaults, lenghts)
def value(self):
v = 0
for widget in self.widgets:
v += widget.value()
return v
def addOption(self, name, index, default='0'):
widget = BinaryWidget(name, index, default)
self.layout().addWidget(widget)
self.widgets.append(widget)
widget.changed.connect(self.changed)
self.bitLength += len(default)
def addOptions(self, defaults, lenghts = None):
if lenghts is None:
lenghts = [1] * len(defaults)
# reverse bit order for per-setting indexing
defaultsIndex = 0
bitIndex = len(defaults)
for i, l in enumerate(lenghts):
self.addOption(
'Setting {}'.format(i + 1),
bitIndex - l,
defaults[defaultsIndex:defaultsIndex + l])
bitIndex -= l
defaultsIndex += l
def resetValues(self):
for widget in self.widgets:
widget.resetValues()
class Tester(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QGridLayout()
self.setLayout(layout)
self.tabWidget = QtWidgets.QTabWidget()
layout.addWidget(self.tabWidget)
resultLayout = QtWidgets.QHBoxLayout()
layout.addLayout(resultLayout, layout.rowCount(), 0, 1, layout.columnCount())
self.tabs = []
self.labels = []
for (group, lenghts), defaults in zip(groups, defaultValues):
tab = Group(group, defaults, lenghts)
self.tabWidget.addTab(tab, group)
tab.changed.connect(self.updateResults)
self.tabs.append(tab)
tabLabel = QtWidgets.QLabel()
self.labels.append(tabLabel)
resultLayout.addWidget(tabLabel)
self.resetButton = QtWidgets.QPushButton('Reset values')
layout.addWidget(self.resetButton)
self.resetButton.clicked.connect(lambda: [tab.resetValues() for tab in self.tabs])
self.updateResults()
def values(self):
return [tab.value() for tab in self.tabs]
def updateResults(self):
for value, tab, label in zip(self.values(), self.tabs, self.labels):
label.setText('''
{0}: <span style="font-family:monospace;">{1} <b>{1:0{2}b}</b></span>
'''.format(tab.name, value, tab.bitLength))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Tester()
w.show()
sys.exit(app.exec_())

Cannot connect PySide QTableView selectionChanged signal

I am designing a program composed of a 3D viewer and a table with Python 3.4 and PySide bindings.
I have created a TableView with this class:
from PySide import QtGui
from PySide.QtCore import Qt
class MyTableView(QtGui.QWidget):
def __init__(self, parent=None):
super(MyTableView, self).__init__()
self.parent = parent
self.title = "Results"
self.initUI()
def initUI(self):
self.grid = QtGui.QGridLayout(self)
self.table = QtGui.QTableView()
self.grid.addWidget(self.table, 0, 0)
# Configure table
self.table.verticalHeader().setVisible(False)
self.table.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)
self.table.setSortingEnabled(True)
self.table.setAlternatingRowColors(True)
self.table.setShowGrid(False)
self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
and the model with this other class:
class MyModel(QStandardItemModel):
def __init__(self, path, *args, **kwargs):
super(MyModel, self).__init__()
self.path = path
self.parse()
def parse(self):
with open(self.path) as f:
self.mydata = yaml.load(f)
self.setColumnCount(len(self.mydata['headers']) + 1)
self.setHorizontalHeaderLabels(
['ID'] + self.mydata['headers'])
row = 0
for ind, val in self.mydata['rows'].items():
col = 0
self.insertRow(row)
self.setItem(row, col, QStandardItem(ind))
for v in val:
col += 1
self.setItem(row, col, QStandardItem(str(v)))
row += 1
which are then tied together in this controller:
from PySide.QtCore import Qt
class MyController(object):
def __init__(self, model, view):
self.model = model
self.tableview = view.table
self.fill_table()
self.connect_signals()
def fill_table(self):
self.tableview.setModel(self.model)
self.tableview.sortByColumn(0, Qt.AscendingOrder)
def connect_signals(self):
selectionModel = self.tableview.selectionModel()
selectionModel.selectionChanged.connect(self.selection_changed)
def selection_changed(self, selected, deselected):
print("Selection changed.")
Then, the program is executed through this script:
def main():
app = QtGui.QApplication(sys.argv)
MyController(MyModel(sys.argv[1]), MyView())
sys.exit(app.exec_())
if __name__ == '__main__':
main()
(Note that I haven't posted the main window class, but you get the idea)
The table gets rendered OK, but I am not able to connect the selectionChanged signal to the handler (which should udpate the viewer, but for testing purposes it's only a print statement).
What am I doing wrong? Thanks!
[EDIT]
I have discovered that it works if I use a lambda function to call the handler method. Can someone explain why?!
selectionModel.selectionChanged.connect(lambda: self.selection_changed(selectionModel.selectedRows()))
I tried to implement what you wrote and it worked - so I can't be 100% sure of why you are having trouble. But I suspect it is because of what I had to fix to get it to work at all: I had to sort out some problems you have with garbage collection.
In the example code you give, you create a MyController, a MyModel and a MyView. But they will all then be garbage collected (in CPython) since you don't keep a reference to them. If you add a reference to MyController
my_controller = MyController(MyModel(sys.argv[1]), MyView())
you are almost there, but I think the MyTableView might also then be garbage collected since the controller only keeps a reference to the QTableVIew not the MyTableView.
Presumably using the lanbda function changes the references you are preserving - it preserves the controller and the selection model - and that may be why it is working in that case.
Generally it's a good idea to use the Qt parenting mechanism. If you simply parented all these objects on the main window (or their natural parent widget) that would have prevented most of these problems.

PyQt - Set model of a proxymodel in QThread

In my project i noticed that the main dialog freezes when it is setting the model of some ProxyModel, so i decided to create a new thread for this task to provide the responsiveness of the window, but right now it keeps popping an error that say:
TypeError: QTableView.setModel(QAbstractItemModel): argument 1 has unexpected type 'tuple'
and i don't know why...
Here's my code:
This is the QThread for updating the proxyModel with the arguments i provide
class ThreadedProxyModel(QThread):
def __init__(self, contacts, contactsProxyModel, groups, groupsProxyModel,
chatSession, chatSessionProxyModel, msgs, msgsProxyModel):
QThread.__init__(self)
self.contacts = contacts
self.contactsProxyModel = contactsProxyModel
self.groups = groups
self.groupsProxyModel = groupsProxyModel
self.chatSession = chatSession
self.chatSessionProxyModel = chatSessionProxyModel
self.msgs = msgs
self.msgsProxyModel = msgsProxyModel
def run(self):
self.contactsProxyModel.setSourceModel(recordsTableModel(self.contacts))
self.contactsProxyModel.setFilterKeyColumn(-1)
self.contactsProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.groupsProxyModel.setSourceModel(recordsTableModel(self.groups))
self.groupsProxyModel.setFilterKeyColumn(-1)
self.groupsProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.chatSessionProxyModel.setSourceModel(recordsTableModel(self.chatSession))
self.chatSessionProxyModel.setFilterKeyColumn(-1)
self.chatSessionProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.msgsProxyModel.setSourceModel(recordsTableModel(self.msgs))
self.msgsProxyModel.setFilterKeyColumn(-1)
self.msgsProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
def getContactsProxyModel(self):
return self.contactsProxyModel,
def getGroupsProxyModel(self):
return self.groupsProxyModel
def getChatSessionProxyModel(self):
return self.chatSessionProxyModel
def getMsgsProxyModel(self):
return self.msgsProxyModel
And this is the method calling the setProxyModel thread in the dialog class. Notice that all the data (contacts, groups, chatsession...) is fine:
def setProxyModel(self):
progress = QProgressDialog("Initializing UI ...", "Abort", 0, 0, self)
progress.setWindowTitle("WhatsApp Browser ...")
progress.setWindowModality(Qt.WindowModal)
progress.setMinimumDuration(0)
progress.setCancelButton(None)
progress.show()
queryTh = ThreadedProxyModel(self.contacts, self.contactsProxyModel, self.groups, self.groupsProxyModel,
self.chatSession, self.chatSessionProxyModel, self.msgs, self.msgsProxyModel,)
queryTh.start()
while queryTh.isRunning():
QApplication.processEvents()
self.contactsProxyModel = queryTh.getContactsProxyModel()
self.groupsProxyModel = queryTh.getGroupsProxyModel()
self.chatSessionProxyModel = queryTh.getChatSessionProxyModel()
self.msgsProxyModel = queryTh.getMsgsProxyModel()
progress.close()
And this is in the init method in my dialog, i create the proxymodels and call the method for updating them in the Thread and then i set them up in various QTableView:
self.contactsProxyModel = QSortFilterProxyModel(self)
self.groupsProxyModel = QSortFilterProxyModel(self)
self.groupMembersProxyModel = QSortFilterProxyModel(self)
self.chatSessionProxyModel = QSortFilterProxyModel(self)
self.chatMsgsProxyModel = QSortFilterProxyModel(self)
self.msgsProxyModel = QSortFilterProxyModel(self)
self.setProxyModel()
self.contactsTableView.setModel(self.contactsProxyModel)
self.contactsTableView.resizeColumnsToContents()
self.groupsTableView.setModel(self.groupsProxyModel)
self.groupsTableView.resizeColumnsToContents()
self.chatSessionTableView.setModel(self.chatSessionProxyModel)
self.chatSessionTableView.resizeColumnsToContents()
self.chatSessionTableView.clicked.connect(self.setChatMsgsProxyModel)
self.chatMsgsTableView.resizeColumnsToContents()
self.groupsTableView.clicked.connect(self.setGroupMembersProxyModel)
self.groupMembersTableView.resizeColumnsToContents()
self.msgsTableView.setModel(self.msgsProxyModel)
self.msgsTableView.resizeColumnsToContents()
Thank you for any advice, i'm pretty stuck...
Not sure, but it seems that
def getContactsProxyModel(self):
return self.contactsProxyModel,
return a tuple, try to delete the comma

Fix values that are typed into a doublespinbox in PyQt

I want to create a doublespin box that changes values in steps of 0.2. But when the user enters a value that is not correct according to the steps. I normalizes that to the nearest correct value.
I tried something like the code shown below but I don't know how to stop values like 0.5 to be entered. Please help me on this.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class SigSlot(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setWindowTitle('spinbox value')
self.resize(250,150)
self.lcd1 = QLCDNumber(self)
self.spinbox1 = QDoubleSpinBox(self)
self.spinbox1.setSingleStep(0.2)
self.spinbox1.setCorrectionMode(1)
# create a Grid Layout
grid = QGridLayout()
grid.addWidget(self.lcd1, 0, 0)
grid.addWidget(self.spinbox1, 1, 0)
self.setLayout(grid)
# allows access to the spinbox value as it changes
self.connect(self.spinbox1, SIGNAL('valueChanged(double)'), self.change_value1)
def change_value1(self, event):
val = self.spinbox1.value()
self.lcd1.display(val)
app = QApplication([])
qb = SigSlot()
qb.show()
app.exec_()
You have two choices:
You can subclass the QSpinBox, override validate method and use an appropriate Q*Validator (e.g. QRegExpValidator) inside.
You can check the value in slot connected to valueChanged before using and correct it if necessary.
Since you are already using the valueChanged signal, second option should be fairly easy to implement. Just change your change_value method like this:
def change_value1(self, val): # new value is passed as an argument
# so no need for this
# val = self.spinbox1.value()
new_val = round(val*5)/5 # one way to fix
if val != new_val: # if value is changed, put it in the spinbox
self.spinbox1.setValue(new_val)
self.lcd1.display(new_val)
By the way, since you are using only one decimal precision, it might be logical to also use:
self.spinbox1.setDecimals(1)
in your __init__. And try to use the new style signals and slots. i.e.:
self.connect(self.spinbox1, SIGNAL('valueChanged(double)'), self.change_value1)
could be written as:
self.spinbox1.valueChanged[float].connect(self.change_value1)
Edit
Subclassing:
class MySpinBox(QDoubleSpinBox):
def __init__(self, parent=None):
super(MySpinBox, self).__init__(parent)
# any RegExp that matches the allowed input
self.validator = QRegExpValidator(QRegExp("\\d+[\\.]{0,1}[02468]{0,1}"), self)
def validate(self, text, pos):
# this decides if the entered value should be accepted
return self.validator.validate(text, pos)
then instead of using QDoubleSpinBox you would use MySpinBox and leave the input checking to this class.
In your change value method you can do something like this
val = round(self.spinbox1.value(), 1)
if val/2*10 - int(val/2*10):
val = round(val, 1) + .1
It's probably not the best way but it works.

How to pass arguments to functions by the click of button in PyQt?

I want to pass the arguments to a function when I click the button. What should I add to this line button.connect(button, QtCore.SIGNAL('clicked()'), calluser(name)) so it will pass the value to the function:
def calluser(name):
print name
def Qbutton():
button = QtGui.QPushButton("button",widget)
name = "user"
button.setGeometry(100,100, 60, 35)
button.connect(button, QtCore.SIGNAL('clicked()'), calluser(name))
One more thing, buttons will be generated using for loop; so name value will vary. So I want to attach each name with the button. I have done same thing in Pytk by using for loop and calling the argument base function when clicked.
You can simply write
name = "user"
button.clicked.connect(lambda: calluser(name))
I tried an efficient way of doing it and it worked out well for me. You can use the code:
from functools import partial
def calluser(name):
print name
def Qbutton():
button = QtGui.QPushButton("button",widget)
name = "user"
button.setGeometry(100,100, 60, 35)
button.clicked.connect(partial(calluser,name))
Usually GUIs are built using classes. By using bound methods as callbacks (see self.calluser below) you can "pass" information to the callback through self's attributes (e.g. self.name):
For example, using slightly modified code from this tutorial:
import sys
import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui
class QButton(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.button = QtGui.QPushButton('Button', self)
self.name='me'
self.button.clicked.connect(self.calluser)
def calluser(self):
print(self.name)
def demo_QButton():
app = QtGui.QApplication(sys.argv)
tb = QButton()
tb.show()
app.exec_()
if __name__=='__main__':
demo_QButton()
Since the callback per se is always called with no additional arguments, when you need to pass distinct additional information to many callbacks, you need to make different callbacks for each button.
Since that can be laborious (if done manually), use a function factory instead. See below for an example. The function factory is a closure. It can be passed additional arguments, which the inner function can access when called:
class ButtonBlock(QtGui.QWidget):
def __init__(self, *args):
super(QtGui.QWidget, self).__init__()
grid = QtGui.QGridLayout()
names = ('One', 'Two', 'Three', 'Four', 'Five',
'Six', 'Seven', 'Eight', 'Nine', 'Ten')
for i, name in enumerate(names):
button = QtGui.QPushButton(name, self)
button.clicked.connect(self.make_calluser(name))
row, col = divmod(i, 5)
grid.addWidget(button, row, col)
self.setLayout(grid)
def make_calluser(self, name):
def calluser():
print(name)
return calluser
app = QtGui.QApplication(sys.argv)
tb = ButtonBlock()
tb.show()
app.exec_()
Here is another way. --- PARTIAL -- I find this one most simple:
widget = QWidget()
widgetLayout = QVBoxLayout()
for action in list:
button = QPushButton("{action}".format(action=action['name']),self)
button.clicked.connect(partial(self.action_selected,action=action['name']))
widgetLayout.addWidget(button)
widget.setLayout(widgetLayout)
def action_selected(self,action):
print action
found on:
http://tech-artists.org/forum/showthread.php?3118-PyQt-Maya-How-to-pass-arguments-to-a-function-when-connecting-it-to-PyQt-button
In Python, class instances are callable. You can just use an instance of a class as a function. That class can contain whatever you want. (In some languages or frameworks, a callable object is called a functor or a function object.)
class CallUser:
def __init__(self, name):
self.name = name
def __call__(self):
print(self.name)
def Qbutton():
button = QtGui.QPushButton("button",widget)
name = "user"
button.setGeometry(100,100, 60, 35)
button.clicked.connect(CallUser(name))
# Object of type CallUser will work as a function!
The code shown below illustrates a method of associating data with generated buttons. For example, you could change the statement self.keydata[b] = key to store a tuple of data into self.keydata[b] for use when processing a button event later.
Note, in the following code, assets is a previously-defined dictionary containing titles for buttons. In the processButton(self) routine, self.sender() is equal to an entry in the class variable buttons[].
class Tab5(QtGui.QWidget):
buttons, keydata = {}, {}
def __init__(self, fileInfo, parent=None):
super(Tab5, self).__init__(parent)
layout = QtGui.QVBoxLayout()
for key in sorted(assets):
b = self.buttons[key] = QtGui.QPushButton(assets[key], self)
b.clicked.connect(self.processButton)
layout.addWidget(b)
print 'b[key]=',b, ' b-text=',assets[key]
self.keydata[b] = key
layout.addStretch(1)
self.setLayout(layout)
def processButton(self):
print 'sender=',self.sender(), ' s-text=',self.sender().text(), ' data[.]=', self.keydata[self.sender()]
pass
Output was like the following, where the first four lines printed during the for loop, and the last four printed when the four buttons were pressed in order.
b[key]= <PySide.QtGui.QPushButton object at 0x7f382f2ca830> b-text= K1
b[key]= <PySide.QtGui.QPushButton object at 0x7f382f2ca908> b-text= K2
b[key]= <PySide.QtGui.QPushButton object at 0x7f382f2ca950> b-text= K3
b[key]= <PySide.QtGui.QPushButton object at 0x7f382f2ca998> b-text= K4
sender= <PySide.QtGui.QPushButton object at 0x7f382f2ca830> s-text= K1 data[.]= L1
sender= <PySide.QtGui.QPushButton object at 0x7f382f2ca908> s-text= K2 data[.]= L2
sender= <PySide.QtGui.QPushButton object at 0x7f382f2ca950> s-text= K3 data[.]= L3
sender= <PySide.QtGui.QPushButton object at 0x7f382f2ca998> s-text= K4 data[.]= L4
You can do with parameters as well,
button.cliked.connect(lambda self.change_value( "extra"))
the function
def change_value( extraVariable):
print( extraVariable)

Categories