VTK知识学习(51)- 交互与Widget(三)
1、概述
从前面的 内容可知,交互器样式(如 vtknteractorStylelmage)主要是根据不同的键盘、鼠标等消息来控制相机(vtkCamera)、Actor 等相关参数,从而达到交互的目的。而在渲染场景中,这些交互器样式是没有表达实体的。也就是说,在交互之前,用户必须知道哪些键盘消息或者鼠标消息是与哪些事件绑定的,在整个交互过程中,用户“看不到”交互器样式长什么样子,比如,使用 vtkInteractorStylelmage 交互器样式时,必须知道按键(R)是用于窗宽窗位、相机参数等的重置,鼠标中键可以平移图像,按住鼠标左键不放然后移动鼠标可以调节窗宽窗位等。
但是,在与渲染场景中的对象进行交互时,如果可以“看得见”交互的样式,这样的交互过程也许会变得更加人性化,比如,要在地图上测量 AB两点之间的距离,直观的做法就是:在A点上单击,当松开鼠标后,程序在单击的位置上生成一个端点(该端点可以是圆形十字形或者其他任何形状),然后移动鼠标至终点,鼠标移动过程中,在A点与鼠标光标的当前位置之间生成一条直线,当鼠标移动至B点时再单击B点位置,即可显示出 AB两点的距离以及在 AB两点之间生成一条直线。显然,这样的交互方式比上一节的交互器样式更加直观、生动。
VTK的交互除了提供各种交互器样式,还提供了功能更为强大的、可以“看得见”的交互部件,即 Widget。VTK 的 Widget 类主要包括 vtk3DWidget 和 vtkAbstractWidget 两个父类其继承关系如图8-3所示。从图8-3可知,vtk3DWidget和vtkAbstractWidget 都派生自vtkInteractorObserver,其中前者主要是在三维渲染场景中生成一个可以用于控制数据的可视化实体,比如点、线段(曲线)、平面、球体、包围盒(线框)等;而后者是 VTK 里实现“交互/表达实体(Interaction/Representation)”设计的所有 Widget 的基类。
vtk3DWidget和 vtkAbstractWidget 的共同基类vtkInteractorObserver里的虚函数 OnChar()主要是用于响应交互的开关状态,即当用户按下(I)键时,可以实现 Widget 表达实体的显示与隐藏及决定其是否响应用户消息。对应的方法为:
vtkInteractorObserver::SetEnable(int);
vtkInteractorObserver::EnableOn(;
vtkInteractorObserver::EnableOffO;
vtkInteractorObserver::On();
vtkInteractorObserver::Of()
VTK 中 Widget 的设计是从 VTK 5.0版本开始引入的,最初的 Widget 是从 vtk3DWidget派生出来的,从 VTK 5.1版本开始,VTK Widget重新进行设计,主要的设计理念是将 Widget的消息处理与几何表达实体分离,但还是保留了vtk3DWidget 及其子类。vtkAbstractWidget作为基类,只定义一些公共的 API以及实现了“交互/表达实体”分离的设计机制,其中,把从 vtkRenderWindowInteractor 路由过来的消息(事件)交给 vtkAbstractWidget 的“交互”部分处理,而 Widget 的“表达实体”则对应一个 vkProp对象(或者是vtkWidgetRepresentation的子类)。这样做的好处是:事件处理与Widget的表达实体互不干扰,而且可以实现同类 Widget使用不同的表达形式,比如,对于测量距离的Widget 来说,可以定义两个十字形来作为该Widget 的两个端点(也可以定义两个球体来表达)。
此外,vtkAbstractWidget类提供了访问vtkWidgetEventTranslator 对象的函数,即GetEventTranslator(),该对象的作用可以将VTK事件(见表8-1)映射为 Widget 事件(定义于 vtkWidgetEvent.h 文件中),通过 vtkWidgetEventTranslator类,用户可以定制符合自己使用习惯的控制 Widget 的事件绑定,比如,对于一个测量长度的 Widget(vtkDistanceWidget),默认的操作是鼠标左键可以确定两个端点的位置,如果对这种操作不习惯,想用鼠标右键来实现同样功能,可以通过以下代码来完成:
vtkSmartPointer<vtkSliderWidget> sliderWidget =
vtkSmartPointer<vtkSliderWidget>::New();
sliderWidget->GetEventTranslator()->SetTranslation(vtkCommand::RightButtonPressEvent,
vtkWidgetEvent::Select);
sliderWidget->GetEventTranslator()->SetTranslation(vtkCommand::RightButtonReleaseEvent,
vtkWidgetEvent::EndSelect);
每个 vtkAbstractWidget 子类的内部,都会根据各个子类的功能,使用类 vtkWidgetEventTranslator,将 VTK 事件翻译成 Widget 事件,同时,利用类 vtkWidgetCallbackMapper 将相应的 Widget 事件与各个受保护的静态操作函数关联起来,它们之
间的关系如图 所示。
以 vtkDistanceWidget为例,在该类的构造函数中,有如下代码:
上述代码中的 CallbackMapper 即为vtkWidgetCallbackMapper 类型,SetCallbackMethod(函数的代码如下:
从以上两段代码可以看出,vtkWidgetCallbackMapper::SetCallbackMethod()将 VTK 消息与实际的操作函数联系起来,SetCallbackMethod()函数内部则是调用vtkWidgetEventTranslator:.SetTranslation()方法将 VTK事件翻译成 Widget事件,这种实现机制有点类似Ot里的信号-槽连接。
2、创建Widget交互
虽然每个 Widget 都提供了不同的功能以及不同的 API,但是 Widget 的创建以及使用基本都是类似的,一般步骤如下。
1)实例化 Widget。
2)指定渲染窗口交互器。Widget 可以通过它监听用户事件。
3)必要时使用观察者/命令模式创建回调函数。与 Widget 交互时,它会调用一些通用的 VTK事件,如 StartInteractionEvent、InteractionEvent 以及EndInteractionEvent。用户通过监听这些事件并做出响应,从而可以更新数据、可视化参数或者应用程序的用户图形界面等。
4)创建合适的几何表达实体,并用 SetRepresentation()函数把它与 Widget 关联起来,或
者使用 Widget 默认的几何表达实体。
5)最后必须激活 Widget,使其在渲染场景中显示。默认情况下,按键(I)用于激活Widget,使其在场景中可见。
正如前面所述,如果对 Widget默认的事件绑定不满意,需要根据自己习惯自定义事件绑定,可以使用 vtkWidgetEventTranslator 类。同样,也可以使用该类的 RemoveTranslation()函数取消已经绑定的事件,代码如下:
vtkWidgetEventTranslator *eventTranslator = contourWidget->GetEventTranslator();
eventTranslator->RemoveTranslation( vtkCommand::RightButtonPressEvent );
VTK Widget 除了响应来自用户的事件以外,也响应一些其他事件,比如时钟事件。以vtkBalloonWidget为例,该 Widget主要是用于当鼠标在某个 Actor 上停留指定的时间间隔后,弹出文本或图像等类型的提示信息。所以,对于这个Widget来说,它会监听交互器上的MouseMoveEvent和TimerEvent 事件,当鼠标在某个 Actor 上停留的时间达到用户设定的“TimerDuration’(时间间隔)时,就会执行相应操作。
对于渲染窗口交互器的事件来说,有可能在某一时刻有多个对象监听,这些类包括vtkInteractorObserver 的所有子类,如 vtkInteractorStyle 或者场景中的一个或多个 Widget 类。在渲染场景中移动鼠标时,如果不是在某个Widget上移动,鼠标的移动事件就会被vtkInteractorStyle 捕获;如果是在某个 Widget 上移动,鼠标的移动事件就会被这个 Widget 捕获。这种情景可能会导致事件的竞争。而对事件竞争的处理机制就是优先级(Priorities)。所有 vtkInteractorObserver 的子类都会通过 SetPriority0)函数设置一个优先级。拥有高优先级的对象比低优先级的对象优先处理事件,还可以对捕获到的事件选择处理还是丢弃,实际上就是获取到了“焦点”(Focus)。实际上,Widget可以比 vtkInteractorStyle 优先处理事件也是因为它拥有比 vtkIneractorStyle 高的优先级。
VTK中主要的Widget类以及相应的样式: