C/C++ Qt StandardItemModel 資料模型應用

lyshark發表於2021-12-05

QStandardItemModel 是標準的以項資料為單位的基於M/V模型的一種標準資料管理方式,Model/View 是Qt中的一種資料編排結構,其中Model代表模型,View代表檢視,檢視是顯示和編輯資料的介面元件,而模型則是檢視與原始資料之間的介面,通常該類結構都是用在資料庫中較多,例如模型結構負責讀取或寫入資料庫,檢視結構則負責展示資料,其條理清晰,編寫程式碼便於維護。

QStandardItemModel元件通常會配合TableView元件一起使用,當資料庫或文字中的記錄發生變化時會自動同步到元件中,首先繪製UI介面。

其次繫結頂部ToolBar選單,分別對選單增加對應的功能屬性的描述等。


初始化建構函式: 當程式執行時,我們需要對頁面中的控制元件逐一初始化,並將Table表格與模型通過呼叫ui->tableView->setModel(model)進行繫結。

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

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

#include <QFileDialog>
#include <QTextStream>

#include <QList>

// 預設建構函式
// https://www.cnblogs.com/lyshark
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 初始化部分
    model = new QStandardItemModel(3,FixedColumnCount,this);  // 資料模型初始化
    selection = new QItemSelectionModel(model);               // Item選擇模型

    // 為TableView設定資料模型
    ui->tableView->setModel(model);               // 設定資料模型
    ui->tableView->setSelectionModel(selection);  // 設定選擇模型

    // 預設禁用所有Action選項,只保留開啟
    ui->actionSave->setEnabled(false);
    ui->actionView->setEnabled(false);
    ui->actionAppend->setEnabled(false);
    ui->actionDelete->setEnabled(false);
    ui->actionInsert->setEnabled(false);

    // 建立狀態列元件,主要來顯示單元格位置
    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);

    //選擇當前單元格變化時的訊號與槽
    connect(selection,SIGNAL(currentChanged(QModelIndex,QModelIndex)),this,SLOT(on_currentChanged(QModelIndex,QModelIndex)));
}

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

初始化時同時需要繫結一個on_currentChanged(QModelIndex,QModelIndex)訊號,當使用者選中指定單元格時相應使用者。

// 選擇單元格變化時的響應,通過在建構函式中繫結訊號和槽函式實現觸發
// https://www.cnblogs.com/lyshark
void MainWindow::on_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;
        aItem=model->itemFromIndex(current); //從模型索引獲得Item
        this->LabCellText->setText("單元格內容:"+aItem->text()); //顯示item的文字內容
    }
}

當頁面被初始化時,預設介面如下:


開啟並填充元件: 當工具欄中開啟檔案被點選後則觸發,開啟檔案時通過aFile.open開啟,迴圈讀入檔案,並將檔案中的內容逐行追加到QStringList fFileContent中,當追加完畢後,直接呼叫iniModelFromStringList(fFileContent);完成對頁面TableView元件的初始化,並設定其他控制元件狀態為可點選。

void MainWindow::on_actionOpen_triggered()
{
    QString curPath=QCoreApplication::applicationDirPath(); // 獲取應用程式的路徑
    
    // 呼叫開啟檔案對話方塊開啟一個檔案
    // https://www.cnblogs.com/lyshark
    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);//狀態列顯示
}

如上iniModelFromStringList(fFileContent);函式是後期增加的,我們需要自己實現,該函式的作用是從傳入的StringList中獲取資料,並將資料初始化到TableView模型中,實現程式碼如下。

