Qt基础学习笔记03

转载标注来源

Model/View结构

GUI应用程序的一个很重要的功能是由用户在界面上编辑和修改数据,典型的如数据库应用程序。数据库应用程序中,用户在界面上执行各种操作,实际上是修改了界面组件所关联的数据库内的数据。

将界面组件与所编辑的数据分离开来,又通过数据源的方式连接起来,是处理界面与数据的一种较好的方式。Qt使用Model/View结构来处理这种关系,Model/View的基本结构如下图:

  • 数据(Data)是实际的数据,如数据库的一个数据表或SQL查询结果,内存中的一个StringList,或磁盘文件结构等。
  • 视图或视图组件(View)是屏幕上的界面组件,视图从数据模型获得每个数据项的模型索引(model index),通过模型索引获取数据,然后为界面组件提供显示数据。Qt提供一些现成的数据视图组件,如QListView、QTree View和QTable View等。
  • 模型或数据模型(Modl)与实际数据通信,并为视图组件提供数据接口。它从原始数据提取需要的内容,用于视图组件进行显示和编辑。Q中有一些预定义的数据模型,如
    QStringListModel可作为StringList的数据模型,QSqlTableModel可以作为数据库中一个数据表的数据模型。

由于数据源与显示界面通过Mode/View结构分离开来,因此可以将一个数据模型在不同的视
图中显示,也可以在不修改数据模型的情况下,设计特殊的视图组件。

在Model/View结构中,还提供了代理(Delegate)功能,代理功能可以让用户定制数据的界面显示和编辑方式。在标准的视图组件中,代理功能显示一个数据,当数据被编辑时,代理通过模型索引与数据模型通信,并为编辑数据提供一个编辑器,一般是一个QLineEdit组件。

模型、视图和代理之间使用信号和槽通信。当源数据发生变化时,数据模型发射信号通知视
图组件:当用户在界面上操作数据时,视图组件发射信号表示这些操作信息:当编辑数据时,代理发射信号告知数据模型和视图组件编辑器的状态。

数据模型

所有的基于项数据(item data)的数据模型(Model)都是基于QAbstractItemModel类的,这个类定义了视图组件和代理存取数据的接口。数据无需存储在数据模型里,数据可以是其他类、文件、数据库或任何数据源。Qt中与数据模型相关的几个主要的类的层次结构如下图:

抽象类是不能直接使用的,需要由子类继承来实现一些纯虚函数。Qt提供了一些模型类用于项数据处理

视图组件

视图组件(Viw)就是显示数据模型的数据的界面组件,Qt提供的视图组件如下:

视图组件在显示数据时,只需调用视图类的setModelO)函数,为视图组件设置一个数据模型就可以实现视图组件与数据模型之间的关联,在视图组件上的修改将自动保存到关联的数据模型 里,一个数据模型可以同时在多个视图组件里显示数据。

代理

Model/View结构的一些概念

  • Model/View的基本结构

    在Model/View结构中,数据模型为视图组件和代理提供存取数据的标准接口。在Qt中,所有的数据模型类都从QAbstractItemModel继承而来,不管底层的数据结构是如何组织数据的,QAbstractItemModel的子类都以表格的层次结构表示数据,视图组件通过这种规则来存取模型中的数据,但是表现给用户的形式不一样。

    不管数据模型的表现形式是怎么样的,数据模型中存储数据的基本单元都是项(item),每个项有一个行号、一个列号,还有一个父项(parent item)。在列表和表格模式下,所有的项都有一个相同的顶层项(root item):在树状结构中,行号、列号、父项稍微复杂一点,但是由这3个参数完全可以定义一个项的位置,从而存取项的数据。

  • 模型索引

    为了保证数据的表示与数据存取方式隔离,数据模型中引入了模型索引(model index)的概念。通过数据模型存取的每个数据都有一个模型索引,视图组件和代理都通过模型索引来获
    取数据。
    QModelIndex表示模型索引的类。模型索引提供数据存取的一个临时指针,用于通过数据模型提取或修改数据。因为模型内部组织数据的结构随时可能改变,所以模型索引是临时的。如果需要使用持久性的模型索引,则要使用QPersistentModelIndex类。

  • 行号和列号

    数据模型的基本形式是用行和列定义的表格数据,但这并不意味着底层的数据是用二维数组存储的,使用行和列只是为了组件之间交互方便的一种规定。通过模型索引的行号和列号就可以存取数据。
    要获得一个模型索引,必须提供3个参数:行号、列号、父项的模型索引。例如,对于如上图中的表格数据模型中的3个数据项A、B、C,获取其模型索引的代码是:

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

  • 父项

    当数据模型是列表或表格时,使用行号、列号存储数据比较直观,所有数据项的父项(parent itm)就是顶层项:当数据模型是树状结构时,情况比较复杂(树状结构中,项一般习惯于称为节点),一个节点可以有父节点,也可以是其他节点的父节点,在构造数据项的模型索引时,必须指定正确的行号、列号和父节点。

  • 项的角色

