8

C++ Qt开发:TableView与TreeView组件联动 - lyshark

 8 months ago
source link: https://www.cnblogs.com/LyShark/p/17930688.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

C++ Qt开发:TableView与TreeView组件联动

Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍TableViewTreeView组件联动的常用方法及灵活运用。

本章我们继续实现表格的联动效果,当读者点击TableViewTreeView中的某一行时,我们让其实现自动跟随功能,且当用户修改行中特定数据时也让其动态的跟随改变,首先绘制一个主界面如图,分别放置两个组件框,底部保留两个按钮,按钮1用于该表表格的行列个数,按钮2则用于设置TableView表格表头参数,整个表格我们将其设置为可编辑状态。

1379525-20231227140204354-480684471.png

在函数中我们需要定义一个QStandardItemModel模型,这个模型的作用在之前的文章中有具体介绍,它是一个灵活且功能强大的模型类,适用于需要自定义数据结构、支持编辑、表头等功能的场景。通常用于与视图组件(如 QTableViewQTreeView 等)一起使用。它提供了一个表格结构,可以包含行和列,每个单元格可以存储一个 QStandardItem 对象。

这里的QStandardItemModel只适用于将两个不同类型的组件进行关联,简单点来说就是将两个组件指向同一个数据容器内,这样当用户修改任意一个组件内的数据另一个组件也会同步发生变更,但要想实现联动则还需要使用QItemSelectionModel模型,它负责跟踪哪些项被选中,以及在模型中项的选择状态发生变化时发出信号。

以下是 QItemSelectionModel 的一些重要特性和方法:

  • 选择项: 负责管理模型中的项的选择状态,可以单独选择项、选定范围内的项或清除所有选择项。
  • 信号: 当选择状态发生变化时,QItemSelectionModel 会发出相应的信号,如 selectionChanged 信号。
  • 选择模式: 提供多种选择模式,包括单选、多选、扩展选择等,可通过设置 SelectionMode 进行配置。
  • 选择策略: 提供多种选择策略,用于定义选择行为,如 SelectItemsSelectRowsSelectColumns 等。
  • 与视图的集成: 通常与 QTableViewQTreeView 等视图组件结合使用,以实现对视图中项的选择操作。

该组件是实现模型-视图架构中选择的关键组件。通过它,可以轻松管理和操作模型中的项的选择状态,实现各种灵活的用户交互。下面是 QItemSelectionModel 类的一些主要方法:

方法 描述
QItemSelectionModel(QAbstractItemModel *model, QObject *parent = nullptr) 构造函数,创建一个与指定模型关联的 QItemSelectionModel 对象。
QModelIndexList selectedIndexes() const 获取当前被选中的项的索引列表。
void clear() 清除所有的选择项。
void setSelectionMode(QItemSelectionModel::SelectionFlags mode) 设置选择模式,可以选择多个项、单个项等。
void setSelectionBehavior(QItemSelectionModel::SelectionBehavior behavior) 设置选择策略,如选择单个项、选择整行、选择整列等。
void select(const QModelIndex &topLeft, const QModelIndex &bottomRight, QItemSelectionModel::SelectionFlags command) 在指定范围内进行选择操作,使用 SelectionFlags 定义选择操作。
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) 当选择状态发生变化时发出的信号,可以通过连接这个信号来处理选择状态变化的事件。
bool hasSelection() const 判断是否有选中的项。

上述方法提供了管理选择项的一些基本操作,包括清除选择、获取选中项的索引、设置选择模式和策略,以及在指定范围内进行选择操作。

MainWindow构造函数中,我们以此执行如下关键部分,来实现对主界面的初始化工作;

创建模型和选择模型

首先创建一个包含4行5列的 QStandardItemModel 模型,并为其创建了一个 QItemSelectionModel 选择模型。

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

关联到 tableViewtreeView

将模型和选择模型关联到 tableViewtreeView 上,这样它们会共享同一份数据模型,也就是无论两个组件哪一个发生变化均会影响双方组件中的内容。

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

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

添加表头与初始化数据

创建一个包含列名的 HeaderList 字符串列表,并将其设置为模型的水平表头标签。继续创建一个包含三个字符串列表的数组 DataList,每个列表代表一行数据。然后使用嵌套的循环遍历数组,将数据逐个添加到模型中。

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" << "女" << "是";

通过循环添加数据到模型

使用两个循环,外层循环遍历数组,内层循环遍历每个数组中的元素,创建 QStandardItem 对象并将其添加到模型的相应位置。

cppCopy codeint 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++)
    {
        Item = new QStandardItem(DataList[x][y]);
        model->setItem(x, y, Item);
    }
}

如上这段代码初始化了一个包含表头和数据的 QStandardItemModel 模型,然后将模型和选择模型关联到 tableViewtreeView 上,最后通过循环将数据逐个添加到模型中。这样就创建了一个主窗口,其中包含了一个表格视图和一个树形视图,它们共享相同的数据模型。如下图所示;

