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

1、交互器样式

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

2、vtkRenderWindowInteractor

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

1)示例代码
cs 复制代码
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函数体中,代码如下:

cpp 复制代码
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 或其子类的消息响应函数中,从而完成整个消息的传递过程。

相关推荐
西岸行者7 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意7 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码8 天前
嵌入式学习路线
学习
毛小茛8 天前
计算机系统概论——校验码
学习
babe小鑫8 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms8 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下8 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。8 天前
2026.2.25监控学习
学习
im_AMBER8 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J8 天前
从“Hello World“ 开始 C++
c语言·c++·学习