自定义Model实现一个增删改查等功能的复用型表格QTableView

news2024/11/13 9:28:52

目录

一、表格功能总结与预览

1.功能总结

2.尚欠缺的功能(后续可能会补充)

二、功能设计选择原因

1.QTableWidget与MVC模式的QTableview的选择

2.处理数据的方式

所以后续将会选择第二种方式,直接操作数据库,并且通过数据库来管理同步数据,此Demo为未链接数据库版本

三、功能实现方法

注:头文件和源文件在一个代码框中注意分辨,有些代码可能无法全部剥离,还请理解功能读懂代码或者跳转到最后直接拿到完整Demo。

1.与QT自带的QTableWIdget不同可以承受大量的数据量(基于MVC模式的显示模式)

2.可以自定义数据的传入格式与数据操作,让数据和视图隔离(继承自QAbstractTableModel类的自定义model)

2.1 数据类

2.2 水平表头类

2.3 TableModel类

3.可自定义编辑时,单元格中的编辑效果以及表格背景间隔颜色(继承自QStyledItemDelegate类的自定义delegate)

4.可以控制那些鼠标事件和按键事件是否可用和点击效果(继承自QTableView类的自定义TableView)

5.新增

 6.删除

7.编辑

8.复制

9.查询或搜索(简单匹配版,遍历所有行)

10.上移和下移

11.第一列是否有复选框,选中行则复选框选中(表头为三态复选框)

总:以上的内容均由封装表格类UtilTableWidget融合MVC模式实现

四、完整Demo与总结

1.遇到的问题

1.1筛选model的使用问题

2.可以优化的地方

2.1数据库接入

2.2数据通用性优化

2.3数据修改方式优化

2.4搜索筛选方式优化

3.框架修改

4.Demo链接


一、表格功能总结与预览

1.功能总结

1.1与QT自带的QTableWIdget不同可以承受大量的数据量(基于MVC模式的显示模式)

1.2.可以自定义数据的传入格式与数据操作,让数据和视图隔离(继承自QAbstractTableModel类的自定义model)

1.3.可自定义编辑时,单元格中的编辑效果以及表格背景间隔颜色(继承自QStyledItemDelegate类的自定义delegate)

1.4.可以控制那些鼠标事件和按键事件是否可用和点击效果(继承自QTableView类的自定义TableView)

1.5.新增

1.6.删除

1.7.编辑

1.8.复制

1.9.查询或搜索(简单匹配版,遍历所有行)

1.10.上移和下移

1.11.第一列是否有复选框,选中行则复选框选中(表头为三态复选框)

2.尚欠缺的功能(后续可能会补充)

2.1.数据库中取出不同的表数据转换为规定格式塞入model的功能

2.2.表格更改内容与数据库同步功能,及sql语句连接

2.3.有可能需要恢复更改前的数据功能,所以可能需要备份一份数据能够恢复的功能

二、功能设计选择原因

1.QTableWidget与MVC模式的QTableview的选择

QT自带的QTableWidget与MVC模式中的自定义modelTableViewdelegate在功能和使用方式上有一些显著的不同。

首先,QTableWidget是QT对话框设计中常用的显示数据表格的控件。它继承自QTableView,但只能使用标准的数据模型。QTableWidget中的单元格数据是通过QTableWidgetItem对象来实现的,这意味着不需要数据源,单元格内的信息需要逐个填充。因此,QTableWidget在处理小型数据集或需要直接操作单元格内容的场景时非常有用。

相比之下,MVC模式(Model-View-Controller)是一种更灵活和可扩展的架构,用于实现大量的数据存储、处理及显示。在MVC模式中,model是应用对象,用来表示数据;view是模型的用户界面,用来显示数据;controller定义了用户界面对用户输入的反应方式。这种模式使得数据的表示、处理和显示可以分开管理,提高了代码的可维护性和可重用性。

在MVC模式的自定义实现中,model可以是任何自定义的数据模型,它负责处理数据的存储和逻辑。TableView作为视图组件,负责数据的展示。与QTableWidget不同,TableView本身不直接处理数据,而是通过绑定到model来获取和显示数据。这种方式使得数据的处理和展示更加灵活和可定制。

此外,delegate在MVC模式中扮演了重要角色。它用于定制数据的渲染和编辑方式。通过自定义delegate,你可以控制单元格的显示样式、编辑行为等。这与QTableWidget中直接操作单元格的方式有所不同,提供了更高级别的定制能力。

总结来说,QTableWidget适合简单的数据展示和编辑需求,而MVC模式的自定义modelTableViewdelegate则提供了更灵活、可扩展和可定制的数据处理、展示和编辑能力。在选择使用哪种方式时,需要根据具体的应用场景和需求进行权衡。

查询或搜索,可以从数据库直接查询,但是每次查询都要去访问数据库,会对性能有要求,而放入QSortFilterProxyModel进行筛选,则是操作已经从数据库读出来拷贝的一本显示数据,所以不会访问数据库

这里对代理和模型做了如下选择

a. 继承自QAbstractTableModel类的自定义model

b. 继承自QStyledItemDelegate类的自定义delegate

c. 继承自QTableView类的自定义TableView

d. 使用QT源生的QItemSelectionModel

2.处理数据的方式

在处理数据的时候,可以通过两种方式来处理数据的变化

        其一是通过获取选择的行传入自定义model中由model来处理(方便并且适用于有选择行的操作,并且不用刷新和拷贝数据,只是改变data但是有无法进行没办法获取行的情况,并且最后其实还是要和数据库的数据同步,无疑有一些绕弯路。)

        其二是统一处理传入自定义model中的data数据,而数据来源于数据库,相当于直接操作数据库(使得整个结构清晰,始终以数据库的数据为准,model只是取出数据显示,任何操作都会马上更新入数据库,实时性。在操作上,就需要有数据的唯一值进行匹配数据,找到数据的位置进行操作而不能根据行)