1379525-20231227144440988-713344813.png

DialogSize.ui

接着来看on_pushButton_clicked按钮是如何实现的,该按钮主要用于实现改变表格行与列,当点击后则会弹出一个DialogSize自定义对话框,至于对话框是如何添加的在之前的文章中已经详细介绍过了。

在如下代码中我们通过model->rowCount()以及model->columnCount()获取到父UI界面中tableView表格的行列数,并通过ptr->setRowColumn将这些数据设置到了子对话框的编辑框上面,而ptr->columnCount()则用于接收子对话框的返回值,并将其动态设置到对应的模型中;

void MainWindow::on_pushButton_clicked()
{
    // //模态对话框,动态创建,用过后删除
    DialogSize *ptr = new DialogSize(this);     // 创建一个对话框
    Qt::WindowFlags flags = ptr->windowFlags(); // 需要获取返回值
    ptr->setWindowFlags(flags | Qt::MSWindowsFixedSizeDialogHint);  // 设置对话框固定大小
    ptr->setRowColumn(model->rowCount(),model->columnCount());      // 对话框数据初始化

    int ref = ptr->exec();             // 以模态方式显示对话框
    if (ref==QDialog::Accepted)        // OK键被按下,对话框关闭
    {
        // 当BtnOk被按下时,则设置对话框中的数据
        int cols=ptr->columnCount();
        model->setColumnCount(cols);

        int rows=ptr->rowCount();
        model->setRowCount(rows);
    }

    // 最后删除释放对话框句柄
    delete ptr;
}

接着来看下子对话框DialogSize做了什么,在对话框代码中rowCount()是给主窗体调用的函数其功能是获取到当前对话框中spinBoxRow组件中的数值,而columnCount()同理用于得到spinBoxColumn组件中的数值,最后的setRowColumn()则是用于接收主窗体的船只,并设置到对应的子对话框上的SpinBox组件内,其代码如下;

DialogSize::DialogSize(QWidget *parent) :QDialog(parent),ui(new Ui::DialogSize)
{
    ui->setupUi(this);
}

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

// 主窗体调用获取当前行数
int DialogSize::rowCount()
{
    return  ui->spinBoxRow->value();
}

// 主窗体调用获取当前列数
int DialogSize::columnCount()
{
    return  ui->spinBoxColumn->value();
}

// 设置主窗体中的TableView行数与列数
void DialogSize::setRowColumn(int row, int column)
{
    ui->spinBoxRow->setValue(row);
    ui->spinBoxColumn->setValue(column);
}

运行程序,并点击左侧第一个按钮,此时我们可以将表格设置为6*6的矩阵,如下图所示;

1379525-20231227151806804-648412780.png

DIalogHead.ui

对于第二个按钮on_pushButton_2_clicked的功能实现与第一个按钮完全一致,该按钮主要实现对父窗体中TableView的表头进行重新设置,在弹出对话框之前,需要将当前表头元素复制到strList列表容器内,并通过使用子对话框中的ptr->setHeaderList将其拷贝到子对话框中,并通过QDialog::Accepted等待对话框按下修改按钮,如下代码所示;

void MainWindow::on_pushButton_2_clicked()
{
    DialogHead *ptr = new DialogHead(this);
    Qt::WindowFlags flags = ptr->windowFlags();
    ptr->setWindowFlags(flags | Qt::MSWindowsFixedSizeDialogHint);

    // 如果表头列数变化,则从新初始化
    if(ptr->headerList().count() != model->columnCount())
    {
        QStringList strList;

        // 获取现有的表头标题
        for (int i=0;i<model->columnCount();i++)
        {
            strList.append(model->headerData(i,Qt::Horizontal,Qt::DisplayRole).toString());
        }

        // 用于对话框初始化显示
        ptr->setHeaderList(strList);
    }

    // 调用弹窗
    int ref = ptr->exec();
    if(ref==QDialog::Accepted)
    {
        // 获取对话框上修改后的StringList
        QStringList strList=ptr->headerList();

        // 设置模型的表头标题
        model->setHorizontalHeaderLabels(strList);
    }

    delete ptr;
}

当读者按下了修改按钮之后,由于通过ui->listView->setModel(model)已经与父窗体建立了关联,则此时通过model->setStringList(headers)就可以实现对父窗体中数据的修改,代码如下所示;

DialogHead::DialogHead(QWidget *parent) :QDialog(parent),ui(new Ui::DialogHead)
{
    ui->setupUi(this);
    model = new QStringListModel;
    ui->listView->setModel(model);
}

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

// 设置当前listView中的数据
void DialogHead::setHeaderList(QStringList &headers)
{
    model->setStringList(headers);
}

// 返回当前的表头
QStringList DialogHead::headerList()
{
    return model->stringList();
}

程序运行后,读者可以先将表格的行与列修改为7*7,接着再通过设置表头的方式更新表头,效果如下;

1379525-20231227153043123-552772770.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK