模型视图简介、QListWidget、QTreeWidget、QTableWidget、QStringListModel、QFileSystemModel

来源:互联网 发布:华为mate8预装软件列表 编辑:程序博客网 时间:2024/06/05 09:05

一、模型视图简介

    有时,我们的系统需要显示大量数据,比如从数据库中读取数据,以自己的方式显示在自己的应用程序的界面中。早期的 Qt 要实现这个功能,需要定义一个组件,在这个组件中保存一个数据对象,比如一个列表。我们对这个列表进行查找、插入等的操作,或者把修改的地方写回,然后刷新组件进行显示。这个思路很简单,也很清晰,但是对于大型程序,这种设计就显得苍白无力。比如,在一个大型系统中,你的数据可能很大,全部存入一个组件的数据对象中,效率会很低,并且这样的设计也很难在不同组件之间共享数据。如果你要几个组件共享一个数据对象,要么你就要用存取函数公开这个数据对象,要么你就必须把这个数据对象放进不同的组件分别进行维护。
    Smalltalk 语言发明了一种崭新的实现,用来解决这个问题,这就是著名的 MVC 模型。对这个模型无需多言。MVC 是  Model-View-Controller 的简写,即模型-视图-控制器。在 MVC 中,模型负责获取需要显示的数据,并且存储这些数据的修改。每种数据类型都有它自己对应的模型,但是这些模型提供一个相同的 API,用于隐藏内部实现。视图用于将模型数据显示给用户。对于数量很大的数据,或许只显示一小部分,这样就能很好的提高性能。控制器是模型和视图之间的媒介,将用户的动作解析成对数据的操作,比如查找数据或者修改数据,然后转发给模型执行,最后再将模型中需要被显示的数据直接转发给视图进行显示。MVC 的核心思想是分层,不同的层应用不同的功能。
    Qt 4 开始,引入了类似的 model/view 架构来处理数据和面向最终用户的显示之间的关系。当 MVC 的 V 和 C 结合在一起,我们就得到了 model/view 架构。这种架构依然将数据和界面分离,但是框架更为简单。同样,这种架构也允许使用不同界面显示同一数据,也能够在不改变数据的情况下添加新的显示界面。为了处理用户输入,我们还引入了委托(delegate)。引入委托的好处是,我们能够自定义数据项的渲染和编辑


    如上图所示,模型与数据源进行交互,为框架中其它组件提供接口。这种交互的本质在于数据源的类型以及模型的实现方式。视图从模型获取模型索引,这种索引就是数据项的引用。通过将这个模型索引反向传给模型,视图又可以从数据源获取数据。在标准视图中,委托渲染数据项;在需要编辑数据时,委托使用直接模型索引直接与模型进行交互。
    总的来说,model/view 架构将传统的 MV 模型分为三部分:模型、视图和委托。每一个组件都由一个抽象类定义,这个抽象类提供了基本的公共接口以及一些默认实现。模型、视图和委托则使用信号槽进行交互:
   (1)来自模型的信号通知视图,其底层维护的数据发生了改变;
   (2)来自视图的信号提供了有关用户与界面进行交互的信息;
   (3)来自委托的信号在用户编辑数据项时使用,用于告知模型和视图编辑器的状态。

    所有的模型都是QAbstractItemModel的子类。这个类定义了供视图和委托访问数据的接口。模型并不存储数据本身。这意味着,你可以将数据存储在一个数据结构中、另外的类中、文件中、数据库中,或者其他你所能想到的东西中。
    QAbstractItemModel提供的接口足够灵活,足以应付以表格、列表和树的形式显示的数据。但是,如果你需要为列表或者表格设计另外的模型,直接继承QAbstractListModel和QAbstractTableModel类可能更好一些,因为这两个类已经实现了很多通用函数。
    Qt 内置了许多标准模型:
    (1)QStringListModel:存储简单的字符串列表。
    (2)QStandardItemModel:可以用于树结构的存储,提供了层次数据。
    (3)QFileSystemModel:本地系统的文件和目录信息。
    (4)QSqlQueryModel、QSqlTableModel和QSqlRelationalTableModel:存取数据库数据。

    正如上面所说,如果这些标准模型不能满足你的需要,就必须继承QAbstractItemModel、QAbstractListModel或者QAbstractTableModel,创建自己的模型类。
    Qt 还提供了一系列预定义好的视图:QListView用于显示列表,QTableView用于显示表格,QTreeView用于显示层次数据。这些类都是QAbstractItemView的子类。这意味着,如果你要创建新的视图类,要继承QAbstractItemView。
    QAbstractItemDelegate则是所有委托的抽象基类。自 Qt 4.4 依赖,默认的委托实现是QStyledItemDelegate。但是,QStyledItemDelegate和QItemDelegate都可以作为视图的编辑器,二者的区别在于,QStyledItemDelegate使用当前样式进行绘制。在实现自定义委托时,推荐使用QStyledItemDelegate作为基类,或者结合 Qt style sheets。
    如果你觉得 model/view 模型过于复杂,或者有很多功能是用不到的,Qt 还有一系列方便使用的类。这些类都是继承自标准的视图类,并且继承了标准模型。这些类并不是为其他类继承而准备的,只是为了使用方便。它们包括QListWidget、QTreeWidget和QTableWidget。这些类远不如视图类灵活,不能使用另外的模型,因此只适用于简单的情形。

