【AI辅助生成】QT模型视图架构实战:构建高效可扩展的数据可视化界面

QT模型视图架构实战:构建高效可扩展的数据可视化界面

这个博客使用AI技术辅助生成。

在QT应用程序开发中,处理和展示大量数据是一项常见挑战。QT的模型/视图(Model/View)架构提供了一种解决方案,它将数据与表示分离,不仅简化了复杂界面的开发,还显著提高了应用程序的性能和可维护性。本文将深入探讨模型/视图架构的核心概念、实现方法以及实战技巧,帮助您构建高效可扩展的数据可视化界面。

文章目录

一、模型/视图架构基础1.1 架构原理1.2 核心类
二、自定义模型实现2.1 实现自定义列表模型2.2 实现自定义表格模型
三、高级视图技巧3.1 使用QTableView实现高级表格3.2 实现树形视图模型
四、委托实现高级定制4.1 实现自定义委托4.2 实现多类型委托
五、性能优化技术5.1 实现延迟加载5.2 使用代理模型
六、实战案例:数据可视化仪表盘6.1 仪表盘设计6.2 实现主界面6.3 使用总结
七、最佳实践与注意事项7.1 最佳实践7.2 常见问题及解决方案7.3 模型/视图与多线程集成7.3.1 异步数据加载7.3.2 线程安全的模型更新7.3.3 使用QFuture和QtConcurrent7.3.4 实现虚拟代理模型7.3.5 注意事项

八、总结

一、模型/视图架构基础

1.1 架构原理

QT的模型/视图架构采用MVC设计模式,将数据模型(Model)、视图(View)和委托(Delegate)分离:

模型(Model):负责管理数据并提供接口访问数据视图(View):负责显示数据并处理用户交互委托(Delegate):负责渲染和编辑视图中的数据项

1.2 核心类

QT提供了多种模型和视图类:

模型类


QAbstractItemModel
:所有项目模型的基类
QAbstractListModel
:用于简单列表数据的模型
QAbstractTableModel
:用于表格数据的模型
QFileSystemModel
:文件系统模型
QSqlQueryModel
:SQL查询模型

视图类


QListView
:列表视图
QTableView
:表格视图
QTreeView
:树形视图
QGraphicsView
:图形视图

委托类


QStyledItemDelegate
:标准样式委托
QItemDelegate
:基础项目委托
QSqlQueryModel
:用于自定义渲染和编辑

二、自定义模型实现

2.1 实现自定义列表模型


class CustomListModel : public QAbstractListModel {
    Q_OBJECT
public:
    enum CustomRoles {
        NameRole = Qt::UserRole + 1,
        AgeRole,
        EmailRole
    };

    explicit CustomListModel(QObject *parent = nullptr) : QAbstractListModel(parent) {
        // 初始化数据
        m_data = {
            {"张三", 28, "zhangsan@example.com"},
            {"李四", 32, "lisi@example.com"},
            {"王五", 25, "wangwu@example.com"}
        };
    }

    // 基本模型接口实现
    int rowCount(const QModelIndex &parent = QModelIndex()) const override {
        Q_UNUSED(parent)
        return m_data.size();
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
        if (!index.isValid())
            return QVariant();

        const QStringList &item = m_data.at(index.row());
        switch (role) {
            case NameRole:
                return item[0];
            case AgeRole:
                return item[1].toInt();
            case EmailRole:
                return item[2];
            default:
                return QVariant();
        }
    }

    // 可编辑模型接口实现
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override {
        if (!index.isValid())
            return false;

        QStringList &item = m_data[index.row()];
        switch (role) {
            case NameRole:
                item[0] = value.toString();
                break;
            case AgeRole:
                item[1] = QString::number(value.toInt());
                break;
            case EmailRole:
                item[2] = value.toString();
                break;
            default:
                return false;
        }

        emit dataChanged(index, index, {role});
        return true;
    }

    // 角色名称注册
    QHash<int, QByteArray> roleNames() const override {
        QHash<int, QByteArray> roles;
        roles[NameRole] = "name";
        roles[AgeRole] = "age";
        roles[EmailRole] = "email";
        return roles;
    }

private:
    QList<QStringList> m_data;
};

2.2 实现自定义表格模型


