Qt自定义封装QTableWidget


前言

        QTableWidget是一个功能强大的Qt小部件,是一个用于显示和编辑表格数据的Qt小部件。它继承自QTableView类,提供了使用表格方式显示数据的功能。QTableWidget可以用于创建静态或可编辑的表格。它可以手动添加和删除行和列,以及设置单元格的内容、样式和编辑属性。

        QTableWidget还可以与其他Qt小部件进行交互。例如,可以使用QComboBox、QCheckBox和QPushButton等小部件作为表格的单元格编辑器,方便用户输入和选择数据。

        本文注重于简化组件的使用,所以进行自定义封装QTable原有的功能,在基础上增加一些好用功能,已达到快速完成界面代码编写的目的。


一、自定义封装KwTable

KwTable使用QAbstractTableModel进行数据绑定,在基础上利用的Qt的元数据机制来进行数据绑定。封装了一些简单功能,并没有对组件样式进行修改。

1、自定义列KwColum

目前设置列单元类型只包含两种(文本、字典),字典类型需要设置数据源、值属性以及显示属性名

class ComboBoxColumnSetting : public QObject
{
    Q_OBJECT
public:
    explicit ComboBoxColumnSetting()
    {

    }
    explicit ComboBoxColumnSetting(QList<QObject*> itemsSource,QString valueMember, QString displayMember)
    {
        ItemsSource = itemsSource;
        ValueMember = valueMember;
        DisplayMember = displayMember;
    }
    ~ComboBoxColumnSetting()
    {
        qDeleteAll(ItemsSource);
        ItemsSource.clear();
    }

    QList<QObject*> ItemsSource;
    QString ValueMember;
    QString DisplayMember;
};


class KwColumn : public QObject
{
    Q_OBJECT
public:
    enum EuColumnType
    {
        Text,
        ComBox,
    };

    explicit KwColumn(QString fieldName, QString header)
    {
        FieldName = fieldName;
        Header = header;
    }
    explicit KwColumn(QString fieldName, QString header,int width, bool isAllowEditing = false)
    {
        FieldName = fieldName;
        Header = header;
        Width = width;
        AllowEditing = isAllowEditing;
    }
    explicit KwColumn(QString fieldName, QString header,int width,EuColumnType columnType,ComboBoxColumnSetting* setting, bool isAllowEditing = false)
    {
        FieldName = fieldName;
        Header = header;
        Width = width;
        AllowEditing = isAllowEditing;
        GridColumnType = columnType;
        ColumnSetting = setting;
    }

    bool AllowEditing;//允许编辑
    int Width=-1;
    QString FieldName;
    QString Header;
    bool Visible = true;
    EuColumnType GridColumnType = Text;
    ComboBoxColumnSetting* ColumnSetting;
};

2、自定义行KwDataRow

没什么特殊的,增加了一个是否选中属性

class KwDataRow : public QObject
{
    Q_OBJECT
public:
    explicit KwDataRow(QObject* data){Data = data;}
    bool IsSelected = false;
    QObject* Data;
};

3、自定义模型KwTableModel

KwTableModel继承QAbstractTableModel,为使model能够绑定数据源对象的属性,则需要重写QAbstractTableModel的setData()、data()等方法

getdata方法中通过元数据反射获取对象的值

checkColumn属性为复选框列,可以设置是否显示复选框

class KwTableModel : public QAbstractTableModel {
    Q_OBJECT

public:
    explicit KwTableModel(QObject* parent = Q_NULLPTR);
    ~KwTableModel();
    void setColumns(QList<KwColumn*>& columns) { this->columns = columns; }
    QList<KwDataRow*> datas;
    void updateData();
    bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
    int rowCount(const QModelIndex& parent = QModelIndex()) const override;
    int columnCount(const QModelIndex& parent = QModelIndex()) const override;
    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
    Qt::ItemFlags flags(const QModelIndex& index) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

    void onCheckStateChanged();

    int checkColumn = 0;
signals:
    void checkStateChanged(int state);
    void rowCheckStateChanged(KwDataRow* row);
public slots:
    void onCheckStateChanged(int state);

private:
    QList<KwColumn*> columns;
    QVariant getData(const QModelIndex& index) const;
    QVariant getItemSourceValue(KwColumn* c, QVariant v) const;
};
#include "kwtablemodel.h"