二、QListWidget

#include "widget.h"#include <QHBoxLayout>#include <QIcon>#include <QString>#include <QObject>Widget::Widget(QWidget *parent)    : QWidget(parent){    /*     * QListWidget 是简单的列表组件。当我们不需要复杂的列表时,可以选择 QListWidget。     * QListWidget 中可以添加 QListWidgetItem 类型作为列表项, QListWidgetItem 即可以     * 有文本,也可以有图标。    */    lb=new QLabel(this);    lb->setFixedWidth(70);    lw=new QListWidget(this);    //更改图标的显示方式    lw->setViewMode(QListView::IconMode);    /*     * 注意这两种添加方式的区别:第一种需要在构造时设置所要添加到的 QListWidget 对象;     * 第二种方法不需要这样设置,而是要调用 addItem()或者 insertItem()自行添加。    */    new QListWidgetItem(QIcon(":/listview/chrome"),tr("Chrome"),lw);    lw->addItem(new QListWidgetItem(QIcon(":/listview/firefox"),tr("Firefox")));    lw->addItem(new QListWidgetItem(QIcon(":/listview/opera"),tr("Opera")));    QListWidgetItem *newitem=new QListWidgetItem;    newitem->setIcon(QIcon(":/listview/ie"));    newitem->setText(tr("IE"));    lw->insertItem(3,newitem);    //使用布局管理器    QHBoxLayout *layout=new QHBoxLayout;    layout->addWidget(lb);    layout->addWidget(lw);    setLayout(layout);    //使用 QListWidget 发出的各种信号来判断是哪个列表项被选择。    connect(lw,&QListWidget::currentTextChanged,lb,&QLabel::setText);}Widget::~Widget(){}

三、QTreeWidget

    顾名思义,这是用来展示树型结构(也就是层次结构)的。同前面说的QListWidget类似,这个类需要同另外一个辅助类QTreeWidgetItem一起使用。不过,既然是提供方面的封装类,即便是看上去很复杂的树,在使用这个类的时候也是显得比较简单的。当不需要使用复杂的QTreeView特性的时候,我们可以直接使用QTreeWidget代替。

