自学内容网 自学内容网

QT模型/视图结构:ListModel与TableModel

简介

模型/视图(model/view)结构是进行数据存储和界面展示的一种编程结构。此种方式将数据的存储与显示进行了解耦,视图组件显示模型中的数据,在视图组件里修改的数据会被自动保存到模型里。模型的数据来源可以是内存中的字符串列表或二维表格型数据,也可以是数据库中的数据表,一种模型可以用不同的视图组件来显示数据,所以模型/视图结构是一种高效、灵活的编程结构。

• 源数据(data)是原始数据,如数据库的一个数据表或 SQL 查询结果、内存中的一个字符串列表或磁盘文件系统结构等。

• 视图(view)也称为视图组件,是界面组件,视图从模型获得数据然后将其显示在界面上。 Qt 提供一些常用的视图组件,如 QListView、QTreeView 和 QTableView 等。

• 模型(model)也称为数据模型,与源数据通信,并为视图组件提供数据接口。它从源数 据提取需要的数据,用于视图组件进行显示和编辑。Qt 中有一些预定义的模型类,如 QStringListModel 是字符串列表的模型类,QSqlTableModel 是数据库中数据表的模型类。

• 代理(delegate)在视图与模型之间交互操作时提供的临时编辑器。模型向视图提供数据是单向的,一般仅用于显示。当需要在视图上编辑数据时,代理会为编辑数据提供一个编辑器,这个编辑器获取模型的数据、接受用户编辑的数据后又将其提交给模型。

模型结构有三种类型:列表模式、表格模式和树状模式,本文只探讨前两种用得较多的模式。

模型的索引

通过模型能访问的每个项都有一 个模型索引,视图组件和代理都 通过模型索引来获取数据。模型的基本形式是用行和列定义的表格数据,但这并不意味着底层的数据是用二维数组存储 的,使用行和列只是为了组件之间交互方便。当模型为列表或表格结构时,使用行号、列号访问数据比较直观,所有项的父项就是顶层项。

例如对于上述表格模型中的 3 个项 A、B、C获取模型索引的方式如下:

QModelIndex indexA = model->index(0, 0, QModelIndex()); 
QModelIndex indexB = model->index(1, 1, QModelIndex()); 
QModelIndex indexC = model->index(2, 1, QModelIndex());

在创建模型 索引的函数中需要传递行号、列号和父项的模型索引。对于列表模型和表格模型,顶层节点总是 用 QModelIndex()表示。

而当模型涉及到列表项时,情况比较复杂,父节点的表示需要注意

例如上述树状模型,节点 A 和节点 C 的父节点是顶层节点,但是,节点 B 的父节点是节点 A,特别需要注意节点B的访问情况:

QModelIndex indexA = model->index(0, 0, QModelIndex()); 
QModelIndex indexC = model->index(2, 1, QModelIndex());
QModelIndex indexB = model->index(1, 0, indexA);

项的角色

模型中每一个项都有角色,设置项的数据的函数 setData(),其函数原型定义如下:

bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)

其中,index 是项的模型索引,value 是需要设置的数据,role 是设置数据的角色。

常见的一些角色和枚举值如下表

同样,在获取一个项的数据时也需要指定角色,以获取不同角色的数据。函数 data(),可返回一个项的不同角色的数据,其函数原型定义如下:

QVariant QAbstractItemModel::data(const QModelIndex &index, int role = Qt::DisplayRole)

通过为一个项的不同角色定义数据,可以告知视图组件和代理如何展示数据。

例如下图,项的 DisplayRole角色数据是显示的字符串,DecorationRole 角色数据是用于装饰显示的元素(如图标),ToolTipRole 角色数据是就地显示的提示信息。

关联数据模型和选择模型

视图组件需要设置关联的模型才能构成完整的模型/视图结构。相关函数定义如下:

void setModel(QAbstractItemModel *model) //设置数据模型

QAbstractItemModel *model() //返回关联的数据模型对象指针

