Qt之如何自定义model

一、需要在模型子类中实现的函数可以分为三组:

1、项数据的处理:所有模型都需要实现一些功能,以使视图和委托能够查询模型的维度、检查项和检索数据。

2、导航和索引创建:层次模型需要提供视图可以调用的函数来导航它们公开的树状结构,并获得项目的模型索引。

3、拖放支持和MIME类型处理:模型继承了控制内部和外部拖放操作执行方式的函数。这些函数允许用其他组件和应用程序能够理解的MIME类型来描述数据项。

二、项数据的处理

1、只读模型:

flags() 其他组件用于获取关于模型提供的每个项的信息。在许多模型中,标志的组合应该包括Qt::ItemIsEnabled和Qt::ItemIsSelectable。
data() 用于向视图和委托提供项数据。通常,模型只需要为Qt::DisplayRole和任何特定于应用程序的用户角色提供数据,但也可以为Qt::ToolTipRole、Qt::AccessibleTextRole和Qt::AccessibleDescriptionRole提供数据
headerData() 提供视图的标题中显示的信息。信息仅由能够显示头信息的视图检索。
rowCount() 模型数据的行数
columnCount() 提供模型公开的数据列的数量。List模型不提供此功能,因为它已经在QAbstractListModel中实现。
#include <QAbstractListModel>
class StringListModel : public QAbstractListModel
{
public:
    StringListModel(const QStringList stringList, QObject *parent = 0);
protected:
    Qt::ItemFlags flags(const QModelIndex &index) const;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
    int rowCount(const QModelIndex &parent = QModelIndex()) const;
private:
    QStringList m_stringList;
};

StringListModel::StringListModel(const QStringList stringList, QObject *parent)
    :QAbstractListModel(parent),m_stringList(stringList)
{

}

Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
    return QAbstractListModel::flags(index);//默认是ItemIsEnabled ItemIsSelectable
}

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
    if(!index.isValid())
        return QVariant();
    if(index.row() > m_stringList.size())
        return QVariant();
    if(role == Qt::DisplayRole)
        return m_stringList.at(index.row());
    else
        return QVariant();
}

QVariant StringListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role != Qt::DisplayRole)
        return QVariant();
    if(orientation == Qt::Horizontal)
        return QString("Column %1").arg(section);//列号
    if(orientation == Qt::Vertical)
        return QString("Row %1").arg(section);//行号
}

int StringListModel::rowCount(const QModelIndex &parent) const
{
    return m_stringList.count();
}

#include <QAbstractTableModel>
class TableModel : public QAbstractTableModel
{
public:
    TableModel(const int row, const int column, QObject *parent = 0);
protected:
    Qt::ItemFlags flags(const QModelIndex &index) const;
    QVariant data(const QModelIndex &index, int role) const;
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const;
private:
    int m_row;
    int m_column;
};

TableModel::TableModel(const int row, const int column, QObject *parent)
    :QAbstractTableModel(parent),m_row(row),m_column(column)
{

}

Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
    QAbstractTableModel::flags(index);
}

QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if(!index.isValid())
        return QVariant();
    if(role == Qt::DisplayRole)
        return QString("(%1,%2)").arg(index.column()).arg(index.row());
    else
        return QVariant();
}

int TableModel::rowCount(const QModelIndex &parent) const
{
    return m_row;
}

int TableModel::columnCount(const QModelIndex &parent) const
{
    return m_column;
}

QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role != Qt::DisplayRole)
        return QVariant();
    if(orientation == Qt::Horizontal)
        return QString("Column %1").arg(section);
    if(orientation == Qt::Vertical)
        return QString("Row %1").arg(section);
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;

    QGroupBox *listGroup = new QGroupBox("只读列表");
    QVBoxLayout *listLayout = new QVBoxLayout;
    QListView *listView = new QListView;
    StringListModel *stringListModel = new StringListModel(QStringList()<<"111"<<"222"<<"333");
    listView->setModel(stringListModel);
    listLayout->addWidget(listView);
    listGroup->setLayout(listLayout);


    QGroupBox *tableGroup = new QGroupBox("只读表格");
    QHBoxLayout *tableLayout = new QHBoxLayout;
    QTableView *tableView = new QTableView;
    tableView->verticalHeader()->hide();//不显示垂直标题栏
    TableModel *tableModel = new TableModel(3,3);
    tableView->setModel(tableModel);
    tableLayout->addWidget(tableView);
    tableGroup->setLayout(tableLayout);

    QHBoxLayout *mainLayout = new QHBoxLayout;
    mainLayout->addWidget(listGroup);
    mainLayout->addWidget(tableGroup);

    w.setLayout(mainLayout);
    w.show();

    return a.exec();
}

2、编辑模型:

flags() 除了返回只读的枚举值 还要加上

Qt::ItemIsEditable

setData() 用于修改与指定模型索引关联的数据项。为了能够接受用户界面元素提供的用户输入,这个函数必须处理与Qt::EditRole关联的数据。更改数据项之后,模型必须发出dataChanged()信号,以通知更改的其他组件。
setHeaderData() 用于修改水平和垂直标题信息。更改数据项之后,模型必须发出headerDataChanged()信号,以通知更改的其他组件。
#include <QAbstractTableModel>
class TableModel : public QAbstractTableModel
{
public:
    TableModel(const int row, const int column, QObject *parent = 0);
protected:
    Qt::ItemFlags flags(const QModelIndex &index) const;
    QVariant data(const QModelIndex &index, int role) const;
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const;

    //为编辑实现
    bool setData(const QModelIndex &index, const QVariant &value, int role);
    bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role);
private:
    int m_row;
    int m_column;
    QVector<QVector<QString>> vectors;
    QStringList headerStrings;
};

TableModel::TableModel(const int row, const int column, QObject *parent)
    :QAbstractTableModel(parent),m_row(row),m_column(column)
{
    for(int i = 0; i < row; ++i){
        QVector<QString> vector;
        for(int j = 0; j < column; ++j){
            vector << QString("(%1,%2)").arg(i).arg(j);
            headerStrings << QString("Column %1").arg(j);
        }
        vectors << vector;
    }
}

Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
    if(!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractTableModel::flags(index)|Qt::ItemIsEditable;
}

QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if(!index.isValid())
        return QVariant();
    if(role == Qt::DisplayRole || role == Qt::EditRole){
        QVector<QString> vector = vectors.at(index.row());
        return vector.at(index.column());
    }
    else
        return QVariant();
}

int TableModel::rowCount(const QModelIndex &parent) const
{
    return m_row;
}

int TableModel::columnCount(const QModelIndex &parent) const
{
    return m_column;
}

QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role != Qt::DisplayRole)
        return QVariant();
    if(orientation == Qt::Horizontal)
        return headerStrings.at(section);
    return QVariant();
}

bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() && role == Qt::EditRole){
        QVector<QString> vector = vectors.at(index.row());
        vector.replace(index.column(),value.toString());
        vectors.replace(index.row(),vector);
        //发出信号 通知 data函数重新读取数据
        dataChanged(index,index);
        return true;
    }
    return false;
}

bool TableModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
    if(role != Qt::EditRole && orientation == Qt::Horizontal){
        headerStrings.replace(section,value.toString());
        headerDataChanged(Qt::Horizontal,section,section);
        return true;
    }
    return false;
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;

    QGroupBox *tableGroup = new QGroupBox("只读表格");
    QHBoxLayout *tableLayout = new QHBoxLayout;
    QTableView *tableView = new QTableView;
    tableView->verticalHeader()->hide();//不显示垂直标题栏
    TableModel *tableModel = new TableModel(3,3);
    tableView->setModel(tableModel);
    tableLayout->addWidget(tableView);
    tableGroup->setLayout(tableLayout);

    QHBoxLayout *mainLayout = new QHBoxLayout;
    mainLayout->addWidget(tableGroup);

    w.setLayout(mainLayout);
    w.show();

    return a.exec();
}

3、插入删除模型:

