深入浅出 Qt 中 QListView 的设计思想,并掌握大规模、高性能列表的实现方法

在大规模列表控件的显示需求中,必须解决2个问题才能获得较好的性能:

  1. 第一就是数据存在哪里, 避免出现数据的副本。
  2. 第二就是如何展示Item,如何复用或避免创建大量的Item控件。

在QListView体系里,QAbstractListModel解决的是“数据存哪”,解决的是第一个问题,而QAbstractItemDelegate解决的是数据“如何展示”,解决的是第二个问题。

因此,在大规模列表的编写代码中,高效的数据存储和高效的数据UI展示,需要用到这两个类。接下来,我们通过三个例子,循序渐进地介绍QListView,使读者掌握QListView的使用技巧和设计思想。

示例 1: 使用 QListWidget 的基本用法

QListWidget 是一个方便的控件,它内部管理了一个项目列表,并提供了一些简单的接口来添加、删除和修改这些项目。但没有对数据存储和数据展示进行过多的优化,这种方式适合于简单的应用场景,其中列表的大小不会很大,因为每个项目都会被存储为一个 QListWidgetItem 对象。

#include <QApplication>
#include <QListWidget>int main(int argc, char *argv[]) {QApplication app(argc, argv);    QListWidget* listWidget = new QListWidget;for (int i = 0; i < 100; ++i) {listWidget->addItem(QString("项目 %1").arg(i));}listWidget->show();    return app.exec();
}

尽管这种方式使用起来非常简单,但它并不适合处理大量数据。因为 QListWidget 会为每个项目创建一个 QListWidgetItem 对象,这将导致大量的内存消耗。

示例 2: 使用 QListView 和 QAbstractItemDelegate(解决数据存哪的问题)

在这个示例中,我们将直接从 QAbstractListModel 派生一个Model类,而不是使用 addItem构造大量的ItemData。这样,我们就无需存储这些数据。这个方法在处理具有可预知数据模式的大量数据时特别有用,因为它避免了冗余的数据存储和内存开销。

首先,我们定义一个 SyntheticListModel 类,它继承自 QAbstractListModel。这个模型将根据索引动态生成数据项:

#include <QAbstractListModel>
#include <QVariant>
#include <QModelIndex>class SyntheticListModel : public QAbstractListModel {Q_OBJECTpublic:SyntheticListModel(int numItems, QObject *parent = nullptr): QAbstractListModel(parent), m_numItems(numItems) {}int rowCount(const QModelIndex &parent = QModelIndex()) const override {// 在顶层,返回项的数量;否则返回0,因为这是一个列表,没有子项return parent.isValid() ? 0 : m_numItems;}QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {if (index.isValid() && index.row() < m_numItems) {if (role == Qt::DisplayRole) {// 根据行号动态生成数据return QString("项目 %1").arg(index.row());}// 可以根据需要添加其他角色的处理}return QVariant(); // 无效索引或角色时返回无效的QVariant}private:int m_numItems; // 列表中项目的数量
};

然后,我们创建一个简单的 QStyledItemDelegate,这个委托可以根据需要自定义项的显示方式:

#include <QStyledItemDelegate>
#include <QPainter>class SimpleDelegate : public QStyledItemDelegate {Q_OBJECTpublic:using QStyledItemDelegate::QStyledItemDelegate; // 继承构造函数// 重写 paint 方法以自定义项目显示方式void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {// 调用基类的 paint 方法进行默认的绘制QStyledItemDelegate::paint(painter, option, index);// 可以添加额外的绘制代码,如绘制边框、背景等}// 如果需要,可以重写 createEditor、setEditorData、setModelData 等方法以自定义编辑行为
};

最后,我们在 main 函数中创建 QListView,并将其与我们的自定义模型和委托相连接:

#include <QApplication>
#include <QListView>int main(int argc, char *argv[]) {QApplication app(argc, argv);// 创建列表视图QListView listView;// 创建模型,这里我们创建了 100000 个假数据项SyntheticListModel model(100000);// 创建委托SimpleDelegate delegate;// 将模型和委托设置到列表视图listView.setModel(&model);listView.setItemDelegate(&delegate);listView.show();return app.exec();
}

