I have a working Qtableview with custom model subclassed QAbstractTableModel and QAbstractItemModel.
I have a Qlineedit, onclicked it will filter the view:
// model.cpp
setFilter(QString strFilter) function searches trough my intern QList (this Qlist is actually attached to model) and if match found then: m_filterSet.insert(i);
This all works great. Problem is, i have CRUD operations for the tableview (insert row, delete row..) which also work great! But when selecting a row from a filtered set, i need to somehow know where in my QList exactly is this selected row from the filtered set (QSet ).
ui.myView->selectionModel()->currentIndex().row();
obvious gives the wrong indexes counting for the current view.
How can i somehow extract the value (int) from the selected row in the QSet?
Because when i added this function to model:
foreach (const int &value, m_filterSet)
qDebug() << value;
It has printed out successfully all the i values, e.g: 3410, 3411, 3412 (those are my client id's)
If i could extract this ID for the selected row in Qset, i could write a function that iterates my intern QList, and find a matching, so to speak:
if(m_Intern[i].nClientID == nId){ // nId = value inside Qset for selected row in view
return nIdx;
}
Qt has a solution for your problem - just use QSortFilterProxyModel. You will need to:
Subclass it and write your own filtering function (filterAccpetsRow)
Proxy your original model through filtering one
Attach filtering model to a view
use QSortFilterProxyModel::mapToSource() to convert between indexes in filtered and original model.
This allows you to have more than one view with just one source data model, each view may have different filters.
I solved it after a while re thinking, i just needed to implement another function inside my model:
int myClass::screenIndex2DataIndex(int nIdxScreen)
{
if(m_bUseFilter)
{
int nIdx =-1;
for(int i=0;i<m_lstIntern.size();i++)
{
if(m_filterSet.contains(i))
{
nIdx++;
if(nIdx == nIdxScreen){
return i;
}
}
}
return -1; //not found
}
else{
return nIdxScreen;
}
}
This way i can find out for the present index on the filtered view, where it is in my intern list.
After this it's easy to get my nClientID trough a return: return m_lstIntern[idx].nClientId
Related
Implemented QML Dynamic View Ordering by Dragging View Items using this Qt tutorial: QML Dynamic View Ordering Tutorial. Original underlying model is QAbstractListModel descendant in our case. Model stores data in a QList<QObject*> objectList; field type. Works fine, however item ordering changed in proxy DelegateModel only.
How to change items order automatically in original underlying model as well for other C++ and QML consumers where order matters? Or I could we otherway access some resulted (sorted) List Model model from C++ somehow?
Thanks for any help!
In the QML Dynamic View Ordering Tutorial 3 example I've replaced visualModel.items.move() call with my ObjectListModel::move() method like this:
ObjectListModel : public QAbstractListModel:
void ObjectListModel::move(int from, int to)
{
if(0 <= from && from < count() && 0 <= to && to < count() && from != to) {
if(from == to - 1) // Allow item moving to the bottom
to = from++;
beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
objectList.move(from, to);
endMoveRows();
}
}
Delegate component:
DropArea {
anchors { fill: parent; }
onEntered: {
let from = drag.source.DelegateModel.itemsIndex
let to = mouseArea.DelegateModel.itemsIndex
objectListModel.move(from, to)
}
}
And above works perfectly for the ListView and ObjectListModel itself - I have checked: items (and therefore objects) are moved correctly, indexes are correct, C++ consumers works just fine and take new order into account correctly, etc.
However another consumer like MapItemView fails to use the model after beginMoveRows/endMoveRows calls: moved item disappeared on the map and other manipulations with an item crashes the app.
Map {
...
MapItemView {
model: objectListModel
delegate: SomeItemIndicator {
}
}
}
Reported QTBUG-81076 bug, which is confirmed.
Workaround:
Found workaround for now: created 2nd duplicate model which content will be replaced completely on every change in the 1st model on every add/delete/moving(reordering). Above works since beginResetModel/endResetModel works correctly for MapItemView. So MapItemView now utilizes only the 2nd model. So on every 1st model change this method is called for the 2nd model:
QObjectList ObjectListModel::swapObjectList(const QObjectList& newlist)
{
QObjectList oldlist(_objectList);
beginResetModel();
_objectList = newlist;
endResetModel();
return oldlist;
}
I implemented sort in a QAbstractTableModel subclass. The sorting itself works but the view doesn't change until I hover over the table itself (i.e. not the header). How can I fix this? At first I thought that I also have to emit a signal but couldn't find an appropriate one.
I use the following code to sort my model in a project:
void MyModel::organize()
{
if (!cache_ || cache_->empty()) return;
beginResetModel(); // (a)
std::stable_sort(cache_->begin(), cache_->end(), compareRow);
endResetModel(); // (b)
} // end:(MyModel::organize)
line (a) tells the model that I'm going to re-organize data, line (b) tells the model that I'm finished, please refresh the associated views.
You may also emit dataChanged(index, index); signal.
In QAbstractItemModel, some functions, such as beginInsertRows, beginRemoveRows, can be used to insert and remove rows. But how to implement replacing a row item by another one?
If I understand you right, you need to notify subcribed views about data changing (one row replaced by new, for views it means that data has been changed) for the specified model index:
// let's the row is index that we want to invalidate
QVector<int> roles;
roles << Qt::DisplayRole;
emit dataChanged(index(row, 0), index(row, columnCount()-1), roles);
If you want to change a lot of data, you should do something like this:
beginResetModel();
// change data
endResetModel();
If you change just one row, emitting dataChanged() should do the trick.
I have a QTreeWidget declared as following
QTreeWidget * datasiftIdpwTree;
datasiftIdpwTree->setColumnCount(2);
datasiftIdpwTree->headerItem()->setText(0, "Username");
datasiftIdpwTree->headerItem()->setText(1, "Api Key");
it is filled with data using the following slot
void Window::addDatasiftIdpw(QString username, QString apikey)
{
datasiftIdpwTree->addTopLevelItem(new QTreeWidgetItem(QStringList(username) << apikey));
}
I can live view the slot addind data to my tree, the probleme is when accessing the data.
I tried using
datasiftIdpwTree->itemAt(x,y)->text(0);
but however the value of x, y I use, it always point to the first item (the one produced when first calling addDatasiftIdpw)
Where did I go wrong ?
I made made a mistake between cooridnates and index. Accessing the i-th element is achived by:
datasiftIdpwThree->topLevelItem(i)->text(0);
I have a QDirModel whose current directory is set. Then I have a QListView which is supposed to show the files in that directory. This works fine.
Now I want to limit the files shown, so it only shows png files (the filename ends with .png). The problem is that using a QSortFilterProxyModel and setting the filter regexp will try to match every parent of the files as well. According to the documentation:
For hierarchical models, the filter is
applied recursively to all children.
If a parent item doesn't match the
filter, none of its children will be
shown.
So, how do I get the QSortFilterProxyModel to only filter the files in the directory, and not the directories it resides in?
As of Qt 5.10, QSortFilterProxyModel has the option to filter recursively. In other words, if a child matches the filter, its parents will be visible as well.
Check out QSortFilterProxyModel::recursiveFilteringEnabled.
For people like me who are interested in the following behaviour : if a child matches the filter, then its ancestors should not be hidden :
bool MySortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const
{
// custom behaviour :
if(filterRegExp().isEmpty()==false)
{
// get source-model index for current row
QModelIndex source_index = sourceModel()->index(source_row, this->filterKeyColumn(), source_parent) ;
if(source_index.isValid())
{
// if any of children matches the filter, then current index matches the filter as well
int i, nb = sourceModel()->rowCount(source_index) ;
for(i=0; i<nb; ++i)
{
if(filterAcceptsRow(i, source_index))
{
return true ;
}
}
// check current index itself :
QString key = sourceModel()->data(source_index, filterRole()).toString();
return key.contains(filterRegExp()) ;
}
}
// parent call for initial behaviour
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent) ;
}
We ran into something similar where I work, and ended up making our own proxy model to do our filtering. However, looking through the documentation for what you want (which seems like it would be a more common case), I came across two possibilities.
You might be able to set a name filter on the QDirModel and filter things that way. I have no idea if this will work like you want, or if the name filters apply to directories also. The documentation is kind of sparse on these.
Subclass the QSortFilterProxyModel and override the filterAcceptsRow function. From the documentation:
Custom filtering behavior can be achieved by reimplementing the filterAcceptsRow() and filterAcceptsColumn() functions.
Then you could presumably use the model index to check if the index item is a directory (automatically accept) or a file (filter on filename).
derive qsortfilterproxymodel and then...
bool YourQSortFilterProxyModel::filterAcceptsRow ( int source_row, const QModelIndex & source_parent ) const
{
if (source_parent == qobject_cast<QStandardItemModel*>(sourceModel())->invisibleRootItem()->index())
{
// always accept children of rootitem, since we want to filter their children
return true;
}
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}