KwTableModel::KwTableModel(QObject* parent)
    : QAbstractTableModel(parent)
{
}
KwTableModel::~KwTableModel()
{
    qDeleteAll(datas);
    datas.clear();
}
void KwTableModel::updateData()
{
    beginResetModel();
    endResetModel();
}
bool KwTableModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if (!index.isValid()) {
        return false;
    }
    if (role == Qt::CheckStateRole && index.column() == checkColumn) {
        emit dataChanged(index, index);
        datas[index.row()]->IsSelected = value == Qt::Checked;
        onCheckStateChanged();
        emit rowCheckStateChanged(datas[index.row()]);
        return true;
    }
    return QAbstractTableModel::setData(index, value, role);
}
QVariant KwTableModel::data(const QModelIndex& index, int role) const
{
    if (!index.isValid()) {
        return QVariant();
    }

    switch (role) {
    case Qt::CheckStateRole:
        if (index.column() == checkColumn) {
            if (datas.count() > index.row()) {
                return datas[index.row()]->IsSelected ? Qt::Checked : Qt::Unchecked;
            }
            return Qt::Unchecked;
        } else {
            return QVariant();
        }
    case Qt::DisplayRole:
        return getData(index);
    default:
        return QVariant();
    }
}
Qt::ItemFlags KwTableModel::flags(const QModelIndex& index) const
{
    if (!index.isValid()) {
        return QAbstractTableModel::flags(index);
    }
    if (index.column() == checkColumn) {
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
    }
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QVariant KwTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    switch (role) {
    case Qt::TextAlignmentRole:
        return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
    case Qt::DisplayRole:
        if (orientation == Qt::Horizontal)
            return columns[section]->Header;
    default:
        return QVariant();
    }
}
int KwTableModel::columnCount(const QModelIndex& parent) const
{
    Q_UNUSED(parent);
    return columns.size();
}
int KwTableModel::rowCount(const QModelIndex& parent) const
{
    Q_UNUSED(parent);
    return datas.size();
}
void KwTableModel::onCheckStateChanged()
{
    int check = 0;
    for (KwDataRow* row : datas) {
        if (row->IsSelected) {
            check++;
        }
    }
    if (check == 0) {
        emit checkStateChanged(Qt::Unchecked);
    } else if (check == datas.count()) {
        emit checkStateChanged(Qt::Checked);
    } else {
        emit checkStateChanged(Qt::PartiallyChecked);
    }
}
void KwTableModel::onCheckStateChanged(int state)
{
    bool isSelected = false;
    if (state == Qt::Checked) {
        isSelected = true;
    } else {
        state = Qt::Unchecked;
    }
    QModelIndex index;
    for (int i = 0; i < datas.count(); i++) {
        datas[i]->IsSelected = isSelected;
        index = this->index(i, 0);
        setData(index, state, Qt::CheckStateRole);
        emit rowCheckStateChanged(datas[index.row()]);
    }
}
QVariant KwTableModel::getData(const QModelIndex& index) const
{
    QVariant v = CommonUtil::GetPropertyValue(datas[index.row()]->Data, columns[index.column()]->FieldName);
    if (columns[index.column()]->GridColumnType == KwColumn::ComBox) {
        QVariant v1 = getItemSourceValue(columns[index.column()], v);
        return v1;
    }
    return v;
}
QVariant KwTableModel::getItemSourceValue(KwColumn* c, QVariant v) const
{
    for (QObject* source : c->ColumnSetting->ItemsSource) {
        QByteArray ba = c->ColumnSetting->ValueMember.toLatin1();
        const char* name = ba.data();
        QVariant v1 = source->property(name);

        if (v == v1) {
            QByteArray ba1 = c->ColumnSetting->DisplayMember.toLatin1();
            const char* name1 = ba1.data();
            return source->property(name1);
        }
    }
    return "";
}

4、自定义列头KwHeaderView

主要实现了复选框的显示,及全选和取消全选的逻辑

