I have a layout that I add a widget, class called ColList. When I run widget.children() I get an expected result, a list of widget. however when I try to run a method of ColList or access a variable pyqt crashes. For now I am trying to run a my sanity check method each ColList instance
I am using two .ui files. if you want me to post them I can. this is the (reduced) code I am trying to get to work
class ColList(QWidget):
def __init__(self, col, parent=None):
super().__init__(parent)
# Load the GUI
uic.loadUi("Listwidget.ui", self)
self.title = col
self.groupBox.setTitle(col)
print(self.parent())
def SC(self):
print("hi")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
uic.loadUi("MainWindow.ui", self)
self.columns = ['a', 'b']
self.loadTags
def loadTags(self):
for i in self.columns:
# TagsH is the name of a Horizontal layout that is itself a child of a QScrollarea named scrollFilters
self.TagsH.insertWidget(self.TagsH.count() - 1, ColList(f"{i}", parent=self))
def applyFilters(self):
# the print works it outputs: [<PyQt6.QtWidgets.QWidget object at 0x00000221161E2830>, <PyQt6.QtWidgets.QWidget object at 0x00000221161E28C0>]
print(self.scrollFilters.children()[1:])
# this print outputs an empty list
print(self.TagsH.children())
for i in self.scrollFilters.children()[1:]:
# this part seems to crash the program
i.widget().SC()
Situation: I have a function, I want to have a child with only one different default parameter. Is it possible to not rewrite every parameters and only the one that I want to change ?
class Weapon():
def __init__(self, weight=12, ammo=22, name="shotgun",useful=True, broken=False, needed_lvl=5, range=2, price=233):
pass # A LOT OF STUFF NOT INTERESTING FOR THIS EXAMPLE
WORKING ANSWER BUT NOT EFFICIENT: useless repeated information and can be easily break if one thing change in the parent.
class ShotGun(Weapon):
def __init__(self, weight=12, ammo=12, name="shotgun",useful=True, broken=False, needed_lvl=5, range=2, price=233):
pass # A LOT OF STUFF NOT INTERESTING FOR THIS EXAMPLE
WANTED ANSWER: If the user doesn't change anything, the default value of the parameter ammo should be 12.
class ShotGun(Weapon):
def __init__(self, **kwargs):
super().__init__(**kwargs)
pass # A LOT OF STUFF NOT INTERESTING FOR THIS EXAMPLE
You can override only the specific argument. Just pass everything else to super().__init__(). But you still need to set the overriden attribute in the child class for the new default value to have any effect:
class Weapon():
def __init__(self, weight=12, ammo=22, name="shotgun",useful=True, broken=False, needed_lvl=5, range=2, price=233):
self.ammo = ammo
self.name = "shotgun"
class ShotGun(Weapon):
def __init__(self, ammo=12, **kwargs): # override only ammo
super().__init__(**kwargs)
self.ammo = ammo # use overriden value instead of parent
s = ShotGun()
print(s.ammo)
print(s.name)
Ouput:
12
shotgun
I have three classes Box, Tab and UI which the first two are being called in the UI. I want to get the box instances (in this case Ghi and Dni) and change their items in a function (as you can see the describtion in the get_boxes() function).
class Box():
def __init__(self):
self.name = ''
self.status = 'no'
class Tab():
def __init__(self):
self.name = ''
class UI():
def __init__(self):
self.__setupui()
def __setupui(self):
self.Ghi = Box()
self.Ghi.name = 'Ghi'
self.Ghi.status = 'yes'
self.Dni = Box()
self.Dni.name = 'Dni'
self.tab = Tab()
self.tab.name = 'tab1'
def get_boxes(self):
# get the Box instances in the UI (such as Ghi and Dni)
# change their status
return # list of Box instances name
ui_sample = UI()
How can I define get_boxes() function? or in general how can I loop trough self.objects and check their type then change their values inside of a class?
I would assign all the items to a list as they are created so it is easy to iterate, like self.controls or something.
You can use python'stype function to get the typename of an object if you only want to perform some actions for some controls in the list.
Otherwise you would probably use dir(self) or self.__dict__ to iterate all the members, and then you would have to do type checking.
To elaborate more on #jnnnnn answer get_boxes will look like this:
def get_boxes(self):
return [a for a in self.__dict__ if isinstance(a, Box)]
Or even better will be to setup things as follows:
class UI():
def __init__(self):
self.boxes = []
self.__setupui()
def __setupui(self):
Ghi = Box()
Ghi.name = 'Ghi'
Ghi.status = 'yes'
self.boxes.append(Ghi)
Dni = Box()
Dni.name = 'Dni'
self.boxes.append(Dni)
self.tab = Tab()
self.tab.name = 'tab1'
def get_boxes(self):
return self.boxes
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.
I have a script using threaded timers that manipulates 2 common lists at random.
Because the class instances manipulate the lists on threaded timers, I cannot pass the variables to the classes & back.
…All instances of the classes need to manipulate a single, up to date list.
Because of that, the scope of those lists are set to global. However, I need the scope to be at the class level, yet be manipulated by multiple classes.
To clarify...
Here's the basic program structure:
Global list variable_1
Global list variable_2
class MasterClass:
# this creates instances of the threaded classes.
There are 50+ instances of MasterClass creating thousands
of instances of ThreadedClass1, 2, & 3. All manipulate
global list variables 1 & 2.
class ThreadedClass1:
# threaded classes manipulate global list variables 1 & 2 on random timers.
class ThreadedClass2:
class ThreadedClass3:
The problem: For each instance of MasterClass I need a separate list variable 1 & 2. Each instance of ThreadedClasses called by that instance of MasterClass must manipulate only the list variables owned by that instance of MasterClass.
Basically I need the equivalent of a global list variable, but I need it to be encapsulated by an instance of MasterClass, and be manipulated by any instance of ThreadedClasses called by that instance of MasterClass only.
How's this done?
Try to pass instance of MasterClass to every produced instance of ThreadedClasses.
Then, define thread save methods in MasterClass, that will perform manipulation with your variable_1, variable_2. ThreadedClasses shall not touch this lists directly, only by calling those methods.
Small example (check subclassing from object):
import threading
class ThreadedClassBase(object):
def __init__(self, master, *args, **kwargs):
self.master = master
def do_something(self):
self.master.append(1, 'some_value')
value = self.master.getitem(1, 0)
class ThreadedClass1(ThreadedClassBase):
def __init__(self, *args, **kwargs):
super(ThreadedClass1, self).__init__(*args, **kwargs)
# ...
# same for ThreadedClass2, 3
class MasterClass(object):
def __init__(self, *args, **kwargs):
self.variable_1 = list()
self.variable_2 = list()
self.lock = threading.Lock()
for i in range(50):
ThreadedClass1(master=self)
# create new thread
def append(list_nb, value):
with self.lock:
getattr('variable_' + list_nb).append(value)
def getitem(list_nb, index):
with self.lock:
return getattr('variable_' + list_nb)[index]
If I understand correctly, you should be able to make them instance variables of MasterClass and pass them into the constructors.
eg.
class MasterClass:
def __init__(self):
self.variable_1 = [...]
self.variable_2 = [...]
self.tc1 = ThreadedClass1(self.variable_1, self.variable_2)
self.tc2 = ThreadedClass2(self.variable_1, self.variable_2)
self.tc3 = ThreadedClass3(self.variable_1, self.variable_2)
Alternatively pass the whole instance in
class MasterClass:
def __init__(self):
self.variable_1 = [...]
self.variable_2 = [...]
self.tc1 = ThreadedClass1(self)
self.tc2 = ThreadedClass2(self)
self.tc3 = ThreadedClass3(self)
class ThreadedClass1:
def __init__(self, parent):
self.mc = parent
etc.