qt Model/view (模型/视图)

model

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

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

virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0;  

inline QModelIndex createIndex(int row, int column, void *data = nullptr) const;

使用内部指针 ptr 为给定的行和列创建模型索引。

 bool hasIndex(int row, int column, const QModelIndex &parent = QModelIndex())

如果模型为具有父级的行和列返回有效的 QModelIndex,则返回 true。

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<int, QVariant> itemData(const QModelIndex &index) const;

 

项(item)

数据模型中存储数据的基本单元都是项(item),每个项有一个行号、一个列号、还有一个父项。列表和表格模式下,所有的项都有一个相同的顶层项(用QModelIndex()表示)。

模型索引(model index)

数据模型的存取的每个数据都有一个模型索引(model index),视图组件和代理都是通过模型索引来获取数据。QModelIndex表示模型索引的类。QModelIndex最重要的作用是,建立起Data中数据的索引与View中数据的索引的映射关系

virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const = 0;

返回由给定的行、列和父索引指定的模型中项目的索引

QVariant data(int role = Qt::DisplayRole) const;

通过模型索引得到对应的数据

自定义子类

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

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

也有文章说也要重写index()、parent()?

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

#ifndef MYBOOKTABLEMODEL_HPP
#define MYBOOKTABLEMODEL_HPP

#include <QTableView>
#include <QAbstractTableModel>
#include <QHeaderView>
#include <QScrollBar>

//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<Book> bookList;
};

#endif // MYTABLEMODEL_HPP



#include "mainwindow.h"
#include <QApplication>

#include "mytablemodel.hpp"
#include <QHBoxLayout>
#include <QPushButton>

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更新数据。

#ifndef QWCOMBOBOXDELEGATE_H
#define QWCOMBOBOXDELEGATE_H

#include    <QStyledItemDelegate>
#include    <QComboBox>

class QWComboBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT

private:
    QStringList m_ItemList;//选择列表
    bool    m_isEdit; //是否可编辑

public:
    QWComboBoxDelegate(QObject *parent=0);

    void    setItems(QStringList items, bool isEdit);//初始化设置列表内容,是否可编辑
//    void    clearItems();
//自定义代理组件必须继承以下4个函数
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const Q_DECL_OVERRIDE;

    void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE;
    void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const Q_DECL_OVERRIDE;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                              const QModelIndex &index) const Q_DECL_OVERRIDE;
};

#endif // QWCOMBOBOXDELEGATE_H


#include "qwcomboboxdelegate.h"

#include    <QComboBox>

QWComboBoxDelegate::QWComboBoxDelegate(QObject *parent):QStyledItemDelegate(parent)
{

}

void QWComboBoxDelegate::setItems(QStringList items, bool isEdit)
{
    m_ItemList=items;
    m_isEdit=isEdit;
}

QWidget *QWComboBoxDelegate::createEditor(QWidget *parent,
       const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(option);
    Q_UNUSED(index);

    QComboBox *editor = new QComboBox(parent);
    for (int i=0;i<m_ItemList.count();i++)   //从字符串列表初始下拉列表
        editor->addItem(m_ItemList.at(i));

    editor->setEditable(m_isEdit); //是否可编辑
    return editor;
}

void QWComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    QString str = index.model()->data(index, Qt::EditRole).toString();

    QComboBox *comboBox = static_cast<QComboBox*>(editor);
    comboBox->setCurrentText(str);
}

void QWComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QComboBox *comboBox = static_cast<QComboBox*>(editor);
    QString str = comboBox->currentText();
    model->setData(index, str, Qt::EditRole);
}

void QWComboBoxDelegate::updateEditorGeometry(QWidget *editor,
                const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(index);
    editor->setGeometry(option.rect);
}


//调用
    QStringList strList;
    strList<<"男"<<"女";
    bool isEditable=false;
    delegateSex.setItems(strList,isEditable);
    ui->tableView->setItemDelegateForColumn(m_pTableModel->fieldIndex("sex"),&delegateSex);


具体模型和视图搭配

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<QStandardItem*> &items);

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

QStandardItem
1.inline QString text() const 
{       
 return qvariant_cast<QString>(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,最后再显示。

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Qt中,Model/View是一种常用的模式,用于将数据和用户界面分离。它允许您将数据存储在一个模型中,并使用视图来显示和编辑这些数据。 模型Model)是一个类,它提供了访问和操作数据的接口。它可以是QAbstractItemModel的子类,也可以是Qt提供的一些现有模型,如QStandardItemModel或QSqlTableModel视图View)用于显示模型中的数据,并允许用户与之交互。常见的视图类包括QTableViewQTreeView和QListView。这些视图类提供了不同的显示方式,以适应不同的数据结构和用户需求。 模型视图之间通过代理(Proxy)进行通信。代理是一个中间层,它可以对模型的数据进行排序、过滤或其他操作,然后将结果传递给视图进行显示。 为了使用Model/View模式,您需要完成以下步骤: 1. 创建您的数据模型,并实现必要的接口函数。 2. 创建一个视图对象,并将其设置为显示特定模型的数据。 3. 可选地创建一个代理对象,对模型的数据进行进一步处理。 4. 将视图和代理与模型连接起来,以便它们可以相互通信。 通过使用Model/View模式,您可以实现数据与界面的解耦,使得对数据的修改更加方便和灵活。您可以通过修改模型来改变数据的存储方式,而不需要修改视图的代码。同时,您还可以使用现有的视图类来显示不同类型的数据,而无需重新编写显示逻辑。 希望这个简要介绍对您有帮助!如果您有任何进一步的问题,请随时提问。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值