class KwHeaderView : public QHeaderView {
    Q_OBJECT
public:
    explicit KwHeaderView(Qt::Orientation orientation, QWidget* parent = nullptr);
    int checkColumn = 0;

protected:
    void paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const override;
    bool event(QEvent* e) override;
    void mousePressEvent(QMouseEvent* e) override;
    void mouseReleaseEvent(QMouseEvent* e) override;
public slots:
    void onCheckStateChanged(int state);
signals:
    void checkStateChanged(int state);

private:
    bool mPressed;
    bool mChecked;
    bool mTristate;
    bool mNoChange;
    bool mMoving;
};

cpp

#include "kwheaderview.h"

KwHeaderView::KwHeaderView(Qt::Orientation orientation, QWidget *parent) : QHeaderView(orientation,parent),mPressed(false),
    mChecked(false),mTristate(false),mNoChange(false),mMoving(false)
{
    setHighlightSections(false);
    setMouseTracking(false);
    setSectionsClickable(true);
}

void KwHeaderView::onCheckStateChanged(int state)
{
    if(state==Qt::PartiallyChecked)
    {
        mTristate = true;
        mNoChange = true;
    }
    else
    {
        mNoChange = false;
    }
    mChecked = state!=Qt::Unchecked;
    updateSection(checkColumn);
}

void KwHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
    painter->save();
    QHeaderView::paintSection(painter,rect,logicalIndex);
    painter->restore();
    if(logicalIndex==checkColumn)
    {
        QStyleOptionButton option;
        option.initFrom(this);
        if(mChecked)
        {
            option.state|=QStyle::State_Sunken;
        }
        if(mTristate&&mNoChange)
        {
            option.state|=QStyle::State_NoChange;
        }
        else
        {
            option.state|=mChecked?QStyle::State_On:QStyle::State_Off;
        }
        if(testAttribute(Qt::WA_Hover)&&underMouse())
        {
            if(mMoving)
            {
                option.state|=QStyle::State_MouseOver;
            }
            else
            {
                option.state&=~QStyle::State_MouseOver;
            }
        }
        QCheckBox box;
        option.rect = QRect(0,2,20,20);
        style()->drawPrimitive(QStyle::PE_IndicatorCheckBox,&option,painter,&box);
    }
}
void KwHeaderView::mousePressEvent(QMouseEvent *e)
{
    int c = logicalIndexAt(e->pos());
    if((e->buttons()&Qt::LeftButton)&&(c==checkColumn))
    {
        mPressed = true;
    }
    else
    {
        QHeaderView::mousePressEvent(e);
    }
}
void KwHeaderView::mouseReleaseEvent(QMouseEvent *e)
{
    if(mPressed)
    {
        if(mTristate&&mNoChange)
        {
            mChecked = true;
            mNoChange = false;
        }
        else
        {
            mChecked = !mChecked;
        }
        updateSection(checkColumn);
        Qt::CheckState state = mChecked ? Qt::Checked:Qt::Unchecked;
        emit checkStateChanged(state);
    }
    else
    {
        QHeaderView::mouseReleaseEvent(e);
    }
    mPressed = false;
}
bool KwHeaderView::event(QEvent *e)
{
    updateSection(0);
    if(e->type()==QEvent::Enter||e->type()==QEvent::Leave)
    {
        QMouseEvent*pEvent = static_cast<QMouseEvent*>(e);
        int c = logicalIndexAt(pEvent->x());
        if(c==checkColumn)
        {
            mMoving = (e->type()==QEvent::Enter);
            update();
            return true;
        }
    }
    return QHeaderView::event(e);
}

5、自定义KwTable

这里可设置属性

isDefaultSelected:是否默认选中(首次加载默认选中第一行,再次刷新默认选中上一次选中位置)

isShowCheckBoxSelectorColumn:是否显示表格复选框

load会重新加载列及数据源

selectedItemChanged选中行切换事件

class KwTableData {
public:
    QList<KwColumn*> columns;
    uint isDefaultSelected = true;
    uint isShowCheckBoxSelectorColumn = false;
    KwDataRow* selectedItem = Q_NULLPTR;
};

class KwTable : public QWidget {
    Q_OBJECT
public:
    Q_INVOKABLE KwTable(QWidget* parent);
    ~KwTable();