对数据库的操作,建立的表,最好是有主键的,这样才能方便查找提升性能

        数据库中的表可以没有主键。当表没有主键时,查找操作仍然可以执行,但可能会受到一些限制和影响。

        在数据库中,主键的主要作用是唯一标识表中的每一行数据,并确保数据的完整性和一致性。没有主键的表意味着没有这样的唯一标识符,这可能会导致一些潜在的问题,如数据重复或不一致。

        当表没有主键时,你仍然可以使用其他列进行查找。这通常涉及到在查询中使用WHERE子句来指定搜索条件。例如,你可以根据某个特定列的值来检索数据。

然而,没有主键的表在数据更新和删除方面可能会更加复杂和困难。由于没有唯一标识符来准确定位要更新的行或要删除的行,你可能需要依赖于其他列的组合或使用更复杂的查询逻辑来实现这些操作。

        此外,没有主键的表在性能上也可能会受到一定的影响。主键通常与索引相关联,这有助于加速查询操作。没有主键的表可能无法充分利用索引的优势,从而导致查询性能下降。

因此,虽然数据库中的表可以没有主键,但在设计数据库时,建议尽可能为每个表指定一个主键。主键不仅有助于确保数据的完整性和一致性,还能简化数据更新、删除和查询操作,并提高性能。

所以后续将会选择第二种方式,直接操作数据库,并且通过数据库来管理同步数据,此Demo为未链接数据库版本

三、功能实现方法

注:头文件和源文件在一个代码框中注意分辨,有些代码可能无法全部剥离,还请理解功能读懂代码或者跳转到最后直接拿到完整Demo。

1.与QT自带的QTableWIdget不同可以承受大量的数据量(基于MVC模式的显示模式)

QTableview有setModel和SetItemDelegate的方法,就可以很简单的完成这一模式的搭建,需要的是实现功能而重写不同的类继承自那些默认的类,下面三个就是继承了不同的QT类重写的。

    m_tableview->setModel(m_tablemodel);
    m_selectionModel = m_tableview->selectionModel();
    m_tableview->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    m_tableview->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    m_tableview->setItemDelegate(m_tableDelegate);
    m_tableview->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    m_tableview->verticalHeader()->setVisible(false);
    m_tableview->setAlternatingRowColors(true);
    m_tableview->setSelectionBehavior(QAbstractItemView::SelectRows);

2.可以自定义数据的传入格式与数据操作,让数据和视图隔离(继承自QAbstractTableModel类的自定义model)

2.1 数据类
//后续数据的传入,可以通过模版来控制
//template<typename T>
class TableData
{
//   explicit TableData(const int& column){
//        m_column = column;
//    }
public:
    //第一列的复选框参数
    bool m_bChecked;
    //数据,后期数据可以通过模板数据类或别的结构进来
    QVector<QString> m_data;
};
2.2 水平表头类
class TableHeaderView:public QHeaderView
{
    Q_OBJECT
public:
    TableHeaderView(Qt::Orientation orientation, QWidget *parent);
    ~TableHeaderView();

public:
    void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const;
    bool event(QEvent *e) Q_DECL_OVERRIDE;
    void mousePressEvent(QMouseEvent *e) Q_DECL_OVERRIDE;
    void mouseReleaseEvent(QMouseEvent *e) Q_DECL_OVERRIDE;
public slots:
    void onStateChanged(int state);
signals:
    void stateChanged(int);

public:
    bool m_bPressed;
    bool m_bChecked;
    bool m_bTristate;
    bool m_bNoChange;
    bool m_bMoving;
};





TableHeaderView::TableHeaderView(Qt::Orientation orientation, QWidget *parent)
    : QHeaderView(orientation, parent),
      m_bPressed(false),
      m_bChecked(false),
      m_bTristate(false),
      m_bNoChange(false),
      m_bMoving(false)
{
    setSectionsClickable(true);
}

TableHeaderView::~TableHeaderView()
{

}

// 槽函数,用于更新复选框状态
void TableHeaderView::onStateChanged(int state)
{
//    qDebug() << " TableHeaderView::onStateChanged" ;
    if (state == Qt::PartiallyChecked)
    {
        m_bTristate = true;
        m_bNoChange = true;
    }
    else
    {
        m_bNoChange = false;
    }
    m_bChecked = (state != Qt::Unchecked);
    update();
}



// 绘制复选框
void TableHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
    painter->save();
    QHeaderView::paintSection(painter, rect, logicalIndex);
    painter->restore();
    if (logicalIndex == CHECK_BOX_COLUMN)
    {
        QStyleOptionButton option;
        option.initFrom(this);

        if (m_bChecked)
            option.state |= QStyle::State_Sunken;

        if (m_bTristate && m_bNoChange)
            option.state |= QStyle::State_NoChange;
        else
            option.state |= m_bChecked ? QStyle::State_On : QStyle::State_Off;
        if (testAttribute(Qt::WA_Hover) && underMouse()) {
            if (m_bMoving)
                option.state |= QStyle::State_MouseOver;
            else
                option.state &= ~QStyle::State_MouseOver;
        }

        QCheckBox checkBox;
        option.rect = QRect(4,5,15,15);//绘制复选框的位置与大小
        style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter, &checkBox);
    }
}

// 鼠标按下表头
void TableHeaderView::mousePressEvent(QMouseEvent *event)
{
    int nColumn = logicalIndexAt(event->pos());
    if ((event->buttons() & Qt::LeftButton) && (nColumn == CHECK_BOX_COLUMN))
    {
        m_bPressed = true;
    }
    else
    {
        QHeaderView::mousePressEvent(event);
    }
}