在这个示例中,我们展示了如何使用 QListView 和自定义的 QAbstractListModel 来动态生成数据,而不需要在内存中维护一份数据存储的副本。在实际业务中,数据可以直接从业务模块中获取,这样避免出现数据的两个副本SimpleDelegate 负责定制列表项的视觉呈现,但在这个简化的例子中,我们仅使用了默认的绘制逻辑。如果需要更复杂的项显示或编辑功能,可以在委托中进一步扩展 paint 和其他相关方法。这些内容我们在示例3中展现。

示例 3: 使用 QListView 和自定义 QAbstractListModel(解决数据如何展示问题)

实例2中没有展开SimpleDelegate 的实现,在实际开发场景中,界面展示的需求往往更加复杂,特别是QListView的View模型采用的是paint函数来呈现,和其他图形界面框架(如AndroidFramework)构造一个QWidget*控件的形式不同,paint的形式用起来更复杂,但性能天花板更高

下面是一个使用自定义模型 LargeListModel 和委托 SimpleDelegate 的例子。在这个示例中,我们将创建一个自定义的 QAbstractListModel 类,名为 LargeListModel,它将处理大量数据项。此外,我们还将扩展 SimpleDelegate 类来自定义 QListView 中项的视觉呈现。这个委托将负责绘制项的背景、文本和一些装饰元素,从而提供更丰富的用户界面。

首先,我们定义 LargeListModel 类,该类派生自 QAbstractListModel

#include <QAbstractListModel>class LargeListModel : public QAbstractListModel {Q_OBJECT
public:explicit LargeListModel(int numItems, QObject *parent = nullptr): QAbstractListModel(parent), m_numItems(numItems) {}int rowCount(const QModelIndex &parent = QModelIndex()) const override {return parent.isValid() ? 0 : m_numItems;}QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {if (!index.isValid() || index.row() >= m_numItems)return QVariant();switch (role) {case Qt::DisplayRole:// 动态生成显示的数据return QStringLiteral("Item %1").arg(index.row());// 这里可以根据需要添加对其他角色的处理default:return QVariant();}}private:int m_numItems; // 数据项的数量
};

接下来,我们将自定义 SimpleDelegate 类,以便在 QListView 中渲染更复杂的项:

#include <QStyledItemDelegate>
#include <QPainter>class SimpleDelegate : public QStyledItemDelegate {
public:using QStyledItemDelegate::QStyledItemDelegate; // 继承构造函数void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {// 保存当前的绘图状态painter->save();// 绘制项的背景if (option.state & QStyle::State_Selected) {painter->fillRect(option.rect, option.palette.highlight());} else {painter->fillRect(option.rect, option.palette.base());}// 绘制自定义内容,例如文本QString text = index.data(Qt::DisplayRole).toString();painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, text);// 还原绘图状态painter->restore();}// 如果需要项的大小不是默认值,可以重写 sizeHint 方法QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override {return QSize(option.rect.width(), 50); // 设置项的高度为50}
};

最后,我们在 main 函数中创建 QListView,设置自定义的模型和委托,并显示它们:

#include <QApplication>
#include <QListView>int main(int argc, char *argv[]) {QApplication app(argc, argv);// 创建自定义模型和委托LargeListModel *model = new LargeListModel(100000); // 假设有 10 万个数据项SimpleDelegate *delegate = new SimpleDelegate();// 创建并设置 QListViewQListView listView;listView.setModel(model);listView.setItemDelegate(delegate);// 设置其他视图属性,如果需要listView.setSelectionMode(QAbstractItemView::SingleSelection);listView.setAlternatingRowColors(true); // 设置交替行颜色listView.show();return app.exec();
}

在这个示例中,LargeListModel 负责提供数据项,而 SimpleDelegate 负责自定义每个项的绘制。这种方法使得处理大量数据项时能够保持高性能,同时提供了丰富的视觉呈现和用户交互能力。通过委托的 paint 方法,我们可以绘制文本、图标、背景或任何其他图形元素来增强用户界面的视觉效果。通过重写 sizeHint 方法,我们可以为每个项定制大小,以适应不同的内容和设计需求。

这种模型-视图-委托的方法为高效地处理和展示大型数据集提供了灵活的解决方案,使得开发者可以在保持应用程序性能的同时,实现复杂且具有吸引力的用户界面。

进一步优化:处理大量动态数据

以上示例已经展示了如何使用 QListView 和自定义模型以及委托来处理静态数据。但在实际应用中,我们可能需要处理动态变化的数据集,其中项目可能会被添加、移除或更新。为了保持UI的响应性和数据的一致性,我们需要在模型中正确地处理这些变化。

数据添加示例

假设我们的 LargeListModel 需要动态添加数据,我们可以在模型中实现添加数据的逻辑,并通知视图更新:

class LargeListModel : public QAbstractListModel {// ...(省略已有代码)public:// ...(省略已有代码)// 添加新项的方法void addItem(const QString &title) {const int index = itemCount;beginInsertRows(QModelIndex(), index, index);titles.append(title);checkedItems.insert(index, false);itemCount++;endInsertRows();}// ...(省略已有代码)
};// 使用示例:
int main(int argc, char *argv[]) {QApplication app(argc, argv);// ...(省略已有代码)LargeListModel* model = new LargeListModel(0); // 初始时没有数据// ...(省略已有代码)// 动态添加数据for (int i = 0; i < 100000; ++i) {model->addItem(QString("动态项目 %1").arg(i));}// ...(省略已有代码)return app.exec();
}

在这个例子中,我们通过 addItem 方法在模型中添加新的数据项。在添加数据之前和之后,我们分别调用 beginInsertRowsendInsertRows 方法,这样 QListView 就会自动更新显示新添加的数据。

数据更新示例

如果我们需要更新现有数据,我们同样需要确保视图能够得到通知:

class LargeListModel : public QAbstractListModel {// ...(省略已有代码)public:// ...(省略已有代码)// 更新某项的方法void updateItem(int index, const QString &newTitle) {if(index >= 0 && index < itemCount) {titles[index] = newTitle;QModelIndex modelIndex = createIndex(index, 0);emit dataChanged(modelIndex, modelIndex);}}// ...(省略已有代码)
};

在这里,我们通过 updateItem 方法更新一条数据,并通过 dataChanged 信号告知视图特定项的数据已经改变。createIndex 方法用来创建一个指向已更新项的 QModelIndex 对象,这是发出 dataChanged 信号所必需的。

性能注意事项

处理大量数据时,以下是一些提高性能的常见做法:

  • 使用 beginInsertRowsendInsertRows(或对应的删除和更新版本)时,请确保避免同时进行大量的单独插入或删除操作,因为这会导致视图频繁更新,从而降低性能。相反,应该批量插入或删除。
  • 避免在 data 方法中执行耗时的计算。如果需要,可以将数据缓存或使用后台线程来准备数据。
  • 如果列表项的大小是固定的,使用 setUniformItemSizes(true) 可以提高滚动和渲染的性能。
  • 如果数据的读取是昂贵的操作,考虑实现延迟加载或数据分页,这样只有当数据真正需要显示时才读取。

一个更复杂的完整例子

刚刚我们循序渐进地了解了QListView高性能大规模列表的设计思想和实现步骤,接下来我们实现一个稍微复杂的例子,这个例子在列表的每一项中增加了一个复选框和一个按钮,表示这是一个复杂列表项的呈现。

