Automatically refreshing a QTableView when data changed - c++

I have written a custom data model to be displayed with several QTableViews.
Technically, everything works fine: my views show the changes made from my model. My data model is editable, and the setData() method does emit the dataChanged() signal and returns true on successful edition.
However, my problem is that I have to move my mouse over a QTableView for it to show the actual change, while I would like all views to show the changes when they were made, without needing to interact with the views in order for them to be updated.
Any idea? Thanks,
It may be relevant to mention that I do not use the default Qt::EditRole role to edit data, but rather a custom enum value (named ActiveRole).
Here is what I am seeking: my data model contains properties on how to display data, used to generate style sheets that are fed to the viewS.
Thus, when altering the model, for each view, all its items are impacted, which is why the dataChanged() signal is sent with indices covering all cells.
I also tried to emit layoutChanged(), but it does not seem to change the behavior in my case.
Here is an excerpt of the setData() method:
bool DataModel::setData(QModelIndex const& idx, QVariant const& value, int role)
{
if (ActiveRole == role)
{
// Update data...
QModelIndex topLeft = index(0, 0);
QModelIndex bottomRight = index(rowCount() - 1, columnCount() - 1);
emit dataChanged(topLeft, bottomRight);
emit layoutChanged();
return true;
}
return false;
}
Here is a sample of the data() method:
QVariant DataModel::data(QModelIndex const& idx, int role) const
{
if (ActiveRole == role)
{
boost::uuids::uuid id;
return qVariantFromValue(id);
}
return QVariant();
}
And flags() does indicate an editable model:
Qt::ItemFlags DataModel::flags(QModelIndex const& idx) const
{
if (false == idx.isValid())
{
return Qt::ItemIsEditable;
}
return QAbstractTableModel::flags(idx) | Qt::ItemIsEditable;
}
I have a custom delegate, which relies heavily on this SO thread for overriding the paint and the sizeHint methods in order to draw a QTextDocument. Also, it provides the content of the ActiveRole to the editor in setEditorData, and calls DataMode::setData in setModelData:
void DataModelDelegate::setEditorData(QWidget* editor, QModelIndex const& idx) const
{
auto active = qVariantValue<boost::uuids::uuid>(idx.data(ActiveRole));
static_cast<DataModelEditor*>(editor)->setActive(active);
}
void DataModelDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, QModelIndex const& idx) const
{
auto active = static_cast<DataModelEditor*>(editor)->getActive();
model->setData(idx, qVariantFromValue(active), ActiveRole);
}
In the createEditor(), I plug a signal from the editor to a slot of my delegate for committing data:
QWidget* DataModelDelegate::createEditor(QWidget* parent, QStyleOptionViewItem const& option, QModelIndex const& idx) const
{
auto editor = new DataModelEditor(parent);
connect(editor, SIGNAL(activeItem()), this, SLOT(commitEditorData()));
return editor;
}
When clicking on an item, the editor triggers the activeItem signal; the connected slot commitEditorData in turn raises the commitData signal with the editor in argument.
So all my views use these custom delegate, editor, and data model. The view that I am interacting with does show the change immediately, but the other views need to have the mouse hovering over them to show changes as well.

I actually found the problem, which was that my other view was not properly notified of the data changes: my views each showed different portions of my data, so the other views needed to be notified of the dataChanged(), but for their own, proper, indices.
On a side note, I also had the problem of updating my views while my Qt application was not the active window in my window manager. The solution was to call repaint() on the main window.

I have met the same problem, and let me add a detailed explanation to the piwi's answers. If you change the data,and what to update the single or several columns(or rows,depending on your requirement), you should emit a set of index for topleft to bottomright.For example,if you have a table like below:
and, now you have changed some data, and want to update the cell row 1, column 1-2, then you should emit signal dataChange
emit datachange(index(1,1),index(1,2));