class CustomTableModel : public QAbstractTableModel {
    Q_OBJECT
public:
    explicit CustomTableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
        // 初始化表格数据
        m_data.resize(5); // 5行
        for (auto &row : m_data) {
            row.resize(4); // 4列
        }
    }

    int rowCount(const QModelIndex &parent = QModelIndex()) const override {
        Q_UNUSED(parent)
        return m_data.size();
    }

    int columnCount(const QModelIndex &parent = QModelIndex()) const override {
        Q_UNUSED(parent)
        return m_data.isEmpty() ? 0 : m_data.first().size();
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
        if (!index.isValid() || role != Qt::DisplayRole)
            return QVariant();

        return m_data[index.row()][index.column()];
    }

    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override {
        if (role != Qt::DisplayRole)
            return QVariant();

        if (orientation == Qt::Horizontal) {
            return QString("列 %1").arg(section + 1);
        } else {
            return QString("行 %1").arg(section + 1);
        }
    }

    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override {
        if (!index.isValid() || role != Qt::EditRole)
            return false;

        m_data[index.row()][index.column()] = value.toString();
        emit dataChanged(index, index);
        return true;
    }

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

        return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
    }
  
private:
    QList<QStringList> m_data;
};

三、高级视图技巧

3.1 使用QTableView实现高级表格


class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {
        m_model = new CustomTableModel(this);
      
        // 创建表格视图
        m_tableView = new QTableView(this);
        m_tableView->setModel(m_model);
      
        // 设置列宽
        m_tableView->setColumnWidth(0, 150);
        m_tableView->setColumnWidth(1, 200);
        m_tableView->setColumnWidth(2, 150);
        m_tableView->setColumnWidth(3, 100);
      
        // 启用排序
        m_tableView->setSortingEnabled(true);
      
        // 设置选择模式
        m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
        m_tableView->setSelectionMode(QAbstractItemView::SingleSelection);
      
        // 设置网格线
        m_tableView->setShowGrid(true);
        m_tableView->setGridStyle(Qt::DashLine);
      
        // 设置交替行颜色
        m_tableView->setAlternatingRowColors(true);
      
        // 设置行高
        m_tableView->verticalHeader()->setDefaultSectionSize(25);
      
        // 设置表头样式
        m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
        m_tableView->horizontalHeader()->setHighlightSections(false);
        m_tableView->verticalHeader()->setVisible(false);
      
        // 添加到主布局
        QWidget *centralWidget = new QWidget(this);
        QVBoxLayout *layout = new QVBoxLayout(centralWidget);
        layout->addWidget(m_tableView);
        setCentralWidget(centralWidget);
      
        // 添加按钮
        QPushButton *addRowBtn = new QPushButton("添加行", this);
        QPushButton *delRowBtn = new QPushButton("删除行", this);
        QHBoxLayout *btnLayout = new QHBoxLayout();
        btnLayout->addWidget(addRowBtn);
        btnLayout->addWidget(delRowBtn);
        layout->addLayout(btnLayout);
      
        // 连接信号槽
        connect(addRowBtn, &QPushButton::clicked, this, &MainWindow::addRow);
        connect(delRowBtn, &QPushButton::clicked, this, &MainWindow::deleteRow);
    }

private slots:
    void addRow() {
        int rowCount = m_model->rowCount();
        m_model->insertRow(rowCount);
      
        // 设置新行数据
        for (int col = 0; col < m_model->columnCount(); ++col) {
            QModelIndex index = m_model->index(rowCount, col);
            m_model->setData(index, QString("新数据 %1-%2").arg(rowCount+1).arg(col+1));
        }
    }
  
    void deleteRow() {
        QModelIndexList selected = m_tableView->selectionModel()->selectedRows();
        if (!selected.isEmpty()) {
            int row = selected.first().row();
            m_model->removeRow(row);
        }
    }

private:
    QTableView *m_tableView;
    CustomTableModel *m_model;
};

3.2 实现树形视图模型


class TreeModel : public QAbstractItemModel {
    Q_OBJECT
public:
    explicit TreeModel(QObject *parent = nullptr) : QAbstractItemModel(parent) {
        // 初始化树形数据
        m_rootItem = new TreeItem({"名称", "值"});
        setupModelData();
    }

    ~TreeModel() {
        delete m_rootItem;
    }

    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override {
        if (!hasIndex(row, column, parent))
            return QModelIndex();

        TreeItem *parentItem = parent.isValid() ? static_cast<TreeItem*>(parent.internalPointer()) : m_rootItem;

        TreeItem *childItem = parentItem->child(row);
        return childItem ? createIndex(row, column, childItem) : QModelIndex();
    }

    QModelIndex parent(const QModelIndex &index) const override {
        if (!index.isValid())
            return QModelIndex();

        TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer());
        TreeItem *parentItem = childItem->parent();

