qt Model/view (模型/视图)

model

model 数据模型都是基于QAbstractItemModel类,数据无需存储在数据模型里,数据可以是其他类,文件、数据库或任何数据源。

mode类有 QAbstractListMode、QStringListMode、QStandardItemModel、QAbstractTableMode、QSqlQueryModel、QSqlTableModel、QFileSystemModel。

数据模型中存储数据的基本单元都是项(item),每个项有一个行号、一个列号、还有一个父项。列表和表格模式下,所有的项都有一个相同的顶层项。数据模型的存取的每个数据都有一个模型索引(model index),视图组件和代理都是通过模型索引来获取数据。QModelIndex表示模型索引的类。

1.Q_INVOKABLE virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const = 0;
2.Q_INVOKABLE virtual QModelIndex parent(const QModelIndex &child) const = 0;
3.Q_INVOKABLE virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0;    
4.Q_INVOKABLE virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
5.virtual QMap itemData(const QModelIndex &index) const;

  同时也可以子类化QAbstractItemModel、QAbstractListModel或QAbstractTableModel来创建自定义模型。

要实现从这些基类派生出自定义的类,必须要重写rowCount()、columnCount()、data()函数。

另外可编辑的模型需要实现setData()flags()函数,后者返回值需包含Qt::ItemIsEditable

#ifndef MYBOOKTABLEMODEL_HPP
#define MYBOOKTABLEMODEL_HPP

#include 
#include 
#include 
#include 

//MSVC编译器界面显示乱码问题
#if _MSC_VER >= 1600
    #pragma execution_character_set("utf-8")
#endif

//一本书的属性
struct Book
{
    QString name = "None";      // 书名
    QString publisher = "None";  // 出版社
    QString type = "None";       // 类别
    double price = 0.0;       // 价格
};
//属性对应的表头(列表头)
const QStringList titles = {"书名", "出版社", "类别", "价格"};


class MyBookTableModel : public QAbstractTableModel
{
public:
    explicit MyBookTableModel(QWidget* parent = Q_NULLPTR) : QAbstractTableModel(parent){}
    ~MyBookTableModel(){}


    virtual int rowCount(const QModelIndex &/*parent*/ = QModelIndex()) const override
    {
//        书本数量
        return bookList.size();
    }

    virtual int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const override
    {
//        书本属性值数量
        return titles.size();
    }

//    核心函数,View类从Model中取数据,传入的参数在之前的文章中有介绍
    virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
    {
        if(!index.isValid() || index.column()>=columnCount() || index.row()>=rowCount()) return QVariant();

        switch(role)
        {
//        显示数据用
        case Qt::DisplayRole:
            switch(index.column())
            {
            case 0:
                return bookList[index.row()].name;
                break;
            case 1:
                return bookList[index.row()].publisher;
                break;
            case 2:
                return bookList[index.row()].type;
                break;
            case 3:
                return bookList[index.row()].price;
                break;

            default:
                return QVariant();
                break;
            }

            break;

    //            对齐处理
            case Qt::TextAlignmentRole:
                return Qt::AlignCenter;
                break;

//            其余不处理
        default:
            return QVariant();
            break;
        }
    }

    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
    {
//        水平表头显示信息
        switch(role)
        {
        case Qt::DisplayRole:
            if(orientation==Qt::Horizontal && section>=0 && section<=columnCount())
                return titles.at(section);
            break;


        default:
            break;
        }


        return QAbstractItemModel::headerData(section, orientation, role);
    }

//    编辑相关函数
    virtual Qt::ItemFlags flags(const QModelIndex &index) const override
    {
        return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
    }

//    修改核心函数
    virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
    {
        if(role != Qt::EditRole || !index.isValid() || index.row()>=rowCount() || index.column()>=columnCount()) return false;

        bool ok = false;
        switch(index.column())
        {
        case 0: // 书名
            bookList[index.row()].name = value.toString();
            break;

        case 1: // 出版社
            bookList[index.row()].publisher = value.toString();
            break;

        case 2: // 类型
            bookList[index.row()].type = value.toString();
            break;

        case 3: // 价格,并做简单输入判断
            value.toDouble(&ok);
            if(ok) bookList[index.row()].price = value.toDouble(&ok);
            else return false;
            break;

        default:
            return false;
            break;
        }

        emit dataChanged(index, index);
        return true;
    }

private:
//    行修改函数:添加多行和删除多行
    virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override
    {
//        起始行row超限时,修正到两端插入
        if(row > rowCount()) row = rowCount();
        if(row < 0) row = 0;

//        需要将修改部分的代码使用begin和end函数包起来
        beginInsertRows(parent, row, row+count-1);

//        添加数据
        for(int i = 0; i < count; ++i) bookList.insert(bookList.begin()+row+i, Book());

        endInsertRows();

        emit dataChanged(createIndex(row, 0), createIndex(row+count-1, columnCount()-1));
        return true;
    }

    virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex())
    {
        if(row < 0 || row >= rowCount() || row + count > rowCount()) return false;


//        需要将修改部分的代码使用begin和end函数包起来
        beginRemoveRows(parent, row, row+count-1);

//        删除数据
        for(int i = 0; i < count; ++i)
        {
            bookList.remove(row);
        }

        endRemoveRows();

        return true;
    }

public:
//    2个简单公有接口
//    最后添加一行
    void appendRow()
    {
//        insertRow为内联函数,重写insertRows后即有
        insertRow(rowCount());
    }
//    最后删除一行
    void popBack()
    {
        removeRow(rowCount()-1);
    }

private:
//    存储书本信息的数组
    QVector bookList;
};

#endif // MYTABLEMODEL_HPP



#include "mainwindow.h"
#include 

#include "mytablemodel.hpp"
#include 
#include 

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
//    MainWindow w;
//    w.show();


//    view和model联动
    QTableView *tbl = new QTableView;
    MyBookTableModel model;
    tbl->setModel(&model);

//    隐藏滚动条
    tbl->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    tbl->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);


//    准备主界面
    QWidget w;

//    2个按钮,用来改变行数,采用垂直布局
    QVBoxLayout *v = new QVBoxLayout;
    QPushButton *btn1 = new QPushButton("添加一行");
    QPushButton *btn2 = new QPushButton("删除最末行");

    qApp->connect(btn1, &QPushButton::clicked, [&](){
        model.appendRow();
    });
    qApp->connect(btn2, &QPushButton::clicked, [&](){
        model.popBack();
    });

    v->addWidget(btn1);
    v->addWidget(btn2);

//    主界面的布局采用水平布局,左边是表格,右边是刚才的2个按键
    QHBoxLayout *h = new QHBoxLayout;
    h->addWidget(tbl);
    h->addLayout(v);

//    设置主界面的布局
    w.setLayout(h);

    w.show();

//    当内容改变时自适应列宽
    qApp->connect(&model, &MyBookTableModel::dataChanged,[&](){

        tbl->resizeColumnsToContents();
        int width = 0;
        for(int i = 0; i < model.columnCount(); ++i)
        {
            width += tbl->columnWidth(i);
        }
        width += tbl->verticalHeader()->width();



//        设置表格最小宽度
        tbl->setMinimumWidth(width);
//		  设置主窗口大小以刷新界面布局
        w.resize(w.minimumWidth(), 300);
    });

//    最开始添加2行
    model.appendRow();
    model.appendRow();

    return a.exec();
}


视图view

视图组件基于QAbstractItemView继承而来,有QListView、QTreeView、QTableView等。

而便利类如QlistWidget、Qtreewidget、QtableWidge 是前面3个类的子类,这些便利类没有数据模型,但实际上是用项的方式集成了数据模型的功能,所有便利类只适用小型数据的显示和编辑,缺乏对大型数据的灵活处理。

视图通过setModel函数设置数据模型,视图通过setItemDelegateForColumnsetItemDelegateForRow设置代理。

函数:

1.void setRootIndex(const QModelIndex &index) Q_DECL_OVERRIDE;   //设置视图的根节点

2. virtual void setModel(QAbstractItemModel *model); //设置选择数据模型

3.QAbstractItemModel *model() const;

4.QModelIndex currentIndex() const;

5.QModelIndex rootIndex() const;

6.void setItemDelegateForRow(int row, QAbstractItemDelegate *delegate);
7.void setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate);

信号:
1.void clicked(const QModelIndex &index);

代理

Qt使用抽象类QAbstractItemDelegate(基类)来描述代理,Qt实现了两个代理类(子类),QStyledItemDelegate和QItemDelegate,这两个委托之中只能使用其中一个,其区别在于QItemDelegate总是使用一种默认的样式绘制数据项,而QStyledItemDelegate使用当前的样式来绘制数据项,通常使用的是QStyledItemDelegate。Qt默认使用QStyledItemDelegate。

