C/C++ QT之Mode/View 结构

Model/View其实就是QT中的一种数据编排结构,其中Model代表模型,View代表视图,视图是显示和编辑数据的界面组件,而模型则是视图与原始数据之间的接口,通常该类结构都是用在数据库中较多,例如模型结构负责读取或写入数据库,视图结构则负责展示数据,其条理清晰,编写代码更加方便。

我们主要的视图组件有:QListView,QTreeView,QTableView 这三种,而这三种结构其实是QListWidget,QTreeWidget,QTableQWidget三个类的便利类,也可以理解为QListView是基类,继承者是QListWIdget,只不过其功能更加强大,继承后做了二次封装。

一般模型都是以Model结尾,例如QStringListModel可作为StringList的数据模型,QSqlTableModel则可作为数据库中一个数据表的数据模型,这样的结构分明,可以提高程序编写效率与质量。

QFileSystemModel

该模型提供了一个访问本机文件系统的接口,通常QFileSystemModel和视图组件QTreeView结合使用,可用来展示系统中的目录文件层级关系。

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    model = new QFileSystemModel(this);        // 初始化,提供单独线程
    model->setRootPath(QDir::currentPath());   // 设置文件根目录

    QStringList filter;
    filter << "*.txt" << "*.mp4";

    model->setNameFilters(filter);
    model->setNameFilterDisables(false);
    ui->treeView->setModel(model);             // 设置数据模型
}

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

// 当TreeView被点击则触发事件
void MainWindow::on_treeView_clicked(const QModelIndex &index)
{
}

C/C++ QT之Mode/View 结构

QStringListModel

该组件常用于处理字符串列表的数据模型,可作为QListView的模型接口使用,QStringListModel提供了编辑修改删除等功能的实现。

#include "mainwindow.h"
#include "ui_mainwindow.h"

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

    // 初始化一个StringList字符串列表
    QStringList theStringList;
    theStringList << "北京" << "上海" << "广州";

    // 创建并使用数据模型
    model = new QStringListModel(this);     // 创建模型
    model->setStringList(theStringList);    // 导入模型数据

    ui->listView->setModel(model);          // 为listView设置模型
    ui->listView->setEditTriggers(QAbstractItemView::DoubleClicked |
                                  QAbstractItemView::SelectedClicked);
}

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

// 添加一行
void MainWindow::on_btnListAppend_clicked()
{
    model->insertRow(model->rowCount());                       // 在尾部插入一行
    QModelIndex index = model->index(model->rowCount()-1,0);   // 获取最后一行的索引
    QString LineText = ui->lineEdit->text();
    model->setData(index,LineText,Qt::DisplayRole);            // 设置显示文字
    ui->listView->setCurrentIndex(index);                      // 设置当前行选中
    ui->lineEdit->clear();
}

// 插入一行数据到ListView
void MainWindow::on_btnListInsert_clicked()
{
    QModelIndex index;

    index= ui->listView->currentIndex();             // 获取当前选中行
    model->insertRow(index.row());                   // 在当前行的前面插入一行
    QString LineText = ui->lineEdit->text();
    model->setData(index,LineText,Qt::DisplayRole);             // 设置显示文字
    model->setData(index,Qt::AlignRight,Qt::TextAlignmentRole); // 设置对其方式
    ui->listView->setCurrentIndex(index);                       // 设置当前选中行
}

// 删除当前选中行
void MainWindow::on_btnListDelete_clicked()
{
    QModelIndex index;
    index = ui->listView->currentIndex();    // 获取当前行的ModelIndex
    model->removeRow(index.row());           // 删除选中行
}

// 清除当前列表
void MainWindow::on_btnListClear_clicked()
{
   model->removeRows(0,model->rowCount());
}

// 显示数据模型文本到QPlainTextEdit
void MainWindow::on_btnTextImport_clicked()
{
    QStringList pList;

    pList = model->stringList();    // 获取数据模型的StringList
    ui->plainTextEdit->clear();     // 先清空文本框

    // 循环追加数据
    for(int x=0;x< pList.count();x++)
    {
        ui->plainTextEdit->appendPlainText(pList.at(x) + QString(","));
    }
}

// 当ListView列表项被选中时,显示QModelIndex的行、列号
void MainWindow::on_listView_clicked(const QModelIndex &index)
{
        ui->LabInfo->setText(QString::asprintf("当前项:row=%d, column=%d",
                            index.row(),index.column()));
}

C/C++ QT之Mode/View 结构

QStandardItemModel

该模型是标准的一项数据为基础的标准数据模型类,通常与QTableView组合成M/V结构,实现通用二维数据的管理功能。