        return (parentItem == m_rootItem || !parentItem) ? QModelIndex() : createIndex(parentItem->row(), 0, parentItem);
    }

    int rowCount(const QModelIndex &parent = QModelIndex()) const override {
        TreeItem *parentItem = parent.isValid() ? static_cast<TreeItem*>(parent.internalPointer()) : m_rootItem;
        return parentItem->childCount();
    }

    int columnCount(const QModelIndex &parent = QModelIndex()) const override {
        Q_UNUSED(parent)
        return m_rootItem->columnCount();
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
        if (!index.isValid())
            return QVariant();

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

        TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
        return item->data(index.column());
    }

    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override {
        if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
            return m_rootItem->data(section);

        return QVariant();
    }

private:
    void setupModelData() {
        // 添加顶级节点
        TreeItem *category1 = new TreeItem({"类别1", "描述1"}, m_rootItem);
        TreeItem *category2 = new TreeItem({"类别2", "描述2"}, m_rootItem);
      
        // 添加子节点
        new TreeItem({"项目1", "值1"}, category1);
        new TreeItem({"项目2", "值2"}, category1);
      
        new TreeItem({"项目3", "值3"}, category2);
        new TreeItem({"项目4", "值4"}, category2);
      
        // 添加更深层次的节点
        TreeItem *subItem = new TreeItem({"子项目1", "子值1"}, category1->child(0));
        new TreeItem({"子子项目1", "子子值1"}, subItem);
    }
  
    TreeItem *m_rootItem;
};

// 树形节点类
class TreeItem {
public:
    explicit TreeItem(const QStringList &data, TreeItem *parent = nullptr) {
        m_parentItem = parent;
        m_itemData = data;
    }
  
    ~TreeItem() {
        qDeleteAll(m_childItems);
    }
  
    void appendChild(TreeItem *item) {
        m_childItems.append(item);
    }
  
    TreeItem *child(int row) {
        return m_childItems.value(row);
    }
  
    int childCount() const {
        return m_childItems.count();
    }
  
    int columnCount() const {
        return m_itemData.count();
    }
  
    QVariant data(int column) const {
        return m_itemData.value(column);
    }
  
    int row() const {
        if (m_parentItem)
            return m_parentItem->m_childItems.indexOf(const_cast<TreeItem*>(this));
        return 0;
    }
  
    TreeItem *parent() const {
        return m_parentItem;
    }
  
private:
    QList<TreeItem*> m_childItems;
    QList<QVariant> m_itemData;
    TreeItem *m_parentItem;
};

四、委托实现高级定制

4.1 实现自定义委托


class SpinBoxDelegate : public QStyledItemDelegate {
    Q_OBJECT
public:
    SpinBoxDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        Q_UNUSED(option)
        QSpinBox *editor = new QSpinBox(parent);
        editor->setRange(0, 100);
        editor->installEventFilter(const_cast<SpinBoxDelegate*>(this));
        return editor;
    }

    void setEditorData(QWidget *editor, const QModelIndex &index) const override {
        int value = index.model()->data(index, Qt::EditRole).toInt();
        QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
        spinBox->setValue(value);
    }

    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override {
        QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
        spinBox->interpretText();
        int value = spinBox->value();
        model->setData(index, value, Qt::EditRole);
    }

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

4.2 实现多类型委托


class MultiTypeDelegate : public QStyledItemDelegate {
    Q_OBJECT
public:
    MultiTypeDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        QString type = index.data(Qt::UserRole).toString();
      
        if (type == "text") {
            QLineEdit *editor = new QLineEdit(parent);
            editor->installEventFilter(const_cast<MultiTypeDelegate*>(this));
            return editor;
        } else if (type == "number") {
            QSpinBox *editor = new QSpinBox(parent);
            editor->setRange(0, 100);
            editor->installEventFilter(const_cast<MultiTypeDelegate*>(this));
            return editor;
        } else if (type == "boolean") {
            QComboBox *editor = new QComboBox(parent);
            editor->addItem("是");
            editor->addItem("否");
            editor->installEventFilter(const_cast<MultiTypeDelegate*>(this));
            return editor;
        }
      
