VTK知识学习(51)- 交互与Widget(二)

news2025/4/16 23:02:31

1、交互器样式

        前面所讲的观察者/命令模式是 VTK实现交互的方式之一。在前面示例 所示的窗口中可以使用鼠标与柱体进行交互,比如用鼠标滚轮可以对柱体放大、缩小;按下鼠标左键不放,然后移动鼠标,可以转动柱体;按下鼠标左键,同时按下(Shif)键,移动鼠标,可以移动整个柱体;按下〈Ctrl)键时,再按下鼠标左键可以实现旋转功能;鼠标停留在柱体上然后按下(P)键可以实现对象的选取;按下〈E)键可以退出 VTK应用程序等。

2、vtkRenderWindowInteractor

        vtkRenderWindowInteractor 类即渲染窗口交互器,它提供一种平台独立的响应鼠标/按键/时钟事件的交互机制,可将平台相关的鼠标/按键/时钟等消息路由至vtkInteractorObserver 或其子类。也就是说,vkRenderWindowInteractor作为一个基类,其具体的功能是由平台相关的子类(如 vtkWin32RenderWindowInteractor)来完成的。当它从窗口系统中监听到感兴趣的事件(消息)时,通过调用InvokeEvent()函数将平台相关的事件翻译成VTK事件,而这些 VTK 事件是平台独立的,然后再路由至 vtkInteractorObserver 或其子类,再由已经对该事件进行注册的 vtkInteractorObserver 或其子类响应具体的操作。

1)示例代码
private void TestInteraction()
        {
            vtkJPEGReader reader = vtkJPEGReader.New();
            reader.SetFileName("F:\\code\\VTK\\TestActiViz\\data\\VTKBook-TestImage.jpg");
            reader.Update();

            vtkImageActor imageActor = vtkImageActor.New();
            imageActor.SetInputData(reader.GetOutput());

            vtkRenderer renderer = vtkRenderer.New();
            renderer.AddActor(imageActor);
            renderer.SetBackground(1, 1, 1);

            vtkRenderWindow renWin = new vtkRenderWindow();// renderWindowControl.RenderWindow;
            renWin.AddRenderer(renderer);
            renWin.SetSize(640, 480);
            this.Dispatcher.Invoke(() => renWin.Render());
            renWin.SetWindowName("InteractionDemo");
            vtkRenderWindowInteractor iren = vtkRenderWindowInteractor.New();
            iren.SetRenderWindow(renWin);

            //该交互模具工预设了针对二维图像的交互功能,如同时按下《Ctrl》键和鼠标左键可以实现图像的旋转等。
            vtkInteractorStyleImage style = vtkInteractorStyleImage.New();
            iren.SetInteractorStyle(style);
            iren.Initialize();

            //  this.Dispatcher.BeginInvoke(new Action( kkk), null);
            iren.Start();
        }
2)效果

3)说明

        示例先读入一幅 JPG 图像,然后用 vtkImageActor、vtkRenderervtkRenderWindow等建立可视化管线。值得注意的是,在以上示例中,使用类vtknteractorStylelmage 作为交互器样式。该交互器样式预设了针对二维图像的交互功能,如同时按下〈Ctrl)键和鼠标左键可以实现图像的旋转;同时按下(Shif)键和鼠标左键可以实现图像平移;按住鼠标左键并移动鼠标可以调节图像的窗宽和窗位;按(R)键可以实现图像的窗宽和窗位的重置;滑动鼠标滚轮可以实现图像的放缩等。

        vkRenderWindowInteractor 是一个基类,具体的操作是由平台相关的子类实现。该示例程序是运行于Win32平台下的,因此,该平台下的消息先由vtkWin32RenderWindowInteractor 类捕获。这里以窗宽和窗位的重置功能为例,跟踪当用户按下〈R)键时,消息是如何传递的。

        首先分析当用户在渲染窗口中按下(R)键时,可能引发的消息有哪些。VTK染窗口在获得焦点的前提下,当用户按下(R〉键,先是触发了“按键按下”的消息,即Windows下的 WM KEYDOWN;然后触发 WM CHAR消息(这里先不考虑 WM KEYUP 消息)。

        主程序中实例化的是vtkRenderWindowInteractor 对象,程序调用的却是 vtkWin32RenderWindowInteractor 对象,VTK里是如何根据具体的平台来调用相关的类的呢?

        代码是调用 vtkGraphicsFactory::CreateInstance()函数来创建 vtkRenderWindowInteractor,从类的名字可以看出,这是从对象工厂中创建所需的对象实例。

        代码先根据对象类名从对象工厂中创建实例,如果成功创建即返回。会发现由 vtkObjectFactory::CreateInstance(vtkclassname)的返回值是0。后面即调用 vtkGraphicsFactory::GetRenderLibrary()来获取当前请求的渲染库类型。再继续,可以看出该返回值为“Win32OpenGL”从 vtkGraphicsFactory.cxx文件里的 vtkGraphicsFactory::CreateInstance()中。

         InteractionDemo 中调用了 vtkRenderWindowInteractor 的 Start()函数,该函数会调用一个名为StartEventLoop的虚函数。vtkWin32RenderWindowInteractor 覆盖了该函数。

在函数的最后就是一个列循环,即不断地调用Windows的APIGetMessage()函数从消息队列中获取消息,并将所获取的消息进行转换,再分发到当前的窗口程序中。

4)总结

        当在主程序中实例化 vkRenderWindowInteractor对象时,VTK 程序内部根据不同平台的渲染库实例化平台相关的 vtkRenderWindowInteractor 子类,由具体的平台相关的子类来响应窗口消息(如 vtkWin32RenderWindowInteractor)。vtkWin32RenderWindowInteractor::StartEventLoop()函数不断地从消息队列中获取消息,并分发给该类的回调函数vtkHandleMessage2(),该回调函数根据不同的消息调用相应的 OnXXXO消息响应函数(XXX指代消息名字),在每个消息响应函数里,通过调用 vtkObiect::InvokeEvent()将平台相关的消息再翻译成 VTK 事件,如按键按下的事件为 vtkCommand::KeyPressEvent。

