Qt之模型/视图编程

一、简介

1、Model/Veiw是建立在MVC基础上的。什么是MVC?这里有详细介:https://blog.csdn.net/zuiyingong6567/article/details/80150834

2、Qt中的MVC略有不同,变成了MVD。如下图:

3、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更新数据。从这里可以看出来,view和delegate都可以使用QModelIndex,但是view只能使用QModelIndex进行读取,delegate只能修改。

4、Model:所有model都基于QAbstractItemModel类。该类定义了view和delegate用于访问data的接口。data本身不必存储在model中,它可以存储在由单独的类、文件、数据库或其他数据结构中。Qt提供了一些现成的model:

    1)QStringListModel:用于存储简单的QString列表。

    2)QStandardItemModel:适用于各种结构,每个项都可以包含任意数据。

    3)QFileSystemModel:提供有关本地归档系统中的文件和目录的信息。

    4)QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel:用于访问数据库。

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

5、Views:QListView列表视图,QTableView表格视图,QTreeView树形视图。

6、Delegates:QAbstractItemDelegate是模型/视图框架中委托的抽象基类。默认委托实现由QStyledItemDelegate提供,Qt的标准视图将其用作默认委托。如果需要展示更复杂的数据,需要我们重写Delegates。

7、模型视图中的方便类:QListWidget、QTreeWidget和QTableWidget。这些类不如视图类灵活,不能与任意模型一起使用。

二、如何使用model/view

1、model详解:

    1)model的三种分层结构:

    2)模型索引:为了确保数据的表示与访问数据的方式保持分离,引入了模型索引的概念。通过模型获得的每一条信息都由模型索引表示。视图和委托使用这些索引请求要显示的数据项。因此,只有模型需要知道如何获取数据。模型索引是临时的,当模型数据变化后,此模型索引变失效了,除非使用了QPersistentModelIndex。要获得与数据项相对应的模型索引,必须为模型指定三个属性:行号、列号和父项的模型索引:QModelIndex  QAbstractItemModel::index(int row, int column, const QModelIndex&parent = QModelIndex()) const。

    3)Item Role项角色:模型中的项可以为其他组件执行各种角色,允许为不同的情况提供不同类型的数据。例如,Qt::DisplayRole用于访问可以在视图中显示为文本的字符串。通常,项包含许多不同角色的数据,标准角色由Qt::ItemDataRole定义。我们可以通过向模型传递与项目对应的模型索引,并指定一个角色来获取我们想要的数据类型,从而获得需要的数据。

    4)总结上文:

        a)视图和委托可以通过模型索引获得数据。

        b)通过行号、列号和父项的模型索引可以获得模型索引。

        c)模型索引是根据其他组件(如视图和委托)的请求由模型构建的。

        d)如果在使用index()请求索引时为父项指定了有效的模型索引,则返回的索引引用模型中父项下的项,如果在使用index()请求索引时为父项指定了无效的模型索引,则返回的索引引用模型中的顶级项。

        e)角色区分与项关联的不同类型的数据。

    5)如何使用模型索引:

        a)可以使用rowCount()和columnCount()找到模型的维度。这些函数通常需要指定父模型索引。

        b)模型索引用于访问模型中的项。

        c)要访问模型中的顶级项,使用QModelIndex()指定空模型索引作为父索引。

        d)项包含不同角色的数据。要获取特定角色的数据,必须向模型提供模型索引和角色。

    6) 使用Qt自带的model

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QSplitter *splitter = new QSplitter;
    QFileSystemModel *model = new QFileSystemModel;
    model->setRootPath("F:/");//指定此路径的根路径为model的根路径
    QModelIndex index = model->index("F:/");//指定索引项即展示的根

    QListView *list = new QListView;
    list->setModel(model);
    list->setRootIndex(index);
    splitter->addWidget(list);

    QTableView *table = new QTableView;
    table->setModel(model);
    table->setRootIndex(index);
    splitter->addWidget(table);

    QTreeView *tree = new QTreeView;
    tree->setModel(model);
    tree->setRootIndex(index);
    splitter->addWidget(tree);

    setCentralWidget(splitter);
}

    7)自定义model。

        a)QAbstractItemModel类提供了一个足够灵活的接口来支持以分层结构排列信息的数据源,从而允许以某种方式插入、删除、修改或排序数据。它还支持拖放操作。

在为现有数据结构创建新模型时,重要的是考虑应该使用哪种类型的模型来提供到数据的接口。子类化QAbstractListModel或QAbstractTableModel适合列表或表,子类化QAbstractItemModel适合层次树结构。

        b)如何自定义model。

