When does Qheaderview get its model? - qt

You can access the underlying model of a Headerview with self.model(), but when you use this in the constructor it returns None.
For example, this will print 'None'
class MyHeaderView(QHeaderView):
def __init__(self, orientation, parent):
super().__init__(orientation, parent)
print(self.model())
The Headerview is set in the constructor of a QTableView subclass, which at that time already has its model set.
self.setHorizontalHeader(MyHeaderView(Qt.Horizontal, self))
So it should be able to know what its model is during construction, but it doesn't seem to. When the GUI is running, the model of the headerview can be accessed without problems.
Why does this happen? and When does the model become available?

The parent argument alone is not enough to set the model (since the parent could be any kind of widget, not only an item view), but it's important to add it in order to properly "initialize" it with the widget's style, palette, font, etc.
The model is actually set only when the header is set on the view (setHeader() for tree views, setHorizontalHeader() and setVerticalHeader() for tables), but only if no other model is already set on the header.
The model is again set when setModel() is called on the view, and in this case any previous model is replaced with the new one for the headers.
If you want to do something with the model (for instance, connect to a custom function when rows or columns are added/removed/moved), you should override setModel() of the view:
class MyHeaderView(QHeaderView):
def setModel(self, newModel):
currentModel = self.model()
if currentModel == newModel:
return
elif currentModel:
currentModel.rowsInserted.disconnect(self.myFunction)
currentModel.rowsRemoved.disconnect(self.myFunction)
currentModel.rowsMoved.disconnect(self.myFunction)
super().setModel(newModel)
if newModel:
newModel.rowsInserted.connect(self.myFunction)
newModel.rowsRemoved.connect(self.myFunction)
newModel.rowsMoved.connect(self.myFunction)
def myFunction(self):
# do something

Related

Notify user of unsaved changes across fields of different types