void MainWindow::iniModelFromStringList(QStringList& aFileContent)
{
    int rowCnt=aFileContent.count();     // 文字行數,第1行是標題
    model->setRowCount(rowCnt-1);        // 實際資料行數,要在標題上減去1

    // 設定表頭
    QString header=aFileContent.at(0);         // 第1行是表頭

    // 一個或多個空格、TAB等分隔符隔開的字串、分解為一個StringList
    // https://www.cnblogs.com/lyshark
    QStringList headerList=header.split(QRegExp("\\s+"),QString::SkipEmptyParts);
    model->setHorizontalHeaderLabels(headerList); // 設定表頭文字

    // 設定表格中的資料
    int x = 0,y = 0;
    QStandardItem *Item;

    // 有多少列資料就迴圈多少次
    // https://www.cnblogs.com/lyshark
    for(x=1; x < rowCnt; x++)
    {
        QString LineText = aFileContent.at(x);    // 獲取資料區的一行

        // 一個或多個空格、TAB等分隔符隔開的字串、分解為一個StringList
        QStringList tmpList=LineText.split(QRegExp("\\s+"),QString::SkipEmptyParts);

        // 迴圈列數,也就是迴圈FixedColumnCount,其中tmpList中的內容也是.
        for(y=0; y < FixedColumnCount-1; y++)
        {
            Item = new QStandardItem(tmpList.at(y)); // 建立item
            model->setItem(x-1,y,Item);              // 為模型的某個行列位置設定Item
        }

        // 最後一個資料需要取出來判斷,並單獨設定狀態
        Item=new QStandardItem(headerList.at(y));   // 最後一列是Checkable,需要設定
        Item->setCheckable(true);                   // 設定為Checkable

        // 判斷最後一個數值是否為0
        if (tmpList.at(y) == "0")
            Item->setCheckState(Qt::Unchecked);   // 根據資料設定check狀態
        else
            Item->setCheckState(Qt::Checked);

        model->setItem(x-1,y,Item); //為模型的某個行列位置設定Item
    }
}

初始化元件後效果如下:


實現新增一行資料: 為TableView新增一行資料,在檔案末尾插入。

void MainWindow::on_actionAppend_triggered()
{
    QList<QStandardItem *> ItemList;   // 建立臨時容器
    QStandardItem *Item;

    // 模擬新增一列的資料
    for(int x=0; x<FixedColumnCount-1; x++)
    {
        Item = new QStandardItem("測試(追加行)");    // 迴圈建立每一列
        ItemList << Item;                          // 新增到連結串列中
    }

    // 建立最後一個列元素,由於是選擇框所以需要單獨建立
    // https://www.cnblogs.com/lyshark
    // 1.獲取到最後一列的表頭下標,最後下標為6
    QString str = model->headerData(model->columnCount()-1,Qt::Horizontal,Qt::DisplayRole).toString();

    Item=new QStandardItem(str); // 建立 "是否合格" 欄位
    Item->setCheckable(true);    // 設定狀態為真
    ItemList << Item;            // 最後一個選項追加進去

    model->insertRow(model->rowCount(),ItemList);                 // 插入一行,需要每個Cell的Item
    QModelIndex curIndex=model->index(model->rowCount()-1,0);     // 建立最後一行的ModelIndex

    selection->clearSelection();                                      // 清空當前選中項
    selection->setCurrentIndex(curIndex,QItemSelectionModel::Select); // 設定當前選中項為當前選擇行
}

插入程式碼演示效果:


實現插入一行資料: 為TableView插入一行資料(在檔案任意位置插入資料)

// https://www.cnblogs.com/lyshark
void MainWindow::on_actionInsert_triggered()
{
    QList<QStandardItem*> ItemList;       // QStandardItem的列表類
    QStandardItem *Item;

    // 模擬插入前五列資料
    for(int i=0;i<FixedColumnCount-1;i++)
    {
        Item= new QStandardItem("測試(插入行)");  // 新建一個QStandardItem
        ItemList << Item;                        // 新增到列表類
    }

    QString str;                               // 獲取表頭文字
    str=model->headerData(model->columnCount()-1,Qt::Horizontal,Qt::DisplayRole).toString();
    Item=new QStandardItem(str);      // 建立Item
    Item->setCheckable(true);         // 設定為可使用CheckBox
    ItemList<<Item;                   // 新增到列表類

    QModelIndex curIndex=selection->currentIndex(); // 獲取當前選中項的索引
    model->insertRow(curIndex.row(),ItemList);      // 在當前行的前面插入一行
    selection->clearSelection();                                       // 清除當前選中項
    selection->setCurrentIndex(curIndex,QItemSelectionModel::Select);  // 設定當前選中項為當前選擇行
}

插入程式碼演示效果:


實現刪除一行資料: 刪除資料之前需要通過selection->currentIndex()確定當前選中行,並通過model->removeRow()移除即可。

