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

一、模型视图简介

    有时,我们的系统需要显示大量数据,比如从数据库中读取数据,以自己的方式显示在自己的应用程序的界面中。早期的 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
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~青萍之末~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值