        return nullptr;
    }

    void setEditorData(QWidget *editor, const QModelIndex &index) const override {
        QString type = index.data(Qt::UserRole).toString();
      
        if (type == "text") {
            QLineEdit *lineEdit = static_cast<QLineEdit*>(editor);
            lineEdit->setText(index.data(Qt::EditRole).toString());
        } else if (type == "number") {
            QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
            spinBox->setValue(index.data(Qt::EditRole).toInt());
        } else if (type == "boolean") {
            QComboBox *comboBox = static_cast<QComboBox*>(editor);
            comboBox->setCurrentIndex(index.data(Qt::EditRole).toBool() ? 0 : 1);
        }
    }

    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override {
        QString type = index.data(Qt::UserRole).toString();
      
        if (type == "text") {
            QLineEdit *lineEdit = static_cast<QLineEdit*>(editor);
            model->setData(index, lineEdit->text(), Qt::EditRole);
        } else if (type == "number") {
            QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
            model->setData(index, spinBox->value(), Qt::EditRole);
        } else if (type == "boolean") {
            QComboBox *comboBox = static_cast<QComboBox*>(editor);
            model->setData(index, comboBox->currentIndex() == 0, Qt::EditRole);
        }
    }

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        QString type = index.data(Qt::UserRole).toString();
      
        if (type == "boolean") {
            // 自定义布尔值显示
            bool value = index.data(Qt::EditRole).toBool();
            QString text = value ? "是" : "否";
          
            QStyleOptionViewItem opt = option;
            initStyleOption(&opt, index);
          
            opt.text = text;
            opt.displayAlignment = Qt::AlignCenter;
          
            QStyledItemDelegate::paint(painter, opt, index);
        } else {
            QStyledItemDelegate::paint(painter, option, index);
        }
    }

    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        QString type = index.data(Qt::UserRole).toString();
      
        if (type == "boolean") {
            // 自定义布尔值大小
            return QSize(50, option.rect.height());
        }
      
        return QStyledItemDelegate::sizeHint(option, index);
    }
};

五、性能优化技术

5.1 实现延迟加载


class LazyLoadTreeModel : public QAbstractItemModel {
    Q_OBJECT
public:
    explicit LazyLoadTreeModel(QObject *parent = nullptr) : QAbstractItemModel(parent) {
        m_rootItem = new TreeItem({"名称", "类型"});
        setupTopLevelItems();
    }

    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override {
        if (!hasIndex(row, column, parent))
            return QModelIndex();

        TreeItem *parentItem = parent.isValid() ? static_cast<TreeItem*>(parent.internalPointer()) : m_rootItem;
        TreeItem *childItem = parentItem->child(row);
      
        return childItem ? createIndex(row, column, childItem) : QModelIndex();
    }

    // ... 其他基本模型接口实现 ...

    // 覆盖canFetchMore和fetchMore方法实现延迟加载
    bool canFetchMore(const QModelIndex &parent) const override {
        if (!parent.isValid())
            return false;

        TreeItem *item = static_cast<TreeItem*>(parent.internalPointer());
        return item->canFetchMore();
    }

    void fetchMore(const QModelIndex &parent) override {
        if (!parent.isValid())
            return;

        TreeItem *item = static_cast<TreeItem*>(parent.internalPointer());
        int first = item->childCount();
        int count = item->fetchMore();
      
        if (count > 0) {
            beginInsertRows(parent, first, first + count - 1);
            endInsertRows();
        }
    }

private:
    void setupTopLevelItems() {
        // 添加顶级节点
        for (int i = 0; i < 5; ++i) {
            TreeItem *item = new TreeItem({QString("类别 %1").arg(i+1), "文件夹"}, m_rootItem);
            // 设置为懒加载
            item->setLazyLoading(true);
        }
    }

    TreeItem *m_rootItem;
};

// 在TreeItem类中添加懒加载支持
class TreeItem {
public:
    // ... 其他成员 ...
  
    bool canFetchMore() const {
        return m_lazyLoad && m_childItems.isEmpty() && !m_loaded;
    }
  
    int fetchMore() {
        if (!canFetchMore())
            return 0;
          
        // 模拟从数据库或文件系统加载数据
        for (int i = 0; i < 10; ++i) {
            QString name = QString("项目 %1-%2").arg(m_name).arg(i+1);
            new TreeItem({name, "文件"}, this);
        }
      
        m_loaded = true;
        return m_childItems.count();
    }
  
    void setLazyLoading(bool lazy) {
        m_lazyLoad = lazy;
    }
  
private:
    // ... 其他成员 ...
    bool m_lazyLoad = false;
    bool m_loaded = false;
};

5.2 使用代理模型


class ProxyModelFilter : public QSortFilterProxyModel {
    Q_OBJECT
public:
    explicit ProxyModelFilter(QObject *parent = nullptr) : QSortFilterProxyModel(parent) {}

protected:
    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override {
        // 过滤逻辑
        if (m_filterType.isEmpty())
            return true;
          
        QModelIndex nameIndex = sourceModel()->index(sourceRow, 0, sourceParent);
        QString name = sourceModel()->data(nameIndex, Qt::DisplayRole).toString();
      
        if (m_filterType == "name") {
            return name.contains(m_filterText, Qt::CaseInsensitive);
        }
      
        // 可以添加更多过滤条件
        return true;
    }
  
