Как упорядочить элементы по группам, используя QSortFilterProxyModel? - PullRequest
0 голосов
/ 03 мая 2019

У меня есть такая таблица

enter image description here

В этой таблице Abbreviation и Meaning являются заголовками.Когда мы нажимаем на это, элементы должны быть расположены.Но порядок, который мне нужен, когда мы нажимаем на это:

|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

, что означает, что я размещаю элементы только внутри каждой группы (Last Used, Something и Lorem Ipsum), порядокгруппы должны остаться.

Это мои даты:

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.ч

#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Чтобы удовлетворить требование, я думаю, что я должен использовать Proxymodel.Но у меня что-то не так с моей моделью прокси, поэтому я получаю такой результат, когда загружаю модель (я еще не нажимал на заголовки для сортировки элементов)

enter image description here

Как вы можете видеть, Lorem Ipsum перемещается в конец списка, порядок в самом начале неправильный.Поэтому я думаю, что у меня есть ошибки в моей модели прокси.Не могли бы вы показать мне, где именно в моей модели прокси?Или любые другие решения также приветствуются.Это моя модель прокси:

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;
}

Ответы [ 2 ]

1 голос
/ 03 мая 2019

У вас есть два правила для вашего прокси сортировки:

Предмет - это категория. Так что это корневой элемент без родителя, и вам нужно отсортировать номер строки, чтобы сохранить исходный порядок.

В противном случае это дочерний элемент, и вы можете отсортировать его по значению.

Ваш прокси довольно прост: если у элемента есть родительский элемент, сортируйте его по строке. В противном случае используйте оригинальные правила сортировки (или другое).

Единственная сложная часть - это порядок (по возрастанию или по убыванию). Таким образом, вам придется иметь дело с этим (или ваша категория будет инвертирована).

Например:

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);
    }
};

Тесты:

    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();
0 голосов
/ 03 мая 2019

В этом случае я только что нашел решение.Я думаю, что модель должна быть обновлена, поскольку я импортирую в нее данные.Поэтому я поместил beginResetModel() и endResetModel() в функцию setCompleterData, и теперь она работает.

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();
}
...