insertRows() 用于向所有类型的模型中添加新的数据行和数据项。实现必须在将新行插入任何底层数据结构之前调用beginInsertRows(),然后立即调用endInsertRows()。
removeRows() 用于从所有类型的模型中删除行及其包含的数据项。实现必须在从任何底层数据结构中删除行之前调用beginRemoveRows(),然后立即调用endRemoveRows()。
insertColumns() 用于向表模型和层次模型添加新的列和数据项。实现必须在将新列插入任何底层数据结构之前调用beginInsertColumns(),然后立即调用endInsertColumns()。
removeColumns() 用于从表模型和层次模型中删除列及其包含的数据项。实现必须在从任何底层数据结构中删除列之前调用beginRemoveColumns(),然后立即调用endRemoveColumns()。
#include <QAbstractTableModel>
class TableModel : public QAbstractTableModel
{
public:
    TableModel(const int row, const int column, QObject *parent = 0);

    Qt::ItemFlags flags(const QModelIndex &index) const;
    QVariant data(const QModelIndex &index, int role) const;
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const;

    //为编辑实现
    bool setData(const QModelIndex &index, const QVariant &value, int role);
    bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role);

    //为插入和删除实现
    bool insertRows(int row, int count, const QModelIndex &parent);
    bool removeRows(int row, int count, const QModelIndex &parent);
    bool insertColumns(int column, int count, const QModelIndex &parent);
    bool removeColumns(int column, int count, const QModelIndex &parent);
private:
    int m_row;
    int m_column;
    QVector<QVector<QString>> vectors;
    QStringList headerStrings;
};

TableModel::TableModel(const int row, const int column, QObject *parent)
    :QAbstractTableModel(parent),m_row(row),m_column(column)
{
    for(int i = 0; i < row; ++i){
        QVector<QString> vector;
        for(int j = 0; j < column; ++j){
            vector << QString("(%1,%2)").arg(i).arg(j);
        }
        vectors << vector;
    }

    for(int i = 0; i < column; ++i)
        headerStrings << QString("Column %1").arg(i);
}

Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
    if(!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractTableModel::flags(index)|Qt::ItemIsEditable;
}

QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if(!index.isValid())
        return QVariant();
    if(role == Qt::DisplayRole || role == Qt::EditRole){
        QVector<QString> vector = vectors.at(index.row());
        return vector.at(index.column());
    }
    else
        return QVariant();
}

int TableModel::rowCount(const QModelIndex &parent) const
{
    return m_row;
}

int TableModel::columnCount(const QModelIndex &parent) const
{
    return m_column;
}

QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role != Qt::DisplayRole)
        return QVariant();
    if(orientation == Qt::Horizontal)
        return headerStrings.at(section);
    return QVariant();
}

bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() && role == Qt::EditRole){
        QVector<QString> vector = vectors.at(index.row());
        vector.replace(index.column(),value.toString());
        vectors.replace(index.row(),vector);
        //发出信号 通知 data函数重新读取数据
        dataChanged(index,index);
        return true;
    }
    return false;
}

bool TableModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
    if(role != Qt::EditRole && orientation == Qt::Horizontal){
        headerStrings.replace(section,value.toString());
        headerDataChanged(Qt::Horizontal,section,section);
        return true;
    }
    return false;
}

bool TableModel::insertRows(int row, int count, const QModelIndex &parent)
{
    beginInsertRows(parent,row,row+count-1);
    for(int i = 0; i < count; ++i){
        QVector<QString> vector;
        for(int j = 0; j < m_column; ++j){
            vector << QString("新插入");
        }
        vectors.insert(row,vector);
        m_row++;
    }
    endInsertRows();
    return true;
}

bool TableModel::removeRows(int row, int count, const QModelIndex &parent)
{
    if(vectors.isEmpty() || m_row < row + count)
        return false;
    beginRemoveRows(parent,row,row+count-1);
    for(int i = 0; i < count; ++i){
        vectors.remove(row);
        m_row--;
    }
    endRemoveRows();
}

