QTreeView not displaying items - python

I've created a simple QTreeView widget and I'm subclassing QStandardItemModel but for some reason none of my rows appear to display in the view or print when trying to access it's Data. This seems like a very simple thing. I must be overlooking something in my Model that is making it not work at all. The columns display properly though. I put print statements in the def data(...) but they never trigger, why?
import os, sys, datetime
from Qt import QtCore, QtGui, QtWidgets
class ValidationItem(object):
def __init__(self, message, **kwargs):
super(ValidationItem, self).__init__()
self.status = kwargs.get('status', 'info')
self.message = message
class ValidationItemModel(QtGui.QStandardItemModel):
# Constructor
def __init__(self):
super(ValidationItemModel, self).__init__()
self._items = []
self.setHorizontalHeaderLabels(['Status', 'Message'])
# Overrides
def clear(self):
self.beginResetModel()
self._items = []
self.endResetModel()
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self._items)
def index(self, row, column, parent=QtCore.QModelIndex()):
return self.createIndex(row, column, parent)
def data(self, index, role=QtCore.Qt.DisplayRole):
print 'GETTING DATA'
if not index.isValid():
return None
row = index.row()
col = index.column()
print row, col
item = self.itemByIndex(index.row())
if not item:
return None
print item
if role == QtCore.Qt.UserRole:
return item
return None
# Methods
def itemByIndex(self, index):
print index
if (index < 0 or index >= len(self._items)):
return None
return self._items[index]
def appendItem(self, item):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self._items.append(item)
self.endInsertRows()
print self.rowCount()
class SimpleDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(SimpleDialog, self).__init__(parent=parent)
self.resize(500,300)
self.itemModel = ValidationItemModel()
self.itemModel.appendItem(ValidationItem('Hello world! 1', status='warning'))
self.itemModel.appendItem(ValidationItem('Hello world! 2', status='info'))
self.itemModel.appendItem(ValidationItem('Hello world! 3', status='error'))
self.itemModel.appendItem(ValidationItem('Hello world! 4', status='valid'))
self.proxyModel = QtCore.QSortFilterProxyModel()
self.proxyModel.setSourceModel(self.itemModel)
self.treeView = QtWidgets.QTreeView()
self.treeView.setModel(self.proxyModel)
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.treeView)
self.setLayout(self.layout)
def main():
app = QtWidgets.QApplication(sys.argv)
ex = SimpleDialog()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
UPDATED
Below is my updated code which I got working. The reason for posting this code, if because a user suggested below that i should be customizable QStandardItem instead of the model. I'm hoping he can demonstrate this with my code so i can better understand on how to do this. The main thing to notice is users can double click an item and it will execute the run function of each item accessing it through the UserRole.
import os, sys, datetime
from Qt import QtCore, QtGui, QtWidgets
class ValidationItem(object):
def __init__(self, message, **kwargs):
super(ValidationItem, self).__init__()
self.status = kwargs.get('status', 'info')
self.message = message
def run(self):
print 'STUBBED IN METHOD', self.status, self.message
class ValidationItemURL(ValidationItem):
def __init__(self, message, **kwargs):
super(ValidationItem, self).__init__()
self.status = kwargs.get('status', 'info')
self.message = message
def run(self):
os.startfile('https://stackoverflow.com/')
class ValidationItemModel(QtCore.QAbstractTableModel):
VALIDATION_STATUSES = {
'error': QtGui.QColor(220,70,55),
'warning': QtGui.QColor(240,180,10),
'valid': QtGui.QColor(10,170,70)
}
# Constructor
def __init__(self):
super(ValidationItemModel, self).__init__()
self._items = []
self._headers = ['Status', 'Message']
# Overrides
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self._headers[section]
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def clear(self):
self.beginResetModel()
self._items = []
self.endResetModel()
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self._items)
def columnCount(self, parent):
return len(self._headers)
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
item = self.itemByIndex(index.row())
if not item:
return None
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole:
if column == 0:
return item.status.title()
elif column == 1:
return item.message
elif role == QtCore.Qt.ToolTipRole:
if column == 0:
return item.status.title()
elif column == 1:
return item.message
elif role == QtCore.Qt.ForegroundRole:
if column == 0:
if item.status in self.VALIDATION_STATUSES:
return self.VALIDATION_STATUSES[item.status]
if role == QtCore.Qt.UserRole:
return item
return None
# Methods
def itemByIndex(self, index):
if (index < 0 or index >= len(self._items)):
return None
return self._items[index]
def appendItem(self, item):
if isinstance(item, ValidationItem):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self._items.append(item)
self.endInsertRows()
class SimpleDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(SimpleDialog, self).__init__(parent=parent)
self.resize(500,300)
self.itemModel = ValidationItemModel()
self.itemModel.appendItem(ValidationItem('Hello world! 1', status='warning'))
self.itemModel.appendItem(ValidationItem('Hello world! 2', status='info'))
self.itemModel.appendItem(ValidationItem('Hello world! 3', status='error'))
self.itemModel.appendItem(ValidationItemURL('stackoverflow (double-click to visit)', status='valid'))
self.proxyModel = QtCore.QSortFilterProxyModel()
self.proxyModel.setSourceModel(self.itemModel)
self.treeView = QtWidgets.QTreeView()
self.treeView.setModel(self.proxyModel)
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.treeView)
self.setLayout(self.layout)
# Signals
self.treeView.doubleClicked.connect(self.slotDoubleClickedItem)
# Slots
def slotDoubleClickedItem(self, index):
if index.isValid():
item = index.data(role=QtCore.Qt.UserRole)
if item:
item.run()
def main():
app = QtWidgets.QApplication(sys.argv)
ex = SimpleDialog()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Answer
The reason your UPDATED code works is because you have implemented headerData(). Per the Model Subclassing Reference docs (added emphasis is mine):
To provide read-only access to data provided by a model, the following functions must be implemented in the model’s subclass
flags()
data()
headerData()
rowCount()
These four functions must be implemented in all types of model, including list models (QAbstractListModel subclasses) and table models (QAbstractTableModel) subclasses.
This is the case even if you do not want headers in your view. To hide them, call setHeaderHidden(True) on your QTreeView instance.
For doing more than read-only access, the docs go on to state which additional methods must be implemented.
NOTE: My answer references PySide6 , including its documentation and source code. I do not know which python binding or version of Qt you are using, but my answer should be generally correct.
Aside
#eyllanesc alluded to the fact that your model must notify the view that it has been updated. The best way to know how and where to do this is by reading the Qt C++ source code, but there are also 2 good examples provided by PySide:
Simple (Ready-Only) Tree Model Example
Editable Tree Model Example
QTreeView is a subclass of QAbstractItemView. If you look at the source code for QTreeView.setModel(), you will see it calls QAbstractItemView.setModel(). Both connect up several sets of signals, but the relevant ones are in QAbstractItemView.
QAbstractItemView connects the model signals
dataChanged()
rowsInserted()
rowsAboutToBeRemoved()
modelReset()
to view slots of the same name (except for modelReset(), the view slot for which is simply reset()).
Each of these slots calls viewport.update(), which draws the view.
If you look at the source code for QStandardItemModel, you will see
insertRows() calls rowsInserted
removeRows() calls rowsAboutToBeRemoved
setChild() (and some others) call dataChanged
NOTE: Any functions used in qstandarditemmodel_p.h, qstandarditemmodel.h, or qstandarditemmodel.cpp , but not defined there, are defined in QAbstractItemModel, (either in one of its header files (as an inline function) or its .cpp file), since QStandardItemModel inherits from QAbstractItemModel.
NOTE: C++ allows overloaded functions, whereas Python does not. Instead, in Python, you can specify optional keyword arguments (i.e. those given a default value) to mimic C++ overloading. C++ does not (natively) support keyword arguments.
The consequence of this is, when reading the Qt source, you need to be sure to look at all overloaded methods and choose whether to combine their logic into one method using optional keyword arguments, or use only one of the overloaded methods.
NOTE: Qt practices the Pointer to an Implementation, or pIMPL idiom. This is a kind of language-specific pattern/technique that removes implementation details of a class from its object representation. You can (loosely) think of it as the C++ version of Python modules. In C++, whenever a translation unit ("file") is modified, it and every other translation unit that uses it must be recompiled. This is an expensive and tedious process, esp. for large code bases, and can cause existing user code linked against it to fail. Using a pointer adds a level of indirection that prevents this from happening. (In Python, every .py file is a module and is compiled into byte code (a .pyc file) when executed (the bytecode is run by the interpreter). Changing that file causes only that file to need to be recompiled; the rest of your code will still work with it.) In fact C++20 finally introduced modules into the language.
Qt does this by using what it calls D-Pointers and Q-Pointers. The source for these implementation details are placed in private headers suffixed with _p.h. The names of these classes are suffixed Private as well.
Wherever you see d->..., it is using a private class method. You can think of "d" as standing for detail in "implementation detail."
WARNING: Every method of qstandarditemmodel.cpp calls d->itemFromIndex(), which is inlined in qstandarditemmodel_p.h, but qstandarditemmodel.cpp also defines its own itemFromIndex() to be used by model instances. If you do not use the inlined method, your standard item model will not work.
Note also that the inlined method returns root.data(), which is not the same as the model data method. root is a QScopedPointer of QStandardItems. The QScopedPointer.data() method simply returns the value of the pointer it references, which is the root item. When you see this, simply replace it with your python self.root_item.
When translating to Python, I recommend creating a pseudo-private (i.e. name-mangled) method __itemFromIndex() to be used internally in your method implementations, and "public" one itemFromIndex() to be used externally by instances of your model.
Since dataChanged appears to be the only signal exposed in PySide (rowsInserted and rowsAboutToBeRemoved are methods on the private QStandardItemPrivate class in C++), the most straightforward solution is to emit dataChanged at the end of your clear() and appendRow() methods (immediately before endResetModel() and endInsertRows(), resp.).
In general,
beginInsertRows() emits the rowsAboutToBeInserted() signal (see the docs and the source),
endInsertRows() emits the rowsInserted() signal (see the source)
and similarly for the corresponding Columns() methods.
These should typically be sufficient to update the view, but it's also a good idea to explicitly emit dataChanged() when you do so. In fact, it should be emitted if setData() is successful, but the subclassing docs also state:
The dataChanged() and headerDataChanged() signals must be emitted explicitly when reimplementing the setData() and setHeaderData() functions, respectively.

