實例QT程序 —— QTableWidget 表格移動帶控件的單元格列

目錄

1.簡介
2.效果圖
3.重點講解
4.源碼



1.簡介

本文主要介紹瞭如何在QTableWidget表格中移動帶控件的單元格列,重點內容包含

  • 帶控件單元格的移動;
  • 標題欄的同步更新;
  • 點擊單元格內控件同時更新表格的選中單元格信息。

本文還有動態效果圖、重點講解和源碼,讀者們可以方便查看學習和交流。


回目錄

2.效果圖

列1移動(列信息、標題欄信息不變不丟失)運行效果圖
列1移動(列信息、標題欄信息不變不丟失)運行效果圖


列2移動(列信息、標題欄信息不變不丟失)運行效果圖
列2移動(列信息、標題欄信息不變不丟失)運行效果圖


多個不同列的左右移動(列信息、標題欄信息不變不丟失)運行效果圖
多個不同列的左右移動(列信息、標題欄信息不變不丟失)運行效果圖


帶不同控件單元格的選中運行效果圖
帶不同控件單元格的選中運行效果圖

回目錄

3.重點講解

1)帶控件單元格的移動

把原單元格中的控件移動到新的單元格的操作是:先獲取原有單元格的控件,然後設置到新的單元格中即可,這樣單元格控件的信息也不會丟失,也不用在新單元格中重新創建新控件的及複製原有單元格內容信息的操作(況且Qt的QWidget不支持好拷貝和賦值)。

	QWidget *wdg = pTable->cellWidget(row, nFrom);
	pTable->setCellWidget(row, nTo, wdg);

以下刪除列時,不用擔心會刪除控件的問題,因爲原列中單元格中的控件已經被設置(移動)到新的單元格中。

    // 刪除舊的列
    pTable->removeColumn(nFromColumn);

2)標題欄的同步更新

移除原有標題欄的item,直接設置到新的列標題中即可。這裏的操作步驟同以上單元格cellWidget的移動處理。只是我想在這裏吐槽下,同樣是移除再設置,這裏的獲取item的方法命名中有’take’字樣,明顯表示移除,容易讓人理解,而上面的獲取單元格cellWidget()方法,沒有’take’字樣,容易讓人誤解是否是移除成功。

    // 移除原位置標題到新的位置
    QTableWidgetItem *hHeaderItemFrom = ui->tableWidget->takeHorizontalHeaderItem(nFromColumn);
    ui->tableWidget->setHorizontalHeaderItem(nInsertColumn, hHeaderItemFrom);

3)點擊單元格內控件同時更新表格的選中單元格信息

本次實例程序的主要功能是移動列,在移動的時候,我們需要獲取當前用戶選中的列(如下面的語句),然後再移動。

    // 移除原位置標題到新的位置
    QTableWidgetItem *hHeaderItemFrom = ui->tableWidget->takeHorizontalHeaderItem(nFromColumn);
    ui->tableWidget->setHorizontalHeaderItem(nInsertColumn, hHeaderItemFrom);

按照常規的理解,點擊單元格就表示表格的單元格被選中了,用戶選中的列信息我們也可以獲取到的,即通過以下語句獲取列信息。

    int currentColumn = ui->tableWidget->currentColumn();

然而在本例中,每個單元格完全被控件佔據了,我們點擊單元格控件的時候,並未觸發到表格單元格被點擊的發生,也就是我們真實想要的列信息並未更新,通過上面說的方法獲取到的列信息是之前選中的列信息(舊信息)。

因此,爲了達到我們想要的目的(直接點擊到(選中)表格中的單元格),這裏有2個方法:

(1) 鼠標穿透
點擊單元格控件的時候,讓控件忽略掉鼠標點擊,而讓表格的單元格響應接收鼠標的點擊操作;
(2) 事件處理
在單元格控件響應鼠標點擊事件的時候,同時告知表格當前單元格被點擊了的方法,即多一次消息轉發處理的辦法。

兩種方法的優缺點對比

