在 CAD 和可视化软件中,鼠标悬停高亮是一种常见的交互反馈方式。当鼠标移动到某个对象上时,该对象会以特殊效果显示,帮助用户快速识别当前指向的对象。本文将介绍如何在 ParaView/VTK 二次开发中实现这一功能。
功能需求
- 鼠标移动时实时检测悬停的 Block
- 悬停的 Block 显示高亮轮廓
- 悬停高亮与选择高亮区分显示
- 显示 Tooltip 提示 Block 名称
- 性能优化:避免频繁的 pick 操作
整体架构
1 2 3 4 5
| HoverHighlightManager ├── 鼠标事件监听 (eventFilter) ├── Pick 检测 (vtkSMRenderViewProxy::PickBlock) ├── 高亮渲染 (vtkActor + vtkFeatureEdges) └── Tooltip 显示 (QToolTip)
|
核心实现
1. 事件过滤器
通过 QObject::eventFilter 拦截鼠标移动事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| bool HoverHighlightManager::eventFilter(QObject *watched, QEvent *event) { if (!m_enabled || watched != m_viewWidget) return QObject::eventFilter(watched, event); switch (event->type()) { case QEvent::MouseMove: onMouseMove(static_cast<QMouseEvent*>(event)); break; case QEvent::Leave: clearHoverHighlight(); break; default: break; } return QObject::eventFilter(watched, event); }
|
2. 鼠标移动处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| void HoverHighlightManager::onMouseMove(QMouseEvent *event) { if (m_pickTimer.isValid() && m_pickTimer.elapsed() < m_pickInterval) { return; } m_pickTimer.restart(); int x = event->pos().x(); int y = m_viewWidget->height() - event->pos().y() - 1; vtkSMRenderViewProxy *renderViewProxy = m_renderView->getRenderViewProxy(); vtkSMProxy *reprProxy = nullptr; vtkIdType flatIndex = renderViewProxy->PickBlock(x, y, &reprProxy); if (flatIndex < 0) { clearHoverHighlight(); return; } if (flatIndex == m_currentHoverIndex) return; m_currentHoverIndex = flatIndex; updateHoverHighlight(flatIndex, reprProxy); showTooltip(event->globalPos(), flatIndex); }
|
3. Pick 频率优化
频繁的 pick 操作会影响性能,使用计时器限制调用频率:
1 2 3 4 5 6
| class HoverHighlightManager { private: QElapsedTimer m_pickTimer; int m_pickInterval = 50; vtkIdType m_currentHoverIndex = -1; };
|
4. 高亮渲染
悬停高亮使用与选择高亮不同的颜色,便于区分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void HoverHighlightManager::setupHighlightActor() { m_surfaceFilter = vtkSmartPointer<vtkDataSetSurfaceFilter>::New(); m_featureEdges = vtkSmartPointer<vtkFeatureEdges>::New(); m_featureEdges->BoundaryEdgesOn(); m_featureEdges->FeatureEdgesOn(); m_featureEdges->ManifoldEdgesOff(); m_featureEdges->NonManifoldEdgesOff(); m_featureEdges->SetFeatureAngle(30.0); m_mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); m_mapper->ScalarVisibilityOff(); m_hoverActor = vtkSmartPointer<vtkActor>::New(); m_hoverActor->SetMapper(m_mapper); m_hoverActor->SetPickable(false); m_hoverActor->VisibilityOff(); vtkProperty *prop = m_hoverActor->GetProperty(); prop->SetColor(0.0, 1.0, 1.0); prop->SetLineWidth(2.0f); prop->SetLighting(false); prop->SetAmbient(1.0); prop->SetDiffuse(0.0); }
|
5. 更新悬停高亮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| void HoverHighlightManager::updateHoverHighlight(vtkIdType flatIndex, vtkSMProxy *reprProxy) { if (!m_hoverActor) return; vtkDataObject *dataObj = getBlockData(flatIndex, reprProxy); if (!dataObj) { clearHoverHighlight(); return; } vtkDataSet *dataSet = vtkDataSet::SafeDownCast(dataObj); if (!dataSet || dataSet->GetNumberOfCells() == 0) { clearHoverHighlight(); return; } m_surfaceFilter->SetInputData(dataSet); m_surfaceFilter->Update(); m_featureEdges->SetInputConnection(m_surfaceFilter->GetOutputPort()); m_featureEdges->Update(); m_mapper->SetInputConnection(m_featureEdges->GetOutputPort()); m_hoverActor->VisibilityOn(); vtkRenderWindow *renderWindow = getActiveRenderWindow(); if (renderWindow) { renderWindow->Render(); } }
|
6. 清除悬停高亮
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void HoverHighlightManager::clearHoverHighlight() { m_currentHoverIndex = -1; if (m_hoverActor) { m_hoverActor->VisibilityOff(); } QToolTip::hideText(); vtkRenderWindow *renderWindow = getActiveRenderWindow(); if (renderWindow) { renderWindow->Render(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| void HoverHighlightManager::showTooltip(const QPoint &globalPos, vtkIdType flatIndex) { QString blockName = getBlockName(flatIndex); if (blockName.isEmpty()) { blockName = QString("Block %1").arg(flatIndex); } QToolTip::showText(globalPos, blockName, m_viewWidget); }
QString HoverHighlightManager::getBlockName(vtkIdType flatIndex) { if (!m_inspector) return QString(); int currentIndex = 0; for (int i = 0; i < m_inspector->topLevelItemCount(); ++i) { QTreeWidgetItem *item = findItemByFlatIndex( m_inspector->topLevelItem(i), flatIndex, currentIndex); if (item) { return item->text(0); } } return QString(); }
|
与选择高亮的协调
悬停高亮和选择高亮需要协调工作:
1 2 3 4 5 6 7 8 9 10
| void HoverHighlightManager::updateHoverHighlight(vtkIdType flatIndex, vtkSMProxy *reprProxy) { if (m_selectionManager && m_selectionManager->isBlockSelected(flatIndex)) { clearHoverHighlight(); return; } }
|
性能优化技巧
1. 限制 Pick 频率
1 2 3 4
| if (m_pickTimer.elapsed() < 50) return; m_pickTimer.restart();
|
2. 缓存当前悬停状态
1 2 3
| if (flatIndex == m_currentHoverIndex) return;
|
3. 复用 VTK Filter
1 2 3
| m_surfaceFilter->SetInputData(dataSet); m_surfaceFilter->Update();
|
1 2 3 4 5 6 7 8
| m_tooltipTimer->start(500);
void HoverHighlightManager::onTooltipTimeout() { if (m_currentHoverIndex >= 0) { QToolTip::showText(QCursor::pos(), getBlockName(m_currentHoverIndex)); } }
|
完整类定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| class HoverHighlightManager : public QObject { Q_OBJECT public: explicit HoverHighlightManager(QObject *parent = nullptr); ~HoverHighlightManager(); void setRenderView(pqRenderView *renderView); void setMultiBlockInspector(MultiBlockInspector *inspector); void setSelectionManager(SelectionHighlightManager *selectionManager); void setEnabled(bool enabled); bool isEnabled() const; void setHoverColor(double r, double g, double b); void setHoverLineWidth(float width); void setPickInterval(int milliseconds); void setTooltipEnabled(bool enabled); signals: void blockHovered(int blockIndex); void hoverCleared(); protected: bool eventFilter(QObject *watched, QEvent *event) override; private: void onMouseMove(QMouseEvent *event); void updateHoverHighlight(vtkIdType flatIndex, vtkSMProxy *reprProxy); void clearHoverHighlight(); void showTooltip(const QPoint &globalPos, vtkIdType flatIndex); QString getBlockName(vtkIdType flatIndex); vtkDataObject* getBlockData(vtkIdType flatIndex, vtkSMProxy *reprProxy); vtkRenderWindow* getActiveRenderWindow(); vtkRenderer* getActiveRenderer(); private: pqRenderView *m_renderView = nullptr; QWidget *m_viewWidget = nullptr; MultiBlockInspector *m_inspector = nullptr; SelectionHighlightManager *m_selectionManager = nullptr; bool m_enabled = true; bool m_tooltipEnabled = true; vtkSmartPointer<vtkActor> m_hoverActor; vtkSmartPointer<vtkDataSetSurfaceFilter> m_surfaceFilter; vtkSmartPointer<vtkFeatureEdges> m_featureEdges; vtkSmartPointer<vtkPolyDataMapper> m_mapper; QElapsedTimer m_pickTimer; int m_pickInterval = 50; vtkIdType m_currentHoverIndex = -1; double m_hoverColor[3] = {0.0, 1.0, 1.0}; float m_hoverLineWidth = 2.0f; };
|
总结
实现鼠标悬停高亮功能的关键点:
- 事件监听:通过
eventFilter 拦截 MouseMove 和 Leave 事件
- Pick 检测:使用
vtkSMRenderViewProxy::PickBlock 检测悬停对象
- 性能优化:限制 pick 频率、缓存悬停状态、复用 VTK Filter
- 视觉反馈:使用与选择高亮不同的颜色,配合 Tooltip 显示名称
- 协调工作:与选择高亮配合,已选中的对象不显示悬停高亮
这套实现可以为用户提供流畅的交互体验,让用户在操作前就能预览将要选择的对象。
参考资料