// 鼠标从表头释放,发送信号,更新model数据
void TableHeaderView::mouseReleaseEvent(QMouseEvent *event)
{
    if (m_bPressed)
    {
        if (m_bTristate && m_bNoChange)
        {
            m_bChecked = true;
            m_bNoChange = false;
        }
        else
        {
            m_bChecked = !m_bChecked;
        }

        update();

        Qt::CheckState state = m_bChecked ? Qt::Checked : Qt::Unchecked;
        emit stateChanged(state);
    }
    else
    {
        QHeaderView::mouseReleaseEvent(event);
    }

    m_bPressed = false;
}



// 鼠标滑过、离开,更新复选框状态
bool TableHeaderView::event(QEvent *event)
{
    updateSection(0);
    if (event->type() == QEvent::Enter || event->type() == QEvent::Leave)
    {
        QMouseEvent *pEvent = static_cast<QMouseEvent *>(event);
        int nColumn = logicalIndexAt(pEvent->x());
        if (nColumn == CHECK_BOX_COLUMN)
        {
            m_bMoving = (event->type() == QEvent::Enter);
            update();
            return true;
        }
    }
    return QHeaderView::event(event);
}
2.3 TableModel类
#define CHECK_BOX_COLUMN 0


class TableModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    explicit TableModel(QObject *parent = nullptr);

    // 实现QAbstractTableModel的虚函数
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    //插入行
    bool insertRows(int row, int count, const QModelIndex &parent) override;
    //删除行
    bool removeRows(int row, int count, const QModelIndex &parent) override;
//    bool setTableData(const QVector<QVector<QString>>& data);
    //给model传入data
    bool setTableData(const QVector<TableData>& data);
    //给model传入表头
    void setTableHeaderData(const QStringList& headerData);
    //设置第一列是否为checkbox
    void setFirstColumnCheckBox(bool state);
    //设置某行的checkbox选中状态
    void setCheckBoxSelect(const int& row, bool state);
    //设置表格是否可以编辑
    void setTableisEditable(bool editable);
    void onStateChanged();
    //插入行
    bool insertRowData(int row, int count, const QModelIndex &parent, TableData data);
    //更新指定行数据
    void updateData(int row, TableData data);
    //获取一行的数据
    TableData getOneRowData(int row);
    //获取所有数据
    QVector<TableData> getALLRowData();
    //上移一行
    void moveRowUp(int row);
    //下移一行
    void moveRowDown(int row);
public slots:
    void onStateChanged(int state);
signals:
    void stateChanged(int);

    void signalRowChecked(int row, bool bchecked);

    void signalSelectAll();

    void signalDeSelectALL();
private:
    QStringList m_headerData;
    bool m_bEnableCheckBox;
    bool m_bEditable;
    int m_nfirstcolumn;
//    QList<bool> m_bCheckedlist;
//    QVector<QVector<QString>> m_data; // 表格数据
    QVector<TableData> m_tabledata; //传入的表格数据模版不同格式
};





TableModel::TableModel(QObject *parent) : QAbstractTableModel(parent)
{
    m_nfirstcolumn = 0;
    m_bEnableCheckBox = false;
    m_bEditable = false;
}

int TableModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    //    return m_data.size();
    return m_tabledata.size();
}

int TableModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    //    return m_data.isEmpty() ? 0 : m_data.first().size()+m_nfirstcolumn;
    return m_tabledata.isEmpty() ? 0 : m_tabledata.first().m_data.size() + m_nfirstcolumn;
}

QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{

    if(orientation != Qt::Horizontal)
        return QVariant("only support horizontal");

    if(role != Qt::DisplayRole)
        return QVariant();

    switch (role)
    {
    case Qt::TextAlignmentRole:
        return QVariant(Qt::AlignCenter | Qt::AlignVCenter);// 表头内容位置
    case Qt::DisplayRole:
    {
        if (orientation == Qt::Horizontal)
        {
            if (m_bEnableCheckBox && section == CHECK_BOX_COLUMN)
                return QStringLiteral("");
            //                return QStringLiteral("全选");

            if(section-m_nfirstcolumn >= m_headerData.size())
                return QVariant("NoName");

            return m_headerData.at(section-m_nfirstcolumn);
        }
    }
    default:
        return QVariant();
    }

    return QVariant();
}

QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    int nRow = index.row();
    int nColumn = index.column();
    //    bool bChecked = m_bCheckedlist.at(nRow);
    bool bChecked = m_tabledata.at(nRow).m_bChecked;

    switch (role)
    {
    case Qt::TextColorRole:
        return QColor(Qt::white);
    case Qt::TextAlignmentRole:
        return QVariant(Qt::AlignCenter | Qt::AlignVCenter);// 表格数据内容位置
    case Qt::DisplayRole:
    {
        if (m_bEnableCheckBox && nColumn == CHECK_BOX_COLUMN)
            return QVariant();
        if(nColumn != CHECK_BOX_COLUMN)
        {
            //            qDebug() << "TableModel::data" << m_data[index.row()][index.column()-m_nfirstcolumn];
            //            return m_data[index.row()][index.column()-m_nfirstcolumn];
//            qDebug() << "TableModel::data" << m_tabledata[index.row()].m_data[index.column()-m_nfirstcolumn];
            return m_tabledata[index.row()].m_data[index.column()-m_nfirstcolumn];
        }
    }
    case Qt::CheckStateRole:
    {
        if (m_bEnableCheckBox && nColumn == CHECK_BOX_COLUMN)
            return bChecked ? Qt::Checked : Qt::Unchecked;
    }
    default:
        return QVariant();
    }

    return QVariant();
}

bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid())
        return false;
    int nColumn = index.column();
    TableData tabledata = m_tabledata.at(index.row());
    switch(role)
    {
    case Qt::EditRole:
    {
        qDebug() << "Qt::EditRole";
        // 更新内部数据结构
        if(nColumn != CHECK_BOX_COLUMN)
        {
            qDebug() << "Qt::EditRole" << value;
            tabledata.m_data[index.column()-m_nfirstcolumn] = value.toString();
            m_tabledata.replace(index.row(), tabledata);
            emit dataChanged(index, index);
            return true;
        }
    }
    case Qt::DisplayRole:
    {
        if(nColumn != CHECK_BOX_COLUMN)
        {
            //m_data[index.row()][index.column()-m_nfirstcolumn] = value.toString();;
            tabledata.m_data[index.column()-m_nfirstcolumn] = value.toString();
            emit dataChanged(index, index);
            return true;
        }
    }
    case Qt::CheckStateRole:
    {
        if(m_bEnableCheckBox && nColumn == CHECK_BOX_COLUMN)
        {
            //            bool bchecked = (value.toInt() == Qt::Checked);
            //            qDebug() << "case Qt::CheckStateRole:" << bchecked;
            tabledata.m_bChecked = (value.toInt() == Qt::Checked);
            m_tabledata.replace(index.row(), tabledata);
            emit dataChanged(index, index);
            qDebug() << "di" << index.row()+1<< "hang" << "checkboxstatus:" << m_tabledata.at(index.row()).m_bChecked;
            emit signalRowChecked(index.row(), tabledata.m_bChecked);
            onStateChanged();
            return true;
        }
    }
    default:
        return false;
    }
    return false;

}

Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
    //    if (!index.isValid())
    //        return Qt::NoItemFlags;

    //    return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
    if (!index.isValid())
        return QAbstractItemModel::flags(index);

    Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
    if(!m_bEditable)
    {
        flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
    }

    if (m_bEnableCheckBox && index.column() == CHECK_BOX_COLUMN)
        flags |= Qt::ItemIsUserCheckable;

    return flags;
}

bool TableModel::insertRows(int row, int count, const QModelIndex &parent)
{
    //如果插入零行,则返回false,表示插入失败
    if(count == 0) return false;

    //没有父类
    if(!parent.isValid())
    {
        //从row开始插入行, 知道row + count -1 处
        beginInsertRows(QModelIndex(), row, row + count - 1);
    }else{
        //
        beginInsertRows(parent, row, row + count - 1);
    }
    //对model中存储的数据做相应的修改
    for(int addCount = 0; addCount < count; addCount++)
    {
        TableData emptydata;
        emptydata.m_bChecked = false;
        m_tabledata.insert(row + addCount, emptydata);
    }

    endInsertRows();

    emit layoutChanged();

    return true;
}

bool TableModel::removeRows(int row, int count, const QModelIndex &parent)
{
    //如果删除零行,则返回false,表示删除行失败
    if(count == 0) return false;

    //没有父类
    if(!parent.isValid())
    {
        //从row开始删除行, 知道row + count -1 处
        beginRemoveRows(QModelIndex(), row, row + count - 1);
    }else{
        //
        beginRemoveRows(parent, row, row + count - 1);
    }

    //对model中存储的数据做相应的修改
    for(int removeCount = 0; removeCount < count; removeCount++)
    {
        m_tabledata.removeAt(row + removeCount);
    }

    endRemoveRows();

    emit layoutChanged();

    return true;

}


//bool TableModel::setTableData(const QVector<QVector<QString> > &data)
//{
//    //checkdata
//    m_data = data;
//    for(int i = 0; i < m_data.size(); i++)
//    {
//        m_bCheckedlist.append(false);
//    }
//    return 0;
//}

bool TableModel::setTableData(const QVector<TableData> &data)
{
    //checkdata
    beginResetModel();
    m_tabledata = data;
    endResetModel();
    return 0;
}

void TableModel::setTableHeaderData(const QStringList &headerData)
{
    m_headerData = headerData;
}

void TableModel::setFirstColumnCheckBox(bool state)
{
    if(state)
    {
        m_nfirstcolumn = 1;
    }
    else
    {
        m_nfirstcolumn = 0;
    }
    m_bEnableCheckBox = state;
}

void TableModel::setCheckBoxSelect(const int &row, bool state)
{
    TableData data = m_tabledata.at(row);
    data.m_bChecked = state;
    updateData(row, data);
    onStateChanged();
}

void TableModel::setTableisEditable(bool editable)
{
    m_bEditable = editable;
}

void TableModel::onStateChanged()
{
    int checked = 0, unchecked = 0;
    //    foreach (bool bChecked, m_bCheckedlist) {
    //    if(bChecked)
    //        checked++;
    //    else
    //        unchecked++;
    //}
    //if(checked == m_bCheckedlist.count())
    //    emit stateChanged(Qt::Checked);
    //else if(unchecked == m_bCheckedlist.count())
    //    emit stateChanged(Qt::Unchecked);
    //else
    //    emit stateChanged(Qt::PartiallyChecked);
    foreach (TableData oneRowTabledata, m_tabledata) {
        if(oneRowTabledata.m_bChecked)
            checked++;
        else
            unchecked++;
    }
    if(checked == m_tabledata.count())
        emit stateChanged(Qt::Checked);
    else if(unchecked == m_tabledata.count())
        emit stateChanged(Qt::Unchecked);
    else
        emit stateChanged(Qt::PartiallyChecked);
}

bool TableModel::insertRowData(int row, int count, const QModelIndex &parent, TableData data)
{
    //如果插入零行,则返回false,表示插入失败
    if(count == 0) return false;
//    if(count != data.count())
//    {
//        qDebug() << "插入的行数与数据不匹配";
//        return false;
//    }

    //没有父类
    if(!parent.isValid())
    {
        //从row开始插入行, 知道row + count -1 处
        beginInsertRows(QModelIndex(), row, row + count - 1);
    }else{
        //
        beginInsertRows(parent, row, row + count - 1);
    }
    //对model中存储的数据做相应的修改
    for(int addCount = 0; addCount < count; addCount++)
    {
        m_tabledata.insert(row + addCount, data);
        for (int i = 0; i < m_tabledata.size(); i++) {
            qDebug() << "insertRowData.m_tabledata.size()" << i << m_tabledata.size() << m_tabledata.at(i).m_data;
        }
    }

    endInsertRows();

    emit layoutChanged();

    return true;
}