如何添加,菜单栏,只需要在空白处右键选择【添加工具栏】即可。

C/C++ QT之Mode/View 结构

添加不同的菜单,只需要在下方添加,然后手动将菜单拖到工具栏上即可。

C/C++ QT之Mode/View 结构

打开文件,可以这样写。

// 【打开文件】:当工具栏中打开文件被点击后则触发
void MainWindow::on_actionOpen_triggered()
{
    QString curPath=QCoreApplication::applicationDirPath(); // 获取应用程序的路径
    // 调用打开文件对话框打开一个文件
    QString aFileName=QFileDialog::getOpenFileName(this,"打开一个文件",curPath,"数据文件(*.txt);;所有文件(*.*)");
    if (aFileName.isEmpty())
    {
        return; // 如果未选择文件则退出
    }

    QStringList fFileContent;                              // 文件内容字符串列表
    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); // 添加到文本框显示
            fFileContent.append(str);                // 添加到StringList
        }
        aFile.close();                               // 关闭文件

        iniModelFromStringList(fFileContent);        // 从StringList的内容初始化数据模型
    }

    // 打开文件完成后,就可以将Action全部开启了
    ui->actionSave->setEnabled(true);
    ui->actionView->setEnabled(true);
    ui->actionAppend->setEnabled(true);
    ui->actionDelete->setEnabled(true);
    ui->actionInsert->setEnabled(true);

    // 打开文件成功后,设置状态栏当前文件列
    this->LabCurFile->setText("当前文件:"+aFileName);//状态栏显示
}

文件读写,保存文件可以这样。

// 【保存文件】:当保存文件被点击后触发
void MainWindow::on_actionSave_triggered()
{
    QString curPath=QCoreApplication::applicationDirPath(); // 获取应用程序的路径

    // 调用打开文件对话框选择一个文件
    QString aFileName=QFileDialog::getSaveFileName(this,tr("选择一个文件"),curPath,"数据文件(*.txt);;所有文件(*.*)");

    if (aFileName.isEmpty()) // 未选择文件则直接退出
        return;

    QFile aFile(aFileName);

    // 以读写、覆盖原有内容方式打开文件
    if (!(aFile.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)))
        return;

    QTextStream aStream(&aFile);    // 用文本流读取文件
    QStandardItem *Item;
    QString str;
    int x = 0,y = 0;

    ui->plainTextEdit->clear();

// 获取表头文字
    for (x=0; x<model->columnCount(); x++)
    {
        Item=model->horizontalHeaderItem(x);     // 获取表头的项数据
        str= str + Item->text() + "		";        // 以TAB制表符隔开
    }
    aStream << str << "
";                      // 文件里需要加入换行符

    ui->plainTextEdit->appendPlainText(str);

// 获取数据区文字
    for ( x=0; x < model->rowCount(); x++)
    {
        str = "";
        for( y=0; y < model->columnCount()-1; y++)
        {
            Item=model->item(x,y);
            str=str + Item->text() + QString::asprintf("		");
        }

        // 对最后一列需要转换一下,如果判断为选中则写1否则写0
        Item=model->item(x,y);
        if (Item->checkState()==Qt::Checked)
            str= str + "1";
        else
            str= str + "0";

         ui->plainTextEdit->appendPlainText(str);
         aStream << str << "
";
    }
}

格式化文件输入输出。 QStandardItem -与表格的导入导出 ,对表格的各种操作。

C/C++ QT之Mode/View 结构

QAbstractItemDelegate 自定义代理类

代理类的作用是用来实现重写的,例如我们的TableView中默认是可编辑的,这个可编辑的组件是QT默认为我们重写了QLineEdit组件,也可理解为将组件嵌入到了表格中,实现了,可对表格的编辑功能,例如如下案例中,的样子,默认情况下,表格是可编辑的,这就是默认重写。

C/C++ QT之Mode/View 结构

初始化代码如下。

#include <QMainWindow>
#include <QLabel>
#include <iostream>
#include <QStandardItem>
#include <QItemSelectionModel>

#define FixedColumnCount 5    // 定义最大5列

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();


private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;

    QStandardItemModel *model;      // 定义数据模型
    QItemSelectionModel *selection; // 定义Item选择模型
};
#endif // MAINWINDOW_H


#include "mainwindow.h"
#include "ui_mainwindow.h"