#include "widget.h"#include <QApplication>#include <QTreeWidget>#include <QTreeWidgetItem>#include <QStringList>#include <QString>#include <QList>int main(int argc, char *argv[]){    QApplication a(argc, argv);    /*     * 创建一个 QTreeWidget 实例,使用 QStringList 设置了 headers,也就是树的表头。     * 接下来我们使用的还是 QStringList 设置数据。这样,我们实现的是带有层次结构的     * 树状表格。利用这一属性,我们可以比较简单地实现类似 Windows 资源管理器的界面。    */    QTreeWidget tw;    QStringList headers;    headers<<"Name"<<"Number";    tw.setHeaderLabels(headers);    QStringList roottextlist1;    roottextlist1<<"Root"<<"0";    QStringList roottextlist2;    roottextlist2<<"Root2"<<"0";    /*     * 向 QTreeWidget 添加QTreeWidgetItem。 QTreeWidgetItem 有很多重载的构造函数。     * 这里有2个参数,第一个参数用于指定这个项属于哪一个树,类似QListWidgetItem,     * 如果指定了这个值,则意味着该项被直接添加到树中;第二个参数指定显示的文字。     * 第二个参数是 QStringList 类型。我们创建了作为根的 QTreeWidgetItem root。     * 然后添加了第一个叶节点,之后又添加一个,而这个则设置了可选标记。最后,我们将     * 这个 root 添加到一个QTreeWidgetItem 的列表,作为 QTreeWidget 的数据项。    */    QTreeWidgetItem *root=new QTreeWidgetItem(&tw,roottextlist1);    new QTreeWidgetItem(root,QStringList()<<QString("leaf 1")<<"1");    QTreeWidgetItem *leaf2=new QTreeWidgetItem(root,QStringList()<<QString("leaf 2")<<"2");    leaf2->setCheckState(0,Qt::Checked);    QTreeWidgetItem *root2=new QTreeWidgetItem(&tw,roottextlist2);    new QTreeWidgetItem(root2,QStringList()<<QString("leaf 1")<<"1");    tw.show();    return a.exec();}

四、QTableWidget

#include "widget.h"#include <QApplication>#include <QTableWidget>#include <QTableWidgetItem>#include <QString>#include <QStringList>int main(int argc, char *argv[]){    QApplication a(argc, argv);    /*     * 首先我们创建了 QTableWidget 对象,然后设置列数和行数。接下来使用一个 QStringList,     * 设置每一列的标题。我们可以通过调用 setItem()函数来设置表格的单元格的数据。这个函数     * 前两个参数分别是行索引和列索引,这两个值都是从 0 开始的,第三个参数则是一个     * QTableWidgetItem 对象。 Qt 会将这个对象放在第 row 行第 col 列的单元格中。    */    QTableWidget tw;    tw.setColumnCount(3);    tw.setRowCount(5);    QStringList headers;    headers<<"ID"<<"Name"<<"Age";    tw.setHorizontalHeaderLabels(headers);    tw.setItem(0,0,new QTableWidgetItem(QString("01")));    tw.setItem(1,0,new QTableWidgetItem(QString("02")));    tw.setItem(2,0,new QTableWidgetItem(QString("03")));    tw.setItem(3,0,new QTableWidgetItem(QString("04")));    tw.setItem(4,0,new QTableWidgetItem(QString("05")));    tw.setItem(0,1,new QTableWidgetItem(QString("Eric")));    tw.show();    return a.exec();}

    前面我们已经了解到有关 list、table 和 tree 三个最常用的视图类的便捷类的使用。前面也提到过,由于这些类仅仅是提供方便,功能、实现自然不如真正的 model/view 强大。
    QStringListModel是最简单的模型类,具备向视图提供字符串数据的能力。QStringListModel是一个可编辑的模型,可以为组件提供一系列字符串作为数据。我们可以将其看作是封装了QStringList的模型。QStringList是一种很常用的数据类型,实际上是一个字符串列表(也就是QList<QString>)。既然是列表,它也就是线性的数据结构,因此,QStringListModel很多时候都会作为QListView或者QComboBox这种只有一列的视图组件的数据模型。

五、QStringListModel