    void updataData(KwDataRow* row); //刷新行数据
    void updataData(KwDataRow* row, const QString fieldName); //刷新单元格数据
    void setColumnHidden(int column, bool hide);
    void refresh(); //重新加载数据源并刷新
    virtual void loadData();
    typedef std::function<QList<QObject*>()> loadDatafun;
    inline void loadDataSourceFun(loadDatafun fun) { LoadDataSource = std::move(fun); }
    void appendColumn(KwColumn* const& column);
    void clearColumn();
    inline KwDataRow* selectedItem() { return data->selectedItem; }
    inline QObject* selectedData() { return data->selectedItem ? data->selectedItem->Data : 0; }
    void load();
    inline const QList<KwDataRow*>& datas() { return model->datas; }

    void setIsDefaultSelected(bool v);
    void setIsShowCheckBoxSelectorColumn(bool v);
    void updateCheckState()
    {
        model->onCheckStateChanged();
        update();
    }

signals:
    void selectedItemChanged(KwDataRow* row);
    void selectedItemsCollectionChanged(KwDataRow* row);

private slots:
    void slotSelectedItemChanged(const QModelIndex& current);

private:
    QScopedPointer<KwTableData> data;
    loadDatafun LoadDataSource = Q_NULLPTR;
    QTableView* tableview;
    KwHeaderView* headerView;
    KwTableModel* model;

    inline QVariant getItemSourceValue(KwColumn* c, QVariant v);
    inline void defaultSelected(const QModelIndex index);
};

cpp

#include "kwtable.h"

KwTable::KwTable(QWidget* parent)
    : QWidget(parent)
{
    QVBoxLayout* box = new QVBoxLayout(this);
    tableview = new QTableView(this);
    model = new KwTableModel(this);
    headerView = new KwHeaderView(Qt::Horizontal, this);

    headerView->setSectionResizeMode(QHeaderView::Stretch); //设置表格列自适应充满
    //headerView->setSectionResizeMode(QHeaderView::ResizeToContents);//列自适应宽度
    //headerView->setSectionResizeMode(QHeaderView::Interactive);//自定义设置宽度

    tableview->setEditTriggers(QAbstractItemView::NoEditTriggers); //不可编辑
    tableview->setSelectionBehavior(QAbstractItemView::SelectRows); //选择行
    tableview->setSelectionMode(QAbstractItemView::SingleSelection); //单选
    tableview->setHorizontalHeader(headerView);
    tableview->setModel(model);
    data.reset(new KwTableData());
    connect(tableview->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &KwTable::slotSelectedItemChanged);
    connect(model, &KwTableModel::rowCheckStateChanged, this, [=](KwDataRow* row) {
        emit selectedItemsCollectionChanged(row);
    });
    box->addWidget(tableview);
    box->setMargin(0);
}
KwTable::~KwTable()
{
    qDeleteAll(data->columns);
    data->columns.clear();
}
void KwTable::setIsDefaultSelected(bool v)
{
    data->isDefaultSelected = v;
}
void KwTable::setIsShowCheckBoxSelectorColumn(bool v)
{
    data->isShowCheckBoxSelectorColumn = v;
}
void KwTable::appendColumn(KwColumn* const& column)
{
    data->columns.append(column);
}
void KwTable::clearColumn()
{
    qDeleteAll(data->columns);
    data->columns.clear();
}
void KwTable::load()
{
    if (data->isShowCheckBoxSelectorColumn) {
        connect(headerView, SIGNAL(checkStateChanged(int)), model, SLOT(onCheckStateChanged(int)));
        connect(model, SIGNAL(checkStateChanged(int)), headerView, SLOT(onCheckStateChanged(int)));
    } else {
        model->checkColumn = -1;
        headerView->checkColumn = -1;
    }
    model->setColumns(data->columns);
    refresh();
    for (int i = 0; i < data->columns.count(); i++) {
        model->setHeaderData(i, Qt::Horizontal, data->columns[i]->Header);
        if (!data->columns[i]->Visible) {
            tableview->setColumnHidden(i, true);
        }
        if (data->columns[i]->Width > 0) {
            tableview->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Interactive); //自定义设置宽度
            tableview->setColumnWidth(i, data->columns[i]->Width);
        }
    }
}
void KwTable::setColumnHidden(int column, bool hide)
{
    tableview->setColumnHidden(column, hide);
}
void KwTable::refresh()
{
    KwWait wait;
    wait.show();
    QModelIndex index = tableview->currentIndex();
    loadData();
    defaultSelected(index);
}
void KwTable::updataData(KwDataRow* row)
{
    int rowIndex = model->datas.indexOf(row);
    if (rowIndex < 0) {
        return;
    }
    emit model->dataChanged(model->index(rowIndex, 0), model->index(rowIndex, data->columns.size() - 1));
}
void KwTable::updataData(KwDataRow* row, const QString fieldName)
{
    int rowIndex = model->datas.indexOf(row);
    if (rowIndex < 0) {
        return;
    }
    for (int i = 0; i < data->columns.size(); i++) {
        if (data->columns[i]->FieldName == fieldName) {
            emit model->dataChanged(model->index(rowIndex, i), model->index(rowIndex, i));
        }
    }
}
inline void KwTable::defaultSelected(const QModelIndex index)
{
    if (model->rowCount() == 0) {
        data->selectedItem = NULL;
        return;
    }
    if (data->isDefaultSelected) {
        if (index.row() == -1) {
            tableview->setCurrentIndex(model->index(0, 0));
        } else if (index.row() >= 0 && model->rowCount() > index.row()) {
            tableview->setCurrentIndex(index);
        }
    }
}