Are you calling the setData()? Is the dataChanged() signal really emitted? Connect some debug logging slot to it. I dare to speculate that this is very similar problem to yours:
http://www.qtcentre.org/threads/18388-Refreshing-a-QTableView-when-QAbstractTableModel-changes?s=fd88b7c4e59f4487a5457db551f3df2c

Related

Update sourceModel() without applying filtering on QSortFilterProxyModel

I have a custom model (QAbstractListModel) and a custom proxy model (QSortFilterProxyModel) working as a filter.
In the view, when I update the model, I need to execute "emit dataChanged(...)" in order to see this changes displayed.
But then, proxy model is automatically updated (filterAcceptsRow is called) and the view applies the corresponding filtering options.
I need to disable this behavior, to be able to update the view and only apply filtering when clicking a button, and not automatically.
For example:
My Model has two fields, id (int) and selected (bool)
In the view, I filter for only those elements that are selected (selected == true)
With this filter applied, I unselect one element.
With current behavior, this element disappears, since filter is applied when model changes. I want to display item with this change and apply filtering only when a button is clicked.
You could implement a public method in your custom proxy model which you use for enabling/disabling the filter. In my example code I'm using Q_INVOKABLE macro to make enableFilter() callable from QML too. So you can enable the filter when a button is clicked by calling the method.
class ExampleFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit ExampleFilterProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) {}
Q_INVOKABLE void setFilter(const QString & pattern) {
QRegularExpression re(pattern, QRegularExpression::CaseInsensitiveOption);
if (re.isValid()) {
QSortFilterProxyModel::setFilterRegularExpression(pattern);
}
invalidateFilter();
}
Q_INVOKABLE void enableFilter(bool enabled) {
m_enabled = enabled;
invalidateFilter();
}
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override {
if (m_enabled) {
QModelIndex index = sourceModel()->index(sourceRow, filterKeyColumn(), sourceParent);
QString str = sourceModel()->data(index, filterRole()).toString();
return str.contains(filterRegularExpression());
}
return true;
}
private:
bool m_enabled = false;
};
When you instantiate the model if you don't explicitly call enableFilter with true the filterAcceptsRow() method will return true for all rows.
myFilterProxyModel->setFilterRole(MyListModel::ExampleRole);
myFilterProxyModel->setFilter("^fullstringmatch$");
myFilterProxyModel->setSourceModel(myModel);
//myFilterProxyModel->enableFilter(true);
If you are using QML then you make the custom proxy model visible to the QML context e.g. by setting it as a context property (e.g. filterProxyModel) and enable the filter by calling
filterProxyModel.enableFilter(true)
Setting dynamic sort filter has worked for me. It was false by default in older versions of QT but I realized that it's true for current version!

Disable QAction if the QTreeView item has no children