QFileSystemModel

QFileSystemModel类的基本功能

这里还有另一种写法:

1
2
3
connect(ui->treeView,&QTreeView::clicked,[=](){
ui->listView->setRootIndex(ui->treeView->currentIndex());
});

效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
void MainWindow::on_treeView_clicked(const QModelIndex &index)
{
ui->chkIsDir->setChecked(model->isDir(index));
ui->LabPath->setText(model->filePath(index));
ui->LabType->setText(model->type(index));
ui->LabFileName->setText(model->fileName(index));
int sz = model->size(index) / 1024;
if(sz < 1024){
ui->LabFileSize->setText(QString("%1 KB").arg(sz));
}else {
ui->LabFileSize->setText(QString::asprintf("%.1f MB",sz / 1024.0));
}
}

效果:

QStringListModel

效果:

  • 编辑、添加、删除项的操作

    • 编辑

      1
      2
      3
      4
      5
      ui->listView->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked);// 表示在双击,或选择并单击列表项后,就进入编辑状态。
      //setEditTrigger函数设置QListView是否可编辑,以及进入编辑状态。

      //若要设置为不可编辑,则可以设置为:
      ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers);

    • 添加项

      1
      2
      3
      4
      5
      6
      7
      void Widget::on_btnListAppend_clicked()
      {
      theModel->insertRow(theModel->rowCount());
      QModelIndex index = theModel->index(theModel->rowCount() - 1,0);
      theModel->setData(index,"new item",Qt::DisplayRole);
      ui->listView->setCurrentIndex(index);
      }

    • 插入项

    • 删除当前项

    • 删除列表

QStandardItemModel

界面设计与主窗口类定义

QStandardItemModel的使用

  • 从文本文件导入数据

    QStandardltemModel 是标准的基于项数据的数据模型,以类似于二维数组的形式管理内部数据,适合于处理表格型数据,其显示一般采用QTableView。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setCentralWidget(ui->splitter);
theModel = new QStandardItemModel(1,FixedColumuCount,this); // 数据模型
theSelection = new QItemSelectionModel(theModel);
connect(ui->tableView,&QTableView::clicked,[=](){
QModelIndex curindex = theSelection->currentIndex();
on_currentChanged(curindex);
});
ui->tableView->setModel(theModel);
ui->tableView->setSelectionModel(theSelection);
ui->tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
ui->tableView->setSelectionBehavior(QAbstractItemView::SelectItems);
}

MainWindow::~MainWindow()
{
delete ui;
}


void MainWindow::on_currentChanged(const QModelIndex &current){
if(current.isValid()){
ui->labCellPos->setText(QString::asprintf("当前单元格:%d行,%d列",current.row() + 1,current.column()+ 1));
QStandardItem *aItem = theModel->itemFromIndex(current);
ui->labCellText->setText("单元格内容:" + aItem->text());
QFont font = aItem->font();
ui->actFontBold->setChecked(font.bold());
}

}

void MainWindow::on_actOpen_triggered()
{
QString curPath = QCoreApplication::applicationDirPath();
QString aFileName = QFileDialog::getOpenFileName(this,"打开一个文件",curPath,"井数据文件(*.txt);;所有文件(*.*)");
if(aFileName.isEmpty()){
return;
}

QStringList fFileCoentent;
QFile aFile(aFileName);
if(aFile.open(QIODevice::ReadOnly | QIODevice::Text)){
QTextStream aStream(&aFile);
ui->plainTextEdit->clear();
while (!aStream.atEnd()) {
QString str = aStream.readLine();
ui->plainTextEdit->appendPlainText(str);
fFileCoentent.append(str);
}
aFile.close();
ui->labCurFile->setText("当前文件:" + aFileName);
iniModelFormStringList(fFileCoentent);
}

}
void MainWindow::iniModelFormStringList(QStringList & aFileCoentent){
int rowCnt = aFileCoentent.count();
theModel->setRowCount(rowCnt - 1);
QString header = aFileCoentent.at(0);
QStringList headerList = header.split(QRegExp("\\s+"),QString::SkipEmptyParts);
theModel->setHorizontalHeaderLabels(headerList);

QStandardItem *aItem;
QStringList tmpList;
int j;
for (int i = 1; i < rowCnt; i++) {
QString aLineText = aFileCoentent.at(i);
tmpList = aLineText.split(QRegExp("\\s+"),QString::SkipEmptyParts);
for (j = 0;j < FixedColumuCount - 1; j++) {
aItem = new QStandardItem(tmpList.at(j));
theModel->setItem(i - 1,j,aItem);
}
aItem = new QStandardItem(tmpList.at(j));
aItem->setCheckable(true);
if(tmpList.at(j) == "0"){
aItem->setCheckState(Qt::Unchecked);
}else {
aItem->setCheckState(Qt::Checked);
}
theModel->setItem(i - 1,j,aItem);
}
}