4个函数,原型是固定的。需要重新定义,即一般继承QStyledItemDelegate类,在子类重新定义实现。

1.createeditor()函数创建用于编辑模型数据的widget组件,如一个qcombobox组件。

2.seteditordata()从数据模型获取数据,供widget组件进行编辑。

3.setmodeldata()将widget上的数据更新到数据模型。

4.updateeditorgeometry()用于给widget组件设置一个合适的大小。

model、view、delegate 之间的作用关系简单概括如下:

    1)model和data相互通信,然后model为view和delegate提供接口。

    2)view通过调用model的接口,从model中获取模型索引QModelIndex,通过QModelIndex可以获得data。

    3)delegate为view展示data,delegate可以被编辑修改删除。而当在delegate上编辑时,它会用QModelIndex于model通信,通知model更新数据。

具体模型和视图搭配

QFileSystemModel 

一般和qtreeview结合使用。

是可访问本机文件系统的数据模型,并且是采用单独的线程获取目录文件结构。需要调用setrootpath设置根目录。而QDirModel也可以获取文件目录和文件,但不是采用单独的线程。

函数:

// QFileSystemModel specific API   
1. QModelIndex setRootPath(const QString &path);

QStringListModel 

基类是QAbstractListModel,一般和qlistView视图结合使用。

用于处理字符串列表的数据模型。

setstringlist函数初始化数据模型的字符串列表的内容。

stringlist函数返回数据模型内的字符串内容

 1. QStringList stringList() const;    
 2. void setStringList(const QStringList &strings);

QStandardItemModel

通常和QTableView结合使用。

是标准的以项数据(item data)为基础的标准数据模型,

QStandardItemModel :每个项是一个QStandardItem类的变量,用于存储项的数据、字体格式、对齐方式等。

常用的函数有:

QStandardItemModel :

1.QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;

2.bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE;

3.QStandardItem *itemFromIndex(const QModelIndex &index) const;

4.QModelIndex indexFromItem(const QStandardItem *item) const;

5.void setItem(int row, int column, QStandardItem *item);

6.void setRowCount(int rows);

7.void insertRow(int row, const QList &items);

8.QStandardItem *item(int row, int column = 0) const;

QStandardItem
1.inline QString text() const 
{       
 return qvariant_cast(data(Qt::DisplayRole));    
}    
2. inline void setText(const QString &text);
3. explicit QStandardItem(const QString &text);

QModelIndex  :模型索引

model函数是通过模型索引获取到模型

QStandardItem :数据管理的基本单元

QStandardItemModel可以取到QModelIndex  QStandardItem ,并且两者可以itemFromIndex或者indexFromItem相互转换。

QItemSelectionModel 用于跟踪视图组件的单元格选择状态的类

    theModel = new QStandardItemModel(2,FixedColumnCount,this); //数据模型
    theSelection = new QItemSelectionModel(theModel);//Item选择模型

//选择当前单元格变化时的信号与槽
    connect(theSelection,SIGNAL(currentChanged(QModelIndex,QModelIndex)),
            this,SLOT(on_currentChanged(QModelIndex,QModelIndex)));

    //为tableView设置数据模型
    ui->tableView->setModel(theModel); //设置数据模型
    ui->tableView->setSelectionModel(theSelection);//设置选择模型

QStandardItemModel可以使用setItem函数再配合QStandardItem添加数据。

            aItem=new QStandardItem(tmpList.at(j));//创建item
            theModel->setItem(i-1,j,aItem); //为模型的某个行列位置设置Item

效率最高的做法是:

1.调用setRowCount设置行数。

2.调用setdata设置显示的数据。

pmodel2->setData(pmodel2->index(nrow, ncol), strText, Qt::EditRole);//效率提升很大

延伸知识点:

1.模型视图上控件的拖拽

https://blog.csdn.net/baidu_16370559/article/details/129124680?spm=1001.2014.3001.5502

2.QTableView 点击表头进行排序

前提是mode应该是继承自或者就是QAbstractItemModelQStandardItemModel

把QTableView 对象的setSortingEnabled()函数设为True后就可以了。点击表头排序的背后调用的是QTableView 类的sortByColumn()这个函数。sortByColumn内部调用的是mode的sort函数。所以mode必须是重写好的sort。

如果数据实时变化,自动排好序显示,上面这种排序办法不行。只有先把数据排好序,然后添加到mode,最后再显示。

你可能感兴趣的:(qt,qt,开发语言)