Related

QCheckBox to check/uncheck all QCheckboxes from another class in PyQt5

I have two QWidget classes: Widget1 and Widget2, and I would like to have a QCheckBox in the first class (Widget1) that can check/uncheck all the QcheckBoxes generated by the second class (Widget2). Is there a way to do this?
Many thanks in advance for your help.
class Widget1(QWidget):
def __init__(self, ids):
super().__init__()
self.ids = ids # ids is a list of list [[1, 1], [2, 2], [3, 3], ..., [n, n] generated elsewhere in the code
self.initUI()
def initUI(self):
self.widget1_layout = QVBoxLayout()
self.setLayout(self.Widget1_Layout)
self.master_checkbox = QCheckBox("Select all")
self.master_checkbox.stateChanged.connect(self.selectAll)
self.widget1_layout.addWidget(self.master_checkbox)
for i, id in enumerate(self.ids):
self.singleID_checkbox = Widget2(self, i, id)
self.widget1_layout.addWidget(self.singleID_checkbox)
def selectAll(self):
if self.master_checkbox.isChecked():
function_that_check_all_Widget2_checkboxes()
else:
function_that_UNcheck_all_Widget2_checkboxes()
class Widget2(QWidget):
def __init__(self, parent, i, id, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.i = i
self.id = id
self.initUI()
def initUI(self):
self.singleIDcheckbox_layout = QGridLayout(self)
self.singleIDcheckbox = QCheckBox(str(self.id))
self.singleIDcheckbox_layout.addWidget(self.singleIDcheckbox, self.i, 0)
The two functions
function_that_check_all_Widget2_checkboxes()
and
function_that_UNcheck_all_Widget2_checkboxes()
do not exist.
They are are here as examples to better present my problem, as I guess that this is where I should put some code to do what I'd like to achieve.
The simplest solution is to add each widget to a list whenever you're creating them, then check/uncheck them according to the "master" state.
Note that:
when creating multiple objects there's no use in setting them as instance members: the purpose of an instance attribute is to have a persistent reference to an object, if you continously overwrite that reference you lose that benefit;
most signals provide arguments, especially those relating "changes"; calling isChecked() is unnecessary, as stateChanged already returns the current state, you just need to add the argument to the function;
unless you're requiring a tristate checkbox, the correct signal is toggled (which returns a bool state), not the stateChanged (which returns a Qt.CheckState flag that has 2 for the checked state);
class Widget1(QWidget):
# ...
def initUI(self):
self.widget1_layout = QVBoxLayout(self)
self.master_checkbox = QCheckBox("Select all")
self.master_checkbox.toggled.connect(self.selectAll)
self.widget1_layout.addWidget(self.master_checkbox)
self.checkboxes = []
for i, id in enumerate(self.ids):
singleID_checkbox = Widget2(self, i, id)
self.widget1_layout.addWidget(singleID_checkbox)
self.checkboxes.append(singleID_checkbox)
def selectAll(self, state):
for check in self.checkboxes:
check.setChecked(state)
class Widget2(QWidget):
# ...
def setChecked(self, state):
self.singleIDcheckbox.setChecked(state)
Note that, since you're using a specialized class, you could also use findChildren, which returns all child objects that are instances of the specified class:
def selectAll(self, state):
for check in self.findChildren(Widget2):
check.setChecked(state)
Use the above with care, though, as by default it looks recursively through the whole object tree, so using a list is still a better solution.

What is the proper way to write getter and setter in Python?

I am from the C# background.
I am trying to write getter and setters methods. In other words, I am trying to create properties for a class.
class ParamDefinition:
def __init__(self, type_name):
self.__type_name = type_name
#property
def get_type_name(self):
return self.__type_name
#get_type_name.setter
def set_type_name(self, type_name):
self.__type_name = type_name
def printf(self):
print(self.__type_name)
def test():
compDef = ParamDefinition(None)
compDef.type_name = "int"
compDef.printf()
if __name__ == "__main__":
test()
Output:
None
What is incorrect with my property-definition?
Both the getter and setter need to have the same name:
class ParamDefinition:
def __init__(self, type_name):
self.__type_name = type_name
#property
def type_name(self):
return self.__type_name
#type_name.setter
def type_name(self, type_name):
self.__type_name = type_name
def printf(self):
print(self.__type_name)
def test():
compDef = ParamDefinition(None)
compDef.type_name = "int"
compDef.printf()
if __name__ == "__main__":
test()
Output:
int
EDIT:
While this isn't technically part of the question I thought I should add some more thoughts. You say you are from a C# background, and in C# it's fairly normal to make every field a property by default since you can add extra logic later without breaking the interface, and allows reflection.
These advantages don't really exist with Python and as you can see the boilerplate needed to turn a field into a property is much more significant than in C#. Therefore I'd recommend not using properties in Python until it's really what you need.

How would I 'listen' to/decorate a setter from an imported class

I'm not sure whether this is a great approach to be using, but I'm not hugely experienced with Python so please accept my apologies. I've tried to do some research on this but other related questions have been given alternative problem-specific solutions - none of which apply to my specific case.
I have a class that handles the training/querying of my specific machine learning model. This algorithm is running on a remote sensor, various values are fed into the object which returns None if the algorithm isn't trained. Once trained, it returns either True or False depending on the classification assigned to new inputs. Occasionally, the class updates a couple of threshold parameters and I need to know when this occurs.
I am using sockets to pass messages from the remote sensor to my main server. I didn't want to complicate the ML algorithm class by filling it up with message passing code and so instead I've been handling this in a Main class that imports the "algorithm" class. I want the Main class to be able to determine when the threshold parameters are updated and report this back to the server.
class MyAlgorithmClass:
def feed_value(self):
....
class Main:
def __init__(self):
self._algorithm_data = MyAlgorithmClass()
self._sensor_data_queue = Queue()
def process_data(self):
while True:
sensor_value = self._sensor_data_queue.get()
result, value = self._algorithm_data.feed_value(sensor_value)
if result is None:
# value represents % training complete
self._socket.emit('training', value)
elif result is True:
# value represents % chance that input is categoryA
self._socket.emit('categoryA', value)
elif result is False:
...
My initial idea was to add a property to MyAlgorithmClass with a setter. I could then decorate this in my Main class so that every time the setter is called, I can use the value... for example:
class MyAlgorithmClass:
#property
def param1(self):
return self._param1
#param1.setter
def param1(self, value):
self._param1 = value
class Main:
def __init__(self):
self._algorithm_data = MyAlgorithmClass()
self._sensor_data_queue = Queue()
def watch_param1(func):
def inner(*args):
self._socket.emit('param1_updated', *args)
func(*args)
My problem now, is how do I decorate the self._algorithm_data.param1 setter with watch_param1? If I simply set self._algorithm_data.param1 = watch_param1 then I will just end up setting self._algorithm_data._param1 equal to my function which isn't what I want to do.
I could use getter/setter methods instead of a property, but this isn't very pythonic and as multiple people are modifying this code, I don't want the methods to be replaced/changed for properties by somebody else later on.
What is the best approach here? This is a small example but I will have slightly more complex examples of this later on and I don't want something that will cause overcomplication of the algorithm class. Obviously, another option is the Observer pattern but I'm not sure how appropriate it is here where I only have a single variable to monitor in some cases.
I'm really struggling to get a good solution put together so any advice would be much appreciated.
Thanks in advance,
Tom
Use descriptors. They let you customize attribute lookup, storage, and deletion in Python.
A simplified toy version of your code with descriptors looks something like:
class WatchedParam:
def __init__(self, name):
self.name = name
def __get__(self, instance, insttype=None):
print(f"{self.name} : value accessed")
return getattr(instance, '_' + self.name)
def __set__(self, instance, new_val):
print(f"{self.name} : value set")
setattr(instance, '_' + self.name, new_val)
class MyAlgorithmClass:
param1 = WatchedParam("param1")
param2 = WatchedParam("param2")
def __init__(self, param1, param2, param3):
self.param1 = param1
self.param2 = param2
self.param3 = param3
class Main:
def __init__(self):
self._data = MyAlgorithmClass(10, 20, 50)
m = Main()
m._data.param1 # calls WatchedParam.__get__
m._data.param2 = 100 # calls WatchedParam.__set__
The WatchedParam class is a descriptor and can be used in MyAlgorithmClass to specify the parameters that need to be monitored.
The solution I went for is as follows, using a 'Proxy' subclass which overrides the properties. Eventually, once I have a better understanding of the watched parameters, I won't need to watch them anymore. At this point I will be able to swap out the Proxy for the base class and continue using the code as normal.
class MyAlgorithmClassProxy(MyAlgorithmClass):
#property
def watch_param1(self):
return MyAlgorithmClass.watch_param1.fget(self)
#watch_param1.setter
def watch_param1(self, value):
self._socket.emit('param1_updated', *args)
MyAlgorithmClass.watch_param1.fset(self, value)

Passing a python list of objects to qml

I am trying to pass a list of objects from python to qml. on the qml side, I will interpret this information and using repeater and listmodel element, display these information in a table like manner.
if i simply pass an object or a list of integers, i could read the information on the qml side. but otherwise when trying to pass a list of objects. how can i read a list of objects on the qml side? do i have to use different properties?
below is what i have so far:
class File(QObject):
def __init__(self, fileName, commentsStatus, diagnosisStatus, parent=None):
QObject.__init__(self, parent)
self.fileName = fileName
self.commentsStatus = commentsStatus
self.diagnosisStatus = diagnosisStatus
class DisplayComponent(QObject):
def __init__(self, parent = None):
QObject.__init__(self, parent)
self.list = [File("file 1", True, False), File("file 2", False, True)]
#pyqtProperty(QQmlListProperty)
def getDicomFilesList(self):
return QQmlListProperty(File, self, self.list)
exposing to the qml side the following way:
context.setContextProperty("dicomFiles", displayComponent)
and this is how i am reading the list on the qml side:
HanaContainer {
Text {
id: display
text: "no signal detected yet"
}
Component.onCompleted: {
console.log(dicomFiles.getDicomFilesList[1]) // prints File(0x7f8a6d454c70)
console.log(dicomFiles.getDicomFilesList[1].fileName) // prints undefined
}
}
ps: am completely new to Qml and Qt5. if i am making any fundamental errors in my concepts, please do let me know
For an attribute to be visible in qml this must be a property, for this you should use pyqtProperty as shown below:
class File(QObject):
def __init__(self, fileName, commentsStatus, diagnosisStatus, parent=None):
QObject.__init__(self, parent)
self._fileName = fileName
self._commentsStatus = commentsStatus
self._diagnosisStatus = diagnosisStatus
#pyqtProperty(str)
def fileName(self):
return self._fileName
#pyqtProperty(bool)
def commentsStatus(self):
return self._commentsStatus
#pyqtProperty(bool)
def diagnosisStatus(self):
return self._diagnosisStatus
The above will make the attribute only readable, if we want to be editable implementetar setters, eg:
#fileName.setter
def fileName(self, value):
self._fileName = value

python: multiple objects that can modify the attributes of a single 'master' object

Suppose I have a single instance of a master object that contains a set of parameters, then multiple instances of slave objects that should be able to access and modify the attributes of the master. In my particular case the slaves are wxPython GUI objects, where there might be multiple GUI controls that can modify the same parameter.
Obviously this is trivial if I explicitly refer to master.parameter when the slave wants to update it. However I would really prefer not to have to do this, since it would require me to write different functions to handle events from each slave object.
I'm currently doing something like this, with the master having separate get_value and set_value methods for each property:
class Master(object):
def __init__(self):
self.value = 0
def get_value(self):
return self.value
def set_value(self,value):
self.value = value
class Slave(object):
def __init__(self,getfunc,setfunc):
self.getfunc = getfunc
self.setfunc = setfunc
def update(self,value):
self.setfunc(value)
def run():
master = Master()
slave = Slave(master.get_value,master.set_value)
print "\nMaster: %i\nSlave: %i" %(master.value,slave.getfunc())
slave.update(1)
print "\nMaster: %i\nSlave: %i" %(master.value,slave.getfunc())
if __name__ == "__main__":
run()
What I really want to be able to do is set something like slave.bound_value, which would behave like a pointer to master.value, so that when any slave modifies its bound_value then the corresponding attribute of master is updated. I'm fully aware that Python doesn't support pointers, but I was wondering if there's a nice Pythonic way to achieve the same thing?
You can accomplish what your are asking by making bound_value a property attribute that wraps the calls to the master's get_value and set_value methods. This will make it appear that bound_value is just a member variable of the Slave class.
class Slave(object):
def __init__(self,getfunc,setfunc):
self.getfunc = getfunc
self.setfunc = setfunc
#property
def bound_value(self):
return self.getfunc()
#bound_value.setter
def bound_value(self, value):
self.setfunc(value)
Then, you can simply refer to slave.bound_value like it is a member variable whenever you want to get or set the value.
Thanks to bogatron for setting me on the right path - here's what I opted for in the end:
class Master(object):
value = 0
class Slave(object):
def __init__(self,master,attrname):
self._master = master
self._attrname = attrname
#property
def value(self):
return self._master.__getattribute__(self._attrname)
#value.setter
def value(self,newvalue):
self._master.__setattr__(self._attrname,newvalue)
def run():
master = Master()
slave = Slave(master,'value')
print master.value,slave.value
slave.value = 2
print master.value,slave.value
if __name__ == "__main__":
run()

Categories