3、vtkInteractorStyle

        继续以WMKEYDOWN消息为例,当示例程序停留在vtkWin32RenderWindowInteractor::OnKeyDown()函数的 InvokeEvent()处(即该类源文件的第600行)时,按(F11)键进入类vtkObiect的函数InvokeEvent(),代码如下:

        vtkObject::InvokeEvent()实际上调用的是SubjectHelper的同名函数。在变量名SubjectHelper 上右击,从弹出的快捷菜单中选择“Go To Definition”命令,可以看到该变量类型为 vtkSubjectHelper。该类在 vtkObject 内部定义,其主要作用是用于保存观察者(Observer)的列表,并负责注册事件,将事件分发给观察者。而vkSubiectHelper类内部事件的分发,则是由另一个辅助类 vtkObserver 来完成的,这个类也是在 vtkObiect 内部定义的。继续按(F11)键,跳至类vtkSubjectHelper::InvokeEventO函数体中,代码如下:

int vtkSubjectHelper::InvokeEvent(unsigned long event, void *callData,
                                   vtkObject *self)
{
  int focusHandled = 0;

  int saveListModified = this->ListModified;
  this->ListModified = 0;


  typedef std::vector<unsigned long> VisitedListType;
  VisitedListType visited;
  vtkObserver *elem = this->Start;
 
  const unsigned long maxTag = this->Count;

 
  vtkObserver *next;
  while (elem)
  {
   
    next = elem->Next;
    if (elem->Command->GetPassiveObserver() &&
        (elem->Event == event || elem->Event == vtkCommand::AnyEvent) &&
        elem->Tag < maxTag)
    {
      VisitedListType::iterator vIter =
        std::lower_bound(visited.begin(), visited.end(), elem->Tag);
      if (vIter == visited.end() || *vIter != elem->Tag)
      {
        // Sorted insertion by tag to speed-up future searches at limited
        // insertion cost because it reuses the search iterator already at the
        // correct location
        visited.insert(vIter, elem->Tag);
        vtkCommand* command = elem->Command;
        command->Register(command);
        elem->Command->Execute(self,event,callData);
        command->UnRegister();
      }
    }
    if (this->ListModified)
    {
      vtkGenericWarningMacro(<<"Passive observer should not call AddObserver or RemoveObserver in callback.");
      elem = this->Start;
      this->ListModified = 0;
    }
    else
    {
      elem = next;
    }
  }

  // 1. Focus loop
  //
  if (this->Focus1 || this->Focus2)
  {
    elem = this->Start;
    while (elem)
    {
     
      next = elem->Next;
      if (((this->Focus1 == elem->Command) || (this->Focus2 == elem->Command)) &&
          (elem->Event == event || elem->Event == vtkCommand::AnyEvent) &&
          elem->Tag < maxTag)
      {
        VisitedListType::iterator vIter =
          std::lower_bound(visited.begin(), visited.end(), elem->Tag);
        if (vIter == visited.end() || *vIter != elem->Tag)
        {
          // Don't execute the remainder loop
          focusHandled = 1;
          // Sorted insertion by tag to speed-up future searches at limited
          // insertion cost because it reuses the search iterator already at the
          // correct location
          visited.insert(vIter, elem->Tag);
          vtkCommand* command = elem->Command;
          command->Register(command);
          command->SetAbortFlag(0);
          elem->Command->Execute(self,event,callData);
          // if the command set the abort flag, then stop firing events
          // and return
          if(command->GetAbortFlag())
          {
            command->UnRegister();
            this->ListModified = saveListModified;
            return 1;
          }
          command->UnRegister();
        }
      }
      if (this->ListModified)
      {
        elem = this->Start;
        this->ListModified = 0;
      }
      else
      {
        elem = next;
      }
    }
  }

  // 2. Remainder loop
  //
  if (!focusHandled)
  {
    elem = this->Start;
    while (elem)
    {
      // store the next pointer because elem could disappear due to Command
      next = elem->Next;
      if ((elem->Event == event || elem->Event == vtkCommand::AnyEvent) &&
          elem->Tag < maxTag)
      {
        VisitedListType::iterator vIter =
          std::lower_bound(visited.begin(), visited.end(), elem->Tag);
        if (vIter == visited.end() || *vIter != elem->Tag)
        {
          // Sorted insertion by tag to speed-up future searches at limited
          // insertion cost because it reuses the search iterator already at the
          // correct location
          visited.insert(vIter, elem->Tag);
          vtkCommand* command = elem->Command;
          command->Register(command);
          command->SetAbortFlag(0);
          elem->Command->Execute(self,event,callData);
          // if the command set the abort flag, then stop firing events
          // and return
          if(command->GetAbortFlag())
          {
            command->UnRegister();
            this->ListModified = saveListModified;
            return 1;
          }
          command->UnRegister();
        }
      }
      if (this->ListModified)
      {
        elem = this->Start;
        this->ListModified = 0;
      }
      else
      {
        elem = next;
      }
    }
  }

  this->ListModified = saveListModified;
  return 0;
}

        该函数的实现比较复杂,这里只需关注三个while循环体里的if语句。以上函数在处理事件时,将事件观察者分为三类,分别是被动观察者(Passive)、焦点观察者(Focus)及其他类型。被动观察者是指其所监听的事件或命令是不改变系统状态的,可以通过vtkCommand::GetPassiveObserver()获取该标志的值;焦点观察者是指该观察者所监听的事件可以让窗口获得焦点,比如,用户用鼠标单击窗口后,窗口可以获得焦点,则监听VTK 事件LeftButtonPressEvent(见表8-1)的观察者即为焦点观察者。

        显然,监听WM KEYDOWN 消息所对应的VTK事件KeyPressEvent的观察者是以上两种观察者之外的类型。所以,在以上代码的第三个循环体(第602行代码)放置一个断点,然后按(F5)键运行程序。
        这时,程序会停留在放置的断点位置上(第602行),这时可以在VS2008窗口中将vtkSubjectHelper:InvokeEvent()里的参数event拖至变量的观测窗口中看看该变量的值。该事件的值为20,对照表8-1可知编号为20的事件为KeyPressEvent。继续按(F11〉键,程序跳至类vtkCallbackCommand::Excute()函数中,代码如下:

        变量 Callback 的值是在 vtkSubjectHelper:InvokeEvent()函数里赋值的。继续按(F11)键,程序正如变量 Callback 的值所示,将跳至 vtkInteractorStyle::ProcessEvents(函数中,代码如下:

        

        vtkInteractorStyle::ProcessEvents()函数很长,但并不复杂。从上述代码可以看出,该函数主要就是一个 switch 语句,根据不同的 VTK事件,调用 vtkInteractorStyle 不同的函数进行响应,比如所跟踪的 KeyPressEvent 事件,程序将调用 OnKeyDown()和 OnKeyPress()函数进行响应,而响应 VTK 事件的函数都声明为虚函数,换言之,这些事件都是在 vtkInteractorStyle的子类中实现的。对于KeyPressEvent 事件,vtkInteractorStyle 的子类vtkInteractorStyleTrackballCamera 和 vtkInteractorStylelmage 都没有重载 OnKeyDown()和 OnKeyPress()函数。

        前面提过,当用户在渲染窗口中按下(R)键时,先触发VTK的KeyPressEvent 事件,然后触发 CharEvent 事件。

         OnChar()是虚函数,vtkInteractorStyle 的子类 vtkInteractorStylelmage 已经覆盖了该函数,而且刚好子类vtkInteractorStylelmage::OnChar()函数中有针对(R〉键的响应,所以父类的 OnChar()函数也就不调用了,代码如下:

        从上述代码可以看出,响应用户(R)键消息的OnChar0)函数实际实现的功能就是设置
图像的窗宽和窗位等信息(代码第440~443行)。

        至此,键盘按键按下消息(KeyPressEvent和CharEvent)的传递过程已经比较清晰了。

        总结如下:Windows 消息被 vtkWin32RenderWindowInteractor 捕获以后,先由该类的回调函数 vtkHandleMessage2()分发至各个消息响应函数,在每个消息响应函数的最后,通过调用vtkObject::InvokeEvent()将 Windows 消息翻译成 VTK 事件。

在 vkObject::InvokeEvent()函数里,通过类 vtkSubjectHelper::InvokeEvent()函数再将各个 VTK 事件分发到不同的观察者中,观察者调用回调函数 vtkInteractorStyle::ProcessEvents()处理不同的 VTK事件,再将这些VTK 事件分发至 vtkInteractorStyle 或其子类的消息响应函数中,从而完成整个消息的传递过程。

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

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

相关文章

底盘---麦克纳姆轮(Mecanum Wheel)

一、基本定义与起源 定义&#xff1a;麦克纳姆轮是一种实现全向移动的特殊轮式结构&#xff0c;通过在主轮周边安装多个倾斜的辊子&#xff08;小轮&#xff09;&#xff0c;使设备能够在平面上向任意方向移动&#xff08;包括横向、斜向、旋转等&#xff09;&#xff0c;无需…

深入源码级别看spring bean创建过程

我们通常聊到spring bean的生命周期&#xff0c;大多是从网上找帖子背些基本概念&#xff0c;这样我们学到的东西是不够直观清晰的&#xff0c;这篇文章我就试着从源码级别来讲清楚bean的创建过程。 一、准备demo代码 我们既然要深入源码来看bean的创建过程&#xff0c;那么就…

I/O进程1

day1 一、标准IO 1.概念 在C库中定义的一组用于输入输出的函数 2.特点 (1).通过缓冲机制减少系统调用&#xff0c;提高效率 (2.)围绕流进行操作&#xff0c;流用FILE *来描述(3).标准IO默认打开了三个流&#xff0c;stdin&#xff08;标准输入&#xff09;、stdout&#xff08;…

解决Python与Anaconda中pip的冲突,安装包失败问题(此应用无法在你电脑上运行,无法访问)

1、Anaconda安装在D盘 2、Python安装在C盘&#xff08;当时刚换电脑&#xff0c;新电脑还未分盘&#xff0c;着急用python直接安装&#xff09; 问题&#xff1a; &#xff08;1&#xff09;winr&#xff0c;cmd无法访问c盘下的pip&#xff0c;安装包失败。采用管理者身份&…

Java全栈面试宝典:JMM内存模型与Spring自动装配深度解析

目录 一、Java内存模型&#xff08;JMM&#xff09;核心原理 &#x1f525; 问题8&#xff1a;happens-before原则全景解析 JMM内存架构图 happens-before八大规则 线程安全验证案例 &#x1f525; 问题9&#xff1a;JMM解决可见性的三大武器 可见性保障机制 volatile双…

AI前端组件库Ant DesIgn X

Ant Design X AI&#xff1a;体验新秩序 Ant Design 团队精心打造 RICH 设计范式&#xff0c;为 AI 界面提供卓越解决方案&#xff0c;引领智能交互新体验。 设计语言与理论 官网&#xff1a; Ant Design X - 轻松打造 AI 驱动的界面。 AI 设计范式 —— RICH 是我们在蚂蚁…

追踪大型语言模型的思维过程:提示词工程重要

追踪大型语言模型的思维过程:提示词工程重要 目录 追踪大型语言模型的思维过程:提示词工程重要**1. 分步思考能力:像人类一样打草稿****2. 跨语言概念词典:突破语言符号的束缚****3. 诗歌押韵规划:神经元提前预留韵脚****4. 编造专业解释:数据模式导致的“客服式回应”**…

BGP路由协议之属性1

公认属性是所有 BGP 路由器都必须能够识别的属性 公认必遵 (Well-known Mandatory) : 必须包括在每个 Update 消息里公认任意 (Well-known Discretionary) : 可能包括在某些 Update 消息里。 可选属性不需要都被 BGP 路由器所识别 可选过渡(OptionalTransitive) : BGP 设备不…

什么是 k8s 的 Taints(污点) 和 Tolerations(容忍度)

什么是 k8s 的 Taints&#xff08;污点&#xff09; 和 Tolerations&#xff08;容忍度&#xff09; 在 Kubernetes&#xff08;K8s&#xff09;中&#xff0c;Taints&#xff08;污点&#xff09;和 Tolerations&#xff08;容忍度&#xff09;用于影响 Pod 调度到节点的行为…

C++类模板的运用

使用vector实现一个简单的本地注册登录系统 注册&#xff1a;将账号密码存入vector里面&#xff0c;注意防重复判断 登录&#xff1a;判断登录的账号密码是否正确 #include <iostream> #include <vector> #include <string> #include <algorithm>us…

coze生成流程图和思维导图工作流

需求&#xff1a;通过coze平台实现生成流程图和思维导图&#xff0c;要求支持文档上传 最终工作流如下&#xff1a; 入参&#xff1a; 整合用户需求文件内容的工作流&#xff1a;https://blog.csdn.net/YXWik/article/details/147040071 选择器分发&#xff0c;不同的类型走…

【数据库】达梦arm64安装

话不多说&#xff0c;快速开始~ 1.下载 进入官网&#xff1a; 产品下载 | 达梦在线服务平台 下载安装包。 选飞腾、鲲鹏都可以&#xff0c;都是arm架构的。我选择的是&#xff1a; 直接下载地址是https://download.dameng.com/eco/adapter/DM8/202502/dm8_20250117_HWarm920…

leetcode274.H指数

直接排序完后进行遍历 class Solution {public int hIndex(int[] citations) {Arrays.sort(citations);int result 0;for (int i citations.length-1; i >0; i--) {if(citations[i]>citations.length-i)resultcitations.length-i;elsebreak;}return result;} }

内网文件传输新体验,聊天、传输、自定义,一应俱全

Flix 是一款高效、便捷的跨平台局域网文件传输工具&#xff0c;支持 Windows、macOS、Android、iOS 和 Linux 等多种操作系统。它以简洁直观的聊天式界面为特色&#xff0c;让用户能够像发送消息一样轻松地传输文件&#xff0c;无需复杂的设置或登录。Flix 支持大文件和多种格式…

Vue PDF Annotation plugin library online API examples

This article introduces the online version of the ElasticPDF API tutorial for the PDF annotation plug-in library in Vue projects. The API includes ① Export edited PDF data; ② Export annotations json data; ③ Reload old annotations; ④ Change files; ⑤ Se…

C语言传参寄存器压栈流程总结

相关 《Linux函数调用栈的实现原理&#xff08;X86&#xff09;》 总结 rsp向低地址生长&#xff08;栈顶&#xff09;&#xff0c;rbp记录旧值&#xff08;栈底&#xff09;。 intel x86测试&#xff0c;六个和六个以内的参数用寄存器传递。8个参数场景&#xff0c;6个用寄存…

C盘清理——快速处理

C盘清理 | 快速处理 软件&#xff1a;小番茄C盘清理 https://ccleancdn.xkbrowser.com/cleanmaster/FanQieClean_13054_st.exe 前言&#xff1a;为什么需要专业的C盘清理工具&#xff1f; 作为一位长期与Windows系统打交道的技术博主&#xff0c;我深知C盘空间不足带来的痛苦…

前端服务配置详解:从入门到实战

前端服务配置详解&#xff1a;从入门到实战 一、环境配置文件&#xff08;.env&#xff09; 1.1 基础结构 在项目根目录创建 .env 文件&#xff1a; # 开发环境 VUE_APP_API_BASE_URL http://localhost:3000/api VUE_APP_VERSION 1.0.0# 生产环境&#xff08;.env.produc…

历年跨链合约恶意交易详解(四)——Chainswap20210711

漏洞合约函数 function receive(uint256 fromChainId, address to, uint256 nonce, uint256 volume, Signature[] memory signatures) virtual external payable {_chargeFee();require(received[fromChainId][to][nonce] 0, withdrawn already);uint N signatures.length;r…

Python基于OpenCV和SVM实现中文车牌识别系统GUI界面

说明&#xff1a;这是一个系统实战项目&#xff0c;如需项目代码可以直接到文章最后关注获取。 项目背景 随着智能交通系统和智慧城市的发展&#xff0c;车牌识别技术在车辆管理、交通监控、停车场收费等领域发挥着重要作用。传统的车牌识别系统主要针对英文和数字的识别&…