#include <QApplication>
#include <QCheckBox>
#include <QListView>
#include <QPainter>
#include <QPushButton>
#include <QStandardItemModel>
#include <QStyledItemDelegate>
#include <QAbstractListModel>
#include <QHash>
#include <QVariant>class LargeListModel : public QAbstractListModel {
public:LargeListModel(int numItems, QObject* parent = nullptr): QAbstractListModel(parent), itemCount(numItems) {titles.resize(itemCount); // 初始化标题数组for (int i = 0; i < itemCount; ++i) {titles[i] = QString("标题文本 %1").arg(i); // 生成初始标题文本}}int rowCount(const QModelIndex& parent = QModelIndex()) const override {if (parent.isValid()) {return 0;}return itemCount;}QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override {if (!index.isValid() || index.row() >= itemCount || index.row() < 0) {return QVariant();}switch (role) {case Qt::DisplayRole:return titles.at(index.row());case Qt::CheckStateRole:{auto it = checkedItems.find(index.row());if (it != checkedItems.end()) {return QVariant(it.value() ? Qt::Checked : Qt::Unchecked);}return QVariant(Qt::Unchecked);}case Qt::EditRole: // 处理编辑角色return titles[index.row()];default:return QVariant();}}bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override {if (!index.isValid() || index.row() >= itemCount || index.row() < 0)return false;switch (role) {case Qt::CheckStateRole:// 更新 checkedItems,记录复选框的状态checkedItems[index.row()] = (value.toBool());emit dataChanged(index, index, { role });return true;case Qt::EditRole:// 更新标题文本titles[index.row()] = value.toString();emit dataChanged(index, index, { role });return true;default:return false;}}Qt::ItemFlags flags(const QModelIndex& index) const override {if (!index.isValid())return Qt::NoItemFlags;// 添加 Qt::ItemIsEditable 以支持编辑return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable;}private:int itemCount;mutable QHash<int, bool> checkedItems; // 存储复选框的状态QVector<QString> titles; // 存储标题文本
};// 自定义委托来绘制和处理列表项
class CustomDelegate : public QStyledItemDelegate {
public:CustomDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {}// 绘制列表项void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override {QStyleOptionButton buttonOption;QRect buttonRect = QRect(option.rect.right() - 80, option.rect.y() + 1, 78, option.rect.height() - 2);buttonOption.rect = buttonRect;QStyleOptionButton checkboxOption;QRect checkboxRect = QRect(option.rect.x() + 5, option.rect.y() + 5, option.rect.height() - 10, option.rect.height() - 10);checkboxOption.rect = checkboxRect;checkboxOption.state |= QStyle::State_Enabled;if (index.data(Qt::CheckStateRole).toBool()) {checkboxOption.state |= QStyle::State_On;} else {checkboxOption.state |= QStyle::State_Off;}painter->save();if (option.state & QStyle::State_MouseOver) {painter->fillRect(option.rect, option.palette.light());}// 根据是否有鼠标悬停,绘制高亮背景if (option.state & QStyle::State_MouseOver) {QRect highlightRect = option.rect;painter->fillRect(highlightRect, option.palette.highlight());}// 绘制复选框QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkboxOption, painter);// 绘制按钮buttonOption.text = "按钮";QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter);// 绘制文本QRect textRect = option.rect.adjusted(checkboxRect.width() + 10, 0, -buttonRect.width() - 10, 0);painter->drawText(textRect, Qt::AlignVCenter, index.data().toString());painter->restore();}// 处理事件,如复选框的点击bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) override {if (event->type() == QEvent::MouseButtonRelease) {QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);QRect buttonRect = QRect(option.rect.right() - 80, option.rect.y() + 5, 70, option.rect.height() - 10);QRect checkboxRect = QRect(option.rect.x() + 5, option.rect.y() + 5, option.rect.height() - 10, option.rect.height() - 10);if (buttonRect.contains(mouseEvent->pos())) {// 按钮被点击qDebug() << "按钮点击,项:" << index.row();} else if (checkboxRect.contains(mouseEvent->pos())) {// 切换复选框状态bool checked = !index.data(Qt::CheckStateRole).toBool();model->setData(index, checked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);qDebug() << "勾选checkbox,项:" << index.row();}return true; // 事件已处理}return QStyledItemDelegate::editorEvent(event, model, option, index);}// 提供项的大小提示QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override {return QSize(option.rect.width(), 34); // 调整为所需的大小}
};int main(int argc, char *argv[]) {QApplication app(argc, argv);QListView* listView = new QListView;auto insertCount = 100000;// 构造展示10w条数据CustomDelegate* delegate = new CustomDelegate(listView);listView->setModel(new LargeListModel(insertCount));listView->setItemDelegate(delegate);// 优化性能listView->setUniformItemSizes(true);listView->setSelectionMode(QAbstractItemView::SingleSelection);listView->show();return app.exec();
}

展示效果:
在这里插入图片描述

结语

通过遵循上述模式和性能最佳实践,可以更好地创建强大的、响应迅速的 Qt 应用程序。

