今日,由于工作需要,要实现在QTableView中点击表头进行排序的功能,但QTableView中并未提供此功能,经过苦苦的网络搜索也为发现可用的代码。最后经过跟踪QTableWidget的排序功能实现,总算实现了此功能。
此文章将使用QT源码中自带的例子做为基础:
(file source: examples/widgets/tutorials/modelview/4_headers/main.cpp)
QT中要使用QTableView,则必须要有继承自QAbstractTableModel或者其他基类的自定义module。
而自定义模型中必须实现以下三个抽象方法才能完成一个module的正常功能。
int rowCount(const QModelIndex &parent = QModelIndex()) const override ;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
rowCount 用于返回模型中行数。
columnCount 用于返回模型中的列数。
data用于返回QModelIndex处((row,column)坐标)处的数据。
实现了以上三个函数就有了基本的模型,但是要实现其他功能,则需要重写其他的相关函数。
比如要实现自定义表头功能就必须实现:
QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE;
而要实现排序函数,则要重写sort函数。
virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
sort函数中第一个参数指定要排序的列,第二个参数指定要排序的数序–从大到小函数从小到大。
而要实现此功能,其核心是要根据指定列的数据重新排列module中的自定义数据。
下边下看个完整的例子:
//main.cpp
#include
#include
#include "mymodel.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTableView tableView;
MyModel myModel(0);
tableView.setModel( &myModel );
QObject::connect(&tableView,SIGNAL(doubleClicked(QModelIndex)),&myModel,SLOT(ShowData(QModelIndex)));
tableView.setSortingEnabled(true);
tableView.show();
return a.exec();
}
//mymodel.h
ifndef MYMODEL_H
#define MYMODEL_H
#include
#include
typedef struct _FILEDATA{
QString Data1;
QString Data2;
int orderNumber;
}DATA;
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
MyModel(QObject *parent);
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE ;
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE;
virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
public slots:
void ShowData(const QModelIndex & index);
private:
QList m_data;
};
#endif // MYMODEL_H
自定义的module中有有一个QList,QList中的每一个元素是一个自定义的结构体DATA,而结构体中的每一个程序变量则对应试图中的1列。
在构造函数中,我们先初始化一下此结构体。
MyModel::MyModel(QObject *parent)
:QAbstractTableModel(parent)
{
for(int i=0;i<10;i++)
{
DATA f;
f.orderNumber=i;
f.Data2=QString("xx");
f.Data1=QString("%1").arg(rand());
m_data.append(f);
}
}
结构中的data1字段采用随机数,data2字段采用固定值,orderNumber则是序号。
下边将实现module必须的几个函数:
//-------------------------------------------------------
int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
return m_data.count();
}
//-------------------------------------------------------
int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
return 3;
}
//-------------------------------------------------------
QVariant MyModel::data(const QModelIndex &index, int role) const
{
QVariant retData;
if(!index.isValid()){
return QVariant();
}
int row = index.row();
int column = index.column();
DATA Data = m_data.at(row);
if(role == Qt::DisplayRole){
switch(column){
case 0:
retData = Data.Data1;
break;
case 1:
retData = Data.Data2;
break;
case 2:
retData = Data.orderNumber;
break;
}
}
return retData;
}
//! [quoting mymodel_c]
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole)
{
if (orientation == Qt::Horizontal) {
switch (section)
{
case 0:
return QString("Data1");
case 1:
return QString("Data1");
case 2:
return QString("orderNumber");
}
}
}
return QVariant();
}
以上几个函数比较简单,就不过多介绍了。
下边将介绍我们主要的目的–排序功能的实现。
经过跟踪QTableWidget的实现,我们知道,要实现一个试图的按列排序功能,主要要做的就是把module中的数据重新排序,同时其索引也要变化。但是在这个例子中我们并没有对索引进行管理,因此可以只对数据进行排序,然后通知试图进行更新。
而对QList m_data;的排序则可看作C语言结构体按照某一个成员进行排序的延伸。如果要实现此功能,则可简单的调用std::sort函数,然后传递一个对结构题某个成员的排序函数来实现。比如,我们例子中要实现按照第三列orderNumber的排序,则只用如下实现:
bool compare(DATA f1,DATA f2)
{
return f1.orderNumber.orderNumber;
}
std::sort(m_data.begin(), m_data.end(), compare);
但是这里有两个问题,上述函数只能实现orderNumber的从小到大排序,但是要实现从大到小排列,我们则需要另外写一个实现函数。同时,如果要根据其他字段(参数column)来排序,则也要有相应的函数,我们这里DATA结构幸好只有3个成员,而排序方法幸好也只有从大到小和从小到大这两种方法,我们或许可以勉为其难实现这六个函数,然后根据不同的column和不同的order来具体实现。可假如我们自己定义的结构体有1000个,1万个成员变量呢?
所以,我们还需要找到一种通用的排序方法。参考QTabelWidget的实现方法,本人实现一个乞丐版的sort函数:
typedef bool (*LessThan)(const QPairint> &left,const QPairint> &right);
bool itemLessThan(const QPairint> &left,
const QPairint> &right)
{
return left.first < right.first;
}
bool itemGreaterThan(const QPairint> &left,
const QPairint> &right)
{
return (right.first < left .first);
}
void MyModel::sort(int column, Qt::SortOrder order)
{
QVectorint> > sortable;
QVector<int> unsortable;
sortable.reserve(rowCount());
unsortable.reserve(rowCount());
for (int row = 0; row < rowCount(); ++row)
{
QVariant itm = this->data(this->index(row,column));
if (!itm.isNull())
{
sortable.append(QPairint>(itm, row));
}
else
{
unsortable.append(row);
}
}
LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan);
std::stable_sort(sortable.begin(), sortable.end(), compare);
QList sort_data;
emit layoutAboutToBeChanged();
int nSwapCount=rowCount();
for(int i=0;iint r = (i < sortable.count()
? sortable.at(i).second
: unsortable.at(i - sortable.count()));
sort_data.append(m_data[r]);
}
m_data=sort_data;
emit layoutChanged();
}
经过我们上边的分析我们知道,data函数的返回值是QVariant类型,同时由于我们为自已定义的结构体,所以基本可以保证每一列的数据的数据类型为一致的,因此要比较相邻的两个数据则直接使用QVariant的相关函数即可实现。
bool operator!=(const QVariant & v) const
bool operator<(const QVariant & v) const
bool operator<=(const QVariant & v) const
bool operator==(const QVariant & v) const
bool operator>(const QVariant & v) const
bool operator>=(const QVariant & v) const
仿照QTableWidget的实现,我们首先提取出要排序列的数据,同时记录此数据的所在行。
//QPair 中一个元素为我们要比较的数据,第二个为数据所在行
QVectorint> > sortable;
QVector<int> unsortable;
sortable.reserve(rowCount());
unsortable.reserve(rowCount());
//保存数据。根据参数column选择要保存的列。如果数据为空则只记录行号,不参与排序
for (int row = 0; row < rowCount(); ++row)
{
QVariant itm = this->data(this->index(row,column));
if (!itm.isNull())
{
sortable.append(QPairint>(itm, row));
}
else
{
unsortable.append(row);
}
}
//根据参数order的要求,选择排序函数
LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan);
//实现单列的排序
std::stable_sort(sortable.begin(), sortable.end(), compare);
QList sort_data;
emit layoutAboutToBeChanged();
//根据上面单列排序后的结构,将数据重拍。
int nSwapCount=rowCount();
for(int i=0;i//取出原来序号
int r = (i < sortable.count()
? sortable.at(i).second
: unsortable.at(i - sortable.count()));
sort_data.append(m_data[r]);
}
这部分不怎么好理解,可以举个例子,假设数据如下:
下标0:abc xx 3
下标1:sjjd xx 1
下标2:dsfkj xx 2
假如我们要根据第三列(3,1,2)进行从小到大的排序,则:
1.提取数据 [QPair(3,0),QPair(1,1),QPair(2,2)]
2.根据QPair的first字段进行排序,排序后为:
[QPair(1,1),QPair(2,2),QPair(3,0)]
3.重新保存数据
sort_data[0]=QPair(1,1)处数据,即原来的m_data[1]
sort_data[1]=QPair(2,3)处数据,即原来的m_data[2]
sort_data[2]=QPair(3,0)处数据,即原来的m_data[0]
4.把sort_data赋值给m_data,发送信号layoutChanged 改变视图。
上面的例子仅演示了所有数据不同的情况,而对于数据相同和部分数据为空的情况并未体现。对与上边的例子如果按照第2类(xx)排序的话,则数据不会有任何变化。
以上完成整个排序功能。但是本人的排序算法还有几个问题:
1.做不多通用,只能是一个module写一个sort算法,因为在上边的实现中引用了具体的成员变量。如果想要像QTableWidget那样实现通用排序,估计要增加其他的QTableWidgetItem来进行管理,这样无疑会增加复杂度。
2.以上的排序算法需要单独申请其他的空间来保存数据,并且最终的交换算法效率不高(需要另外一个同样大小同样类型的sort_data来先保存数据),另外对于sort_data.append(m_data[r]);一直隐隐担心,如果结构体成员中有负责的结构,这里是否会出问题。
3.对应数据量大的module这里的空间和时间消耗估计都会很大。
4.上述算法中的比较算法将最终是依据结构体中定义的基础数据类型进行比较,如果结构体中有类或者结构体等复杂类型,则还需进一步实现><的重载函数。
总之,虽然目前排序算法还有这样那样的缺陷,但是在急切之下也是暂时能满足需求,先用着,之后再优化把。
附上mymodel.cpp的完整代码:
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mymodel.h"
#include
#include
#include
MyModel::MyModel(QObject *parent)
:QAbstractTableModel(parent)
{
for(int i=0;i<10;i++)
{
DATA f;
f.orderNumber=i;
f.Data2=QString("xx");
f.Data1=QString("%1").arg(rand());
m_data.append(f);
}
}
//-------------------------------------------------------
int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
return m_data.count();
}
//-------------------------------------------------------
int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
return 3;
}
//-------------------------------------------------------
QVariant MyModel::data(const QModelIndex &index, int role) const
{
QVariant retData;
if(!index.isValid()){
return QVariant();
}
int row = index.row();
int column = index.column();
DATA Data = m_data.at(row);
if(role == Qt::DisplayRole){
switch(column){
case 0:
retData = Data.Data1;
break;
case 1:
retData = Data.Data2;
break;
case 2:
retData = Data.orderNumber;
break;
}
}
return retData;
}
//! [quoting mymodel_c]
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole)
{
if (orientation == Qt::Horizontal) {
switch (section)
{
case 0:
return QString("Data1");
case 1:
return QString("Data1");
case 2:
return QString("orderNumber");
}
}
}
return QVariant();
}
bool compare(DATA f1,DATA f2)
{
return f1.orderNumbertypedef bool (*LessThan)(const QPairint> &left,const QPairint> &right);
bool itemLessThan(const QPairint> &left,
const QPairint> &right)
{
return left.first < right.first;
}
bool itemGreaterThan(const QPairint> &left,
const QPairint> &right)
{
return (right.first < left .first);
}
void MyModel::sort(int column, Qt::SortOrder order)
{
QVectorint> > sortable;
QVector<int> unsortable;
sortable.reserve(rowCount());
unsortable.reserve(rowCount());
for (int row = 0; row < rowCount(); ++row)
{
QVariant itm = this->data(this->index(row,column));
if (!itm.isNull())
{
sortable.append(QPairint>(itm, row));
}
else
{
unsortable.append(row);
}
}
LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan);
std::stable_sort(sortable.begin(), sortable.end(), compare);
QList sort_data;
emit layoutAboutToBeChanged();
int nSwapCount=rowCount();
for(int i=0;iint r = (i < sortable.count()
? sortable.at(i).second
: unsortable.at(i - sortable.count()));
sort_data.append(m_data[r]);
}
m_data=sort_data;
emit layoutChanged();
}
#include
void MyModel::ShowData(const QModelIndex &index)
{
QMessageBox::information(NULL,"test",this->data(index).toString());
}
//! [quoting mymodel_c]