VTK知识学习(50)- 交互与Widget(一)

1、前言

一个强大的可视化系统不仅需要强大的数据处理能力,也需要方便易用的交互功能。图形处理软件ParaView(http://www.paraview.org)、德国癌症研究中心研发的MITK(http://www.mitk.org)等开源软件系统都提供了强大的交互功能,作为ParaView、MITK 等软件构建基础的 VTK 同样也提供了各种各样的交互功能。VTK 的交互除了可以监听来自鼠标、键盘等外部设备的消息,还可以在渲染场景中生成功能各异的交互部件(Widget),用于控制可视化过程的参数,达到用户的渲染要求。本章内容将介绍VTK的交互功能,包括命令/观察者模式(Command/Observer)以及 VTK 提供的各种用于交互的 Widget。

2、观察者/命令模式

观察者/命令模式(Observer/Command)是 VTK里用得较多的设计模式。从 vtkCommand 派生出类 vklmageInteractionCallback,并实现了该类的Execute()函数来完成通过滑动鼠标切换三维图像切面的功能。本节将详细介绍 VTK 中这种交互行为是如何实现的。

VTK中绝大多数的类都派生自vtkObject。查看类vtkObject的接口可以找到AddObserver0)、RemoveObserver()、GetCommand()等函数,从函数的字面意思可以看出,这些函数是与观察者/命令模式相关的。

观察者/命令模式是指一个Obiect可以有多个 Observer,它定义了对象间的一种一对多的依赖关系,当一个 Obiect 对象的状态发生改变时,所有依赖于它的Observer 对象都得到通知而被自动更新。命令模式属于对象行为模式,它将一个请求封装为一个对象,并提供一致性发送请求的接口,当一个事件发生时,它不直接把事件传递给事件调用者,而是在命令和调用者之间增加一个中间者,将这种直接关系切断,同时将两者都隔离。事件调用者只是和接口打交道,不和具体事件实现交互。在 VTK中,可以通过两种方式来实现观察者/命令模式,它们分别是使用事件回调函数以及从vtkCommand 派生出具体的子类。

3、事件回调函数

public uint AddObserver(string arg0, vtkCommand arg1, float priority);

public uint AddObserver(uint arg0, vtkCommand arg1, float priority);

AddObserver0函数的作用就是针对某个事件添加观察者到某个 VTK 对象中,当该对象发生观察者感兴趣的事件时,就会自动调用回调函数,执行相关的操作。

1)示例
cpp 复制代码
void MyCallbackFunc(vtkObject*, unsigned long eid, void* clientdata, void* calldata)
{
	std::cout << "You have clicked: " << ++pressCounts << " times." << std::endl;
}

void TestObserverCommand()
{
	vtkSmartPointer<vtkPNGReader> reader = vtkSmartPointer<vtkPNGReader>::New();
	reader->SetFileName("F:\\code\\VTK\\TestActiViz\\data\\VTK-logo.png");
	reader->Update();

	vtkSmartPointer<vtkImageViewer2> viewer = vtkSmartPointer<vtkImageViewer2>::New();
	viewer->SetInputData(reader->GetOutput());

	vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
	viewer->SetupInteractor(interactor);
	viewer->Render();

	viewer->GetRenderer()->SetBackground(1.0, 1.0, 1.0);
	viewer->SetSize(640, 480);
	viewer->GetRenderWindow()->SetWindowName("ObserverCommandDemo1");

	vtkSmartPointer<vtkCallbackCommand> mouseCallback = vtkSmartPointer<vtkCallbackCommand>::New();
	mouseCallback->SetCallback(MyCallbackFunc);

	interactor->SetRenderWindow(viewer->GetRenderWindow());
	interactor->AddObserver(vtkCommand::LeftButtonPressEvent, mouseCallback);

	interactor->Initialize();
	interactor->Start();
}

C#代码没有找到vtkCallbackCommand类 可能是这个9.3版本又做调整了,当然也可能是没有封装了,毕竟c++这里调用是正常的。

2)效果
3)说明

示例的功能非常简单,首先读入一幅PNG 图像,然后监听鼠标左键消息,如果单击图像,就在控制台打印出相应的信息。由此可以看出,VTK里使用回调函数实现观察者/命令模式主要分为以下三个步骤。

①定义回调函数。回调函数的函数签名只能是以下形式:

void func(vtkObject*obj, unsigned long eid, void* clientdata, void *calldata)

其中 obj 是调用事件的对象(即调用 AddObserver()函数的对象,在本例中即为interactor);eid 为所要监听的事件ID,VTK中的事件定义于vtkCommand.h文件中:clientdata 是与vtkCallbackCommand 实例相关联的数据,简单来说,是指回调函数里需要访问主程序里的数据时,由主程序向回调函数传递的数据,可以通过 vtkCallbackCommand::SetClientData()函数设置;calldata是执行 vkObject::InvokeEvent()函数时,随着回调函数发送的数据,比如,当调用 ProgessEvent 事件时,会自动发送当前的进度值作为calldata。