// 默认构造函数
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    model = new QStandardItemModel(4,FixedColumnCount,this);
    selection = new QItemSelectionModel(model);

    ui->tableView->setModel(model);
    ui->tableView->setSelectionModel(selection);


    // 添加表头
    QStringList HeaderList;
    HeaderList << "序号" << "姓名" << "年龄" << "性别" << "婚否";
    model->setHorizontalHeaderLabels(HeaderList);

    // 批量添加数据
    QStringList DataList[3];
    QStandardItem *Item;

    DataList[0] << "1001" << "admin" << "24" << "男" << "是";
    DataList[1] << "1002" << "lyshark" << "23" << "男" << "否";
    DataList[2] << "1003" << "lucy" << "37" << "女" << "是";

    int Array_Length = DataList->length();                          // 获取每个数组中元素数
    int Array_Count = sizeof(DataList) / sizeof(DataList[0]);       // 获取数组个数

    for(int x=0; x<Array_Count; x++)
    {
        for(int y=0; y<Array_Length; y++)
        {
            // std::cout << DataList[x][y].toStdString().data() << std::endl;
            Item = new QStandardItem(DataList[x][y]);
            model->setItem(x,y,Item);
        }
    }
}

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

在QT中,代理类是有层级关系的,所有的代理都来自于QAbstractItemDelegate这个抽象基类,下面学习以下代理类的使用。

先来实现一个代理,代理到Spin组件上,首先需要在项目上右键,选择addnew -> C++ Class 输入自定义类名称QWintSpinDelegate,然后基类继承QStyledItemDelegate/QMainWindow,然后下一步结束向导。

C/C++ QT之Mode/View 结构

qwintspindelegate.h 改为

#ifndef QWINTSPINDELEGATE_H
#define QWINTSPINDELEGATE_H

#include <QMainWindow>
#include <QStyledItemDelegate>

class QWIntSpinDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    QWIntSpinDelegate(QObject *parent=0);
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,const QModelIndex &index) const Q_DECL_OVERRIDE;
    void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE;
    void setModelData(QWidget *editor, QAbstractItemModel *model,const QModelIndex &index) const Q_DECL_OVERRIDE;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,const QModelIndex &index) const Q_DECL_OVERRIDE;
};


#endif // QWINTSPINDELEGATE_H

qwintspindelegate.cpp

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

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

}
QWidget *QWIntSpinDelegate::createEditor(QWidget *parent,const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(option);
    Q_UNUSED(index);

    QSpinBox *editor = new QSpinBox(parent);
    editor->setFrame(false);
    editor->setMinimum(0);
    editor->setMaximum(10000);
    return editor;
}

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

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

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

最后在mainwindow.h引用

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QLabel>
#include <iostream>

#include <QStandardItem>
#include <QStandardItemModel>
#include <QItemSelectionModel>

#include "qwintspindelegate.h"


QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();


private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;

    QStandardItemModel *model;      // 定义数据模型
    QItemSelectionModel *selection; // 定义Item选择模型

    QWIntSpinDelegate intSpinDelegate; //整型数
};
#endif // MAINWINDOW_H

mainwindow.cpp设置代理

#include "mainwindow.h"
#include "ui_mainwindow.h"

// 默认构造函数
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 初始化模型数据
    model = new QStandardItemModel(4,5,this);
    selection = new QItemSelectionModel(model);

    ui->tableView->setModel(model);
    ui->tableView->setSelectionModel(selection);

    // 添加表头
    QStringList HeaderList;
    HeaderList << "序号" << "姓名" << "年龄" << "性别" << "婚否";
    model->setHorizontalHeaderLabels(HeaderList);

    // 批量添加数据
    QStringList DataList[3];
    QStandardItem *Item;

    DataList[0] << "1001" << "admin" << "24" << "男" << "是";
    DataList[1] << "1002" << "lyshark" << "23" << "男" << "否";
    DataList[2] << "1003" << "lucy" << "37" << "女" << "是";

    int Array_Length = DataList->length();                          // 获取每个数组中元素数
    int Array_Count = sizeof(DataList) / sizeof(DataList[0]);       // 获取数组个数

    for(int x=0; x<Array_Count; x++)
    {
        for(int y=0; y<Array_Length; y++)
        {
            // std::cout << DataList[x][y].toStdString().data() << std::endl;
            Item = new QStandardItem(DataList[x][y]);
            model->setItem(x,y,Item);
        }
    }

    // 为各列设置自定义代理组件
    ui->tableView->setItemDelegateForColumn(0,&intSpinDelegate);
}

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

}

运行观察代理组件,可发现,第一个位置变成了我们想要的了。

C/C++ QT之Mode/View 结构

代理组件实现选择列表,通过重写comboxdelegate实现,.h代码

#ifndef QWCOMBOBOXDELEGATE_H
#define QWCOMBOBOXDELEGATE_H