void TableModel::updateData(int row, TableData data)
{
    if(row >= 0 && row < m_tabledata.size())
    {
        m_tabledata.replace(row, data);

        //发送数据变化信号,通知视图更新
        QModelIndex topLeft = createIndex(row, 0);
        QModelIndex bottomRight = createIndex(row, columnCount() - 1);
        emit dataChanged(topLeft, bottomRight);
    }
}

TableData TableModel::getOneRowData(int row)
{
    return m_tabledata.at(row);
}

QVector<TableData> TableModel::getALLRowData()
{
    return m_tabledata;
}

void TableModel::moveRowUp(int row)
{
    if(row >= 0 && row < m_tabledata.size())
    {
        beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1);
        std::swap(m_tabledata[row], m_tabledata[row - 1]);
        endMoveRows();
    }
}

void TableModel::moveRowDown(int row)
{
    if(row >= 0 && row < m_tabledata.size() - 1)
    {
        beginMoveRows(QModelIndex(), row + 1, row + 1, QModelIndex(), row);
        std::swap(m_tabledata[row], m_tabledata[row + 1]);
        endMoveRows();
    }
}


void TableModel::onStateChanged(int state)
{
//    qDebug() << "TableModel::onStateChanged";
    state==Qt::Checked?Qt::Checked:Qt::Unchecked;//判断全选是选中还是未选中
    if(state == Qt::Checked)
    {
        emit signalSelectAll();
    }else if(state == Qt::Unchecked)
    {
        emit signalDeSelectALL();
    }

    QModelIndex index;
    //    for (int i = 0; i < m_bCheckedlist.count(); ++i)
    //    {
    //        index = this->index(i, 0);
    //        setData(index, state, Qt::CheckStateRole);//使用自己重写的setData更新状态
    //    }
    for (int i = 0; i < m_tabledata.count(); ++i)
    {
        index = this->index(i, 0);
        setData(index, state, Qt::CheckStateRole);//使用自己重写的setData更新状态
    }
}

3.可自定义编辑时,单元格中的编辑效果以及表格背景间隔颜色(继承自QStyledItemDelegate类的自定义delegate)

TableDelegate::TableDelegate(QObject *parent) : QStyledItemDelegate(parent) {

}

QWidget * TableDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                      const QModelIndex &index) const {
    QLineEdit *editor = new QLineEdit(parent);
    editor->setText(index.model()->data(index, Qt::EditRole).toString());
    return editor;
}

void TableDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const {
    if (QLineEdit *lineEdit = qobject_cast<QLineEdit*>(editor)) {
        lineEdit->setText(index.data(Qt::EditRole).toString());
    } else if (QComboBox *comboBox = qobject_cast<QComboBox*>(editor)) {
        QString currentText = index.data(Qt::EditRole).toString();
        int index = comboBox->findText(currentText);
        comboBox->setCurrentIndex(index);
    } else {
        QStyledItemDelegate::setEditorData(editor, index);
    }
}

void TableDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                  const QModelIndex &index) const {
    QString value = qobject_cast<QLineEdit*>(editor)->text();
    model->setData(index, value, Qt::EditRole);
}

void TableDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const {
    editor->setGeometry(option.rect);
}

void TableDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if (index.row() % 2 == 0) {
        painter->fillRect(option.rect, QColor(83,83,83));
    } else {
        painter->fillRect(option.rect, QColor(66,66,66));
    }
    QStyledItemDelegate::paint(painter, option, index);
}

4.可以控制那些鼠标事件和按键事件是否可用和点击效果(继承自QTableView类的自定义TableView)

#include <QTableView>
#include <QMouseEvent>
#include <QKeyEvent>

class TableView : public QTableView {
    Q_OBJECT

public:
    TableView(QWidget *parent = nullptr) : QTableView(parent) {}

protected:
    void mousePressEvent(QMouseEvent *event) override {
        // 禁用某些鼠标事件,例如左键点击
//        if (event->button() == Qt::LeftButton) {
//            event->ignore();
//            return;
//        }
        // 禁用 Shift + 鼠标左键等组合键
        if ((event->modifiers() == Qt::ShiftModifier) && (event->button() == Qt::LeftButton)) {
            event->ignore();
            return;
        }
        QTableView::mousePressEvent(event);
    }

    void mouseMoveEvent(QMouseEvent *event) override {
        // 禁用鼠标移动事件导致的选中
        event->ignore();
    }

    void keyPressEvent(QKeyEvent *event) override {
        // 禁用 Shift + 鼠标左键等组合键
//        if ((event->modifiers() == Qt::ShiftModifier) && (event->button() == Qt::LeftButton)) {
//            event->ignore();
//            return;
//        }
        QTableView::keyPressEvent(event);
    }
};

5.新增

bool UtilTableWidget::insertTableRows(const int& insertRow, const TableData& newRowData)
{
    QModelIndex insertindex = m_tablemodel->index(insertRow, 0);
    bool insertresult = m_tablemodel->insertRowData(insertRow, 1, insertindex, newRowData);
//    m_selectionModel->clearSelection();
    selectRow(insertRow);
    m_tableview->scrollTo(insertindex);
    return insertresult;
}

 6.删除