不同的视图组件使用不同类型的模型,QListView 组件一般用 QStringListModel 对象作为数据 模型,用于编辑字符串列表;QTableView 一般用 QStandardItemModel 对象作为数据模型,用于编辑表格数据。视图组件还可以设置选择模型,在界面上选择的项发生变化时,通过选择模型可以获取所有被选择项的模型索引。

相关函数定义如下:

void setSelectionModel(QItemSelectionModel *selectionModel) //设置选择模型

QItemSelectionModel *selectionModel() //返回关联的选择模型对象指针

常用接口函数

QAbstractItemView 定义了很多接口函数,下面是常用的几个。

QModelIndex currentIndex() //返回当前项的模型索引,例如当前单元格的模型索引

void setCurrentIndex(const QModelIndex &index) //设置模型索引为 index 的项为当前项

void selectAll() //选择视图中的所有项,例如选择 QTableView 组件中的所有单元格

void clearSelection() //清除所有选择

如果设置为单选,视图组件上就只有一个当前项,函数 currentIndex()返回当前项的模型索引,

通过模型索引就可以从模型中获取项的数据。

常用信号

QAbstractItemView 定义了几个信号,常用的几个信号定义如下,信号触发条件见注释。

void clicked(const QModelIndex &index) //点击某个项时

void doubleClicked(const QModelIndex &index) //双击某个项时

void entered(const QModelIndex &index) //鼠标移动到某个项上时

void pressed(const QModelIndex &index) //鼠标左键或右键被按下时

QStringListModel QListView

QStringListModel 内部存储了一个字符串列表,这个字符串列表的内容自动显示在关联的 QListView 组件上,在 QListView 组件上双击某一行时,可以通过默认的代理组件(QLineEdit 组件)修改这一行字符串的内容,修改后的这行字符串自动保存到数据模型的字符串列表里。

在字符串列表中添加或删除行是通过 QStringListModel 的接口函数实现的,QListView 没有接 口函数用于修改数据,它只是用作数据显示和编辑的界面组件。通过 QStringListModel 的接口函 数修改字符串列表的内容后,关联的 QListView 组 件会自动更新显示内容。

例程详解

构造函数中创建了数据模型 m_model 并初始化其字符串列表数据,再将 m_model 设置 为界面上的 QListView 组件 listView 的数据模型,构造模型/视图结构。 程序运行后,界面上的 listView 里就会显示初始化的字符串列表的内容。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //初始化一个字符串列表的内容
    m_strList<<"北京"<<"上海"<<"天津"<<"河北"<<"山东"<<"四川"<<"重庆"<<"广东"<<"河南";

    m_model= new QStringListModel(this);    //创建数据模型
    m_model->setStringList(m_strList);      //为模型设置StringList,会导入StringList的内容

    ui->listView->setModel(m_model);        //为listView设置数据模型:将数据模型与界面组件绑定
    ui->chkEditable->setChecked(true);
    ui->listView->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked);
    
}

数据模型的操作

可以对数据模型进行操作,例如添加项、删除项、 移动项等,相关操作会自动更新到界面


void MainWindow::on_btnIniList_clicked()
{ //恢复列表
    m_model->setStringList(m_strList);    //重新载入到模型,界面会自动发生变化
}

void MainWindow::on_btnListAppend_clicked()
{ //添加项
    m_model->insertRow(m_model->rowCount());  //在尾部插入一个数据项
    QModelIndex index=m_model->index(m_model->rowCount()-1,0, QModelIndex());  //获取刚插入的项的模型索引
    m_model->setData(index,"new item",Qt::DisplayRole);    //设置显示文字,界面会自动发生变化
    ui->listView->setCurrentIndex(index);    //将光标定位到新加的项
}

void MainWindow::on_btnListInsert_clicked()
{//插入项
    QModelIndex index=ui->listView->currentIndex();  //当前项的模型索引
    m_model->insertRow(index.row());       //在当前项的前面插入一项
    m_model->setData(index,"inserted item",Qt::DisplayRole);   //设置显示文字
//    theModel->setData(index,Qt::AlignRight,Qt::TextAlignmentRole);  //设置对齐方式,不起作用
    ui->listView->setCurrentIndex(index);   //设置当前项
}


