Step1:怎么使用现有的Datainteractor
MITK有些已经写好的DataInteractor,可以直接在插件或模块中使用。它们可以在MITK源文件的Core/Code/Interactions中可以找到。
mitk::DataInteractor 包含四个部分:描述功能的类以及两个XML文件(statemachine和config);每个mitk::DataInteractor 都必须作用于一个mitk::DataNode。这几个部分必不可少。
例子
参考mitk::PointSetDataInteractor插件。PointSetDataInteractor是DataInteractor的子类。
为使用PointSetDataInteractor,首先需要一个DataNode来保存PointSets,node必须添加到mitk::DataStorage。
mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); GetDataStorage()->Add(dataNode.GetPointer());
然后创建mitk::PointSetDataInteractor对象,并载入已经写好的statemachine和configuration文件
m_CurrentInteractor = mitk::PointSetDataInteractor::New(); m_CurrentInteractor->LoadStateMachine("PointSet.xml"); m_CurrentInteractor->SetEventConfig("PointSetConfig.xml");
最后DataNode添加到mitk::DataInteractor中:
m_CurrentInteractor->SetDataNode(dataNode);
这样,DataInteractor就能发挥作用了。
Step 2: 如何修改DataInteractor的行为
mitk::DataInteractor的行为由两方面决定。state machine决定动作执行的顺序。configuration决定用户交互触发了什么动作。
如何修改display interactor的行为
mitk::DisplayInteractor控制zooming、panning和scrolling等。当我们想改变它的行为时,可以调用InteractionEventObserver,给DisplayInteractor指派一个新的configuration。如下例所示:
std::ifstream* configStream = new std::ifstream( #path to alternative configuration file# );
mitk::EventConfig newConfig(configStream);
// Requesting all registered EventObservers
std::list<mitk::ServiceReference> listEventObserver = GetModuleContext()->GetServiceReferences<InteractionEventObserver>();
for (std::list<mitk::ServiceReference>::iterator it = listEventObserver.begin(); it != listEventObserver.end(); ++it)
{
DisplayInteractor* displayInteractor = dynamic_cast<DisplayInteractor*>(GetModuleContext()->GetService<InteractionEventObserver>(*it));
// filtering: only adjust the DisplayInteractor
if (displayInteractor != NULL)
{
displayInteractor->SetEventConfig(newConfig);
}
}
如何编写新的DataInteractor
本章介绍如何根据自己的需要编写DataInteractor,实现对node数据的操作。MITK交互机制初步知识请参考MITK交互概念。你可能还想知道交互概念的实施。
下面第一节介绍config文件中所有用到的参数以及使用方法。第二节介绍编写
state machine文件;最后一节介绍怎么组合config和state machine文件,同时给出例子,继承mitk::DataInteractor编写自己的DataInteractor。
如何写Config文件
Event
事件由其参数描述。每个事件都有自己的一组参数。如果有的参数被省略,意味着它被设为默认值。下面列出了所有可能的参数。
事件的参数以属性的方式存在。事件的描述由 event class和event variant完成。如
<event_variant class="InteractionKeyEvent" name="StdA">
注:event class的介绍见下面
1. Mouse Buttons
mitk::InteractionEvent::MouseButtons表示鼠标按钮。它可以作为两个属性被使用:EventButton属性表示触发事件的按钮,通常是单个按钮;ButtonState属性表示事件生成时有哪些按钮被按下。比如,假设鼠标右键和中间键已经按下,现在左键也被按下再次触发一个事件,这个状态可以被描述为:
<attribute name="EventButton" value="LeftMouseButton"/>
<attribute name="ButtonState" value="RightMouseButton,MiddleMouseButton"/>
注意: 技术上来讲,LeftMouseButton也被按下了,应该列在ButtonState中,但这已经被mitk::EventFactory处理好了。
2. Key Events
mitk::InteractionKeyEvent表示一个被按下的键。哪个键被按下可以按照下面表示:
<attribute name="Key" value="A"/>
又比如
<attribute name="Key" value="Escape"/>
注意: 键盘事件不需要显式configuration,因为所有键盘事件已经有预定义好的event variant,名字为 ‘Std’ + value,比如键盘a被命名为’StdA’
特别键盘名字列出如下:
// Special Keys
static const std::string KeyEsc; // = "Escape";
static const std::string KeyEnter; // = "Enter";
static const std::string KeyReturn; // = "Return";
static const std::string KeyDelete; // = "Delete";
static const std::string KeyArrowUp; // = "ArrowUp";
static const std::string KeyArrowDown; // = "ArrowDown";
static const std::string KeyArrowLeft; // = "ArrowLeft";
static const std::string KeyArrowRight; // = "ArrowRight";
static const std::string KeyF1; // = "F1";
static const std::string KeyF2; // = "F2";
static const std::string KeyF3; // = "F3";
static const std::string KeyF4; // = "F4";
static const std::string KeyF5; // = "F5";
static const std::string KeyF6; // = "F6";
static const std::string KeyF7; // = "F7";
static const std::string KeyF8; // = "F8";
static const std::string KeyF9; // = "F9";
static const std::string KeyF10; // = "F10";
static const std::string KeyF11; // = "F11";
static const std::string KeyF12; // = "F12";
static const std::string KeyPos1; // = "Pos1";
static const std::string KeyEnd; // = "End";
static const std::string KeyInsert; // = "Insert";
static const std::string KeyPageUp; // = "PageUp";
static const std::string KeyPageDown; // = "PageDown";
static const std::string KeySpace; // = "Space";
// End special keys
3. Modifier Keys
mitk::InteractionEvent::ModifierKeys表示按下辅助按键,多个辅助按键同时被按下,中间用逗号隔开:
<!-- shift and control key are pressed -->
<attribute name="Modifiers" value="shift,ctrl"/>
4. 滚动方向
mitk::MouseWheelEvent事件,表示鼠标滚轮滚动的方向。
<attribute name="ScrollDirection" value="up"/>
<!-- or -->
<attribute name="ScrollDirection" value="down"/>
5. 例子
键盘事件例子:
<config>
<!-- Event of key 'a' pressed -->
<event_variant class="InteractionKeyEvent" name="StdA">
<attribute name="Key" value="A"/>
</event_variant>
<!-- Event of key 'b' pressed while modifiers ctrl and shift are pressed-->
<event_variant class="InteractionKeyEvent" name="StdB">
<attribute name="Key" value="B"/>
<attribute name="Modifiers" value="shift,ctrl"/>
</event_variant>
</config>
鼠标按键事件:
<!-- Standard left click -->
<config>
<event_variant class="MousePressEvent" name="StdMousePressPrimaryButton">
<attribute name="EventButton" value="LeftMouseButton"/>
</event_variant>
<!-- right click with control key pressed-->
<event_variant class="MousePressEvent" name="RightWithCTRL">
<attribute name="EventButton" value="RightMouseButton"/>
<attribute name="Modifiers" value="ctrl"/>
</event_variant>
</config>
MITK中已经有一个写好的关于大多数事件的标准configuration文件,叫GlobalConfig.xml。可以作为默认config文件,或者是在此基础上扩展。
6. 参数描述
config文件中还可以存储参数,比如:
<config name="example2">
<param name="property1" value="yes"/>
<param name="scrollModus" value="leftright"/>
</config>
怎么写State Machine
状态机以XML文件的格式存储。
1. states
States用state标签标示。每个状态都有一个名字。每个状态机中都必须有一个state作为开始状态,表示状态机构造完成后处于这个初始状态。一个有效的状态机例子如下:
<statemachine>
<state name="start" startstate="true"/>
</statemachine>
有时候,可以选择性地给state增加一个特殊mode,影响事件的转发。这几个mode为:GRAB_INPUT , PREFER_INPUT 和 REGULAR。REGULAR为默认值,无需明确指出。只在必要时使用这些special mode,因为它们让其他DataInteractors都无法接收到事件。
2. Transitions
Transitions表示状态的过渡,对于交互至关重要。Transition包含触发过渡的事件以及所要过渡到的状态。 event class描述事件类型(去mitk::InteractionEvent查看不同的类),event variant代表具体的事件,这两者共同决定哪个事件能够触发过渡。比如StdMousePressPrimaryButton事件发生时(左键按下),状态机将从状态A过渡到状态B。
Event Class
给定如下继承结构的类:
![这里写图片描述](http://docs.mitk.org/nightly-qt4/dot_inline_dotgraph_10.png)
在状态机里,mitk::InteractionPositionEvent 可以被声明为 event class,代表带有位置信息的事件。而它真正是哪个鼠标按键事件可以在config文件中给出。这样, mitk::InteractionPositionEvent itself, 或者mitk::MousePressEvent, mitk::MouseReleaseEvent, mitk::TouchEvent都能在状态机里以同一个身份出现,不管输入设备是什么,只要在相同的类继承结构中,状态机就能以不变应万变。
<statemachine>
<state name="A" startstate="true">
<transition event_class="MousePressEvent" event_variant="StdMousePressPrimaryButton" target="B"/>
<state/>
<state name="B" />
</statemachine>
3. Actions
Actions要被添加到transitions中,表示过渡中需要执行的函数。下面这个状态机监听鼠标左键事件并执行两个动作(循环往复,永不停止)。
<statemachine>
<state name="start" startstate="true">
<transition event_class="MousePressEvent" event_variant="StdMousePressPrimaryButton" target="start">
<action name="addPoint"/>
<action name="countClicks"/>
</transition>
</state>
</statemachine>
为了告诉mitk::DataInteractor需要执行哪个函数, mitk::DataInteractor 使用CONNECT_FUNCTION将action和函数连接起来。下面表示一个继承了DataInteractor的ExampleInteractor类,该类实现了AddPoint和CountClicks两个函数。CONNECT_FUNCTION必须在重写的ConnectActionsAndFunctions()虚拟函数中使用。
void mitk::ExampleInteractor::ConnectActionsAndFunctions()
{
CONNECT_FUNCTION("addPoint", AddPoint);
CONNECT_FUNCTION("countClicks", CountClicks);
}
4. Conditions
Conditions可以添加在transitions中,mitk::DataInteractor要进行状态过渡时会调用它们。condition决定是否能够状态过渡以及是否能够执行action。
例子
<statemachine> <state name="start" startstate="true"> <transition event_class="MousePressEvent" event_variant="StdMousePressPrimaryButton" target="start"> <condition name="checkPoint"/><!-- 如果条件满足,执行下面两个动作 --> <action name="addPoint"/> <action name="countClicks"/> </transition> <transition event_class="MousePressEvent" event_variant="StdMousePressPrimaryButton" target="start"> <condition name="checkPoint" inverted="true"/><!-- 如果条件不满足,执行下面动作 --> <action name="doSomethingElse"/> </transition> </state> </statemachine>
需要在ConnectActionsAndFunctions()函数中进行连接:
void mitk::ExampleInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("checkPoint", CheckPoint); CONNECT_FUNCTION("addPoint", AddPoint); CONNECT_FUNCTION("countClicks", CountClicks); CONNECT_FUNCTION("doSomethingElse", DoSomethingElse); }
注意:上面例子中我们可以看到针对同一condition,我们可以定义这个条件在满足以及不满足(inverted是否设为true)情况下的行为。
连接config和状态机
自定义的config和state machine文件需要存储在自定义插件或模块中的/Resources/Interactions文件夹内。并将文件路径添加到对应的files.cmake文件中。像这样:
set(RESOURCE_FILES
Interactions/CustomStateMachinePattern.xml
Interactions/CustomConfig.xml
)
当从模块中载入此二文件时,需要把模块作为参数:
#include "usModule.h"
#include "usGetModuleContext.h"
... ...
m_CurrentInteractor = mitk::CustomDataInteractor::New();
m_CurrentInteractor->LoadStateMachine("CustomStateMachinePattern.xml", us::GetModuleContext()->GetModule());
m_CurrentInteractor->SetEventConfig("CustomConfig.xml", us::GetModuleContext()->GetModule());
... ...
参考注册状态机和config。
重写mitk::DataInteractor
自定义DataInteractor需要继承mitk::DataInteractor,函数的写法如下:
对于actions:
bool SomeFunctionality(StateMachineAction* , InteractionEvent*);
对于conditions:
bool SomeFunctionality(const InteractionEvent*);
使用ConnectActionsAndFunctions()函数将对应函数连接到actions、conditions:
void mitk::ExampleInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("checkPoint", CheckPoint); CONNECT_FUNCTION("addPoint", AddPoint); CONNECT_FUNCTION("enoughPoints", EnoughPoints); }
现在只要写好state machine 和config就行了。
示例可参考 mitk::PointSetDataInteractor。里面有完整的注释。
使用InternalEvent的Interactor例子
创建DataInteractors时一个有用的工具是mitk::InternalEvent,它能够让mitk::DataInteractor自己发送信号。
下面举例说明如何写一个mitk::DataInteractor。ExampleInteractor类不断地添加点,直到接收到一个 mitk::InternalEvent事件,告诉它点已经达到指定数量。事先指定的数量可以保存在config文件中。下面先写状态机:
<statemachine>
<state name="start" startstate="true" >
<transition event_class="MousePressEvent" event_variant="AddPointClick" target="start">
<condition name="checkPoint"/>
<action name="addPoint"/>
</transition>
<transition event_class="InternalEvent" event_variant="enoughPointsAdded" target="final">
<action name="enoughPoints"/>
</transition>
</state>
<state name="final">
<--! dead state, nothing happens any more, once we reached this -->
</state>
</statemachine>
在config文件中,将点的最大数量设为10,并将AddPointClick定义为鼠标右键和ctrl键同时按下。
<config>
<param name="NumberOfPoints" value="10">
<event_variant class="MousePressEvent" name="AddPointClick">
<attribute name="EventButton" value="RightMouseButton"/>
<attribute name="Modifiers" value="ctrl"/>
</event_variant>
</config>
本例可见
Step10.h
Step10.cpp
在类中,重写函数:
protected:
ExampleInteractor();
virtual ~ExampleInteractor();
virtual void ConnectActionsAndFunctions();
virtual void ConfigurationChanged();
连接函数:
void mitk::ExampleInteractor::ConnectActionsAndFunctions()
{
// connect the action and condition names of the state machine pattern with function within
// this DataInteractor
CONNECT_CONDITION("checkPoint", CheckPoint);
CONNECT_FUNCTION("addPoint", AddPoint);
CONNECT_FUNCTION("enoughPoints", EnoughPoints);
}
ConfigurationChanged函数将在新的configuration文件载入时被调用(由mitk::InteractionEventHandler调用)。这个函数允许执行初始化语句,本例中我们想设定点数上限。
void mitk::ExampleInteractor::ConfigurationChanged()
{
// read how many points we accept from the config properties
mitk::PropertyList::Pointer properties = GetPropertyList();
std::string maxNumber;
properties->GetStringProperty("NumberOfPoints",maxNumber);
m_MaximalNumberOfPoints = atoi(maxNumber.c_str());
}
接下来,需要写action函数了,它们的声明如下:
private:
bool AddPoint(StateMachineAction* , InteractionEvent*); // function to add new points
bool EnoughPoints(StateMachineAction* , InteractionEvent*); // function changes color of pointset to indicate, it is full
bool CheckPoint(const InteractionEvent* interactionEvent); // function checks if the clicked point is valid
bool mitk::ExampleInteractor::AddPoint(StateMachineAction*, InteractionEvent* interactionEvent)
{
// cast InteractionEvent to a position event in order to read out the mouse position
// we stay here as general as possible so that a different state machine pattern
// can reuse this code with MouseRelease or MouseMoveEvents.
InteractionPositionEvent* positionEvent = dynamic_cast<InteractionPositionEvent*>(interactionEvent);
if (positionEvent != NULL)
{
// query the position of the mouse in the world geometry
mitk::Point3D point = positionEvent->GetPositionInWorld();
m_PointSet->InsertPoint(m_NumberOfPoints, point, 0);
m_NumberOfPoints++;
GetDataNode()->SetData(m_PointSet);
GetDataNode()->Modified();
if (m_NumberOfPoints != 0 && m_NumberOfPoints >= m_MaximalNumberOfPoints)
{
// create internal event that signal that the maximal number of points is reached
InternalEvent::Pointer event = InternalEvent::New(NULL,this, "enoughPointsAdded");
// add the internal event to the event queue of the Dispatcher
positionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer());
}
// update the RenderWindow to show new points
mitk::RenderingManager::GetInstance()->RequestUpdateAll();
return true;
}
else
{
return false;
}
}
bool mitk::ExampleInteractor::EnoughPoints(StateMachineAction*, InteractionEvent*)
{
GetDataNode()->SetProperty("contourcolor", ColorProperty::New(1.0, 1.0, 0.0));
mitk::RenderingManager::GetInstance()->RequestUpdateAll();
return true;
} //-
如果条件函数返回false,那么过渡以及它的action将不执行。如果条件不通过,这个事件就被认为是未处理的,将被转交给其它Interactors。
bool mitk::ExampleInteractor::CheckPoint(const InteractionEvent *interactionEvent)
{
// check if a point close to the clicked position already exists
float epsilon = 0.3; // do not accept new points within 3mm range of existing points
InteractionPositionEvent* positionEvent = dynamic_cast<InteractionPositionEvent*>(interactionEvent);
if (positionEvent != NULL)
{
// query the position of the mouse in the world geometry
mitk::Point3D point = positionEvent->GetPositionInWorld();
int retVal = m_PointSet->SearchPoint(point, epsilon);
if ( retVal == -1 ) // SearchPoint returns -1 if no point was found within given range
return true;
}
return false; // if the positionEvent is NULL or a point was found return false. AddPoint will not be executed
//end
这里,一个internal event将在点数达到上限后发送消息。该事件在创建后需要添加到dispatchers事件队列中:
// create internal event that signal that the maximal number of points is reached
InternalEvent::Pointer event = InternalEvent::New(NULL,this, "enoughPointsAdded");
// add the internal event to the event queue of the Dispatcher
positionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer());
nternal events不需要映射到event variants。它们的信号名字与event variant相同。
了解更多可参考
mitk::DataInteractor和mitk::InteractionEventObserver
mitk::PointSetDataInteractor
mitk::DisplayInteractor