VTK_Learning_交互與Widget_觀察者/命令模式

1.前言

一個強大的可視化系統不僅需要強大的數據處理能力,也需要方便易用的交互功能。圖形處理軟件ParaView(hhttp://www.paraview.org)、德國癌症研究中心研發的MITK(http://www.mitk.org)等開源軟件系統都提供了強大的交互功能,作爲ParaView、MITK等軟件構件基礎的VTK同樣也提供了各種各樣的交互功能。
VTK的交互除了可以監聽來自鼠標、鍵盤等外部設備的消息,還可以在渲染場景中生成功能各異的交互部件(Widget),用於控制可視化過程的參數,達到用戶的渲染要求。

 2.觀察者/命令模式(Observe/Command)

觀察者/命令模式是VTK裏用的比較多的設計模式。
VTK中絕大多數的類都派生自vtkObject。查看類vtkObject的接口可以找到AddObserve()、RemoveObserve()、GetCommand()等函數。
觀察者/命令模式是指一個Object可以有多個Observe,他定義了對象間的一種“一對多”的依賴關係,當一個Object對象的狀態發生改變時,所有依賴於它的Observe對象都得到通知而被自動更新。命令模式屬於對象行爲模式,他將一個請求封裝爲一個對象,並提供一致性發送請求的接口,當一個事件發生時,他不直接把事件傳遞給事件調用者,而是在命令和調用者之間增加一箇中間者,講這種直接關係切斷,同時將兩者都隔離。事件調用者只是和接口打交道,不和具體事件實現交互。
在VTK中,可以通過兩種方式來實現觀察者/命令模式,他們分別是使用時間回調函數、從VTKCommand派生出具體的子類。


2.1 觀察者-事件回調方案

在vtkObject中,有如下函數:

unsigned long AddObserver(unsigned long event, vtkCommand *,  float priority = 0.0f);
unsigned long AddObserver(const char* event, vtkCommand *, float priority = 0.0f);

AddObserver()函數的作用就是針對某個事件添加掛插着到某個VTK對象中

當該對象發生觀察者感興趣的事件時,就會自動調用回調函數,執行相關操作

下面是一個非常簡單的例子演示了在VTK裏是如何使用“觀察者-事件回調函數”方案的:

 

#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL);
VTK_MODULE_INIT(vtkInteractionStyle);
 
#include <vtkCallbackCommand.h>
 
long cntPress = 0;
void MyCallbackFunc(vtkObject*, unsigned long eid, void* clientdata, void* calldata)
{
	std::cout << "You have clicked : " << ++cntPress << " times" << std::endl;
}
 
#include <vtkSmartPointer.h>
#include <vtkPNGReader.h>
#include <vtkImageViewer2.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
 
int main()
{
	vtkSmartPointer<vtkPNGReader> reader =
		vtkSmartPointer<vtkPNGReader>::New();
	reader->SetFileName("vtk.png");
	reader->Update();
 
	vtkSmartPointer<vtkImageViewer2> viewer =
		vtkSmartPointer<vtkImageViewer2>::New();
	viewer->SetInputData(reader->GetOutput());
 
	viewer->GetRenderer()->SetBackground(0, 0, 0);
	viewer->SetSize(480, 320);
	viewer->GetRenderWindow()->SetWindowName("Observer-Callback");
 
	vtkSmartPointer<vtkRenderWindowInteractor> rwi =
		vtkSmartPointer<vtkRenderWindowInteractor>::New();
	viewer->SetupInteractor(rwi);
	viewer->Render();
	/*************************************************************/
	//Step1:設置事件回調函數
	vtkSmartPointer<vtkCallbackCommand> mouseCallback =
		vtkSmartPointer<vtkCallbackCommand>::New();
	mouseCallback->SetCallback(MyCallbackFunc); //很重要!!!
 
	//Step2:將vtkCallbackCommand對象添加到觀察者列表。
	rwi->SetRenderWindow(viewer->GetRenderWindow()); //喚醒顯示窗口
	rwi->AddObserver(vtkCommand::LeftButtonPressEvent, mouseCallback);
 
	rwi->Initialize();
	rwi->Start();
	return 0;
}

輸出結果:

通過上例,我們可以總結一下“觀察者-回調函數”交互方案主要可以分爲以下三個步驟:

1.定義回調函數

回調函數的函數簽名只能是以下形式:

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

其中,
obj:是調用事件的對象(即調用AddObserver()函數的對象,對應於本例的rwi);
eid:是所要監聽的事件ID,VTK中的事件定義在vtkCommand.h文件中;
clientdata:是與VTKCallbackCommand實例相關的數據,簡單地說,是指回調函數裏需要訪問主程序裏面得數據時,由主程序向回調函數傳遞的數據。
calldata:是執行vtkObject::InvokeEvent()函數時,隨着回調函數發送得數據,比如說,當調用ProgressEvent事件時,會自動發送當前的進度值作爲callback。
 

2.創建一個VTKCallbackCommand對象,並調用VTKCallbackCommand::SetCallback()函數設置所定義的回調函數

//Step1:設置事件回調函數
vtkSmartPointer<vtkCallbackCommand> mouseCallback =
	vtkSmartPointer<vtkCallbackCommand>::New();
mouseCallback->SetCallback(MyCallbackFunc); //很重要!!!

3.將VTKCallbackCommand對象添加到對象的觀察者列表中

//Step2:將vtkCallbackCommand對象添加到觀察者列表。
rwi->SetRenderWindow(viewer->GetRenderWindow()); //喚醒顯示窗口
rwi->AddObserver(vtkCommand::LeftButtonPressEvent, mouseCallback);

注意:
VTK中的vtkRenderWindowInteractor提供了一種獨立於平臺的交互機制,用來響應不同平臺的鼠標、按鍵和時鐘等消息。當渲染窗口中有事件發生時(比如說單機消息),vtkRenderWindowInteractor內部會調用與平臺相關的子類,將該消息轉換成對應平臺的消息。
因此,該例的核心在於:通過vtkRenderWindowInteractor來監聽鼠標左鍵的消息,一旦監聽到對象的觀察者列表中的消息時,程序會自動調用事件回調函數。
 

2.2 vtkCommand子類

觀察者/命令模式除了使用事件回調函數外,還可以直接從vtkCommand類中派生出子類來實現。

示例代碼如下:

#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL);
VTK_MODULE_INIT(vtkInteractionStyle);
 
#include <vtkCommand.h>
#include <vtkSmartPointer.h>
#include <vtkConeSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleTrackballCamera.h>
 
class myCallback :public vtkCommand
{
public:
	static myCallback* New()
	{
		return new myCallback;
	}
	void SetObject(vtkConeSource* cone)
	{
		m_cone = cone;
	}
	virtual void Execute(vtkObject* caller, unsigned long eventId, void* callData)
	{
		std::cout << "LeftButton has been pressed: " << std::endl
			<< "The Height: " << m_cone->GetHeight() << std::endl
			<< "The Radius: " << m_cone->GetRadius() << std::endl;
	}
 
private:
	vtkConeSource* m_cone;
};
int main()
{
	vtkSmartPointer<vtkConeSource> cone =
		vtkSmartPointer<vtkConeSource>::New();
	cone->SetHeight(10);
	cone->SetRadius(4);
	cone->Update();
	/*************************************************************/
	vtkSmartPointer<vtkPolyDataMapper> mapper =
		vtkSmartPointer<vtkPolyDataMapper>::New();
	mapper->SetInputConnection(cone->GetOutputPort());
	
	vtkSmartPointer<vtkActor> actor =
		vtkSmartPointer<vtkActor>::New();
	actor->SetMapper(mapper);
 
	vtkSmartPointer<vtkRenderer> render =
		vtkSmartPointer<vtkRenderer>::New();
	render->AddActor(actor);
	render->SetBackground(1, 1, 1);
 
	vtkSmartPointer<vtkRenderWindow> rw =
		vtkSmartPointer<vtkRenderWindow>::New();
	rw->AddRenderer(render);
	rw->SetWindowName("Interaction By SubCommand");
	rw->SetSize(320, 320);
 
	vtkSmartPointer<vtkRenderWindowInteractor> rwi =
		vtkSmartPointer<vtkRenderWindowInteractor>::New();
	rwi->SetRenderWindow(rw);
 
	////////////////////////
	vtkSmartPointer<vtkInteractorStyleTrackballCamera> style =
		vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
	rwi->SetInteractorStyle(style);
 
	vtkSmartPointer<myCallback> callback =
		vtkSmartPointer<myCallback>::New();
	callback->SetObject(cone);
 
	rwi->AddObserver(vtkCommand::LeftButtonPressEvent, callback);
	rwi->Initialize();
	rwi->Start();
	return 0;
}

輸出結果爲:

這個例子實現的功能也是監聽鼠標左鍵單機的消息。
基於“觀察者-vtkCommand子類”的實現方案也遵循三個步驟。
1. 從vtkCommand類中派生出子類,並實現vtkCommand::Execute()虛函數。
該函數原型爲:
virtual void Execute(vtkObject* caller,  unsigned long eventId,  void* callData )= 0;
Execute()時純虛函數,所以,從vtkCommand派生類中都必須要實現這個方法。
2.實例化vtkCommand子類的對象,並調用相關的方法。
3.調用觀察者函數。
調用AddObserver()函數監聽感興趣的事件,如果所監聽的事件發生,就會調用vtkCommand子類中定義的Execute()函數。
因此,針對所監聽的事件,程序需要把實現的功能放在Execute函數中。
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章