bool TableModel::insertColumns(int column, int count, const QModelIndex &parent)
{
    beginInsertColumns(parent,column,column+count-1);
    //遍历每一行 为其增加count个元素
    for(int i = 0; i < m_row; ++i){
        QVector<QString> vector = vectors.at(i);
        for(int j = 0; j < count; ++j){
            vector.insert(column,QString("新增的%1").arg(j));
        }
        vectors.replace(i,vector);
    }
    //标题头要更新
    for(int i = 0; i < count; ++i){
        headerStrings << QString("Column %1").arg(m_column++);
    }
    endInsertColumns();
}

bool TableModel::removeColumns(int column, int count, const QModelIndex &parent)
{
    if(vectors.isEmpty() || m_column < column + count)
        return false;
    beginRemoveColumns(parent,column,column+count-1);
    for(int i = 0; i < m_row; ++i){
        QVector<QString> vector = vectors.at(i);
        for(int j = 0; j < count; ++j){
            vector.remove(column);
        }
        vectors.replace(i,vector);
    }

    //删除标题头
    for(int i = 0; i < count; ++i){
        headerStrings.removeAt(column);
        m_column--;
    }
    endRemoveColumns();
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QMainWindow window;
    QAction *removeRow = window.menuBar()->addAction("删除行");
    QAction *insertRow = window.menuBar()->addAction("插入行");
    QAction *removeColumn = window.menuBar()->addAction("删除列");
    QAction *insertColumn = window.menuBar()->addAction("插入列");

    QTableView *view = new QTableView;
//    view->setSelectionBehavior(QTableView::SelectRows);//选中单个item时 表现为选中了行
    view->setSelectionBehavior(QTableView::SelectColumns);//选中单个item时 表现为选中了列
    TableModel *model = new TableModel(3,3);
    view->setModel(model);

    QObject::connect(removeRow,&QAction::triggered,[=]{
        if(view->selectionModel()->hasSelection()){
            QModelIndex currentIndex = view->selectionModel()->currentIndex();
            model->removeRows(currentIndex.row(),1,QModelIndex());
        }else{
            model->removeRows(0,1,QModelIndex());
        }
    });

    QObject::connect(insertRow,&QAction::triggered,[=]{
        if(view->selectionModel()->hasSelection()){
            QModelIndex currentIndex = view->selectionModel()->currentIndex();
            model->insertRows(currentIndex.row(),1,QModelIndex());
        }else{
            model->insertRows(model->rowCount(QModelIndex()),1,QModelIndex());
        }
    });

    QObject::connect(removeColumn,&QAction::triggered,[=]{
        if(view->selectionModel()->hasSelection()){
            QModelIndex index = view->selectionModel()->currentIndex();
            model->removeColumns(index.column(),1,QModelIndex());
        }else{
            model->removeColumns(model->columnCount(QModelIndex()),1,QModelIndex());
        }
    });

    QObject::connect(insertColumn,&QAction::triggered,[=]{
        if(view->selectionModel()->hasSelection()){
            QModelIndex index = view->selectionModel()->currentIndex();
            model->insertColumns(index.column(),1,QModelIndex());
        }else{
            model->insertColumns(model->columnCount(QModelIndex()),1,QModelIndex());
        }
    });

    window.setCentralWidget(view);
    window.show();
    return a.exec();
}

三、自定义模型索引

index() 给定父项的模型索引,此函数允许视图和委托访问该项的子项。如果找不到与指定的行、列和父模型索引对应的有效子项,则函数必须返回QModelIndex(),这是一个无效的模型索引。
parent() 提供与任何给定子项的父项对应的模型索引。如果指定的模型索引对应于模型中的顶级项,或者如果模型中没有有效的父项,那么该函数必须返回一个无效的模型索引,该索引是用空的QModelIndex()构造函数创建的。

上面的两个函数都使用createIndex()工厂函数来生成供其他组件使用的索引。模型通常为这个函数提供一些惟一的标识符,以确保模型索引稍后可以与其对应的项重新关联。

四、拖放支持和MIME类型处理

猜你喜欢

转载自blog.csdn.net/wei375653972/article/details/86592209