bool UtilTableWidget::deleteTableRows(const QList<int> &deleterows)
{
    bool deleteresult = true;
    QList<int> sortdeleterows = deleterows;
    //删除行的时候要从大往小的编号进行删除,因为删除一行就会更新表格数据
    for(int i = sortdeleterows.size() - 1; i >= 0; --i)
    {
        int deleterow = sortdeleterows[i];
        if(deleterow < 0 || deleterow >= m_tablemodel->rowCount())
            continue;

        qDebug() << "删除的行: " << deleterows[i];
        QModelIndex deleteindex = m_tablemodel->index(deleterow, 0);
        if(!m_tablemodel->removeRows(deleterow, 1, deleteindex))
        {
            deleteresult = false;
        }

        //调整后续行的索引,防止还有更大的删除行,就需要进行调整
        for(int j = i + 1; j < sortdeleterows.size(); ++j)
        {
            if(sortdeleterows[j] > deleterow)
                sortdeleterows[j]--;
        }
    }
//    m_selectionModel = m_tableview->selectionModel();
    clearAllSelectedRows();
    return deleteresult;
}

7.编辑

void UtilTableWidget::editTableRow(const int &editRow, const TableData &editRowData)
{
    m_tablemodel->updateData(editRow, editRowData);
}

8.复制

bool UtilTableWidget::copyTableRows(const QList<int> &copyrows)
{
    clearAllSelectedRows();
    bool copyresult = true;
    //需要先将所有需要复制的数据拷贝一份
    QVector<TableData> vecCopydata;
    for(int i = 0; i < copyrows.size(); i++)
    {
        vecCopydata.append(m_tablemodel->getOneRowData(copyrows.at(i)));
    }
    //开始插入
    for(int i = 0; i < copyrows.size(); i++)
    {
        qDebug() << "need copy row:" << copyrows.at(i);
        if(!insertTableRows((copyrows.at(i) + i + 1), vecCopydata.at(i)))
        {
            copyresult = false;
        }
    }
    return copyresult;
}

9.查询或搜索(简单匹配版,遍历所有行)

void UtilTableWidget::sreachTableRows(const QString &searchText)
{
    if(firstsreach)
    {
        // 先保存一份之前的所有行数据,转为读数据库的方式就不需要了,可以从数据库重新拉数据
        m_oldData = m_tablemodel->getALLRowData();
        firstsreach = false;
    }

    if(searchText == NULL)
    {
        m_tablemodel->setTableData(m_oldData);
        firstsreach = true;
        return;
    }

    int rowCount = m_tablemodel->rowCount();
    QList<int> matchingRows; // 存储匹配行的索引
    QVector<TableData> tempsreachRow;
    // 遍历所有行来查找匹配项
    for (int row = 0; row < rowCount; ++row) {
        bool isMatch = false;
        for (int col = 0; col < m_tablemodel->columnCount(); ++col) {
            QModelIndex index = m_tablemodel->index(row, col);
            QString cellText = m_tablemodel->data(index, Qt::DisplayRole).toString();
            qDebug() << "hideNonMatchingRows" << row << searchText << cellText;
            if (cellText.contains(searchText, Qt::CaseInsensitive)) {
                isMatch = true;
                break; // 找到匹配项,跳出内层循环
            }
        }
        if (isMatch) {
            qDebug() << "匹配的行为:" << row;
            matchingRows.append(row); // 添加匹配行的索引到列表
            tempsreachRow.append(m_tablemodel->getOneRowData(row)); //取出所有匹配行的数据暂存
        }
    }

    //
    m_tablemodel->setTableData(tempsreachRow);

    // 更新视图以反映变化
    m_tableview->viewport()->update();
}

10.上移和下移

void UtilTableWidget::moveTableRowUp(int row)
{
    clearAllSelectedRows();
    m_tablemodel->moveRowUp(row);
    selectRow(row - 1);
}

void UtilTableWidget::moveTableRowDown(int row)
{
    clearAllSelectedRows();
    m_tablemodel->moveRowDown(row);
    selectRow(row + 1);
}

11.第一列是否有复选框,选中行则复选框选中(表头为三态复选框)

利用2.2提到的TableHeaderView实现水平表头的复选框,然后利用21提到的数据结构将第一列设置为复选框,然后连接选择的槽函数实现

    connect(m_tablemodel, &TableModel::signalRowChecked, this, &UtilTableWidget::onSelectRow);
    connect(m_selectionModel, &QItemSelectionModel::selectionChanged, this, &UtilTableWidget::onselectionModelChangeSelection);
    connect(m_tablemodel, &TableModel::signalSelectAll, this, &UtilTableWidget::onSelectALLRows);
    connect(m_tablemodel, &TableModel::signalDeSelectALL, this, &UtilTableWidget::onDeSelectALLRows);
void UtilTableWidget::enableCheckBox(bool state)
{
    // 设置垂直表头代理
    //    m_headerview = new SCheckBoxHeaderView(10, Qt::Horizontal, m_tableview);
    //    m_tableview->setHorizontalHeader(m_headerview);
    //    connect(m_headerview, &SCheckBoxHeaderView::checkStausChange, this, [=](bool status){
    //        //控制所有目标的显示与隐藏
    //        for (int i = 0; i < m_tablemodel->rowCount(); i++) {
    //            QModelIndex index = m_tablemodel->index(i, 0);
    //            m_tablemodel->setData(index, status, Qt::CheckStateRole);
    //        }
    //    });
    m_headerview = new TableHeaderView(Qt::Horizontal, this);
    // 设置表头
    m_tableview->setHorizontalHeader(m_headerview);
    m_tablemodel->setFirstColumnCheckBox(state);
    // 关联表头复选框与第一列复选框
    connect(m_headerview, SIGNAL(stateChanged(int)), m_tablemodel, SLOT(onStateChanged(int)));
    connect(m_tablemodel, SIGNAL(stateChanged(int)), m_headerview, SLOT(onStateChanged(int)));
}

总:以上的内容均由封装表格类UtilTableWidget融合MVC模式实现

