Reputation: 379
I have a table like this
In this table, Abbreviation
and Meaning
are headers. When we click onto that, the items should be arranged. But the order I need when we click onto that is:
|Last Used or |Last Used
|--FBI |--PIG
|--PIG |--FBI
|Something |Something
|--ADIDAS |--TEAM
|--DIET |--DIET
|--TEAM |--ADIDAS
|Lorem Ipsum |Lorem Ipsum
|--CLASS |--PwC
|--PMS |--PMS
|--PwC |--CLASS
which means, I arrange the items only inside each group (Last Used
, Something
, and Lorem Ipsum
), the order of the groups should be remained.
These are my daten:
CompleterSourceModel.h
#include <QStandardItemModel>
#include <CompleterData.h>
class CompleterSourceModel : public QStandardItemModel
{
public:
CompleterSourceModel( QObject *p_parent = nullptr );
Qt::ItemFlags flags( const QModelIndex &index ) const override;
void setCompleterData( const CompleterData &p_completerData );
private:
CompleterData m_completerData;
};
CompleterSourceModel.cpp
#include "CompleterSourceModel.h"
CompleterSourceModel::CompleterSourceModel( QObject *p_parent ) : QStandardItemModel( p_parent )
{
}
Qt::ItemFlags CompleterSourceModel::flags( const QModelIndex &p_index ) const
{
if ( !p_index.isValid() ) {
return Qt::NoItemFlags;
}
CompleterDataRow::Type type = m_completerData.data().at( p_index.row() ).type();
if ( type == CompleterDataRow::Type::Data || type == CompleterDataRow::Type::LastUsed ) {
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
return Qt::NoItemFlags;
}
void CompleterSourceModel::setCompleterData( const CompleterData &p_completerData )
{
m_completerData = p_completerData;
setColumnCount( m_completerData.headers().size() + 1 );
setRowCount( m_completerData.data().size() );
for ( int col = 0; col <= m_completerData.headers().size(); col++ ) {
col < m_completerData.headers().size() ? setHeaderData( col, Qt::Horizontal, m_completerData.headers().at( col ) ) : setHeaderData( col, Qt::Horizontal, {} );
}
for ( int row = 0; row < m_completerData.data().size(); row++ ) {
for ( int col = 0; col <= m_completerData.headers().size(); col++ ) {
if ( m_completerData.data().at( row ).type() == CompleterDataRow::Type::Header || m_completerData.data().at( row ).type() == CompleterDataRow::Type::SecondHeader ) {
col == 0 ? setData( index( row, col ), m_completerData.data().at( row ).rowData().at( col ).first, Qt::EditRole ) : setData( index( row, col ), {}, Qt::EditRole );
}
else {
col == m_completerData.headers().size() ? setData( index( row, col ), {}, Qt::EditRole ) : setData( index( row, col ), m_completerData.data().at( row ).rowData().at( col ).first, Qt::EditRole );
}
setData( index( row, col ), QVariant( static_cast<int>( m_completerData.data().at( row ).type() ) ), Qt::UserRole );
}
}
}
CompleterData.h
#include <QList>
#include <QPair>
#include <QVariant>
#include <QVector>
class CompleterDataRow
{
public:
enum class Type
{
Header,
SecondHeader,
Data,
LastUsed
};
CompleterDataRow() = default;
CompleterDataRow( const CompleterDataRow::Type p_rowType, const
QList<QPair<QString, QVariant>> &p_rowData );
void setType( const CompleterDataRow::Type p_type );
CompleterDataRow::Type type() const;
QList<QPair<QString, QVariant>> rowData() const;
void setRowData( const QList<QPair<QString, QVariant>> &p_rowData );
private:
QList<QPair<QString, QVariant>> m_rowData;
Type m_type;
};
class CompleterData
{
public:
CompleterData() = default;
QVector<CompleterDataRow> data() const;
void setData( const QVector<CompleterDataRow> &p_data );
void addData( const CompleterDataRow &p_rowData );
void removeData( int p_row );
void setHeaders( const QStringList &p_headers );
void setTitle( const QString &p_label );
const QStringList &headers() const;
const QString &title() const;
private:
QVector<CompleterDataRow> m_completerData;
QString m_title;
QStringList m_headers;
};
CompleterData.cpp
#include "CompleterData.h"
CompleterDataRow::CompleterDataRow( const CompleterDataRow::Type p_rowType, const QList<QPair<QString, QVariant>> &p_rowData )
{
m_type = p_rowType;
m_rowData = p_rowData;
}
QList<QPair<QString, QVariant>> CompleterDataRow::rowData() const
{
return m_rowData;
}
void CompleterDataRow::setRowData( const QList<QPair<QString, QVariant>> &p_rowData )
{
m_rowData = p_rowData;
}
CompleterDataRow::Type CompleterDataRow::type() const
{
return m_type;
}
void CompleterDataRow::setType( const Type p_type )
{
m_type = p_type;
}
QVector<CompleterDataRow> CompleterData::data() const
{
return m_completerData;
}
void CompleterData::addData( const CompleterDataRow &p_rowData )
{
m_completerData.append( p_rowData );
}
void CompleterData::removeData( int p_row )
{
m_completerData.remove( p_row );
}
void CompleterData::setData( const QVector<CompleterDataRow> &p_data )
{
m_completerData = p_data;
}
void CompleterData::setTitle( const QString &p_title )
{
m_title = p_title;
}
const QString &CompleterData::title() const
{
return m_title;
}
void CompleterData::setHeaders( const QStringList &p_headers )
{
m_headers = p_headers;
}
const QStringList &CompleterData::headers() const
{
return m_headers;
}
MyComboBox.h
#include <QComboBox>
#include <QTreeView>
#include "CompleterData.h"
#include "CompleterSourceModel.h"
#include "CompleterProxyModel.h"
class MyComboBox : public QComboBox
{
public:
MyComboBox( QWidget *p_parent = nullptr );
CompleterData createTestData();
void setDataForCompleter(const CompleterData &p_data); // this function should be set in main.cpp in Qt Project so that problem can be reproduced
private:
QTreeView *m_view = nullptr;
CompleterSourceModel *m_sourceModel = nullptr;
CompleterProxyModel *m_proxyModel =nullptr;
};
MyComboBox.cpp
#include "MyComboBox.h"
MyComboBox::MyComboBox( QWidget *p_parent ) : QComboBox( p_parent )
{
setEditable( true );
m_view = new QTreeView();
m_sourceModel = new CompleterSourceModel( this );
m_proxyModel = new CompleterProxyModel( this );
m_proxyModel->setSourceModel(m_sourceModel);
setModel( m_proxyModel );
setView( m_view );
}
void MyComboBox::setDataForCompleter(const CompleterData &p_data)
{
m_sourceModel->setCompleterData( p_data );
}
CompleterData MyComboBox::createTestData()
{
CompleterData data;
data.addData( CompleterDataRow( CompleterDataRow::Type::Header, { { "Last Used", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::Data, { { "FBI", {} }, { "Female Body Inspector", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::Data, { { "PIG", {} }, { "Pretty Insensitive Guy", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::Header, { { "Something", {}} } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::LastUsed, { { "ADIDAS", {} }, {"All Day I Dream About Soccer", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::LastUsed, { { "DIET", {}}, {"Do I eat today?", {}} } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::Data, { { "TEAM", {} }, { "Together Everyone Achieves More", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::SecondHeader, { { "Lorem Ipsum", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::LastUsed, { { "CLASS", {}}, {"Come late and start sleeping", {}} } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::LastUsed, { { "PMS", {}}, {"Purchase More Shoes", {}} } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::LastUsed, { { "PwC", {}}, {"Partner want Cash", {}} } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::Header, { { "Some Countries", {} } } ) );
data.addData( CompleterDataRow( CompleterDataRow::Type::SecondHeader, { { "Some Cities", {} } } ) );
data.setTitle( "Proposal List" );
data.setHeaders( { "Abbreviation", "Meaning" } );
return data;
}
main.cpp
#include "mycombobox.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyComboBox combo;
combo.setDataForCompleter(combo.createTestData());
combo.show();
return a.exec();
}
In order to satisfy the requirement I think I should use Proxymodel. But I have something wrong with my proxy model, so the result I become is like this, when I load the model (I did not click onto headers to sort items yet)
As you can see, the Lorem Ipsum
is moved to the end of the list, the order right at the beginning is wrong. So I think I have mistakes in my proxy model. Could you show me where exactly in my proxy model? Or any other solutions are also welcomed.
This is my proxy model:
CompleterProxyModel.h
#include <QSortFilterProxyModel>
#include <CompleterData.h>
class CompleterProxyModel : public QSortFilterProxyModel
{
public:
CompleterProxyModel( QObject *p_parent = nullptr );
protected:
bool lessThan( const QModelIndex &p_left, const QModelIndex &p_right ) const override;
};
CompleterProxyModel.cpp
#include "CompleterProxyModel.h"
CompleterProxyModel::CompleterProxyModel( QObject *p_parent ) : QSortFilterProxyModel( p_parent )
{
}
bool CompleterProxyModel::lessThan( const QModelIndex &p_left, const QModelIndex &p_right ) const
{
CompleterDataRow::Type leftType = static_cast<CompleterDataRow::Type>( p_left.data( Qt::UserRole ).toInt() );
CompleterDataRow::Type rightType = static_cast<CompleterDataRow::Type>( p_right.data( Qt::UserRole ).toInt() );
if ( ( leftType == CompleterDataRow::Type::Data && rightType == CompleterDataRow::Type::Data ) ||
( leftType == CompleterDataRow::Type::LastUsed && rightType == CompleterDataRow::Type::LastUsed ) )
{
QString leftString = p_left.data( Qt::EditRole ).toString();
QString rightString = p_right.data( Qt::EditRole ).toString();
qDebug() << leftString << rightString << QString::localeAwareCompare( leftString, rightString );
return QString::localeAwareCompare( leftString, rightString ) < 0;
}
return false;
}
Upvotes: 0
Views: 514
Reputation: 379
In this case I have just found a solution. I think the model should be updated as I import data into it. So I put beginResetModel()
and endResetModel()
into function setCompleterData
and it works now.
void CompleterSourceModel::setCompleterData( const CompleterData &p_completerData )
{
beginResetModel();
m_completerData = p_completerData;
setColumnCount( m_completerData.headers().size() + 1 );
setRowCount( m_completerData.data().size() );
for ( int col = 0; col <= m_completerData.headers().size(); col++ ) {
col < m_completerData.headers().size() ? setHeaderData( col, Qt::Horizontal, m_completerData.headers().at( col ) ) : setHeaderData( col, Qt::Horizontal, {} );
}
for ( int row = 0; row < m_completerData.data().size(); row++ ) {
for ( int col = 0; col <= m_completerData.headers().size(); col++ ) {
if ( m_completerData.data().at( row ).type() == CompleterDataRow::Type::Header || m_completerData.data().at( row ).type() == CompleterDataRow::Type::SecondHeader ) {
col == 0 ? setData( index( row, col ), m_completerData.data().at( row ).rowData().at( col ).first, Qt::EditRole ) : setData( index( row, col ), {}, Qt::EditRole );
}
else {
col == m_completerData.headers().size() ? setData( index( row, col ), {}, Qt::EditRole ) : setData( index( row, col ), m_completerData.data().at( row ).rowData().at( col ).first, Qt::EditRole );
}
setData( index( row, col ), QVariant( static_cast<int>( m_completerData.data().at( row ).type() ) ), Qt::UserRole );
}
endResetModel();
}
Upvotes: 0
Reputation: 6584
You have two rules for your sorting proxy:
An item is a category. So it's a root item with no parent and you have to sort on the row number to keep the original order.
Otherwise, it's a child item and you can sort it by its value.
Your proxy is quite simple: if an item has a parent, sort it regarding to the row. Otherwise use the original sorting rules (or another one).
The only tricky part is the order (ascending or descending). So, you have to deal with it (or your category will be inverted).
For example:
class SortProxyModel: public QSortFilterProxyModel
{
public:
SortProxyModel(): QSortFilterProxyModel() {}
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
{
if (!left.parent().isValid())
{
if (sortOrder() == Qt::DescendingOrder) // Don't care about the order
return left.row() > right.row();
return left.row() < right.row();
}
return QSortFilterProxyModel::lessThan(left, right);
}
};
The tests:
QTreeView* view = new QTreeView();
QStandardItemModel* model = new QStandardItemModel();
QSortFilterProxyModel* proxy = new SortProxyModel();
proxy->setSourceModel(model);
model->setHorizontalHeaderLabels(QStringList() << "Col 1");
QStandardItem* item1 = new QStandardItem("Last Used");
item1->appendRows(QList<QStandardItem*>() << new QStandardItem("A") << new QStandardItem("C") << new QStandardItem("B"));
QStandardItem* item2 = new QStandardItem("Something");
item2->appendRows(QList<QStandardItem*>() << new QStandardItem("F") << new QStandardItem("E") << new QStandardItem("D"));
QStandardItem* item3 = new QStandardItem("Lorem");
item3->appendRows(QList<QStandardItem*>() << new QStandardItem("I") << new QStandardItem("G") << new QStandardItem("H"));
model->appendRow(item1);
model->appendRow(item2);
model->appendRow(item3);
view->setModel(proxy);
view->setSortingEnabled(true);
view->show();
Upvotes: 1