My PyQt5 application contains a bunch of input fields of different types: QLineEdit, QSpinBox, QComboBox, QTableView etc.
I want to alert the user if they input data or change the content of one or more of the fields and try to close the window without saving.
Do I have to connect all the different variations of the textChanged signal to some kind of bookkeeping function, or is there an easier way?
EDIT: More details on sequence of events:
The UI gets build from a Qt Designer .ui file, so some fields have default values (like QSpinBox, QDateEdit)
The models of different QTableViews get initialized with certain default data structures, like a 2d array of None, or a dict whose keys all return None
A bunch of documents get loaded from a document store, and the fields are set to the values of these documents. It might happen that no corresponding key exists in the document, so the field won't be set. But it's also possible that a value in the documents just happens to be the default value.
The user changes the content of some of the fields, and on saving the document in the store will be updated accordingly.
I want to be able to tell if any field has been modified after step 3 which is to say a user made change. I'd like to avoid having to compare all fields against the document store.
For simple widgets that store a single property, the solution is to user the user property of the meta object of each widget.
Item views require a custom checking to compare the model.
You need to create a list of the widgets you need to monitor for changes, get the value during initialization and then verify it when closing.
To simplify things, you can set a dynamic property for all widgets that need checking, so that you can iter through all widgets, check if the property is set, and add those widgets to the list. To add a custom property to a widget in Designer, select the widget and click on the "+" symbol in the property editor; in the following example I used a boolean property ("validate") with a basic truthfulness check: remember that properties that are not set return None, so in this case you must also set the property to True.
class Test(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
uic.loadUi('mapper.ui', self)
self.loadData()
def loadData(self):
# some stuff setting the initial values
# ...
# save the current values
self.fieldData = []
for widget in self.findChildren(QtWidgets.QWidget):
if not widget.property('validate'):
continue
if isinstance(widget, QtWidgets.QAbstractItemView):
model = widget.model()
if not model:
continue
data = []
for row in range(model.rowCount()):
rowData = []
data.append(rowData)
for column in range(model.columnCount()):
rowData.append(model.index(row, column).data())
self.fieldData.append((widget, data))
else:
property = widget.metaObject().userProperty()
self.fieldData.append((widget, widget.property(property.name())))
def ignoreChanges(self):
for widget, default in self.fieldData:
if isinstance(widget, QtWidgets.QAbstractItemView):
model = widget.model()
for row, rowData in enumerate(default):
for column, itemData in enumerate(rowData):
if model.index(row, column).data() != default:
break
else:
property = widget.metaObject().userProperty()
if widget.property(property.name()) != default:
break
else:
return True
res = QtWidgets.QMessageBox.question(self, 'Ignore changes',
'Fields have been modified, do you want to ignore?',
QtWidgets.QMessageBox.Ok|QtWidgets.QMessageBox.Cancel)
if res != QtWidgets.QMessageBox.Ok:
return False
return True
def reject(self):
if self.ignoreChanges():
super().reject()
def closeEvent(self, event):
if not self.ignoreChanges():
event.ignore()

How to always expand items in QTreeView?

I am creating a completer myself, using ComboBox and QTreeView (for the proposal list).
MyComboBox::MyComboBox( QWidget *p_parent ) : QComboBox( p_parent )
{
setEditable(true);
m_view = new QTreeView();
m_view->expandAll(); // this command does not work!!!
m_view->setItemDelegate( new CompleterDelegate(m_view));
CompleterSourceModel *m_sourceModel = new CompleterSourceModel(this);
CompleterProxyModel *m_proxyModel = new CompleterProxyModel(this);
m_proxyModel->setSourceModel(m_sourceModel);
setView(m_view);
setModel(m_proxyModel);
connect(this, &QComboBox::currentTextChanged, this, &MyComboBox::showProposalList);
}
The structure of my data for the tree model here is parent-child. With the constructor above, after I put my data into the model, the children are hidden, only the parents can be seen.
In order to see all the items (children) I have to use m_view->expandAll() after I put the data into the model. Is there any way that we can do it in constructor, so each time i put data into the model (whatever my data is), all the items (parents and children) are automatically expanded ?
Your best bet is probably to connect to the QAbstractItemModel::rowsInserted signal to make sure items are expanded on a just-in-time basis. So, immediately after setting the view's model use something like...
connect(m_view->model(), &QAbstractItemModel::rowsInserted,
[this](const QModelIndex &parent, int first, int last)
{
/*
* New rows have been added to parent. Make sure parent
* is fully expanded.
*/
m_view->expandRecursively(parent);
});
Edit: It was noted in the comments (#Patrick Parker) that simply calling m_view->expand(parent) will not work if the row inserted has, itself, one or more levels of descendants. Have changed the code to use m_view->expandRecursively(parent) (as suggested by #m7913d) to take care of that.

PyQt Custom QSorfFilterProxyModel for filtering checked items

I am making a qt application with PyQt. I have a custom QTableModel that i show in a QTableView. Each row in this model can be checked, when a row is checked I want it to appear in another QTableView. To do this I made a custom proxymodel subclassing from QSortFilterProxyModel.
In my custom model I reimplemented the filterAcceptsRow method to only accept rows that are checked:
def filterAcceptsRow(self, pos, index):
model = self.sourceModel() # The sourcemodel is a 2d array of QStandardItem
row = model.row(pos) # This method returns the row at index pos
if row[0].checkState(): # The first QStandardItem has a checkbox
return True
return False
This works once at the start of my program (I assume when i call setSourceModel) but doesn't update when the checked items change. How do I make sure this function gets called when the checkstate changes in my base model? Or is there another way to do this?

Select in QTableView from QAbstractTableModel item

I created TableView (by inhereting from QTableView) and Model (by inhereting from QAbstractTableModel) and implemented all their functions that I needed, but now I have to add strange feature - those objects (stored in models) have to be able to cause table view to select "their" row.
It comes from the fact that there is always graphic object related to them and whenever I click on a scene on a certain object, I wish to center on its representation in table view. Can I do this?
You model could implement a signal emitted every time you want to change the selection. Something like this:
void CMyModel::sigUpdateSelection(const QItemSelection & selection, QItemSelectionModel::SelectionFlags flags);
And than you could connect this signal to the QItemSelectionModel of your table view. This is how you get the selection model:
QTableView* view = new QTableView(parent);
QItemSelectionModel* selectionModel = view->selectionModel();
QItemSelectionModel has a slot select(). This is where you will connect your signal.
And this is how you will emit:
// Add to current selection
emit sigUpdateSelection(QItemSelection(indexLeft, indexRight), QItemSelectionModel::Select);
// Clear current selection and select new one
emit sigUpdateSelection(QItemSelection(indexLeft, indexRight), QItemSelectionModel::ClearAndSelect);

How to set data inside a QAbstractTableModel

I need to implement a table with Qt.
I believe I'll be suing a QAbstractTableModel, with a QTableView using this model.
I understand I'll have to edit the rowCount(), columnCount(), and data() functions of the model.
However, I don't understand how to exactly set the data inside the model, so that data() function can retrieve it..
Is the setData() function provided for this purpose? I have seen it takes EditRole as its parameter, which I don't want, as I don't want my table to be editable.
So, how do I "set" data inside the model, or have data for the model to get at, using data() function?
Also, how is the data() function called, i.e., who calls it and where would it need to be called?
Please help me with this.
Thanks.
How the actual data is kept in memory, generated or queried from a data store is completely up to you. If it's static data, you can use the Qt container classes or custom data structures.
You only need to reimplement the setData() method for editable models.
There are 4 methods you need to implement in a non-editable QAbstractTableModel subclass:
int rowCount()
int columnCount()
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole )
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole)
These methods are called from the view, usually a QTableView instance. The first two methods should return the dimensions of the table. For example, if rowCount() returns 10 and columnCount() returns 4, the view will invoke the data() method 40 times (once for each cell) asking for the actual data in your model's internal data structures.
As an example suppose you have implemented a custom slot retrieveDataFromMarsCuriosity() in your model. This slot populates a data structure and is connected to a QPushButton instance, so you get fresh data by clicking a button.
Now, you need to let the view know when the data is being changed so it can update properly. That's why you need to emit the beginRemoveRows(), endRemoveRows(), beginInsertRows(), endInsertRows() and its column counterparts.
The Qt Documentation has everything you need to know about this.
You don't need to use setData(...). Instead, you need to subclass QAbstractTableModel in such a way that its methods rowCount(), columnCount(), data(index) and potentially headerData(section, horizontalOrVertical) return the data you wish to display. Here's an example based on PyQt5:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
headers = ["Scientist name", "Birthdate", "Contribution"]
rows = [("Newton", "1643-01-04", "Classical mechanics"),
("Einstein", "1879-03-14", "Relativity"),
("Darwin", "1809-02-12", "Evolution")]
class TableModel(QAbstractTableModel):
def rowCount(self, parent):
# How many rows are there?
return len(rows)
def columnCount(self, parent):
# How many columns?
return len(headers)
def data(self, index, role):
if role != Qt.DisplayRole:
return QVariant()
# What's the value of the cell at the given index?
return rows[index.row()][index.column()]
def headerData(self, section, orientation, role):
if role != Qt.DisplayRole or orientation != Qt.Horizontal:
return QVariant()
# What's the header for the given column?
return headers[section]
app = QApplication([])
model = TableModel()
view = QTableView()
view.setModel(model)
view.show()
app.exec_()
It's taken from this GitHub repository and displays the following table:

Resources