C++ Qt開發:TableView與TreeView元件聯動

lyshark發表於2023-12-27

Qt 是一個跨平臺C++圖形介面開發庫,利用Qt可以快速開發跨平臺窗體應用程式,在Qt中我們可以透過拖拽的方式將不同元件放到指定的位置,實現圖形化開發極大的方便了開發效率,本章將重點介紹TableViewTreeView元件聯動的常用方法及靈活運用。

本章我們繼續實現表格的聯動效果,當讀者點選TableViewTreeView中的某一行時,我們讓其實現自動跟隨功能,且當使用者修改行中特定資料時也讓其動態的跟隨改變,首先繪製一個主介面如圖,分別放置兩個元件框,底部保留兩個按鈕,按鈕1用於該表表格的行列個數,按鈕2則用於設定TableView表格表頭引數,整個表格我們將其設定為可編輯狀態。

在函式中我們需要定義一個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 物件並將其新增到模型的相應位置。

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

如上這段程式碼初始化了一個包含表頭和資料的 QStandardItemModel 模型,然後將模型和選擇模型關聯到 tableViewtreeView 上,最後透過迴圈將資料逐個新增到模型中。這樣就建立了一個主視窗,其中包含了一個表格檢視和一個樹形檢視,它們共享相同的資料模型。如下圖所示;

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的矩陣,如下圖所示;

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,接著再透過設定表頭的方式更新表頭,效果如下;

相關文章