PDA

View Full Version : Problems with QSortFilterProxyModel and QTableView



bleriot13
11th March 2013, 15:33
Hi to all.

I'm quite a newbie; I've been learning and developing in Qt 4.8.4 for just three weeks now. I have to say that I'm very impressed with the power of this platform!

I'm developing a very simple widget that must show a list of image files. To do it, I have used derivatives of Qt classes QTableView, QAbstractTableModel as well as a QSortFilterProxyModel.

The widget is composed of a single toolbar at the top and the table view just below.

The toolbar has two buttons: add and delete. The add button opens a file selector (with multiple selection enabled); the files thus selected are added to the table view below. Obviously, the delete button is enabled only when a row in the table view is selected. When clicked, removes the corresponding entry (row) from the table view.

Since I want to provide the ability to sort the rows in the view, I have used a QSortFilterProxyModel.

The problem arises when I want to delete a row from the table view. Since I'm using a filter, I translate the row number returned by the selection event from indices valid for the filter to those valid to my backend structure (a simple QStringList).

Normally, this translation works pretty well, so deletion works also fine.

But I have noticed that, depending on how are the names of the files added to the list, the index translation goes crazy!

For instance, if all the files loaded in the table view adhere to the pattern "DSC999999" (where '9' stands for a digit) my software works flawlessly. But if other type of file names enter the game, then the index translation procedure fails miserably. For instance, with filenames as "h02_0698_08-15_mar200.tif" this strange behaviour appears. The index translation fails and the row removed is not the correct one.

I wonder if this can be produced by different string codification / locales (UTF, Latin-1...). Since these filenames are obtained using a QFileDialog object that scans my disk, and my Windows 7 is an spanish version... could this explain that the sort filter would behave in such a strange way? If not, could any one take a look to my code and point to the offending / bad code?

I have included the .cpp files that are involved in this problem. I don't include the .hpp(s) because otherwise this message becomes to long and the forum system does not accept it.

Thank you very much in advance for your help!

THE FILES

ImageListModel.cpp


#include "ImageListModel.hpp"

bool
ImageListModel::
appendRow
(QString& filename)
{
{
int position = filenames_->size();

beginInsertRows(QModelIndex(), position, position);
filenames_->append(filename);
endInsertRows();

return true;
}
}

ImageListModel::
ImageListModel
(QStringList *filenames,
QObject *parent) : QAbstractTableModel(parent)
{
{
filenames_ = filenames;
}
}

bool
ImageListModel::
removeRow
(int position)
{
{
beginRemoveRows(QModelIndex(), position, position);
filenames_->removeAt(position);
endRemoveRows();

return true;
}
}

int
ImageListModel::
rowCount(const QModelIndex &parent ) const
{
{
Q_UNUSED(parent);
return filenames_->count();
}
}

int
ImageListModel::
columnCount(const QModelIndex &parent ) const
{
{
Q_UNUSED(parent);
return 2;
}
}

QVariant
ImageListModel::
headerData(int section,
Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal)
{
if (role == Qt::DisplayRole)
{
switch (section)
{
case 0:
return tr("Image name");
case 1:
return tr("Path");
default:
return QVariant();
}
}
}
return QAbstractTableModel::headerData(section, orientation, role);
}

QVariant
ImageListModel::
data(const QModelIndex &index,
int role) const
{
if (!index.isValid()) return QVariant();

if (index.row() >= filenames_->size() || index.row() < 0)
return QVariant();

QFileInfo fi = filenames_->at(index.row());

if (role == Qt::DisplayRole || role == Qt::EditRole)
{
switch (index.column())
{
case 0:
return fi.fileName(); // File name.
case 1:
return fi.path(); // File path.
default:
return QVariant();
}
}

return QVariant();
}

ImageTableView.cpp


#include "ImageTableView.hpp"

ImageTableView::
ImageTableView
(QWidget *parent) :
QTableView(parent)
{
{
connect(this,SIGNAL(clicked(QModelIndex)),this,SLO T(clickedSlot(QModelIndex)));
}
}