// https://www.cnblogs.com/lyshark
void MainWindow::on_actionDelete_triggered()
{
    QModelIndex curIndex = selection->currentIndex();  // 獲取當前選擇單元格的模型索引

    // 先判斷是不是最後一行
    if (curIndex.row()==model->rowCount()-1)
    {
        model->removeRow(curIndex.row()); //刪除最後一行
    }
    else
    {
        model->removeRow(curIndex.row());//刪除一行,並重新設定當前選擇行
        selection->setCurrentIndex(curIndex,QItemSelectionModel::Select);
    }
}

刪除程式碼效果演示:


實現字型資料對齊: 表格中的字型可以實現多種對其方式,對齊方式分為 居中對齊,左對齊,右對齊 三種。

// 設定表格居中對齊
void MainWindow::on_pushButton_clicked()
{
    if (!selection->hasSelection())
        return;

    QModelIndexList selectedIndex=selection->selectedIndexes();

    QModelIndex Index;
    QStandardItem *Item;

    for (int i=0; i<selectedIndex.count(); i++)
    {
        Index=selectedIndex.at(i);
        Item=model->itemFromIndex(Index);
        Item->setTextAlignment(Qt::AlignHCenter);
    }
}

// 設定表格左對齊
// https://www.cnblogs.com/lyshark
void MainWindow::on_pushButton_2_clicked()
{
    if (!selection->hasSelection()) //沒有選擇的項
        return;

//獲取選擇的單元格的模型索引列表,可以是多選
    QModelIndexList selectedIndex=selection->selectedIndexes();

    for (int i=0;i<selectedIndex.count();i++)
    {
        QModelIndex aIndex=selectedIndex.at(i); //獲取其中的一個模型索引
        QStandardItem* aItem=model->itemFromIndex(aIndex);//獲取一個單元格的項資料物件
        aItem->setTextAlignment(Qt::AlignLeft);//設定文字對齊方式
    }
}

// 設定表格右對齊
void MainWindow::on_pushButton_3_clicked()
{
    if (!selection->hasSelection())
        return;

    QModelIndexList selectedIndex=selection->selectedIndexes();

    QModelIndex aIndex;
    QStandardItem *aItem;

    for (int i=0;i<selectedIndex.count();i++)
    {
        aIndex=selectedIndex.at(i);
        aItem=model->itemFromIndex(aIndex);
        aItem->setTextAlignment(Qt::AlignRight);
    }
}

對齊程式碼效果演示:


實現字型資料加粗: 將選中行的字型進行加粗顯示。

// 設定字型加粗顯示
// https://www.cnblogs.com/lyshark
void MainWindow::on_pushButton_4_clicked()
{
    if (!selection->hasSelection())
        return;

//獲取選擇單元格的模型索引列表
    QModelIndexList selectedIndex=selection->selectedIndexes();

    for (int i=0;i<selectedIndex.count();i++)
    {
        QModelIndex aIndex=selectedIndex.at(i); //獲取一個模型索引
        QStandardItem* aItem=model->itemFromIndex(aIndex);//獲取項資料
        QFont font=aItem->font(); //獲取字型
        font.setBold(true); //設定字型是否粗體
        aItem->setFont(font); //重新設定字型
    }
}

加粗程式碼效果演示:


實現儲存檔案: 當儲存檔案被點選後觸發,通過便利TableWidget模型元件中的資料,並將資料通過aStream << str << "\n";寫出到記事本中。

// https://www.cnblogs.com/lyshark
// 【儲存檔案】
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() + "\t\t";        // 以TAB製表符隔開
    }
    aStream << str << "\n";                      // 檔案裡需要加入換行符\n
    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("\t\t");
        }

        // 對最後一列需要轉換一下,如果判斷為選中則寫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 << "\n";
    }
}

// 【匯出Txt檔案】:將TableView中的資料匯出到PlainTextEdit顯示
void MainWindow::on_actionView_triggered()
{
        ui->plainTextEdit->clear();
        QStandardItem *Item;
        QString str;

    //獲取表頭文字
        int x=0,y=0;
        for (x=0; x<model->columnCount(); x++)
        { //
            Item=model->horizontalHeaderItem(x);
            str= str + Item->text() + "\t";
        }
        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("\t");
            }

            Item=model->item(x,y);
            if (Item->checkState()==Qt::Checked)
                str= str + "1";
            else
                str= str + "0";

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

檔案儲存後如下:

相關文章