简而言之,在QListView体系里,QAbstractListModel解决的是“数据存哪”,而QAbstractItemDelegate解决的是数据“如何展示”。这种模型/视图和委托的架构是 Qt 高效数据处理的基石,使得复杂的UI设计和数据操作变得可管理和可扩展。

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

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

相关文章

聚鼎科技:装饰画行业新手怎么入局

进入装饰画行业&#xff0c;对于新手而言既是机遇也是挑战。此行业融合了艺术感与市场需求&#xff0c;要求从业者不仅需要具备良好的审美能力&#xff0c;还要了解市场动态&#xff0c;掌握一定的营销策略。 明确定位是入局的第一步。新手应该确定自己想要涉足的装饰画领域&am…

高清视频+AI算法,EasyCVR视频智能监控方案打造无死角吸烟行为检测

一、背景与意义 1、吸烟危害&#xff1a;吸烟不仅有害健康&#xff0c;而且在特定场所带来的安全隐患极大。据统计&#xff0c;全年火灾事故中有五分之一系抽烟引起&#xff0c;引发的人员伤亡和财产损失巨大。 2、政策与法规&#xff1a;为了保护公共安全&#xff0c;消除消…

如何充分利用 Postgres 的内存设置

为了充分利用 PostgreSQL 的内存设置&#xff0c;你需要调整多个参数以优化数据库性能。这些参数包括共享缓冲区&#xff08;shared_buffers&#xff09;、工作内存&#xff08;work_mem&#xff09;、维护工作内存&#xff08;maintenance_work_mem&#xff09;、有效缓存大小…

【爬虫】requests 结合 BeautifulSoup抓取网页数据

一、BeautifulSoup使用步骤 BeautifulSoup 是一个用于从 HTML 或 XML 文件中提取数据的 Python 库。以下是如何使用 BeautifulSoup 来解析 HTML 并提取信息的基本步骤&#xff1a; 1、安装&#xff1a; 如果你还没有安装 BeautifulSoup&#xff0c;你可以使用 pip 来安装它。…

vue自定义一个回到顶部组件

1.首先创建一个backTop.vue页面&#xff1a; 页面有两个按钮&#xff0c;一个回到顶部按钮&#xff0c;一个刷新按钮(showRefresh:false将刷新按钮隐藏)&#xff0c;实现效果如下&#xff1a; 代码解析&#xff1a; domName:需要监听滚动的dom类名&#xff0c;不传默认监听bod…

优化查询性能:DolphinDB 时间类型数据比较规则详解

在数据库中&#xff0c;时间是一种常见的数据类型。在处理时间数据时&#xff0c;比较操作是非常常见的需求。然而&#xff0c;在不同的场景下&#xff0c;对时间类型数据进行比较时应用的规则不同。本文将从 DolphinDB 支持的时间类型开始&#xff0c;由浅入深分别介绍时间类型…

AI助力密码安全:利用机器学习提升密码安全性

信息安全已经成为了当今数字世界的一个核心问题&#xff0c;随着互联网技术使用场景的不断增加&#xff0c;创建和管理安全的密码已经成为了保证在线账户安全的关键要求。本文将研究和探讨如何利用人工智能&#xff08;AI&#xff09;和机器学习技术来提升密码的安全性。 学习目…

记录一下PHP使用微信小程序支付

记录一下PHP使用微信小程序支付V3版本经历 官方文档&#xff1a;https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_0.shtml 请详细查看文档中小程序支付接入前准备&#xff08;https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml&#xff…

教育培训知识付费在线课程小程序开发

教育培训知识付费在线课程小程序功能概述 核心功能 课程报名与缴费&#xff1a;支持线上报名、缴费&#xff0c;自定义课程时间、人数等。 砍价功能&#xff1a;用户通过分享邀请好友参与砍价&#xff0c;享受低价购买课程的优惠。 视频课程&#xff1a;支持倍速播放&#x…

JVM堆栈的区别、分配内存与并发安全问题、对象定位