    bool lessThan(const QModelIndex &left, const QModelIndex &right) const override {
        QVariant leftData = sourceModel()->data(left, sortRole());
        QVariant rightData = sourceModel()->data(right, sortRole());
      
        if (leftData.type() == QVariant::Int) {
            return leftData.toInt() < rightData.toInt();
        }
      
        return leftData.toString().compare(rightData.toString(), Qt::CaseInsensitive) < 0;
    }
  
public:
    void setFilterType(const QString &type) {
        m_filterType = type;
        invalidateFilter();
    }
  
    void setFilterText(const QString &text) {
        m_filterText = text;
        invalidateFilter();
    }
  
private:
    QString m_filterType;
    QString m_filterText;
};

六、实战案例:数据可视化仪表盘

6.1 仪表盘设计

我们将创建一个综合使用模型/视图架构的数据可视化仪表盘,包含多个视图组件:

数据表格视图数据图表视图树形结构导航数据过滤面板

6.2 实现主界面


class Dashboard : public QMainWindow {
    Q_OBJECT
public:
    Dashboard(QWidget *parent = nullptr) : QMainWindow(parent) {
        // 设置主窗口
        setWindowTitle("数据可视化仪表盘");
        resize(1200, 800);
      
        // 创建中央部件和主布局
        QWidget *centralWidget = new QWidget(this);
        QHBoxLayout *mainLayout = new QHBoxLayout(centralWidget);
      
        // 创建左侧导航树
        m_treeView = new QTreeView(this);
        m_treeModel = new TreeModel(this);
        m_treeView->setModel(m_treeModel);
        m_treeView->setHeaderHidden(true);
        m_treeView->setExpandsOnDoubleClick(true);
        m_treeView->setAlternatingRowColors(true);
      
        // 创建右侧主内容区域
        m_tabWidget = new QTabWidget(this);
      
        // 创建表格视图
        m_tableView = new QTableView(this);
        m_tableModel = new CustomTableModel(this);
        m_tableView->setModel(m_tableModel);
      
        // 设置表格视图样式
        m_tableView->setSortingEnabled(true);
        m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
        m_tableView->setSelectionMode(QAbstractItemView::SingleSelection);
        m_tableView->setAlternatingRowColors(true);
        m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
        m_tableView->verticalHeader()->setDefaultSectionSize(25);
      
        // 创建图表视图
        m_chartView = new QtCharts::QChartView(this);
        m_chart = new QtCharts::QChart();
        m_chart->setTitle("数据趋势图");
        m_chartView->setChart(m_chart);
      
        // 添加标签页
        m_tabWidget->addTab(m_tableView, "数据表格");
        m_tabWidget->addTab(m_chartView, "数据图表");
      
        // 创建状态栏
        QStatusBar *statusBar = new QStatusBar(this);
        setStatusBar(statusBar);
      
        // 添加过滤和搜索控件
        QWidget *filterWidget = new QWidget(this);
        QVBoxLayout *filterLayout = new QVBoxLayout(filterWidget);
      
        m_searchEdit = new QLineEdit(this);
        m_searchEdit->setPlaceholderText("搜索数据...");
        m_categoryCombo = new QComboBox(this);
        m_categoryCombo->addItem("全部类别");
        m_categoryCombo->addItem("类别1");
        m_categoryCombo->addItem("类别2");
      
        filterLayout->addWidget(new QLabel("搜索:"));
        filterLayout->addWidget(m_searchEdit);
        filterLayout->addWidget(new QLabel("类别过滤:"));
        filterLayout->addWidget(m_categoryCombo);
      
        // 将部件添加到主布局
        mainLayout->addWidget(m_treeView, 1);
        mainLayout->addWidget(m_tabWidget, 3);
      
        // 添加过滤面板到主布局右侧
        mainLayout->addWidget(filterWidget, 1);
      
        setCentralWidget(centralWidget);
      
        // 连接信号槽
        connect(m_treeView, &QTreeView::clicked, this, &Dashboard::onTreeClicked);
        connect(m_searchEdit, &QLineEdit::textChanged, this, &Dashboard::onFilterChanged);
        connect(m_categoryCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), 
                this, &Dashboard::onFilterChanged);
        connect(m_tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
                this, &Dashboard::onSelectionChanged);
      
        // 初始化数据
        initializeData();
    }

private slots:
    void onTreeClicked(const QModelIndex &index) {
        if (!index.isValid())
            return;
          
        // 根据选择的树节点加载数据
        TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
        loadNodeData(item);
    }
  
    void onFilterChanged() {
        // 实现过滤逻辑
        QString searchText = m_searchEdit->text();
        QString category = m_categoryCombo->currentText();
      
        // 这里可以应用代理模型或其他过滤逻辑
        updateFilter(searchText, category);
    }
  
    void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
        Q_UNUSED(deselected)
      
        if (selected.isEmpty())
            return;
          
        // 更新图表显示
        updateChart(selected.indexes());
    }
  