②创建一个 vtkCallbackCommand 对象,并调用 vtkCallbackCommand::SetCallback()函数设置所定义的回调函数,代码如下:

vtkSmartPointer<vtkCallbackCommand> mouseCallback=vtkSmartPointer<vtkCallbackCommand>::New();

mouseCallback->SetCallback ( MyCallbackFunc );

③将 vtkCallbackCommand 对象添加到对象的观察者列表中,代码如下:

interactor->AddObserver(vtkCommand::LefButtonPressEvent, mouseCallback);

vtkRenderWindowInteractor 提供了一种独立于平台的交互机制,用来响应不同平台的鼠标、按键和时钟等消息。当渲染窗口中有事件发生时,如单击消息,vkRenderWindowInteractor内部会调用与平台相关的子类,将该消息转换成对应平台的消息。因此,以上示例中通过vtkRenderWindownteractor 来监听鼠标左键按下消息,一旦监听到对象的观察者列表中的消息时,程序会自动调用事件回调函数。

4、vtkCommand子类

观察者/命令模式除了使用事件回调函数外,还可以直接从vtkCommand 类中派生出子类来实现。不过C#没有找到正确的路,构造函数总是要报错。

1)代码
cpp 复制代码
class vtkMyCallbackNew : public vtkCommand
{
public:
	static vtkMyCallbackNew* New()
	{
		return new vtkMyCallbackNew;
	}

	void SetObject(vtkConeSource* cone)
	{
		m_Cone = cone;
	}

	virtual void Execute(vtkObject* caller, unsigned long eventId, void* callData)
	{
		std::cout << "Left button pressed.\n"
			<< "The Height: " << m_Cone->GetHeight() << "\n"
			<< "The Radius: " << m_Cone->GetRadius() << std::endl;
	}

private:
	vtkConeSource* m_Cone;
};
cpp 复制代码
void TestShow()
{
	vtkConeSource* cone = vtkConeSource::New();
	cone->SetHeight(3.0);
	cone->SetRadius(1.0);
	cone->SetResolution(10);
	vtkPolyDataMapper* coneMapper = vtkPolyDataMapper::New();
	coneMapper->SetInputConnection(cone->GetOutputPort());

	vtkActor* coneActor = vtkActor::New();
	coneActor->SetMapper(coneMapper);

	vtkRenderer* ren1 = vtkRenderer::New();
	ren1->AddActor(coneActor);
	ren1->SetBackground(1, 1, 1.0);

	vtkRenderWindow* renWin = vtkRenderWindow::New();
	renWin->AddRenderer(ren1);
	renWin->SetSize(640, 480);
	renWin->Render();
	renWin->SetWindowName("ObservercommandDemo2");

	vtkRenderWindowInteractor* iren = vtkRenderWindowInteractor::New();
	iren->SetRenderWindow(renWin);

	vtkInteractorStyleTrackballCamera* style = vtkInteractorStyleTrackballCamera::New();
	iren->SetInteractorStyle(style);

	//vtkBoxWidget* boxWidget = vtkBoxWidget::New();
	//boxWidget->SetInteractor(iren);
	//boxWidget->SetPlaceFactor(1.25);
	//boxWidget->SetProp3D(coneActor);
	//boxWidget->PlaceWidget();
	vtkMyCallbackNew* callback = vtkMyCallbackNew::New();
	callback->SetObject(cone);

	iren->AddObserver(vtkCommand::InteractionEvent, callback);

	iren->Initialize();
	iren->Start();

	cone->Delete();
	coneMapper->Delete();
	coneActor->Delete();
	callback->Delete();

	ren1->Delete();
	renWin->Delete();
	iren->Delete();
	style->Delete();
}
2)效果
3)说明

示例演示的同样是监听鼠标左键单击消息,如果监听到单击消息,就在控制台中打印出主程序所设置的锥体的高和底面半径等信息。vkMyCallback类是从 vtkCommand 类中派生的,SetObiect()函数用于设置锥体对象。利用这种方式使用 VTK 的观察者/命令模式时也应遵循如下三个步骤。

①从 vtkCommand 类派生出子类,并实现 vtkCommand::Execute()虚函数,该函数原型为:

virtual void Execute(vtkObject *caller, unsigned long eventId,void *callData)=0;

Execute()是纯虚函数,所以从vtkCommand派生的类都必须实现这个方法。另外,vtkMyCallback类还定义了一个接口 SetObject(),用来设置锥体对象。该对象主要用于主程序

向 vtkMyCallback 类中传递数据。

②在主程序中实例化一个 vtkCommand 子类的对象以及调用相关的方法。

③调用 AddObserver0)函数监听感兴趣的事件。如果所监听的事件发生,会调用vtkCommand 子类中的 Execute0函数。因此,针对所监听的事件,程序需要实现的功能一般都放在 Execute()函数中。

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