#include "widget.h"#include <QStringList>#include <QHBoxLayout>#include <QVBoxLayout>#include <QInputDialog>#include <QLineEdit>#include <QMessageBox>MyListView::MyListView(QWidget *parent)    : QWidget(parent){    this->resize(300,300);    /*     * 首先,我们创建了一个 QStringList 对象,向其中插入了几个数据;然后将其作为     * QStringListModel 的底层数据。这样,我们可以理解为, QStringListModel 将     *  QStringList 包装了起来。视图获取模型的信号。    */    QStringList data;    data<<"letter A"<<"letter B"<<"letter C";    model=new QStringListModel(this);    model->setStringList(data);    listView=new QListView(this);    listView->setModel(model);    //界面代码    QHBoxLayout *btnLayout = new QHBoxLayout;//水平分布    QPushButton *insertBtn = new QPushButton(tr("insert"),this);    connect(insertBtn,&QPushButton::clicked,this,&MyListView::insertData);    QPushButton *delBtn = new QPushButton(tr("Delete"),this);    connect(delBtn,&QPushButton::clicked,this,&MyListView::deleteData);    QPushButton *showBtn = new QPushButton(tr("Show"),this);    connect(showBtn,&QPushButton::clicked,this,&MyListView::showData);    btnLayout->addWidget(insertBtn);    btnLayout->addWidget(delBtn);    btnLayout->addWidget(showBtn);    QVBoxLayout *mainLayout = new QVBoxLayout(this);//垂直分布    mainLayout->addWidget(listView);    mainLayout->addLayout(btnLayout);    setLayout(mainLayout);}MyListView::~MyListView(){}void MyListView::insertData(){    bool isok;    /*     * 我们使用 QInputDialog::getText()函数要求用户输入数据。这是 Qt 的标准对话框,     * 用于获取用户输入的字符串。    */    QString text=QInputDialog::getText(this,"Insert",    "请输入新数据:",QLineEdit::Normal,"你正在输入新数据",&isok);    if(isok){        /*         * 当用户点击了 OK 按钮,我们使用listView->currentIndex()函数,获取 QListView当前数据。         * 这个函数的返回值是一个 QModelIndex 类型。现在只要这个类保存了三个重要的数据:         * 行索引、列索引以及该数据属于哪一个模型。该返回值是一个int,也就是当前是第几行。        */        QModelIndex currindex=listView->currentIndex();        /*         * bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());         *该函数会将 count 行插入到模型给定的 row 的位置,新行的数据将会作为 parent 的子元素。         * 如果 row 为 0,新行将被插入到 parent 的所有数据之前,否则将在指定位置的数据之前。         * 如果 parent 没有子元素,则会新插入一个单列数据。函数插入成功返回 true,否则返回         * false。我们在这段代码中调用的是 insertRows(row, 1)。这是 QStringListModel 的一个重载。         * 参数 1 说明要插入 1 条数据。记得之前我们已经把 row 设置为当前行,因此,这行语句实际上         * 是在当前的 row 位置插入 count 行,这里的 count 为 1。由于我们没有添加任何数据,实际         * 效果是,我们在 row 位置插入了 1 个空行。        */        model->insertRows(currindex.row(),1);        /*         * 利用 setData()函数把我们用 QInputDialog接受的数据设置为当前行数据,         *  并调用 edit()函数,使这一行可以被编辑。        */        model->setData(currindex,text);        listView->edit(currindex);    }}void MyListView::deleteData(){    /*     * 用 rowCount()函数判断了一下,要求最终始终保留 1 行。这是因为我们写的简单地插入操作所限制,     * 如果把数据全部删除,就不能再插入数据了。    */    if(model->rowCount()>1){        /*         * 使用模型的 removeRows()函数可以轻松完成这个操作。        */        model->removeRows(listView->currentIndex().row(),1);    }}void MyListView::showData(){    QStringList data = model->stringList();//返回模型的字符串列表便于存储数据    QString str;    foreach(QString s, data) {        str += s + "\n";    }    QMessageBox::information(this, "Data", str);//标准模态对话框}

