MITK Tutorial--Step10: Adding New Interaction

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

给定如下继承结构的类:

这里写图片描述

在状态机里,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

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值