I have QTreeView populated from database.
I have contextmenu configured as:
ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
I have the method to look for a right click to open contextMenu on the item.
void MainWindow::on_treeView_customContextMenuRequested(const QPoint &pos)
{
QModelIndex idx = ui->treeView->indexAt(pos);
if (!idx.isValid())
{return;}
else{
QPoint globalPos = ui->treeView->mapToGlobal(pos);
QAction* selectedItem = contextMenu->exec(globalPos);
if (selectedItem){
qDebug () << selectedItem;
}
}
h.file
QMenu *contextMenu;
How do I check if the selected item from QTreeView is not a parent of any item & it has a parent.
Should I include QTreeView and QStandardItem code here to see or that's irrelevant?
The Qt doc. has a dedicated chapter for this topic:
Model/View Programming
which I recommend to get an overview.
Concerning the actual question of OP:
How do I check if the selected item from QTreeView is not a parent of any item & it has a parent.
The QTreeView inherits QAbstractItemView::model() which provides a pointer to the QAbstractItemModel which in turn provides the underlying model data for the rendered tree view items.
Any provided QModelIndex in a view should refer to this model.
The QAbstractItemModel provides a variety of methods to retrieve data concerning visualization and relations of model items. The QTreeView uses this but it should be used as well for any added function.
So, selected item is not parent of any item is turned around into "selected item has no children" for which QAbstractItemModel::hasChildren() is good for:
bool QAbstractItemModel::hasChildren(const QModelIndex &parent = QModelIndex()) const
Returns true if parent has any children; otherwise returns false.
Use rowCount() on the parent to find out the number of children.
Note that it is undefined behavior to report that a particular index hasChildren with this method if the same index has the flag Qt::ItemNeverHasChildren set.
Note: This function can be invoked via the meta-object system and from QML. See Q_INVOKABLE.
See also parent() and index().
and it has a parent can be retrieved using QAbstractItemModel::parent():
QModelIndex QAbstractItemModel::parent(const QModelIndex &index) const
Returns the parent of the model item with the given index. If the item has no parent, an invalid QModelIndex is returned.
A common convention used in models that expose tree data structures is that only items in the first column have children. For that case, when reimplementing this function in a subclass the column of the returned QModelIndex would be 0.
When reimplementing this function in a subclass, be careful to avoid calling QModelIndex member functions, such as QModelIndex::parent(), since indexes belonging to your model will simply call your implementation, leading to infinite recursion.
Note: This function can be invoked via the meta-object system and from QML. See Q_INVOKABLE.
See also createIndex().
Putting this together, OPs function should look like this:
void MainWindow::on_treeView_customContextMenuRequested(const QPoint &pos)
{
QModelIndex idx = ui->treeView->indexAt(pos);
if (!idx.isValid()
|| !ui->treeView->model()->hasChildren(idx)
&& !ui->treeView->model()->parent(idx).isValid()) {
return;
// bail out -> no context menu for leaf nodes or toplevel nodes
} else {
QPoint globalPos = ui->treeView->mapToGlobal(pos);
QAction* selectedItem = contextMenu->exec(globalPos);
if (selectedItem) {
qDebug () << selectedItem;
}
}
}
I'm not quite sure whether this matches exactly OPs required behavior. It might be necessary to fix the condition but this should be not that hard.

QTreeView Edit UserRole Instead of DisplayRole Upon Double Click

In my project, I have a QTreeView displaying items from a QStandardItemModel. Each item has data stored in several UserRoles.
QStandardItem* item = new QStandardItem();
item->setIcon(iconByte);
item->setData(3, Qt::UserRole+1);
item->setData(name, Qt::UserRole+2);
item->setData(data, Qt::UserRole+3);
... and so on
When the user double clicks on an item, a dialog with two line edits displays allowing the user to edit parts of the UserRole data. When editing ceases, the edits run through some logic and a display name is generated based on the new UserRole data.
However, this gets very tedious very quickly. With dialogs constantly popping up and whatnot, it's a slow and ugly solution.
I now would like to remove the dialog completely and show the line edit widgets WITHIN the item itself. By default, double clicking an item to edit it only shows one line edit widget to change the DISPLAY role. However I want two line edits to change the two USER roles. And then the normal logic continues.
How would I go about modifying the edit item portion of a QTreeView?
Thanks for your time!
I would use a custom subclass of QStyledItemDelegate to solve this. Somewhere near your QTreeView you could have a QComboBox switching between the user roles; your custom delegate would somehow be informed which user role is currently selected and would intercept the method updating the data in the model to set the proper role.
An example implementation (not tested, may contain typos and errors):
class RoleSwitchingDelegate: public QStyledItemDelegate
{
public:
explicit RoleSwitchingDelegate(QComboBox * roleSwitcher, QObject * parent = 0);
virtual void setEditorData(QWidget * editor, const QModelIndex & index) const Q_DECL_OVERRIDE;
virtual void setModelData(QWidget * editor, QAbstractItemModel * model,
const QModelIndex & index) const Q_DECL_OVERRIDE;
private:
QComboBox * m_roleSwitcher;
};
RoleSwitchingDelegate::RoleSwitchingDelegate(QComboBox * roleSwitcher, QObject * parent) :
QItemDelegate(parent),
m_roleSwitcher(roleSwitcher)
{}
void RoleSwitchingDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const
{
// Assuming the model stores strings for both roles so that the editor is QLineEdit
QLineEdit * lineEdit = qobject_cast<QLineEdit*>(editor);
if (!lineEdit) {
// Whoops, looks like the assumption is wrong, fallback to the default implementation
QStyledItemDelegate::setEditorData(editor, index);
return;
}
int role = m_roleSwitcher->currentIndex();
QString data = index.model()->data(index, role).toString();
lineEdit->setText(data);
}
void RoleSwitchingDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const
{
// Again, assuming the model stores strings for both roles so that the editor is QLineEdit
QLineEdit * lineEdit = qobject_cast<QLineEdit*>(editor);
if (!lineEdit) {
// Whoops, looks like the assumption is wrong, fallback to the default implementation
QStyledItemDelegate::setModelData(editor, model, index);
return;
}
int role = m_roleSwitcher->currentIndex();
QString data = lineEdit->text();
model->setData(index, data, role);
}
Once you have the delegate, you just need to set it to the view:
view->setItemDelegate(new RoleSwitchingDelegate(roleSwitchingComboBox, view));

QListView & QStandardItemModel check text before editing row

I want to check the text of a row in QListView before the user is editing it. If it doesn't fit a pattern, I don't want to accept it.
Currently I have a QListView and QStandardItemModel. I can easily add and remove items via the QStandardItemModel. I also set the model of the list view.
Are there some delegates or event function(s) on the list or the model for editing?
you can overload data() and setData() functions from QStandardItemModel, then when user tries to edit item your setData will be called with Qt::EditRole and there you can do your processing.
http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#setData
If I understand you correctly, you want to check the value of an item at the time the user attempts to enter the edit mode?
Using a delegate should work for this fairly well:
class MyItemDelegate : public QItemDelegate {
public:
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
if(index.data() == /* do whatever check you want here */) {
return NULL; // Prevent editing
}
return QItemDelegate::createEditor(parent, option, index);
}
};
listView->setItemDelegate(new MyItemDelegate());