private:
    void initializeData() {
        // 初始化模拟数据
        for (int row = 0; row < 100; ++row) {
            m_tableModel->insertRow(m_tableModel->rowCount());
          
            for (int col = 0; col < 4; ++col) {
                QString value = QString("数据 %1-%2").arg(row+1).arg(col+1);
                QModelIndex index = m_tableModel->index(row, col);
                m_tableModel->setData(index, value);
            }
        }
      
        // 初始化图表
        updateChart(QModelIndexList());
    }
  
    void loadNodeData(TreeItem *item) {
        // 根据选择的节点加载数据
        QString name = item->data(0).toString();
        statusBar()->showMessage("加载数据: " + name);
      
        // 这里可以根据节点名称加载不同的数据
        // 实际应用中可能从数据库或API获取数据
    }
  
    void updateFilter(const QString &searchText, const QString &category) {
        statusBar()->showMessage(QString("过滤条件: 搜索=%1, 类别=%2").arg(searchText).arg(category));
      
        // 实现实际的过滤逻辑
        // 可以使用QSortFilterProxyModel或自定义过滤逻辑
    }
  
    void updateChart(const QModelIndexList &selectedIndexes) {
        m_chart->removeAllSeries();
      
        // 创建图表系列
        QtCharts::QLineSeries *series = new QtCharts::QLineSeries();
        series->setName("趋势数据");
      
        // 模拟数据点
        for (int i = 0; i < 20; ++i) {
            series->append(i, qrand() % 100);
        }
      
        m_chart->addSeries(series);
        m_chart->createDefaultAxes();
    }
  
private:
    QTreeView *m_treeView;
    TreeModel *m_treeModel;
  
    QTableView *m_tableView;
    CustomTableModel *m_tableModel;
  
    QtCharts::QChartView *m_chartView;
    QtCharts::QChart *m_chart;
  
    QTabWidget *m_tabWidget;
  
    QLineEdit *m_searchEdit;
    QComboBox *m_categoryCombo;
};

6.3 使用总结

通过这个实战案例,我们可以看到QT模型/视图架构的强大之处:

数据与界面分离:数据模型与视图分离,使得同一份数据可以以多种方式展示高效处理大量数据:通过延迟加载、代理模型等技术,可以高效处理大量数据灵活的定制能力:通过自定义委托,可以实现复杂的渲染和编辑逻辑统一的数据接口:无论是列表、表格还是树形结构,都使用相同的模型接口,便于统一处理

七、最佳实践与注意事项

7.1 最佳实践

选择合适的模型基类

简单列表数据使用
QAbstractListModel
表格数据使用
QAbstractTableModel
层次结构数据使用
QAbstractItemModel

合理使用信号槽

数据变化时发射
dataChanged
信号结构变化时使用
beginInsertRows
/
endInsertRows
等通知视图

优化性能

实现延迟加载使用代理模型进行过滤和排序避免频繁的数据更新通知

注意内存管理

模型销毁时确保所有相关视图都被正确清理使用智能指针管理自定义模型对象

7.2 常见问题及解决方案

数据更新后视图不刷新

确保在数据变化时正确发射信号

排序和过滤不生效

确保模型正确实现了
sort()
方法

编辑功能不工作

确保模型正确实现了
flags()
方法,返回
Qt::ItemIsEditable

性能问题

使用
QIdentityProxyModel
减少不必要的转换

7.3 模型/视图与多线程集成

在处理大量数据或需要长时间加载的情况下,将模型/视图架构与多线程结合使用可以显著提升应用程序的响应性。然而,这也引入了一些特殊的挑战,需要谨慎处理以确保线程安全。

7.3.1 异步数据加载

模型/视图架构本身并不直接支持多线程操作,但我们可以通过以下方式实现异步数据加载:


class AsyncTableModel : public QAbstractTableModel {
    Q_OBJECT
public:
    explicit AsyncTableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
        // 初始化数据加载线程
        m_loaderThread = new QThread(this);
        m_dataLoader = new DataLoader();
        m_dataLoader->moveToThread(m_loaderThread);
      
        // 连接信号槽
        connect(this, &AsyncTableModel::loadDataRequested, 
                m_dataLoader, &DataLoader::startLoading);
        connect(m_dataLoader, &DataLoader::dataLoaded, 
                this, &AsyncTableModel::handleDataLoaded);
        connect(m_loaderThread, &QThread::finished, 
                m_dataLoader, &DataLoader::deleteLater);
      
        // 启动线程
        m_loaderThread->start();
    }
  
    ~AsyncTableModel() {
        m_loaderThread->quit();
        m_loaderThread->wait();
    }
  
    // ... 其他模型接口实现 ...