void KwTable::loadData()
{
    model->removeRows(0, model->rowCount());
    qDeleteAll(model->datas);
    model->datas.clear();
    data->selectedItem = NULL;
    if (LoadDataSource == NULL) {
        return;
    }
    QList<QObject*> list = LoadDataSource();

    for (QObject* obj : list) {
        model->datas.append(new KwDataRow(obj));
    }
    model->updateData();
}

inline QVariant KwTable::getItemSourceValue(KwColumn* c, QVariant v)
{
    for (QObject* source : c->ColumnSetting->ItemsSource) {
        QByteArray ba = c->ColumnSetting->ValueMember.toLatin1();
        const char* name = ba.data();
        QVariant v1 = source->property(name);

        if (v == v1) {
            QByteArray ba1 = c->ColumnSetting->DisplayMember.toLatin1();
            const char* name1 = ba1.data();
            return source->property(name1);
        }
    }
    return "";
}

void KwTable::slotSelectedItemChanged(const QModelIndex& current)
{
    int row = current.row();
    if (row < 0) {
        return;
    }
    data->selectedItem = model->datas.at(row);
    emit selectedItemChanged(data->selectedItem);
}

二、使用方式

上面定义了一堆复杂类,则使用起来就要及其简单

例1:

MainWindow::MainWindow(QWidget* parent){
    Table = new KwTable(this); 
    Table->setIsShowCheckBoxSelectorColumn(true);   
    Table->appendColumn(new KwColumn("name", "名称", 200));
    Table->appendColumn(new KwColumn("code", "编码"));

    QList<QObject*> enumItems;
    enumItems.append(new EnumItem(0, tr("元器件")));
    enumItems.append(new EnumItem(1, tr("机械件")));
    enumItems.append(new EnumItem(2, tr("零件")));
    ComboBoxColumnSetting* set = new ComboBoxColumnSetting(enumItems, tr("intValue"), tr("description"));
    Table->appendColumn(new KwColumn("euNodeType", tr("类型"), -1, KwColumn::ComBox, set));
    Table->loadDataSourceFun(std::bind(&MainWindow::LoadGridDataSource, this));
    Table->load();

}

QList<QObject*> MainWindow::LoadGridDataSource(){
    QList<QObject*> result;
    //可以访问数据库,也可以从内存里面取
    for(int i = 0; i < 10; i++){
        RisaM_Part *part = new RisaM_Part();
        part->setName("name" + QString::number(i));
        part->setCode("code" + QString::number(i));
        part->setEuNodeType(i % 3);
        result.append(part);
    }
    return result;
}


总结

自定义封装KwTable组件主要为提高代码的复用性、可维护性和可扩展性,提高开发效率。组件封装是现代软件开发中的一种重要的开发方式和思想。

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值