一、堆和栈的区别 堆&#xff08;Heap&#xff09;和栈&#xff08;Stack&#xff09;是两种基本的数据结构&#xff0c;它们在内存管理、程序执行流程控制等方面扮演着重要角色。在编程语言尤其是Java这样的高级语言环境中&#xff0c;堆和栈的概念被用来描述程序运行时的内存…

【计算机网络】TCP报文详解

认识TCP报头 其实协议的形式都是一个结构化的数据&#xff0c;TCP协议也不例外。一起来看看TCP协议的报头是怎么样的。 以上就是TCP报头&#xff0c;实际上是一个结构化的数据&#xff0c;也就是一个结构体。例如&#xff1a; struct tcp_hdr {unsigned int stc_port : 16;un…

分布式光纤测温DTS与红外热成像系统的主要区别是什么?

分布式光纤测温DTS和红外热成像系统在应用领域和工作原理上存在显著的区别&#xff0c;两者具有明显的差异性。红外热成像系统适用于表现扩散式发热、面式场景以及环境条件较好的情况下。它主要用于检测物体表面的温度&#xff0c;并且受到镜头遮挡或灰尘等因素的影响会导致失效…

英语学习笔记35——Our village

Our village 我们的村庄 词汇 Vocabulary photograph n. 照片 通常说&#xff1a;photo 复数&#xff1a;photos     picture 复数&#xff1a;pictures 搭配&#xff1a;take a photo 照相 以o结尾的单词复数es的&#xff1a; potato —— potatoes tomato —— tomatoe…

Prometheus+Grafana监控MySQL

一、准备 grafana服务器&#xff1a;192.168.48.136Prometheus服务器&#xff1a;192.168.48.136被监控服务器&#xff1a;192.168.48.134、192.168.48.135查看时间是否同步 二、安装prometheus server 【2.1】安装 # 解压安装包 tar -zxvf prometheus-2.52.0.linux-amd64.t…

Element UI 日期组件自定义可选范围

官网地址 &#xff1a;Element - The worlds most popular Vue UI framework 自定义方式&#xff1a; 在data&#xff08;&#xff09;函数中定义 // 日期选择器可选范围expireTimeOption: {disabledDate: time > {// 判断是否是在任务时间段if (this.isTaskRandTime(time)…

数字人直播系统源码,不需要高价购买,只需这个价!

在技术领域&#xff0c;系统源码的价格往往令人咋舌&#xff0c;尤其是涉及到高端应用如数字人直播系统时。那么&#xff0c;一套数字人直播系统源码到底需要多少钱&#xff1f;面对高昂的价格&#xff0c;是否还值得进入这个行业&#xff1f; 首先&#xff0c;我们要认识到数…

UE5-不同材质上脚步声

主要是用物理材质给不同的材质加一个标签 创建材质 首先去设置里面创建几个地形材质名称&#xff0c;我这里创建了Grass&#xff0c;Rock&#xff0c;Wood&#xff0c;Water &#xff08;就是名字而已&#xff09; 然后创建物理材质&#xff08;物理材质可以添加到现有的普通…

虚拟仿真实训平台助力院校人才培养与产业发展共赢

一、虚拟仿真实训平台简单介绍 虚拟仿真实训平台适用于虚拟仿真实训教学场所&#xff0c;几乎兼容所有虚拟仿真实训显示设备&#xff0c;能够完成虚拟仿真实训资源进行跨专业、跨院校、跨地域的统筹管理。且虚仿平台具备虚拟仿真实训教学过程的监控分析及虚拟仿真实训资源集合…

WPF中的隧道路由和冒泡路由事件

文章目录 简介&#xff1a;一、事件最基本的用法二、理解路由事件 简介&#xff1a; WPF中使用路由事件升级了传统应用开发中的事件&#xff0c;在WPF中使用路由事件能更好的处理事件相关的逻辑&#xff0c;我们从这篇开始整理事件的用法和什么是直接路由&#xff0c;什么是冒…

探索大数据在信用评估中的独特价值

随着我国的信用体系越来越完善&#xff0c;信用将影响越来越多的人。现在新兴的大数据信用和传统信用&#xff0c;形成了互补的优势&#xff0c;大数据信用变得越来越重要&#xff0c;那大数据信用风险检测的重要性主要体现在什么地方呢?本文将详细为大家介绍一下&#xff0c;…