方法 優點 缺點
鼠標穿透 不用自定義控件 通過鼠標穿透讓表格更新單元格點擊選中操作後,單元格控件還需要恢復爲可操作使用的狀態(即取消支持鼠標穿透功能);
當用戶再次點擊不同單元格的時候,還需要把之前的單元格控件恢復爲再次支持鼠標穿透的控件;
操作較爲繁瑣,需要把控各個控件之間的聯動作用,即在主程序中對各單元格的關係進行把控處理;
在單元格中添加控件的時候,需要標記出控件是否需要鼠標穿透(具有支持鼠標點擊信號的控件不需要,僅需連接到槽函數告知表格單元格被點擊即可)。
事件處理 可以在各控件的鼠標響應事件中發出相同參數的信號,這邊表格所在主程序僅需寫1個槽函數用於處理該信號,各個控件的行爲處理方式較爲獨立、具有模塊化; 對於不具有鼠標點擊信號的控件,需要自定義該控件,重新實現鼠標點擊事件,且重新定義具有相同參數的1個信號,同時需要關聯該控件的信號與對應表格處理的槽函數

本例中採用的是“鼠標穿透”的方法,事件處理方式待後續介紹事件時可以參考。
控件鼠標穿透的核心代碼示例如下所示:

	// 讓 旋鈕控件 鼠標事件傳遞到父對象中(鼠標穿透功能)
	spinBox->setAttribute(Qt::WA_TransparentForMouseEvents,true);
	
	//  關閉鼠標穿透功能,控件重新獲得鼠標事件的響應控制
	spinBox->setAttribute(Qt::WA_TransparentForMouseEvents,false);

回目錄

4.源碼

widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class QTableWidget;

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_btnLeft_clicked();
    void on_btnRight_clicked();

    void on_btnClear_clicked();

private:
    void initForm();    // 初始化窗體
    bool moveColumn(QTableWidget *pTable, int nFrom, int nTo); //移動列
    void copyColumn( QTableWidget *pTable, int nFrom, int nTo ); // 複製行
    void updateCellWidgetObjName(QTableWidget *pTable, int nFrom, int nTo);     //更新單元格控件對象名稱
    void updateObjNameMouseTrans(QString &objName, int nColNew);    // 更新對象(包含鼠標穿透信息)的名字信息
private:
    Ui::Widget *ui;

    int m_curColumn;    // 當前列

    QString m_ObjNamePreMouseTrans;    // 之前解除鼠標穿透的對象名字
};

#endif // WIDGET_H



widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QSpinBox>
#include <QLineEdit>
#include <QComboBox>
#include <QDebug>


const QString &strSep("@");

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

    initForm();
}

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