#include <QItemDelegate>

class QWComboBoxDelegate : public QItemDelegate
{
    Q_OBJECT

public:
    QWComboBoxDelegate(QObject *parent=0);
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,const QModelIndex &index) const Q_DECL_OVERRIDE;
    void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE;
    void setModelData(QWidget *editor, QAbstractItemModel *model,const QModelIndex &index) const Q_DECL_OVERRIDE;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,const QModelIndex &index) const Q_DECL_OVERRIDE;
};

#endif // QWCOMBOBOXDELEGATE_H

comboxdelegate.cpp

#include "comboxdelegate.h"
#include <QComboBox>

QWComboBoxDelegate::QWComboBoxDelegate(QObject *parent):QItemDelegate(parent)
{

}

QWidget *QWComboBoxDelegate::createEditor(QWidget *parent,const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QComboBox *editor = new QComboBox(parent);

    editor->addItem("已婚");
    editor->addItem("未婚");
    editor->addItem("单身");

    return editor;
}

void QWComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    QString str = index.model()->data(index, Qt::EditRole).toString();

    QComboBox *comboBox = static_cast<QComboBox*>(editor);
    comboBox->setCurrentText(str);
}

void QWComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QComboBox *comboBox = static_cast<QComboBox*>(editor);
    QString str = comboBox->currentText();
    model->setData(index, str, Qt::EditRole);
}

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

实现效果。

C/C++ QT之Mode/View 结构

TreeView组件(拓展) 三行代码实现目录枚举

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QSplitter>
#include <QDirModel>
#include <QTreeView>
#include <QTextCodec>

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

    ui->treeView->setModel(model);
    ui->treeView->setRootIndex(model->index("c:\"));
}

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

C/C++ QT之Mode/View 结构

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QSplitter>
#include <QDirModel>
#include <QTreeView>
#include <QTextCodec>

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

    /*
    QDirModel *model = new QDirModel;

    ui->treeView->setModel(model);
    ui->treeView->setRootIndex(model->index("c:\"));
    */

    QStandardItemModel *tree = new QStandardItemModel(0,3,this);

    ui->treeView->setColumnWidth(0,50);      // 设置列chang'd长度
    ui->treeView->setColumnWidth(1,200);
    ui->treeView->setColumnWidth(2,200);

    tree->setHeaderData(0, Qt::Horizontal, tr("序号"));
    tree->setHeaderData(1, Qt::Horizontal, tr("姓名"));
    tree->setHeaderData(2, Qt::Horizontal, tr("年龄"));

    ui->treeView->setModel(tree);

    for (int i = 0; i < 4; ++i)
    {
        // 设置外层
        QList<QStandardItem *> items;
        for (int i = 0; i < 3; ++i)
        {
            QStandardItem *item = new QStandardItem(QString("%0").arg(i));
            if (0 == i)
                item->setCheckable(true);
            items.push_back(item);
        }
        tree->appendRow(items);

        // 设置内层
        for (int i = 0; i < 2; ++i)
        {
            QList<QStandardItem *> childItems;
            for (int i = 0; i < 3; ++i)
            {
             QStandardItem *item = new QStandardItem(QString("%0").arg(i));
             if (0 == i)
                 item->setCheckable(true);
             childItems.push_back(item);
            }
            items.at(0)->appendRow(childItems);
        }
    }
}

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

C/C++ QT之Mode/View 结构

添加单列,单独添加。

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QSplitter>
#include <QDirModel>
#include <QTreeView>
#include <QTextCodec>

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

    /*
    QDirModel *model = new QDirModel;

    ui->treeView->setModel(model);
    ui->treeView->setRootIndex(model->index("c:\"));
    */

    QStandardItemModel *tree = new QStandardItemModel(0,3,this);

    ui->treeView->setColumnWidth(0,50);      // 设置列chang'd长度
    ui->treeView->setColumnWidth(1,200);
    ui->treeView->setColumnWidth(2,200);

    tree->setHeaderData(0, Qt::Horizontal, tr("序号"));
    tree->setHeaderData(1, Qt::Horizontal, tr("姓名"));
    tree->setHeaderData(2, Qt::Horizontal, tr("年龄"));

    ui->treeView->setModel(tree);

    QList<QStandardItem *> ptr;

    QStandardItem *item = new QStandardItem("1001");
    ptr.push_back(item);

    item = new QStandardItem("lyshark");
    ptr.push_back(item);

    item = new QStandardItem("22");
    ptr.push_back(item);

    tree->appendRow(ptr);
}

C/C++ QT之Mode/View 结构