#include <QWidget>
#include <QTableView>
#include <QStandardItemModel>
#include <QHeaderView>
#include <QDebug>
#include <QVBoxLayout>
#include <QCheckBox>
#include <QMouseEvent>
#include <QSortFilterProxyModel>
#include <QMessageBox>
#include <QItemSelectionModel>
#include <QModelIndex>
#include <QModelIndexList>
#include "tablemodel.h"
#include "tabledelegate.h"
#include "tableview.h"

class UtilTableWidget : public QWidget
{
    Q_OBJECT

public:
    explicit UtilTableWidget(QWidget *parent = 0);
    ~UtilTableWidget();

    void setupTableWidget();
    void setTableHeader(const QStringList& horizontalHeader);
    void setTableData(const QVector<TableData>& data);
    QTableView *gettableView();
    //是否启动第一列为checkbox
    void enableCheckBox(bool state);
    //是否启用编辑
    void enableEditable(bool state);
    int getTableViewRow();
    int getTableViewColumn();
    //获取某行数据
    TableData getTableRowData(int row);
    //获取表格所有数据
    QVector<TableData> getTableAllData();
    //新增
    void addTableData();
    //插入
    bool insertTableRows(const int& insertRow, const TableData& newRowData);
    //删除
    bool deleteTableRows(const QList<int>& deleterows);
    //编辑
    void editTableRow(const int& editRow, const TableData& editRowData);
    //复制
    bool copyTableRows(const QList<int>& copyrows);
    //上移
    void moveTableRowUp(int row);
    //下移
    void moveTableRowDown(int row);
    //取消

    //清除选中行
    void clearAllSelectedRows();
    //获取选中的行
    QList<int> getSelectedRows();
    //选中一行
    void selectRow(const int& row);
    //取消选中一行
    void deSelectRow(const int& row);
    //选中多行
    void selectRows(const QList<int>& rows);
    //根据某行是否选中更新复选框是否选中
    void updateCheckboxbySelectRows();

    //搜索
    void sreachTableRows(const QString &searchText);

public slots:
    void onFilterChanged(const QString& text);
    void onSelectRow(int row, bool bchecked);
    void onSelectALLRows();
    void onDeSelectALLRows();
    void onselectionModelChangeSelection(const QItemSelection &selected, const QItemSelection &deselected);
    void onCurrentSelectRowChange(const QModelIndex &current, const QModelIndex &previous);
private:
    TableView *m_tableview;
    TableModel *m_tablemodel;
    QSortFilterProxyModel *m_proxyModel;
    QItemSelectionModel *m_selectionModel;
    TableDelegate *m_tableDelegate;
    TableHeaderView *m_headerview;
    QList<int> m_listSelectRows;
    QVector<TableData>  m_oldData;
    bool firstsreach = true;
};

四、完整Demo与总结

1.遇到的问题

1.1筛选model的使用问题
//    m_proxyModel = new QSortFilterProxyModel(this);
//    m_proxyModel->setSourceModel(m_tablemodel);
//    m_tableview->setModel(m_proxyModel);

2.可以优化的地方

可以优化的地方很多

2.1数据库接入

现在的demo中只写了一个概念类,是怎么实现数据库访问和转为表格展示数据的,后续我会进行优化

2.2数据通用性优化

就是如果是不同的结构体的表格数据,应该可以利用模版的形式来完成这样的转化,不然多一种表格数据,就需要多出一个转化为对应结构体的函数重写,这方面应该是可以提升的。

2.3数据修改方式优化

不再直接操作TableModel中的数据,而是直接操作数据库,但由于每一个操作都是操作数据库,所以需要放入线程中防止卡界面,此时需要数据表中有主键,不能再根据行来操作数据。

而TableModel则不再有增删改查对Model中数据的修改,而只有设置整体数据的接口。

2.4搜索筛选方式优化

QSortFilterProxyModel实现搜索,可以重写控制筛选搜索条件等这个方法应该启用,并解决索引问题,甚至可以根据数据库来筛选等优化方向

3.框架修改

 

4.Demo链接

百度网盘:

http://链接:https://pan.baidu.com/s/1HVplXOWVnuar3oalqd6k2A?pwd=7j9r 提取码:7j9r

这个表格本作者还是想把它做得更加复用化,功能更加强大,并且性能能承受大数据量的压力,虽然还有很多不足,但是希望大家能谅解本作者的水平有限,后续会根据框架修改和制作自定义Model实现一个增删改查等功能的复用型表格TableView——链接数据库版,跟大家互相学习了,有问题可以在下方留言讨论,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1516347.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

公众号怎么转移主体

公众号迁移有什么作用&#xff1f;只能变更主体吗&#xff1f;长期以来&#xff0c;由于部分公众号在注册时&#xff0c;主体不准确的历史原因&#xff0c;或者公众号主体发生合并、分立或业务调整等现实状况&#xff0c;在公众号登记主体不能对应实际运营人的情况下&#xff0…

蓝桥杯前端Web赛道-水果消消乐

蓝桥杯前端Web赛道-水果消消乐 题目链接&#xff1a;2.水果消消乐 - 蓝桥云课 (lanqiao.cn) 题目要求可查看题目文件夹中的effect.gif文件。 下面开始分析题目需要我们做什么 首先显而易见的是&#xff0c;当点到相同的水果的时候&#xff0c;对于的格子消失&#xff1b;如…

论文阅读——Align before Fuse

Align before Fuse: Vision and Language Representation Learning with Momentum Distillation image-text contrastive learning(ITC)用在单模态&#xff0c;masked language modeling (MLM) and image-text matching (ITM) 用在多模态。 单模态编码器的表示上引入了中间图像…

在dpvs上实现ICMP的源进源出

目录 1. 缘起2. 源码分析3. 让ICMP也走源进源出1. 缘起 在网络通信中,当一个请求报文从源主机到达目标主机,并经过中间路由器或交换机进行转发时,请求报文进入主机A的路径和响应报文离开主机A的路径可能不同。这种情况下,就会出现所谓的三角路径问题。如下图: 具体来说,…