void Widget::initForm()
{
    int nRowCount = 8;
    int nColCount = 4;

    ui->tableWidget->setColumnCount(nColCount);
    ui->tableWidget->setRowCount(nRowCount);

    QStringList listLabsH;  // 行標題
    for(int row=0; row<nRowCount; row++){
        listLabsH << QString("行") + QString::number(row+1);
    }
    ui->tableWidget->setVerticalHeaderLabels(listLabsH);

    QList<int> listColNeedTransMouse;   //需要鼠標穿透的列

    QStringList listLabsV;  // 列標題
    for(int col=0; col<nColCount; col++){
        switch (col) {
        case 0:{
            listLabsV << QString("按鈕列%1").arg(col+1);
            break;
        }
        case 1:{
            listLabsV << QString("旋鈕列%1").arg(col+1);
            listColNeedTransMouse.append(col);
            break;
        }
        case 2:{
            listLabsV << QString("輸入框列%1").arg(col+1);
            listColNeedTransMouse.append(col);
            break;
        }
        case 3:{
            listLabsV << QString("下拉框列%1").arg(col+1);
            listColNeedTransMouse.append(col);
            break;
        }
        default:
            break;
        }
    }
    ui->tableWidget->setHorizontalHeaderLabels(listLabsV);

    for(int row=0; row<nRowCount; row++){
//        ui->tableWidget->insertRow(row);
        for(int col=0; col<nColCount; col++){
            int nR = row + 1;
            int nC = col + 1;
            QString txt = QString("%1%2").arg(nR).arg(nC);
            QString txtObjName = QString("%1%2%3").arg(row).arg(strSep).arg(col);
            switch (col) {
            case 0:{
                QString text = QString("按鈕") + txt;
                QPushButton *btn = new QPushButton(text);
                btn->setObjectName(txtObjName);
                connect(btn, &QPushButton::clicked, [=](){
                    QString objN = btn->objectName();
                    QStringList listI = objN.split(strSep);
                    QString row = listI.at(0);
                    QString col = listI.at(1);
                    m_curColumn = col.toInt();
                    ui->tableWidget->setCurrentCell(row.toInt(), col.toInt());
//                    ui->plainTextEdit->appendPlainText(objN);
                    QString printInfo = QString("單元格(%1,%2)被選中").arg(row.toInt()+1).arg(col.toInt()+1);
                    ui->plainTextEdit->appendPlainText(printInfo);
                });
                ui->tableWidget->setCellWidget(row, col, btn);
                break;
            }
            case 1:{
                QString text = QString("旋鈕") + txt;
                QSpinBox *spinBox = new QSpinBox;
                spinBox->setValue(txt.toInt());
                spinBox->setObjectName(txtObjName);
                if( listColNeedTransMouse.contains(col) ){
                    spinBox->setAttribute(Qt::WA_TransparentForMouseEvents,true);
                }
                connect(spinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
                        [=](int){
                    QString objN = spinBox->objectName();
                    QStringList listI = objN.split(strSep);
                    QString row = listI.at(0);
                    QString col = listI.at(1);
                    m_curColumn = col.toInt();
                    ui->tableWidget->setCurrentCell(row.toInt(), col.toInt());
//                    ui->plainTextEdit->appendPlainText(objN);
                });
                ui->tableWidget->setCellWidget(row, col, spinBox);
                break;
            }
            case 2:{
                QString text = QString("輸入框") + txt;
                QLineEdit *lEdit = new QLineEdit(text);
                lEdit->setObjectName(txtObjName);
                if( listColNeedTransMouse.contains(col) ){
                    lEdit->setAttribute(Qt::WA_TransparentForMouseEvents,true);
                }
                connect(lEdit, &QLineEdit::textEdited, [=](){
                    QString objN = lEdit->objectName();
                    QStringList listI = objN.split(strSep);
                    QString row = listI.at(0);
                    QString col = listI.at(1);
                    m_curColumn = col.toInt();
                    ui->tableWidget->setCurrentCell(row.toInt(), col.toInt());
//                    ui->plainTextEdit->appendPlainText(objN);
                });
                ui->tableWidget->setCellWidget(row, col, lEdit);
                break;
            }
            case 3:{
                QString text = QString("下拉框") + txt;
                QComboBox *cBox = new QComboBox;
                cBox->addItem(text);
                cBox->setObjectName(txtObjName);
                if( listColNeedTransMouse.contains(col) ){
                    cBox->setAttribute(Qt::WA_TransparentForMouseEvents,true);
                }
                connect(cBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated),
                        [=](int){
                    QString objN = cBox->objectName();
                    QStringList listI = objN.split(strSep);
                    QString row = listI.at(0);
                    QString col = listI.at(1);
                    m_curColumn = col.toInt();
                    ui->tableWidget->setCurrentCell(row.toInt(), col.toInt());
//                    ui->plainTextEdit->appendPlainText(objN);
                });
                ui->tableWidget->setCellWidget(row, col, cBox);
                break;
            }
            default:
                break;
            }
        }
    }

    connect(ui->tableWidget, &QTableWidget::cellClicked, [=](int row, int col){
        if( !m_ObjNamePreMouseTrans.isEmpty() ){   // 還原回之前解除鼠標穿透功能的控件
            QStringList listInfo = m_ObjNamePreMouseTrans.split(strSep);
            QString rowStr = listInfo.at(0);
            QString colStr = listInfo.at(1);
            QWidget *wdg = ui->tableWidget->cellWidget(rowStr.toInt(), colStr.toInt());
            if( nullptr != wdg ){
                wdg->setAttribute(Qt::WA_TransparentForMouseEvents, true);
            }
            m_ObjNamePreMouseTrans.clear();
        }
        QString txtObjName = QString("%1%2%3").arg(row).arg(strSep).arg(col);
        QString printInfo = QString("單元格(%1,%2)被選中").arg(row+1).arg(col+1);
        ui->plainTextEdit->appendPlainText(printInfo);
        if( listColNeedTransMouse.contains(col) ){
            QWidget *wdg = ui->tableWidget->cellWidget(row, col);
            if( nullptr != wdg ){
                wdg->setAttribute(Qt::WA_TransparentForMouseEvents, false);
                m_ObjNamePreMouseTrans = txtObjName;
            }
        }
    });
}