void MainWindow::on_btnListDelete_clicked()
{//删除当前项
    QModelIndex index=ui->listView->currentIndex();  //获取当前项的模型索引
    m_model->removeRow(index.row());       //删除当前项
}

void MainWindow::on_btnListClear_clicked()
{//清除列表
    //删除row=0为起始,长度为rowCount()的项,在这里即所有项
    m_model->removeRows(0,m_model->rowCount());   //清除数据模型的所有项
}

void MainWindow::on_btnListSort_clicked(bool checked)
{//排序
    if (checked)      //同样通过操作model即可自动更新到界面
        m_model->sort(0,Qt::AscendingOrder);   //升序
    else
        m_model->sort(0,Qt::DescendingOrder);  //降序
}

void MainWindow::on_btnListMoveUp_clicked()
{//上移
    int curRow=ui->listView->currentIndex().row();  //当前行号
    QModelIndex index=QModelIndex();
    m_model->moveRow(index,curRow,index,curRow-1);
}

void MainWindow::on_btnListMoveDown_clicked()
{//下移
    int curRow=ui->listView->currentIndex().row();  //当前行号
    QModelIndex index=QModelIndex();
    m_model->moveRow(index,curRow,index,curRow+2);
}

对数据的操作都是通过数据模型的接口函数实现的。在数据模型 m_model 中添加或删除项后,界面组件 listView 中会立刻自动将其显示出来。

在对数据模型进行插入、添加、删除项操作后,内容会立即在 listView 上显示出来,这是数 据模型与视图组件之间信号与槽的作用的结果,当数据模型的内容发生改变时,通知视图组件更 新显示。在 listView 上双击一行进入编辑状态,修改一行的文字后,修改的文字也会保存到数据 模型里。 数据模型内保存着最新的数据内容,对 QStringListModel 模型来说,通过函数stringList()可以 得到其最新的数据副本。

QStandardItemModel 和 QTableView

QStandardItemModel 是以项为基本数据单元的模型类,每个项是一个 QStandardItem 对象。 项可以存储各种角色的数据,如文字、字体、对齐方式、图标、复选状态等。QStandardItemModel模型可以存储列表、表格、树3种模型数据。如果以多行多列的二维数组形式存储项,就是表格模型;如果表格模型只有一列,就是列表模型;如果在存储项时为项指定父项,就可以构成树状模型。

QStandardItemModel 数据模型中的每个项是一个 QStandardItem 对象。QStandardItem 存储了一 个项的各种特性参数,还可以存储用户自定义数据。一个项可以添加子项,子项也是 QStandardItem 类型的对象,所以,QStandardItem 也可以作为树状模型的项。

相关函数

void setRowCount(int rows) //设置数据模型的行数
void setColumnCount(int columns) //设置数据模型的列数

void setItem(int row, int column, QStandardItem *item) //用于表格模型
void setItem(int row, QStandardItem *item) //用于列表模型

QStandardItem *item(int row, int column = 0) //根据行号和列号返回项
QStandardItem *itemFromIndex(const QModelIndex &index) //根据模型索引返回项

QModelIndex indexFromItem(const QStandardItem *item)

void appendRow(const QList<QStandardItem *> &items) //用于表格模型
void appendRow(QStandardItem *item) //用于列表模型

void appendColumn(const QList<QStandardItem *> &items) //在表格模型中添加列

void insertRow(int row, const QList<QStandardItem *> &items) //用于表格模型
void insertRow(int row, QStandardItem *item) //用于列表模型

bool insertRow(int row, const QModelIndex &parent = QModelIndex()) //用于树状模型

void insertColumn(int column, const QList<QStandardItem *> &items) //用于表格模型
bool insertColumn(int column, const QModelIndex &parent = QModelIndex()) //用于树状模型

QList<QStandardItem *> takeRow(int row) //移除一行,适用于表格模型
QList<QStandardItem *> takeColumn(int column) //移除一列,适用于表格模型

QStandardItem *takeItem(int row, int column = 0) //移除一个项,适用于列表模型

QStandardItemModel 新定义了一个信号 itemChanged(),在任何一个项的数据发生变化时,此