QTableView has unwanted checkboxes in every cell

I'm just getting started with Qt programming, and I'm trying to make a simple tabular data layout using a QTableView control with a model class of my own creation inheriting from QAbstractTableModel. For some reason, my table view ends up looking like this:
(source: nerdland.net)
What in the heck are those things that look like checkboxes (but don't do anything when I click them) in every cell, and how do I make them go away? I haven't changed any of the QTableView properties except for the object's name.
If it matters, my model code is dead simple:
MyTableModel::MyTableModel(QObject* parent)
: QAbstractTableModel(parent)
{
}
MyTableModel::~MyTableModel()
{
}
int MyTableModel::rowCount(const QModelIndex& parent) const
{
return 1000;
}
int MyTableModel::columnCount(const QModelIndex& parent) const
{
return 5;
}
QVariant MyTableModel::data(const QModelIndex& index, int role) const
{
return "Foo";
}
The dialog UI is built in Qt Designer, and inside the class for the dialog I attach the model to the view like this:
MyTableModel testModel = new MyTableModel(this);
ui.testTable->setModel(testModel);
Other than that I perform no operations on ui.testTable.
Using Qt 4.6.
Try changing MyTableModel::data() to the following:
QVariant MyTableModel::data(const QModelIndex& index, int role) const
{
if (role == Qt::DisplayRole)
return "foo";
else
return QVariant();
}
Probably the returned QVariant for role Qt::CheckStateRole was misunderstood by the QTableView.
Do you by any chance happen to set the Qt::ItemIsUserCheckable flag in flags()?