精通Linux的磁盘分区:从理论到实战

前言 在这篇技术文章中&#xff0c;我详细介绍了Linux磁盘分区的理论知识&#xff0c;并结合实际示例演示了如何在CentOS 7系统上使用fdisk工具进行磁盘分区和删除操作。无论您是新手还是有一定经验的用户&#xff0c;本文都将帮助您轻松掌握磁盘分区的关键概念和操作技巧。如…

C++:vector类

vector的介绍及使用 1. vector 是表示可变大小数组的序列容器。 2. 就像数组一样&#xff0c; vector 也采用的连续存储空间来存储元素。也就是意味着可以采用下标对 vector 的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的…

Linux信号灯

概念&#xff1a;是不同进程间或一个给定进程内部不同线程间同步的机制。类似我们的 PV操作概念&#xff1a; 生产者和消费者场景 &#xff30;(&#xff33;) 含义如下:if (信号量的值大于0) { 申请资源的任务继续运行&#xff1b;信号量的值减一&#xff1b;} else { 申…

同态滤波算法详解

同态滤波是一种用于增强图像的方法&#xff0c;特别适用于去除图像中的照明不均和阴影。该算法基于照射反射模型&#xff0c;将图像分解为两个分量&#xff1a;照射分量&#xff08;illumination component&#xff09;和反射分量&#xff08;reflection component&#xff09;…

腾讯云服务器地域是什么?地域选择看着一篇就够了

腾讯云服务器地域怎么选择&#xff1f;不同地域之间有什么区别&#xff1f;腾讯云哪个地域好&#xff1f;地域选择遵循就近原则&#xff0c;访客距离地域越近网络延迟越低&#xff0c;速度越快。腾讯云百科txybk.com告诉大家关于地域的选择还有很多因素&#xff0c;地域节点选择…

如何在Windows系统部署Plex影音站点并实现公网访问内网媒体库

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频&#xff0c;已经算是生活中稀松平常的场景了&#xff0c;特别是各…

NO5 蓝桥杯实践之矩阵键盘的使用(或许是一篇求助帖...)

1 任务 2 思路 视频中老师的思路写的代码过长&#xff0c;所以我想了个自己的思路&#xff0c;但是没完全跑出来&#xff0c;求大神指教&#xff01;&#xff01;&#xff01;&#xff01; 我的思路是首先将矩阵键盘的行和列对应的端口引脚分别存储在类似数组&#xff0c;然后…

【Python】快捷找到最大最小 N 个元素

heapq 简单数据结构取出最大最小N个元素复杂数据结构中取出最大最小N个元素代码解析&#xff1a;lambda Python 中有 heapq 模块可以快捷找到数组中最大最小的 N 个元素&#xff1b; heapq.nlargest(num, arr) # 从arr数组中取出最大num个元素 heapq.nsmallest(num, arr) # …

matplotlib-柱状图

日期&#xff1a;2024.03.14 内容&#xff1a;将matplotlib的常用方法做一个记录&#xff0c;方便后续查找。 # from matplotlib import pyplot as plt# 设置画布大小 plt.figure(figsize(20,8),dpi 300)# 全局设置中文字体 plt.rcParams[font.sans-serif] [Simhei]# 绘制三…

2024年1月粮油调味行业分析(TOP品牌/店铺/商品销售数据分析)

鲸参谋监测的某东1月份粮油调味市场销售数据已出炉&#xff01; 根据鲸参谋电商数据分析平台显示&#xff0c;今年1月份&#xff0c;某东平台上粮油调味品的销量约6200万件&#xff0c;环比上个月增长45%&#xff0c;同比去年下滑15%&#xff1b;销售额约25亿元&#xff0c;环…

宝妈在家带孩子还是出去工作?足不出户,两者如何兼顾?

我是电商珠珠 很多女性生完孩子之后&#xff0c;就在家当家庭主妇带孩子。部分人舍不得吃穿&#xff0c;把从老公那里要来的钱都给孩子买了东西。自己也想过要出去工作&#xff0c;但是空窗期太久&#xff0c;觉得自己什么都不会&#xff0c;没有企业会要。 前段时间有一个硕…

CUDA下载安装与配置

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 CUDA是什么&#xff1f; CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由NVIDIA推出的并行计算平台和编程…

sqllab第十二关通关笔记

知识点&#xff1a; 一般字符型注入分类 单引号闭合双引号闭合这是一个双引号闭合 看界面又是一个输入框的注入;通过admin admin进行登录发现页面还是有回显 直接使用万能密码尝试 构造payload:usernameadminor11 没有任何反应&#xff1b;可能是没加注释符的关闭 构造user…

npm yarn 一起使用报错

项目记录&#xff0c;具有独特性&#xff0c;仅供参考 项目好好的运行&#xff0c;前一天装个测试工具包&#xff0c; 突然就不行了&#xff0c;卸载重装也不行&#xff0c;所有的项目都安装失败&#xff0c;新起一个项目也不行&#xff0c;有时候某个单独安装一个包可以&…

C# 连接neo4j数据库,包括非默认的neo4j默认库

官方文档没找见&#xff0c;自己在源码里面找到的 private string _dbHost "bolt://localhost:7687"; private string _dbUser "neo4j"; private string _dbPassword "******"; private IDriver? _driver;public CQLOperation(string _data…

采购代购系统独立站,接口采集商品上货

采购代购系统独立站的建设与商品上货接口的采集是一个综合性的项目&#xff0c;涉及前端开发、后端开发、数据库设计以及API接口的对接等多个环节。以下是一个大致的步骤和考虑因素&#xff1a; 一、系统规划与需求分析 明确业务需求&#xff1a;确定代购系统的核心功能&…