Working with a QTableView and QAbstractTableModel - when the model emits a dataChanged event for the cell being edited, the string the user has typed in the cell (but not pressed enter to 'commit' the edit) is erased.
Example: Click a cell, type '123', cell is still in edit mode waiting for more text, dataChanged is emitted and the '123' is erased, leaving an empty cell in edit mode.
Does anyone know how to stop this behaviour, or how the model can detect when the cell is being edited to prevent dataChanged events being raised for that cell?
I had the same problem. The thing is, that data() function is called with different role parameter. For displaying role==Qt::DisplayRoleand while editing it is called with role==Qt::EditRole. For example try changing
QVariant MyModel::data(const QModelIndex & index, int role) const
{
if (role == Qt::DisplayRole)
return QString("Text to Edit");
}
to
QVariant MyModel::data(const QModelIndex & index, int role) const
{
if (role == Qt::DisplayRole || role == Qt::EditRole)
return QString("Text to Edit");
}
that should do the trick
I had the same problem and found an approach without writing my own Delegate:
The problem is exactly how you described it: The data gets updated in the Background and everything you Edit gets cleared out because the dataChanged Event updates all values thus calling the data function, which returns an empty QVariant() Object if nothing is specified for the Qt::EditRole. Even Leonid's answer would always overwrite your edits with the same QString("Text to Edit").
So what I did was this:
Introduce a member variable and dafine it mutable so it can be changed by the const data function:
mutable bool m_updateData = true;
In your background data updating function, check for m_update date before emmitting the dataChanged Signal:
if (m_updateData)
emit(dataChanged(index, index));
In your data function, check for the edit role and set m_updateData to false:
if (role == Qt::EditRole)
{
m_updateData = false;
}
After the Edit is finished, the setData function is called, where you update the data in your model. Reset m_updateDate to true after you have done that.
This works perfectly for me :)
Check your model class, you should override the setData method in your model. If every thing is correct it will update model after editing data... please let me know if you have another implementation
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
int row = index.row();
int col = index.column();
//// change data
emit(dataChanged(index, index));
return true;
}
return false;
}
I think that you should use dataChanged event only for indexes that are not being edited or only for Qt::ItemDataRole::DisplayRole.
E.g. to update only second column for every row use:
emit dataChanged(index(0, 1),
index(rowCount() - 1, 1),
QVector<int>{ Qt::ItemDataRole::DisplayRole });
Related
I have created a mixin-like proxy model (Qt5) which just adds an extra first column to another proxy model, for adding a QToolBar of actions to each row of the table view (for example, a "delete" button). The model just provides a way of populating a QList<QVariant> for the first column. The delegate must know what is the meaning of each QVariant (usually ints/enums identifying actions), and populate the QToolBar accordingly. As last feature, if there's no actions, no extra column is added (it behaves like a QIdentityProxyModel in that case). Once added, actions cannot be removed. That's a feature for another day.
The problem of today is that, when I insert actions (which I do before setting the model to the view), the cells are all blanks. So, I'm doing something wrong with the signals or who knows with what (I think the mistake is in the add_action function, at the end of the snippet):
template<class proxy_model>
class action_model : public proxy_model
{
QList<QVariant> l_actions;
public:
using base_t = proxy_model;
using base_t::base_t; // Inheriting constructors.
QModelIndex mapFromSource(const QModelIndex& source_idx) const override
{
if (!l_actions.empty() and source_idx.isValid())
return this->createIndex(source_idx.row(),
source_idx.column() + 1);
else // identity proxy case
return base_t::mapFromSource(source_idx);
} // same for mapToSource but with - 1 instead of + 1.
int columnCount(const QModelIndex& parent = QModelIndex()) const override
{ return this->base_t::columnCount() + !l_actions.empty(); }
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
{
if (!l_actions.empty()) {
if (orientation == Qt::Horizontal and section == 0
and role == Qt::DisplayRole)
return "Actions"; // Testing.
else
return base_t::headerData(section - 1, orientation, role);
} else // identity proxy case
return base_t::headerData(section, orientation, role);
}
QVariant data(const QModelIndex& idx, int role) const override
{
if (!l_actions.empty()) {
if (idx.column() == 0 and role = Qt::DisplayRole)
return l_actions; // All the actions for drawing.
else
return QVariant();
} else // identity proxy case
return base_t::data(idx, role);
}
Qt::ItemFlags flags(QModelIndex const& idx) const
{
if (!l_actions.empty() and idx.column() == 0)
return Qt::NoItemFlags; // No editable or selectable
else
return base_t::flags(idx);
}
// And here, I think, is where the fun starts:
// The action could be added before or after the sourceModel
// is set or this model is connected to a view, but I don't
// how that cases are supposed to be managed.
void add_action(QVariant const& action)
{
bool was_empty = l_actions.empty();
l_actions << action;
if (was_empty and !this->insertColumns(0, 1))
throw std::logic_error("Something went wrong");
Q_EMIT this->dataChanged
(this->createIndex(0, 0),
this->createIndex(this->rowCount(), 0),
{ Qt::DisplayRole });
}
};
Without setting actions, the model works fine, both with QAbstractIdentityProxyModel and QSortFilterProxyModel as proxy_model. But, when setting actions, the view shows every cell blank, both with QSortFilterProxyModel and QAbstractIdentityProxyModel.
Here is a user-land code:
enum sql_action { DELETE };
auto* table_model = /* My QSqlTableModel */;
auto* view_model = new action_model<QIdentityProxyModel>(my_parent);
auto* table_view = new QTableView;
view_model->add_action(static_cast<int>(sql_action::DELETE));
view_model->setSourceModel(table_model);
table_view->setModel(view_model);
table_view->setSortingEnabled(true);
table_view->setAlternatingRowColors(true);
// The last column is printed in white, not with alternate colors.
table_view->show();
table_model->select();
The delegates are not a problem because I have set no one. I expect a first column with white cells, but I get an entirely white table. The column names are shown fine, except the last one, which prints just 0 as column name.
What am I doing wrong?
The problem is in your data() method.
You do not compare role to Qt::DisplayRole you assign to role
If you have actions, you either return the action entry or QVariant(), never any data
I have subclassed QAbstractTableModel and in data() function I am displaying an image in last column of each row and a tooltip on mouse hover.
QVariant MyTableModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::DisplayRole)
{
switch (index.column())
{
// few cases
default:
return QVariant();
}
}
else if (role == Qt::CheckStateRole && index.column() == 0)
{
int state= tableData.at(index.row()).state;
if (state)
return Qt::Checked;
else
return Qt::Unchecked;
}
else if (role == Qt::DecorationRole && index.column() == 7 && index.row() > 1)
{
QPixmap pixMap(fileName);
return pixMap;
}
else if (role == Qt::ToolTipRole && index.column() == 7 && index.row() > 1)
{
return QString("Delete");
}
else
return QVariant();
}
Tooltip text is displayed fine on each row but when I move cursor from last column of a row to another last column just below it(or any row below it) tooltip remains on the upper row.
This issue does not persist if cursor is moved to any other cell before moving to last column of another row.
Thanks for help.
When data displayed as a tooltip is the same for different items in the view the tooltip position isn't adjusted. Not sure if this is a design feature or bug - would need to look into Qt sources.
You can fix that quite easily though. In Qt documentation you can find such information:
Note that, if you want to show tooltips in an item view, the
model/view architecture provides functionality to set an item's tool
tip; e.g., the QTableWidgetItem::setToolTip() function. However, if
you want to provide custom tool tips in an item view, you must
intercept the help event in the QAbstractItemView::viewportEvent()
function and handle it yourself.
Hence, subclassing your view and overriding the viewportEvent method should solve your problem.
I need to load heavy dataset into QTableView. Dataset is no less then 700Mb in memory and I don't want to load all it to memory.
QSqlQueryModel is not ideal for me for 2 reasons - it is not editable and it is not realy load-on-demand (because fetching whole data to memory).
What I want to get
I want to store only some part of data in memory. Just for
displaying and maybe some buffer for fast scrolling.
Model should be editable
It should be low-memory-consumption
Should have no freezes
How I am trying to solve this (straightforward model of my code)
Custom QTableView (tableView)
Custom TableModel (model)
Model wrapper. (wrapper)
Model wrapper select rows count from database, and set this value to model. Now model can answer for int rowCount().
This same value is set for tableView.verticalScrollBar().
tableView.verticalScrollBar signal(valueChanged) is connected to tableview slot(on_valueChanged)
Some code
tableView::on_valueChanged(value)
{
wrapper.changeOffset(value);
}
wrapper::changeOffset(value)
{
if (_offset == value){
return;
}
_selectQuery->seek(value);
int endValue = qMin(value + _cacheSize, model->rowCount());
_list.clear();
for(int i = value; i < endValue-1; i++){
_list.append(_selectQuery->record());
}
model->setRecordList(_list);
_offset = value;
model->setOffset(_offset);
}
_selectQuery in wrapper::changeOffset is previosly executed QSqlQuery cursor for select query results.
I also implemented several methods in model
QVariant SqlRecModel::data(const QModelIndex &index, int role) const
{
int row = index.row() - _offset;
if (row > m_recList.size() || row < 0){
return QVariant();
}
if (role == Qt::DisplayRole)
{
QVariant value = m_recList.at(row).value(index.column());
return value;
}
return QVariant();
}
Setter for model data storage
void SqlRecModel::setRecordList(const QList<QSqlRecord> &records)
{
qDebug() << "r:";
emit layoutAboutToBeChanged();
m_recList = records;
emit layoutChanged();
}
Problem
I can scroll _cacheSize rows, but I get crash (The program has unexpectedly finished.) after going out of old cacheRange.
Any advice? I don't know where to dig. Thanks!
Sorry for disturbing.
Error was in other code block.
This way is just okay to solve the task I have.
btw: if you play with cache buffer you can achive more smooth scroll.
I have a written model (myModel) using internalpointer() for his data() implementation.
I want to filter a tree (based on myModel) using QSortFilterProxyModel,
I got it to work, only when i try to get any data from the tree my app crashes.
I think its because while calling the tree data, expecting to get the myModel indexModel, i get the myQSortFilterProxyModel indexModel.
myItem *myModel::getItem(const QModelIndex &index) const
{
if (index.isValid()) {
myItem *item = static_cast<myItem*>(index.internalPointer());
if (item) return item;
}
return rootItem;
}
myModel data() using internalPointer()
QVariant myModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole)
return QVariant();
myItem *item = getItem(index);
return item->data(index.column());
}
setting the filter model between myModel ant the tree
void myTree::myInit()
{
...
myModel *model = new myModel();
proxyModel = new mySortFilterProxyModel(this);
proxyModel->setSourceModel(model);
this->setModel(proxyModel);
...
myTree is QTreeView subclass.
I want to use tree->model() to get myModel model
how do I get the source model data?
This approach won't work with proxy models, for the reason you pointed out.
Internalpointer/id should only be accessed in methods that are guaranteed to get indexes passed that belong to the model itself, like data() etc.
A better approach to get an item for an index is pass it via a custom role:
enum Roles {
MyItemRole=Qt::UserRole
};
QVariant data( const QModelIndex& index, int role ) const {
...
if ( role == MyItemRole )
return QVariant::fromValue<MyItem>( item );
//MyItem or MyItem*, depending on whether it can be copied/has
// value semantics or not
...
}
And in using code:
const MyItem item = index.data( MyModel::MyItemRole ).value<MyItem>();
You'll need to Q_DECLARE_METATYPE(MyItem) (or MyItem*) in a header and call qRegisterMetaType() at runtime, so that MyItem/MyItem* can be passed as QVariant.
This approach has the advantage that it will work regardless of any proxy models in between, and the code calling data doesn't even have to know about the proxies.
All you have to do is call mapToSource() before your call to getItem().
Here's the data() method I use to change the font for a certain type of item. Otherwise it just calls the QSortFilterProxyModel's data().
virtual QVariant data(const QModelIndex & proxy_index, int role) const
{
QModelIndex source_index = mapToSource(proxy_index);
IT::TreeItem * item = IT::toItem(source_index);
if (role == Qt::FontRole && item->isMessage()) return _bold_font;
return QSortFilterProxyModel::data(proxy_index, role);
}
1)I want to get the name of the folder for a folder monitoring Application..
Is there a way that i can filter out specific folders from being displayed using QFileDialog (For example i don't want the my documents to be displayed in the file dialog)..
2)I don't want the user to select a drive. By default in this code drives can also be selected..
dirname=QtGui.QFileDialog.getExistingDirectory(self,'Open Directory','c:\\',QtGui.QFileDialog.ShowDirsOnly)
print(dirname)
Is there a way that i can gray out the drives or some specific folders so that it can't be selected or can i set the filters for folder to prevent showing it up..
You can try setting a proxy model for your file dialog: QFileDialog::setProxyModel. In the proxy model class override the filterAcceptsRow method and return false for folders which you don't want to be shown. Below is an example of how proxy model can look like; it'c c++, let me know if there are any problems converting this code to python. This model is supposed to filter out files and show only folders:
class FileFilterProxyModel : public QSortFilterProxyModel
{
protected:
virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
};
bool FileFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent);
QFileSystemModel* fileModel = qobject_cast<QFileSystemModel*>(sourceModel());
if (fileModel!=NULL && fileModel->isDir(index0))
{
qDebug() << fileModel->fileName(index0);
return true;
}
else
return false;
// uncomment to execute default implementation
//return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
}
here's how I was calling it
QFileDialog dialog;
FileFilterProxyModel* proxyModel = new FileFilterProxyModel;
dialog.setProxyModel(proxyModel);
dialog.setOption(QFileDialog::DontUseNativeDialog);
dialog.exec();
Note that the proxy model is supported by non-native file dialogs only.
You can try using QDir.Dirs filter.
dialog = QtGui.QFileDialog(parentWidget)
dialog.setFilter(QDir.Dirs)
serge_gubenko gave you the right answer. You only had to check the folder names and return "false" for the ones which should not be displayed. For instance, to filter out any folders named "private" you'd write:
bool FileFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent);
QFileSystemModel* fileModel = qobject_cast<QFileSystemModel*>(sourceModel());
if (fileModel!=NULL && fileModel->isDir(index0))
{
qDebug() << fileModel->fileName(index0);
if (QString::compare(fileModel->fileName(index0), tr("private")) == 0)
return false;
return true;
}
else
return false;
// uncomment to execute default implementation
//return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
}
I already tested this and it works perfectly.
serge_gubenko should receive all due credit.
I know this is not exactly what you were asking, but if you're working with a QFileSystemModel, you can do it with the Name Filters option.
model = QFileSystemModel()
model.setNameFilters(["[abcdefghijklmnopqrstuvwxyz1234567890]*"])
model.setNameFilterDisables(False)
It worked for me, and I couldn't find the answer anywhere else on the internet, so I figured I post it here.
(I know my regex is trash, but using [\\w\\d]* didn't work and I was feeling lazy.)