theModel->setHorizontalHeaderLabels(headerList);函数:设置表头标题。

  • 添加行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    void MainWindow::on_actAppend_triggered()
    {
    QList<QStandardItem*> aItemList;
    QStandardItem *aItem;
    for (int i = 0; i < FixedColumuCount - 1; i++) {
    aItem = new QStandardItem("0");
    aItemList << aItem;
    }
    QString str = theModel->headerData(theModel->columnCount() - 1,Qt::Horizontal,Qt::DisplayRole).toString();
    aItem = new QStandardItem(str);
    aItem->setCheckable(true);
    aItemList << aItem;

    theModel->insertRow(theModel->rowCount(), aItemList);
    QModelIndex curindex = theModel->index(theModel->rowCount() - 1,0);
    theSelection->clearSelection();
    theSelection->setCurrentIndex(curindex,QItemSelectionModel::Select);
    }

  • 插入行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    void MainWindow::on_actInsert_triggered()
    {
    QList<QStandardItem*> aItemList;
    QStandardItem *aItem;
    for (int i = 0; i < FixedColumuCount - 1; i++) {
    aItem = new QStandardItem("0");
    aItemList << aItem;
    }
    QString str = theModel->headerData(theModel->columnCount() - 1,Qt::Horizontal,Qt::DisplayRole).toString();
    aItem = new QStandardItem(str);
    aItem->setCheckable(true);
    aItemList << aItem;

    QModelIndex index = theSelection->currentIndex();
    theModel->insertRow(index.row(), aItemList);
    }

  • 删除行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void MainWindow::on_actDelete_triggered()
    {
    QModelIndex curIndex = theSelection->currentIndex();
    if(curIndex.row() == theModel->rowCount() - 1){
    theModel->removeRow(curIndex.row());//删除最后一行
    }else {
    theModel->removeRow(curIndex.row());
    //当前选择行
    theSelection->setCurrentIndex(curIndex,QItemSelectionModel::Select);
    }
    }
  • 单元格格式设置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //左对齐
    void MainWindow::on_actAlignLeft_triggered()
    {
    if(!theSelection->hasSelection()){
    return;
    }

    QModelIndexList selectIndex = theSelection->selectedIndexes();
    for (int i = 0;i < selectIndex.count();i++) {
    QModelIndex aIndex = selectIndex.at(i);
    QStandardItem *aItem = theModel->itemFromIndex(aIndex);
    aItem->setTextAlignment(Qt::AlignLeft);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //加粗
    void MainWindow::on_actFontBold_triggered()
    {
    if(!theSelection->hasSelection()){
    return;
    }

    QModelIndexList selectIndex = theSelection->selectedIndexes();
    for (int i = 0;i < selectIndex.count();i++) {
    QModelIndex aIndex = selectIndex.at(i);
    QStandardItem *aItem = theModel->itemFromIndex(aIndex);
    QFont font = aItem->font();
    font.setBold(true);
    aItem->setFont(font);
    }
    }
  • 数据另存为文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    void MainWindow::on_actSave_triggered()
    {
    //保存为文件
    QString curPath = QCoreApplication::applicationDirPath();
    QString aFileName = QFileDialog::getSaveFileName(this,"选择一个文件",curPath,"冰鞋数据文件(*.txt);;所有文件(*.*)");
    if(aFileName.isEmpty()){
    return;
    }
    QFile aFile(aFileName);
    if(!(aFile.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate))){
    return;
    }
    QTextStream aStream(&aFile);
    QStandardItem *aItem;
    int i,j;
    QString str;
    ui->plainTextEdit->clear();
    for (i = 0;i < theModel->columnCount();i++) {
    aItem = theModel->horizontalHeaderItem(i);
    str = str + aItem->text() + "\t\t";
    }
    aStream << str << "\n";
    ui->plainTextEdit->appendPlainText(str);
    for (i = 0;i < theModel->rowCount();i++) {
    str = "";
    for (j = 0;j < theModel->columnCount() - 1;j++) {
    aItem = theModel->item(i,j);
    str = str + aItem->text() + QString::asprintf("\t\t");
    }
    aItem = theModel->item(i,j);
    if(aItem->checkState() == Qt::Checked){
    str = str + "1";
    }else {
    str = str + "0";
    }
    ui->plainTextEdit->appendPlainText(str);
    aStream << str << "\n";
    }

    }

自定义代理

自定义代理类的使用

-------------本文结束感谢您的阅读-------------