3、view详解:

    1)视图能做些什么:显示数据、处理项之间的导航、项选择、上下文菜单、拖放、与委托一起提供定制编辑器。

    2)Qt中三个视图类。

        a)QListView可以将模型中的项显示为简单列表,也可以以经典图标视图的形式显示。

        b)QTreeView将模型中的项显示为列表的层次结构,允许以紧凑的方式表示深度嵌套的结构。

        c)QTableView以表格的形式显示模型中的项,非常类似于电子表格应用程序的布局。

    3)如何处理选中项:使用QItemSelectionModel类。默认情况下,可以通过selectionModel()函数获得,使用setSelectionModel()指定替换选择模型。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QTableWidget *table = new QTableWidget;
    table->setColumnCount(3);
    table->setRowCount(3);
    setCentralWidget(table);

    QItemSelectionModel *selection = table->selectionModel();

    //让表在初始化的时候就被选中
    QModelIndex topLeft;
    QModelIndex bottomRight;
    topLeft = table->model()->index(0,0,QModelIndex());
    bottomRight = table->model()->index(1,1,QModelIndex());
    QItemSelection itemSelection;
    itemSelection.select(topLeft,bottomRight);
    selection->select(itemSelection,QItemSelectionModel::Select);

    //当当前项发生更改时,将发出此信号。前面的模型项索引将被当前索引替换,作为所选项的当前项。
    connect(selection,&QItemSelectionModel::currentChanged,
            [=](const QModelIndex &current, const QModelIndex &previous){
        statusBar()->showMessage(
            tr("Moved from (%1,%2) to (%3,%4)")
                .arg(previous.row()).arg(previous.column())
                .arg(current.row()).arg(current.column()));
    });
    //当选择发生更改时,将发出此信号。
    connect(selection,&QItemSelectionModel::selectionChanged,
            [=](const QItemSelection &selected, const QItemSelection &deselected){
        QModelIndexList list = selected.indexes();
        foreach (QModelIndex index, list) {
            QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
            table->model()->setData(index,text);
        }

        QModelIndexList list1 = deselected.indexes();
        foreach (QModelIndex index, list1) {
            table->model()->setData(index,"");
        }
    });
    connect(selection,&QItemSelectionModel::modelChanged,
            [=](QAbstractItemModel *model){

    });
    //如果当前项发生更改,且其列与前一个当前项的列不同,则发出此信号。
    connect(selection,&QItemSelectionModel::currentColumnChanged,
            [=](const QModelIndex &current, const QModelIndex &previous){
//        qDebug() << "previous column:" << previous.column();
//        qDebug() << "current column:" << current.column();
    });
    //如果当前项发生更改,且其行与前一个当前项的行不同,则发出此信号。
    connect(selection,&QItemSelectionModel::currentRowChanged,
            [=](const QModelIndex &current, const QModelIndex &previous){
//        qDebug() << "previous row:" << previous.row();
//        qDebug() << "current row:" << current.row();
    });

    //反转选中的状态
    QAction *toggle = menuBar()->addAction("反转选中状态");
    connect(toggle,&QAction::triggered,[=]{
        QModelIndex topLeft = table->model()->index(0,1,QModelIndex());
        QModelIndex bottomRight = table->model()->index(1,1,QModelIndex());
        QItemSelection toggleSelection(topLeft,bottomRight);
        selection->select(toggleSelection,QItemSelectionModel::Toggle);
    });

    //全选
    QAction *all = menuBar()->addAction("选中全部");
    connect(all,&QAction::triggered,[=]{
        QModelIndex topLeft = table->model()->index(0,0,QModelIndex());
        QModelIndex bottomRight = table->model()->index(table->model()->rowCount(QModelIndex())-1,
                                                        table->model()->columnCount(QModelIndex())-1,
                                                        QModelIndex());
        QItemSelection itemSelection(topLeft,bottomRight);
        selection->select(itemSelection,QItemSelectionModel::Select);
    });
}

    4)如何处理对视图中项的拖放操作

    5)如何与Delegate一起提供定制编辑器,在Delegate中会演示。

4、Delegate详解:

    1)为什么会有Delegate:通常,视图负责向用户表示模型数据,并处理用户输入。但是为了使获取该输入的方式具有一定的灵活性,便交互由Delegate处理。

    2)Delegate由itemDelegate()函数返回。setItemDelegate()设置自定义Delegate。自定义Delegate的例子:

#ifndef SPINBOXDELEGATE_H
#define SPINBOXDELEGATE_H

#include <QStyledItemDelegate>

class SpinBoxDelegate : public QStyledItemDelegate
{
public:
    SpinBoxDelegate(QObject *parent = 0);
protected:
    //创建自定义的控件
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    //设置编辑控件中的值
    void setEditorData(QWidget *editor, const QModelIndex &index) const;
    //将编辑控件中的值保存到model中
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
    //设置编辑控件显示的位置和大小
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};

#endif // SPINBOXDELEGATE_H

#include "spinboxdelegate.h"
#include <QSpinBox>

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

}

QWidget *SpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QSpinBox *editor = new QSpinBox(parent);
    editor->setFrame(true);
    editor->setMinimum(0);
    editor->setMaximum(200);
    return editor;
}

void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    int value = index.model()->data(index,Qt::EditRole).toInt();
    QSpinBox *box = static_cast<QSpinBox*>(editor);
    box->setValue(value);
}

void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QSpinBox *box = static_cast<QSpinBox*>(editor);
    model->setData(index,box->value(),Qt::EditRole);
}

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

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QAbstractItemDelegate *delegate = new SpinBoxDelegate(this);

    QStringList list;
    for(int i = 100; i < 120; ++i){
        list << QString::number(i);
    }
    QStringListModel *model = new QStringListModel;
    model->setStringList(list);
    QListView *view = new QListView;
    view->setModel(model);
    view->setItemDelegate(delegate);
    setCentralWidget(view);
}

猜你喜欢

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