(一)交互概念
1.事件处理过程
交互概念本身是独立于任何特定图形用户界面工具包而实现的。
处理交互事件的对象可被分为两类:
(1)数据交互器(DataInteractor):必须与某个特定数据结点关联,可以操控该数据结点,遵循状态机概念实现。
(2)InteractionEventObserver:不关联任何数据结点,该对象会接收所有用户输
入并仅用于显示,它们绝不应该修改任何数据结点。使用状态机功能是可选的,默认为不使用。
”调度员“Dispatcher:该类接收所有事件,并将它们分发到相关的数据交互实例。
<1> 默认根据数据结点的 layer 属性,降序对数据交互器进行排序;
<2> 然后将事件提供给第一顺序位的数据交互器,后者检查它是否可以处理该
事件。对每个数据交互器都执行此操作,直到遇到第一个能处理该事件为止,此
后将跳过其他剩余的数据交互器并通知所有 InteractionEventObserver。
2.数据交互器
交互概念的核心是,处理一个特定的数据结点的交互事件,可以侦听某些预定义事件并在触发此类事件时执行某些操作。这些都是基于状态机实现的。
(1)状态机
状态机模式:相同的用户交互可以根据当前状态触发不同的动作。 比如,通过鼠标单击来添加线时,前两次单击应该都会是添加一个点,但是第二次单击应另外完成连线的交互,随后的第三次及之后的单击都应被忽略。特定事件动作取决于数据对象的内容和交互状态。状态机就提供了一种很好的方式来建模这种交互。
通过设置多个状态机模式(状态和转换描述),对应不同的触发动作,使得同一数据交换器执行不同的用户交互。每种模式对应一种执行的交互方案。
只需更换状态机模式,就可以将一个数据交互器重复用于不同的任务。这些模式在 XML 文件中描述。
状态机定义:
状态机由四部分组成:
(1)【States】状态,描述交互的当前状态
(2)【transitions】转换,描述从一种状态变为另一种状态所需的事件
(3)【conditions】条件,描述执行转换之前的条件
(4)【actions】动作,在进行转换且该转换的条件已通过时执行
(2)配置
在许多情况下,最好实现独立于特定事件的交互(例如鼠标左键单击),以便可以轻松更改此事件。这是通过配置 InteractionEventHandler 来实现的,这允许在运行时更改行为。
mitk::InteractionEventHandler 类提供一个接口,可以通过加载不同配置文件来轻易修改触发行为的用户输入。
特定事件(specific event)和事件变体(event variant)
- 特定事件由某个事件类定义,该类确定事件的类别(例如 mitk::MousePressEvent 定义了鼠标按钮类)以及使该事件唯一的参数。
- 事件变体是分配给特定事件的名称(例如下面定义的两种事件),并且 InteractionEventHandler 侦听该名称。
(二)如何实现一个新的数据交互器
从头创建数据交互器、状态机和配置文件
修改交互器:
需要为实例化的交互对象添加两个xml文件:分别描述了状态机模式和触发转换行为的配置。一个数据交互器的行为由这两个xml文件决定。
1.创建配置文件
配置文件中的代码描述了定义的用户输入与交互器触发事件(event)之间的映射。每个事件的描述都必须指明事件类(event variant class)和事件变体(event variant name)。事件的参数由属性标签(attribute-tag)设置。
配置文件定义事件的完整示例 <1> 键盘事件:
<config>
<!-- 描述按下键盘a的事件 -->
<event_variant class="InteractionKeyEvent" name="StdA"> <!—键盘事件类和键A事件变体-->
<attribute name="Key" value="A"/> <!—描述事件的属性参数-->
</event_variant>
<!-- 描述同时按下键盘b+ctrl+shift的事件 -->
<event_variant class="InteractionKeyEvent" name="StdB">
<attribute name="Key" value="B"/>
<attribute name="Modifiers" value="shift,ctrl"/> <!—修饰键-->
</event_variant>
</config>
2.创建状态机模式文件
状态机模式文件从状态机定义的state、transition、condition、action四部分入手。
【states】
使用状态标签描述状态,每个状态机必须指定一个启动状态。
【transitions】
Transition 描述所有可能的状态切换,对于建模状态机很重要。它包括描述触发转换的事件(事件类和事件变体)和目标状态(状态机在执行转换后到达的状态)。事件类和事件变体共同确定哪个事件可以触发此转换。比如在“按下鼠标左键”这个事件发生之后,状态由状态 A 转换到了 B。
event class支持事件类的多态性。以通用的方式编写状态机模式。
在状态机模式中,可以将 InteractionPositionEvent 声明为事件类(event_class),它描述包含位置信息的交互事件,然后在配置文件中给出具体事件的描述。
无论使用什么输入设备(比如鼠标和触控),状态机模式都可以保持不变,并且状态机模式可以为新添加的事件类(比如新增 TouchEvent 事件类)进行配置,只要它们与类层次结构匹配即可。
<!--状态机模式-->
<!—触发状态A到B转换的事件类为InteractionPositionEvent,事件变体为AddPointClick -->
<statemachine>
<state name="start" startstate="true">
<transition event_class="InteractionPositionEvent" event_variant="AddPointClick" target="B"/>
</state>
<state name="B"/>
</statemachine>
<!—配置文件-->
<!—事件类为MousePressEvent,事件变体为AddPointClick -->
<config>
<event_variant class="MousePressEvent" name="AddPointClick">
<attribute name="EventButton" value="LeftMouseButton"/>
<attribute name="Modifiers" value="shift"/>
</event_variant>
</config>
【actions】
状态发生转换时执行的函数
【conditions】
状态发生转换时执行的函数,用作判定是否执行转换的actions条件
3.集成状态机模式和配置文件
· 现存文件在使用时只需提供文件名即可。
· 自定义模式和配置文件的加载,由于一般存放在其设计所在模块的Resources/Interactions 目录中,将文件从模块位置加载到交互器时,必须将模块作为参数提供。
m_CurrentInteractor = mitk::MyDataInteractor::New();
m_CurrentInteractor->LoadStateMachine("MyStateMachinePattern.xml", us::GetModuleContext()->GetModule());
m_CurrentInteractor->SetEventConfig("MyConfig.xml", us::GetModuleContext()->GetModule());
注意:该文件所在自定义模块需要在应用程序启动时通过 eager 策略激活,否则会找不到该模块定义的内容。
4.创建新的数据交互器
需要从 mitk::DataInteractor 派生自定义数据交互器 mitk::MyInteractor,并遵照以下规则,完成相关 action 和 condition 函数的定义以及这些函数与状态机模式的绑定。
- 定义actions
- 定义conditions
- 绑定actions、conditions与对应函数
新的数据交互器创建完成,调用时按照(3)完成加载即可。