六、QFileSystemModel

    Qt 内置了两种模型:QStandardItemModel和QFileSystemModel。QStandardItemModel是一种多用途的模型,能够让列表、表格、树等视图显示不同的数据结构。这种模型会将数据保存起来。试想一下,列表和表格所要求的数据结构肯定是不一样的:前者是一维的,后者是二维的。因此,模型需要保存有实际数据,当视图是列表时,以一维的形式提供数据;当视图是表格时,以二维的形式提供数据。QFileSystemModel则是另外一种方式。它的作用是维护一个目录的信息。因此,它不需要保存数据本身,而是保存这些在本地文件系统中的实际数据的一个索引。我们可以利用QFileSystemModel显示文件系统的信息、甚至通过模型来修改文件系统。QTreeView是最适合应用QFileSystemModel的视图

#include "widget.h"#include <QPushButton>#include <QHBoxLayout>#include <QVBoxLayout>#include <QInputDialog>#include <QMessageBox>FileSystemWidget::FileSystemWidget(QWidget *parent)    : QWidget(parent){    /*     * 构造函数很简单,我们首先创建了 QFileSystemModel 实例,然后将其作为一个QTreeView 的模型。     * 注意我们将 QFileSystemModel 的根目录路径设置为当前目录。剩下来的都很简单,我们添加了按钮     * 之类,这些都不再赘述。对于 treeView 视图,我们使用了 setRootIndex()对模型进行过滤。我们     * 可以尝试一下,去掉这一句的话,我们的程序会显示整个文件系统的目录;而这一句的作用是,从模型     * 中找到 QDir::currentPath()所对应的索引,然后显示这一位置。也就是说,这一语句的作用实际是     * 设置显示哪个目录。    */    model = new QFileSystemModel;    model->setRootPath(QDir::currentPath());    treeView = new QTreeView(this);    treeView->setModel(model);    treeView->setRootIndex(model->index(QDir::currentPath()));    treeView->setSortingEnabled(true);//对列头排序    //界面代码及信号与槽    QPushButton *mkdirButton = new QPushButton(tr("新建文件夹"), this);    QPushButton *rmButton = new QPushButton(tr("删除文件夹"), this);    QHBoxLayout *buttonLayout = new QHBoxLayout;    buttonLayout->addWidget(mkdirButton);    buttonLayout->addWidget(rmButton);    QVBoxLayout *layout = new QVBoxLayout;    layout->addWidget(treeView);    layout->addLayout(buttonLayout);    setLayout(layout);    setWindowTitle("File System Model");    resize(600,400);    connect(mkdirButton,&QPushButton::clicked,this,&FileSystemWidget::mkdir);    connect(rmButton,&QPushButton::clicked,this,&FileSystemWidget::rm);}void FileSystemWidget::mkdir(){    /*     * 正如代码所示,首先我们获取选择的目录。后面这个 isValid()判断很重要,因为默认情况     * 下是没有目录被选择的,此时路径是非法的,为了避免程序出现异常,必须要有这一步判断。     * 然后弹出对话框询问新的文件夹名字,如果创建失败会有提示,否则就是创建成功。这时候     * 你会发现,硬盘的实际位置的确创建了新的文件夹。    */    QModelIndex index=treeView->currentIndex();    if(!index.isValid()){        return ;    }    // QInputDialog允许用户输入一个值,并将其值返回    QString dirName=QInputDialog::getText(this,tr("创建新文件夹"),tr("文件名"));    if(!dirName.isEmpty()){        if(!model->mkdir(index,dirName).isValid()){            QMessageBox::information(this,tr("创建新文件夹"),tr("创建失败"));        }    }}void FileSystemWidget::rm(){    /*     * 这里同样需要先检测路径是否合法。另外需要注意的是,目录和文件的删除不是一个函数,     * 需要调用 isDir()函数检测。    */    QModelIndex index=treeView->currentIndex();    if(!index.isValid()){        return ;    }    bool ok;    if(model->fileInfo(index).isDir()){        ok=model->rmdir(index);//删除目录,成功则返回true    }    else{        ok=model->remove(index);//删除文件    }    if(!ok){        QMessageBox::information(this,tr("移除文件"),tr("操作失败"));    }}FileSystemWidget::~FileSystemWidget(){}


阅读全文
0 0
原创粉丝点击