信号就会被发射。信号函数定义如下,其中的参数 item 是数据发生了变化的项。

void itemChanged(QStandardItem *item)

例程详解

构造函数中创建数据模型和选择模型,并设置关联的界面组件(tableView)

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_model = new QStandardItemModel(2,FixedColumnCount,this);     //创建数据模型
    m_selection = new QItemSelectionModel(m_model,this);           //创建选择模型

    //选择当前单元格变化时的信号与槽
    connect(m_selection,&QItemSelectionModel::currentChanged,this,&MainWindow::do_currentChanged);
    //    connect(theSelection,SIGNAL(currentChanged(QModelIndex,QModelIndex)),
    //            this,SLOT(do_currentChanged(QModelIndex,QModelIndex)));

    //为tableView设置数据模型
    ui->tableView->setModel(m_model);  //设置数据模型
    ui->tableView->setSelectionModel(m_selection); //设置选择模型
    ui->tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectItems);

    setCentralWidget(ui->splitter);

    //创建状态栏组件
    labCurFile = new QLabel("当前文件:",this);
    labCurFile->setMinimumWidth(200);

    labCellPos = new QLabel("当前单元格:",this);
    labCellPos->setMinimumWidth(180);
    labCellPos->setAlignment(Qt::AlignHCenter);

    labCellText = new QLabel("单元格内容:",this);
    labCellText->setMinimumWidth(150);

    ui->statusBar->addWidget(labCurFile);
    ui->statusBar->addWidget(labCellPos);
    ui->statusBar->addWidget(labCellText);
}

void MainWindow::do_currentChanged(const QModelIndex &current, const QModelIndex &previous)
{ //选择单元格变化时的响应
    Q_UNUSED(previous);

    if (current.isValid())
    {
        labCellPos->setText(QString::asprintf("当前单元格:%d行,%d列",current.row(),current.column()));    //显示模型索引的行和列号
        QStandardItem *aItem=m_model->itemFromIndex(current);   //从模型索引获得Item
        labCellText->setText("单元格内容:"+aItem->text());       //显示item的文字

        QFont   font=aItem->font();
        ui->actFontBold->setChecked(font.bold());

//   为了简化难度,没有设置ActionGroup,不能互斥选择
//        Qt::Alignment align=aItem->textAlignment();
//        ui->actAlignLeft->setChecked(align == Qt::AlignLeft);
//        ui->actAlignCenter->setChecked(align == Qt::AlignHCenter);
//        ui->actAlignRight->setChecked(align == Qt::AlignRight);
    }
}

读取ttxt文件内容并初始化Model,界面组件显示会自动发生变化

void MainWindow::iniModelData(QStringList &aFileContent)
{
    int rowCnt=aFileContent.size();    //文本行数,第1行是标题
    m_model->setRowCount(rowCnt-1);    //实际数据行数

    //设置表头
    QString header=aFileContent.at(0);  //第1行是表头
    QStringList headerList=header.split(QRegularExpression("\\s+"),Qt::SkipEmptyParts);
    m_model->setHorizontalHeaderLabels(headerList);    //设置表头文字

    //设置表格数据
    int j;
    QStandardItem   *aItem;
    for (int i=0;i<rowCnt-1;i++)
    {
        QString aLineText=aFileContent.at(i);   //获取 数据区 的一行
        //一个或多个空格、TAB等分隔符隔开的字符串, 分解为一个StringList
        QStringList tmpList=aLineText.split(QRegularExpression("\\s+"),Qt::SkipEmptyParts);
        for (j=0;j<FixedColumnCount-1;j++)
        { //不包含最后一列
            aItem=new QStandardItem(tmpList.at(j)); //创建item
            m_model->setItem(i,j,aItem);          //设置Item
        }

        aItem=new QStandardItem(headerList.at(j));  //最后一列是Checkable
        aItem->setCheckable(true);                  //设置为Checkable
        aItem->setBackground(QBrush(Qt::yellow));
        if (tmpList.at(j)=="0")
            aItem->setCheckState(Qt::Unchecked);    //根据数据设置check状态
        else
            aItem->setCheckState(Qt::Checked);
        m_model->setItem(i,j,aItem);     //设置Item
    }
}

