目录
前言
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组件主要为提高代码的复用性、可维护性和可扩展性,提高开发效率。组件封装是现代软件开发中的一种重要的开发方式和思想。