void
ImageTableView::
clickedSlot
(QModelIndex qi)
{
{
emit rowSelected(qi.row());
}
}

ImageListManager.hpp


#ifndef IMAGELISTMANAGER_HPP
#define IMAGELISTMANAGER_HPP

#include <QWidget>
#include <QStringList>
#include <QAction>
#include <QSortFilterProxyModel>
#include <QVBoxLayout>
#include <QToolBar>
#include <QHeaderView>
#include <QFileDialog>

#include "ImageListModel.hpp"
#include "ImageTableView.hpp"

class ImageListManager : public QWidget
{
Q_OBJECT

public:

ImageListManager(QWidget *parent = 0);

protected slots:

void addImages(void);
void deleteSelectedRow (void);
void rowSelected(int rowIndex);

protected:

QAction* actionAddImages_;
QAction* actionRemoveImage_;
int cur_row_;
QStringList filenames_;
ImageListModel* model_;
QSortFilterProxyModel* proxyModel_;
ImageTableView* tableView_;

};

#endif // IMAGELISTMANAGER_HPP

ImageListManager.cpp


#include "ImageListManager.hpp"

void
ImageListManager::
addImages
(void)
{
{
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFiles);
dialog.setNameFilter(trUtf8( "Image Files (*.bmp *.jpg *.jpeg *.png *.tif);;Any Files (*.*)"));

if (dialog.exec())
{
int i;
QStringList fileNames = dialog.selectedFiles();

for (i = 0; i < fileNames.size(); i++)
{
QString* file = new QString(fileNames.at(i));
model_->appendRow(*file);
delete file;
}

tableView_->resizeColumnsToContents();
}
}
}

void
ImageListManager::
deleteSelectedRow
(void)
{
{

if (cur_row_ < 0) return;
if (cur_row_ >= filenames_.size()) return;

QModelIndex source = model_->index(cur_row_,0);
QModelIndex sorted = proxyModel_->mapFromSource(source);


//
// Now we may delete the filename from the list, since we know
// the real position affected.
//

model_->removeRow(sorted.row());


// Reset things.

tableView_->clearSelection();
cur_row_ = -1;
actionRemoveImage_->setEnabled(false);
}
}

ImageListManager::
ImageListManager
(QWidget *parent) : QWidget(parent)
{
{
// Initialize some members.

cur_row_ = -1;

// Create the toolbar, including its actions.

QToolBar* imageListToolBar = new QToolBar(tr("Image list toolbar"), this);
imageListToolBar->setMovable(false);

QIcon removeImageIcon;
removeImageIcon.addFile(QString::fromUtf8(":/resources/delete.png"), QSize(), QIcon::Normal, QIcon::Off);
actionRemoveImage_ = new QAction(removeImageIcon, tr("Remove image"), this);
actionRemoveImage_->setEnabled(false);
connect(actionRemoveImage_, SIGNAL(triggered()), this, SLOT(deleteSelectedRow()));
imageListToolBar->addAction(actionRemoveImage_);

QIcon newImagesIcon;
newImagesIcon.addFile(QString::fromUtf8(":/resources/add.png"), QSize(), QIcon::Normal, QIcon::Off);
actionAddImages_ = new QAction(newImagesIcon, tr("Add images"), this);
actionAddImages_->setEnabled(true);
connect(actionAddImages_, SIGNAL(triggered()), this, SLOT(addImages()));
imageListToolBar->addAction(actionAddImages_);


// Create the files model that will backing the table view.

model_ = new ImageListModel(&filenames_, this);

// Create the proxy model to sort data.

proxyModel_ = new QSortFilterProxyModel(this);
proxyModel_->setSortCaseSensitivity(Qt::CaseInsensitive);
proxyModel_->setDynamicSortFilter(true);
proxyModel_->setSortLocaleAware(true);
proxyModel_->setSourceModel(model_); // Sort the data in model_

// Set up the table view for the points.

tableView_ = new ImageTableView(this);
tableView_->setModel(proxyModel_); // proxyModel_ instead of model_!!!
tableView_->setSortingEnabled(true);
tableView_->setSelectionBehavior(QAbstractItemView::SelectRows );
tableView_->setSelectionMode(QAbstractItemView::SingleSelectio n);
tableView_->verticalHeader()->hide();

//proxyModel_->sort(0, Qt::DescendingOrder);

// Lay out the toolbar and table view.

QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(imageListToolBar);
mainLayout->addWidget(tableView_);
setLayout(mainLayout);

// Connect events coming from the table view.

connect(tableView_, SIGNAL(rowSelected(int)), this, SLOT(rowSelected(int)));

// Set the window title.

setWindowTitle(tr("Images in project"));

}
}