数据修改-添加-插入-删除

void MainWindow::on_actAppend_triggered()
{ //在表格最后添加行
    QList<QStandardItem*>    aItemList;     //列表
    QStandardItem   *aItem;
    for(int i=0;i<FixedColumnCount-1;i++)   //不包含最后1列
    {
        aItem=new QStandardItem("0");       //创建Item
        aItemList<<aItem;   //添加到列表
    }

    //获取最后一列的表头文字
    QString str=m_model->headerData(m_model->columnCount()-1,Qt::Horizontal,Qt::DisplayRole).toString();
    aItem=new QStandardItem(str);   //创建 "测井取样"Item
    aItem->setCheckable(true);
    aItemList<<aItem;               //添加到列表

    m_model->insertRow(m_model->rowCount(),aItemList);    //插入一行
    QModelIndex curIndex=m_model->index(m_model->rowCount()-1,0); //创建最后一行的ModelIndex
    m_selection->clearSelection(); //清空选择项
    m_selection->setCurrentIndex(curIndex,QItemSelectionModel::Select);    //设置刚插入的行为当前选择行
}

void MainWindow::on_actInsert_triggered()
{//插入行
    QList<QStandardItem*>    aItemList;  //QStandardItem的列表类
    QStandardItem   *aItem;
    for(int i=0;i<FixedColumnCount-1;i++)    //创建前5列
    {
        aItem=new QStandardItem("0"); //新建一个QStandardItem
        aItemList<<aItem;//添加到列表类
    }
//    aItem=new QStandardItem("优"); //新建一个QStandardItem
//    aItemList<<aItem;//添加到列表类

    QString str;    //获取表头文字         //创建最后一列
    str=m_model->headerData(m_model->columnCount()-1,Qt::Horizontal,Qt::DisplayRole).toString();
    aItem=new QStandardItem(str); //创建Item
    aItem->setCheckable(true);//设置为可使用CheckBox
    aItemList<<aItem;//添加到列表类


    QModelIndex curIndex=m_selection->currentIndex(); //获取当前选中项的模型索引
    m_model->insertRow(curIndex.row(),aItemList);  //在当前行的前面插入一行
    m_selection->clearSelection();//清除已有选择
    m_selection->setCurrentIndex(curIndex,QItemSelectionModel::Select);
}

void MainWindow::on_actDelete_triggered()
{ //删除行
    QModelIndex curIndex=m_selection->currentIndex();//获取当前选择单元格的模型索引

    if (curIndex.row()==m_model->rowCount()-1)//最后一行
        m_model->removeRow(curIndex.row()); //删除最后一行
    else
    {
        m_model->removeRow(curIndex.row());//删除一行,并重新设置当前选择行
        m_selection->setCurrentIndex(curIndex,QItemSelectionModel::Select);
    }
}

遍历数据模型

void MainWindow::on_actModelData_triggered()
{//模型数据导出到PlainTextEdit显示
    ui->plainTextEdit->clear(); //清空
    QStandardItem   *aItem;
    QString str;
//    int i,j;

    //获取表头文字
    for (int i=0;i<m_model->columnCount();i++)
    {
        aItem=m_model->horizontalHeaderItem(i); //获取表头的一个项数据
        str=str+aItem->text()+"\t"; //用TAB间隔文字
    }
    ui->plainTextEdit->appendPlainText(str); //添加为文本框的一行

    //获取数据区的每行
    for (int i=0;i<m_model->rowCount();i++)
    {
        str="";
        for(int j=0; j<m_model->columnCount()-1;j++)
        {
            aItem=m_model->item(i,j);
            str=str+aItem->text()+QString::asprintf("\t"); //以 TAB分隔
        }

        aItem=m_model->item(i,FixedColumnCount-1); //最后一行是逻辑型
        if (aItem->checkState()==Qt::Checked)
            str=str+"1";
        else
            str=str+"0";

        ui->plainTextEdit->appendPlainText(str);
    }
}


原文地址:https://blog.csdn.net/qq_46144191/article/details/144330177

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!