void Widget::on_btnLeft_clicked()
{
    int currentColumn = ui->tableWidget->currentColumn();
    qDebug() << currentColumn;
    QTableWidgetItem *hHeaderItemFrom = ui->tableWidget->horizontalHeaderItem(currentColumn);
    QString headName = hHeaderItemFrom->text();
    bool isMove = moveColumn(ui->tableWidget, currentColumn, currentColumn-1);
    if( isMove ){
        QString msg = QString("列“%1”左移一列").arg(headName);
        ui->plainTextEdit->appendPlainText(msg);
    }else{
        QString msg = QString("列“%1”已是第一列").arg(headName);
        ui->plainTextEdit->appendPlainText(msg);
    }
}

void Widget::on_btnRight_clicked()
{
    int currentColumn = ui->tableWidget->currentColumn();
    qDebug() << currentColumn;
    QTableWidgetItem *hHeaderItemFrom = ui->tableWidget->horizontalHeaderItem(currentColumn);
    QString headName = hHeaderItemFrom->text();
    bool isMove = moveColumn(ui->tableWidget, currentColumn, currentColumn+1);
    if( isMove ){
        QString msg = QString("列“%1”右移一列").arg(headName);
        ui->plainTextEdit->appendPlainText(msg);
    }else{
        QString msg = QString("列“%1”已是最後一列").arg(headName);
        ui->plainTextEdit->appendPlainText(msg);
    }
}



bool Widget::moveColumn(QTableWidget *pTable, int nFrom, int nTo)
{
    if( pTable == nullptr ) {
        return false;
    }
    if( nFrom == nTo ) {
        return false;
    }
    if( nFrom < 0 || nTo < 0 ) {
        return false;
    }
    int nColumnCount = pTable->columnCount();
    if( nFrom >= nColumnCount  || nTo >= nColumnCount ) {
        return false;
    }

    int nRowCur = 0;
    nRowCur = pTable->currentRow();
    QTableWidgetItem *itCur = pTable->currentItem();
    if( nullptr != itCur ){
        nRowCur = itCur->row();
    }
    int nFromColumn = nFrom;
    int nInsertColumn = nTo;
    if( nTo < nFrom ){  // Left
        nFromColumn = nFrom + 1;
        pTable->insertColumn(nTo);
    }else { // Right
        nInsertColumn = nTo + 1;
        pTable->insertColumn(nInsertColumn);
    }

    // 拷貝信息到新插入的列
    this->copyColumn(pTable, nFromColumn, nInsertColumn);

    // 移除原位置標題到新的位置
    QTableWidgetItem *hHeaderItemFrom = ui->tableWidget->takeHorizontalHeaderItem(nFromColumn);
    ui->tableWidget->setHorizontalHeaderItem(nInsertColumn, hHeaderItemFrom);

    // 刪除舊的列
    pTable->removeColumn(nFromColumn);

    // 更新單元格控件的對象名稱(記錄的了單元格信息)
    updateCellWidgetObjName(pTable, nFrom, nTo);

    updateObjNameMouseTrans(m_ObjNamePreMouseTrans, nTo);

    // 選擇之前移動的列
    pTable->selectColumn( nInsertColumn );

    // 選中前光標所在的單元格
    pTable->setCurrentCell(nRowCur, nTo);

    return  true;
}


void Widget::copyColumn(QTableWidget *pTable, int nFrom, int nTo)
{
    int nRowCount = pTable->rowCount();
    for(int row=0; row<nRowCount; row++){
        QWidget *wdg = pTable->cellWidget(row, nFrom);
        pTable->setCellWidget(row, nTo, wdg);
    }
}

void Widget::updateCellWidgetObjName(QTableWidget *pTable, int nFrom, int nTo)
{
    if( nFrom > nTo ){
        qSwap(nFrom, nTo);
    }

    int nRowCount = pTable->rowCount();
    for(int col=nFrom; col<=nTo; col++){
        for(int row=0; row<nRowCount; row++){
            QWidget *wdg = pTable->cellWidget(row, col);
            if( nullptr != wdg ){
                QString txtObjName = QString("%1%2%3").arg(row).arg(strSep).arg(col);
                wdg->setObjectName(txtObjName);
            }
        }
    }
}

void Widget::updateObjNameMouseTrans(QString &objName, int nColNew)
{
    if( !objName.isEmpty() ){
        QStringList listInfo = objName.split(strSep);
        QString rowStr = listInfo.at(0);
        QString colStr = listInfo.at(1);
        objName = rowStr + strSep + QString::number(nColNew);
    }
}


void Widget::on_btnClear_clicked()
{
    ui->plainTextEdit->clear();
}

回目錄




加油,向未來!GO~
Come on!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章