signals:
    void loadDataRequested(int rows, int columns);

private slots:
    void handleDataLoaded(const QList<QStringList> &data) {
        // 在主线程中安全地更新模型
        beginResetModel();
        m_data = data;
        endResetModel();
      
        emit loadingComplete();
    }

private:
    QThread *m_loaderThread;
    DataLoader *m_dataLoader;
    QList<QStringList> m_data;
};

// 数据加载器类
class DataLoader : public QObject {
    Q_OBJECT
public:
    explicit DataLoader(QObject *parent = nullptr) : QObject(parent) {}

public slots:
    void startLoading(int rows, int columns) {
        // 模拟耗时数据加载
        QList<QStringList> data;
        data.reserve(rows);
      
        for (int i = 0; i < rows; ++i) {
            QStringList row;
            row.reserve(columns);
          
            for (int j = 0; j < columns; ++j) {
                // 模拟数据生成
                row << QString("数据 %1-%2").arg(i+1).arg(j+1);
              
                // 模拟延迟
                if (i % 10 == 0 && j == 0) {
                    QThread::msleep(50); // 每10行延迟50ms
                }
            }
          
            data.append(row);
        }
      
        emit dataLoaded(data);
    }

signals:
    void dataLoaded(const QList<QStringList> &data);
};
7.3.2 线程安全的模型更新

直接在非GUI线程中修改模型数据是不安全的,会导致崩溃或不可预测的行为。以下是几种线程安全的模型更新方法:

方法1:使用QMetaObject::invokeMethod


class WorkerThread : public QThread {
    Q_OBJECT
public:
    WorkerThread(QAbstractTableModel *model, QObject *parent = nullptr)
        : QThread(parent), m_model(model) {}

    void run() override {
        // 在工作线程中准备数据
        QList<QStringList> data;
        // ... 填充数据 ...
      
        // 安全地更新模型
        QMetaObject::invokeMethod(m_model, "updateModel", 
            Qt::QueuedConnection,
            Q_ARG(QList<QStringList>, data));
    }

private:
    QAbstractTableModel *m_model;
};

// 在模型中添加槽
class SafeTableModel : public QAbstractTableModel {
    Q_OBJECT
public:
    // ... 其他代码 ...

public slots:
    void updateModel(const QList<QStringList> &data) {
        beginResetModel();
        m_data = data;
        endResetModel();
    }

private:
    QList<QStringList> m_data;
};

方法2:使用线程安全的数据结构


class ThreadSafeTableModel : public QAbstractTableModel {
    Q_OBJECT
public:
    // ... 其他代码 ...

    void updateData(const QModelIndex &topLeft, const QModelIndex &bottomRight, 
                   const QVector<int> &roles = QVector<int>()) {
        QMutexLocker locker(&m_mutex);
      
        // 更新数据
        for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
            for (int col = topLeft.column(); col <= bottomRight.column(); ++col) {
                QModelIndex index = this->index(row, col);
                m_data[row][col] = m_dataBuffer[row][col];
            }
        }
      
        // 发射信号
        emit dataChanged(topLeft, bottomRight, roles);
    }

    void bufferData(const QList<QStringList> &newData) {
        QMutexLocker locker(&m_mutex);
        m_dataBuffer = newData;
    }

private:
    QMutex m_mutex;
    QList<QStringList> m_data;
    QList<QStringList> m_dataBuffer;
};
7.3.3 使用QFuture和QtConcurrent

class FutureTableModel : public QAbstractTableModel {
    Q_OBJECT
public:
    explicit FutureTableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
        m_watcher = new QFutureWatcher<QList<QStringList>>(this);
        connect(m_watcher, &QFutureWatcher<QList<QStringList>>::finished,
                this, &FutureTableModel::handleDataReady);
    }

    void loadDataAsync(int rows, int columns) {
        // 取消之前的任务
        if (m_watcher->isRunning()) {
            m_watcher->cancel();
            m_watcher->waitForFinished();
        }
      
        // 启动新任务
        QFuture<QList<QStringList>> future = QtConcurrent::run([rows, columns]() {
            // 模拟耗时数据加载
            QList<QStringList> data;
            data.reserve(rows);
          
            for (int i = 0; i < rows; ++i) {
                QStringList row;
                row.reserve(columns);
              
                for (int j = 0; j < columns; ++j) {
                    row << QString("数据 %1-%2").arg(i+1).arg(j+1);
                }
              
                data.append(row);
            }
          
            return data;
        });
      
        m_watcher->setFuture(future);
    }

private slots:
    void handleDataReady() {
        if (m_watcher->isCanceled()) {
            return;
        }
      
        QList<QStringList> data = m_watcher->result();
        beginResetModel();
        m_data = data;
        endResetModel();
      
        emit loadingCompleted();
    }