void
ImageListManager::
rowSelected
(int rowIndex)
{
{
actionRemoveImage_->setEnabled(true);
cur_row_ = rowIndex;
}
}

Just for the sake of completeness, I post here the .hpp files that wouldn't fit in the previous post.

ImageListModel.hpp


#ifndef IMAGELISTMODEL_HPP
#define IMAGELISTMODEL_HPP

#include <QAbstractTableModel>
#include <QStringList>
#include <QFileInfo>

class ImageListModel : public QAbstractTableModel
{
public:
bool appendRow (QString& filename);
int columnCount(const QModelIndex &parent ) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
ImageListModel(QStringList* filenames, QObject *parent=0);
bool removeRow (int position);
int rowCount(const QModelIndex &parent ) const;

protected:

QStringList* filenames_;
};

#endif // IMAGELISTMODEL_HPP

ImageTableView.hpp


#ifndef IMAGETABLEVIEW_HPP
#define IMAGETABLEVIEW_HPP

#include <QTableView>

class ImageTableView : public QTableView
{
Q_OBJECT

public:

explicit ImageTableView(QWidget *parent = 0);

signals:

void rowSelected(int rowIndex);

public slots:

void clickedSlot(QModelIndex qi);

};

#endif // IMAGETABLEVIEW_HPP

ImageListManager.hpp


#ifndef IMAGELISTMANAGER_HPP
#define IMAGELISTMANAGER_HPP

#include <QWidget>
#include <QStringList>
#include <QAction>
#include <QSortFilterProxyModel>
#include <QVBoxLayout>
#include <QToolBar>
#include <QHeaderView>
#include <QFileDialog>

#include "ImageListModel.hpp"
#include "ImageTableView.hpp"

class ImageListManager : public QWidget
{
Q_OBJECT

public:

ImageListManager(QWidget *parent = 0);

protected slots:

void addImages(void);
void deleteSelectedRow (void);
void rowSelected(int rowIndex);

protected:

QAction* actionAddImages_;
QAction* actionRemoveImage_;
int cur_row_;
QStringList filenames_;
ImageListModel* model_;
QSortFilterProxyModel* proxyModel_;
ImageTableView* tableView_;

};

#endif // IMAGELISTMANAGER_HPP

norobro
11th March 2013, 18:23
Looks to me like you have the following backwards:
QModelIndex source = model_->index(cur_row_,0);
QModelIndex sorted = proxyModel_->mapFromSource(source);

You are selecting from your proxy model and want to delete in the source model, right? Try:
QModelIndex index = proxyModel_->index(cur_row_,0);
QModelIndex sorted = proxyModel_->mapToSource(index);

bleriot13
12th March 2013, 07:41
Norobro,

thank you very much. Your hint worked as a charm! Just one minute work and everything behaves as it must!

I got really confused because my translation seemed to work well when I didn't include some kind of file names in the list. In fact, I have implemented another widget including a similar list, including not files but identifiers, that has been coded using the same technique shown in the files I included and it has been working flawlessly. That's what made me think of a possible problem in the sorting / indexing mechanism in the proxy model. I'll correct this other widget too and check it.

Thanks a lot!!!

Bleriot

jaafari
20th April 2014, 20:49
thanks alot for this subject it helps me..