private:
    QFutureWatcher<QList<QStringList>> *m_watcher;
    QList<QStringList> m_data;
};
7.3.4 实现虚拟代理模型

虚拟代理模型(Virtual Proxy Model)可以与后台线程结合使用,实现更高效的延迟加载:


class VirtualProxyModel : public QIdentityProxyModel {
    Q_OBJECT
public:
    explicit VirtualProxyModel(QObject *parent = nullptr) : QIdentityProxyModel(parent) {
        m_loader = new AsyncDataLoader(this);
        connect(m_loader, &AsyncDataLoader::dataLoaded,
                this, &VirtualProxyModel::handleDataLoaded);
    }

    // 重写方法以实现虚拟化
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
        if (!index.isValid()) {
            return QVariant();
        }
      
        // 检查数据是否已加载
        if (!isDataLoaded(index)) {
            // 请求加载数据
            requestLoading(index);
            return QVariant(); // 返回空值表示数据正在加载
        }
      
        // 调用基类方法获取实际数据
        return QIdentityProxyModel::data(index, role);
    }

private:
    bool isDataLoaded(const QModelIndex &index) const {
        // 实现检查数据是否已加载的逻辑
        return m_loadedRows.contains(index.row());
    }
  
    void requestLoading(const QModelIndex &index) {
        // 请求加载数据
        if (!m_requestedRows.contains(index.row())) {
            m_requestedRows.insert(index.row());
            m_loader->loadData(index.row());
        }
    }
  
    void handleDataLoaded(int row) {
        m_loadedRows.insert(row);
        m_requestedRows.remove(row);
      
        // 通知视图数据已更新
        QModelIndex topLeft = sourceModel()->index(row, 0);
        QModelIndex bottomRight = sourceModel()->index(row, sourceModel()->columnCount() - 1);
      
        emit dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight));
    }

private:
    QSet<int> m_loadedRows;
    QSet<int> m_requestedRows;
    AsyncDataLoader *m_loader;
};
7.3.5 注意事项

线程安全:永远不要在非GUI线程中直接修改模型数据或调用模型的方法。所有对模型的修改都应该通过信号槽机制或QMetaObject::invokeMethod在主线程中执行。

取消长时间运行的操作:提供取消机制,允许用户中断长时间运行的数据加载操作。

加载状态反馈:通过信号或状态栏向用户提供数据加载进度的反馈。

内存管理:确保在工作线程中正确管理资源,避免内存泄漏。

避免阻塞:长时间运行的数据加载操作应该定期处理事件循环,避免阻塞线程。


// 示例:定期处理事件循环避免阻塞
void DataLoader::startLoading(int rows, int columns) {
    QList<QStringList> data;
    data.reserve(rows);
  
    for (int i = 0; i < rows; ++i) {
        QStringList row;
        row.reserve(columns);
      
        for (int j = 0; j < columns; ++j) {
            row << QString("数据 %1-%2").arg(i+1).arg(j+1);
          
            // 每100个数据项处理一次事件循环
            if ((i * columns + j) % 100 == 0) {
                QCoreApplication::processEvents();
            }
        }
      
        data.append(row);
    }
  
    emit dataLoaded(data);
}

通过将模型/视图架构与多线程技术结合,可以构建出既高效又响应迅速的数据密集型应用程序,同时保持UI的流畅性和用户体验的连贯性。

八、总结

QT的模型/视图架构是一种强大而灵活的设计模式,它使得开发者能够构建高效、可扩展的数据可视化界面。通过合理选择模型类型、视图组件和委托实现,可以创建出既美观又高效的用户界面。

本文介绍了模型/视图架构的核心概念、自定义模型的实现方法、视图的高级技巧、委托的定制以及性能优化技术,并通过一个实战案例展示了如何将这些技术结合起来构建一个功能完整的数据可视化仪表盘。

掌握模型/视图架构不仅能提高开发效率,还能使应用程序具有更好的性能和可维护性。在实际开发中,应根据具体需求选择合适的模型和视图组件,并注意优化性能,避免常见陷阱。

随着QT的不断演进,模型/视图架构也在持续发展,新的特性和功能不断被添加。因此,持续学习和实践有助于掌握这项技术。

博客专栏作者提供的入门级QT技术教程:
QT项目实践 轻量级 QT编程从基础到高级

博客专栏作者提供的进阶QT视频课程:

QT&QML原理源码界面美化网络编程(QT5视频课程)
QT&QML性能优化网络编程界面美化(QT6视频课程)
QT C++网络编程系列视频课程
QT+OpenCV+开源框架计算机视觉技术项目实战

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
别说你懂的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容