使程序具备将键盘事件与特定函数相关联的能力。在前面的教程中我们已经可以使用更新回调来控制炮塔的旋转。本章中我们将添加一个键盘接口类,实现通过用户的键盘输入来更新炮塔的转角。
GUIEventHandler类向开发者提供了窗体系统的GUI事件接口。这一事件处理器使用GUIEventAdapter实例来接收更新。事件处理器还可以使用GUIActionAdapter实例向GUI系统发送请求,以实现一些特定的操作。
GUIEventAdapter实例包括了各种事件类型(PUSH,RELEASE,DOUBLECLICK,DRAG,MOVE,KEYDOWN,KEYUP,FRAME,RESIZE,SCROLLUP,SCROLLDOWN,SCROLLLEFT)。依据GUIEventAdapter事件类型的不同,其实例可能还有更多的相关属性。例如X,Y坐标与鼠标事件相关。KEYUP和KEYDOWN事件则与一个按键值(例如“a”,“F1”)相关联。
GUIEventHandler使用GUIActionAdapter来请求GUI系统执行动作。这些动作包括重绘请求requestRedraw(),多次更新请求requestContinuousUpdate(),光标位置重置请求requestWarpPointer(x,y)。
GUIEventHandler类主要通过handle方法来实现与GUI的交互。handle方法有两个参数:一个GUIEventAdapter实例用于接收GUI的更新,以及一个GUIActionAdapter用于向GUI发送请求。handle方法用于检查GUIEventAdapter的动作类型和值,执行指定的操作,并使用GUIActionAdapter向GUI系统发送请求。如果事件已经被正确处理,则handle方法返回的布尔值为true,否则为false。
一个GUI系统可能与多个GUIEventAdapter相关联(GUIEventAdapter的顺序保存在视口类的eventHandlerList中),因此这个方法的返回值可以用于控制单个键盘事件的多次执行。如果一个GUIEventHandler返回false,下一个GUIEventHandler将继续响应同一个键盘事件。
后面的例子将演示GUIEventHandler与GUI系统交互的方法:TrackballManipulator类(继承自GUIEventHandler)以GUIEventAdapter实例的形式接收鼠标事件的更新。鼠标事件的解析由TrackballManipulator类完成,并可以实现“抛出”的操作(所谓抛出,指的是用户按下键拖动模型并突然松开,以实现模型的持续旋转或移动)。解析事件时,TrackBallManipulator将发送请求到GUI系统(使用GUIActionAdapter),启动定时器并使自己被重复调用,以计算新的模型方向或者位置数据。
以下主要介绍如何创建一个用于将键盘输入关联到特定函数的键盘接口类。当用户将按键注册到接口类并设定相应的C++响应函数之后,即可建立相应的表格条目。该表格用于保存键值(“a”,“F1”等等),按键状态(按下,松开)以及C++响应函数。本质上讲,用户可以由此实现形同“按下f键,即执行functionOne”的交互操作。由于新的类将继承自GUIEventHandler类,因此每当GUI系统捕获到一个GUI事件时,这些类的handle方法都会被触发。而handle方法触发后,GUI事件的键值和按键状态(例如,松开a键)将与表格中的条目作比较,如果发现相符的条目,则执行与此键值和状态相关联的函数
用户通过addFunction方法可以注册按键条目。这个函数有两种形式。第一种把键值和响应函数作为输入值。这个函数主要用于用户仅处理KEY_DOWN事件的情形。例如,用户可以将“a”键的按下事件与一个反锯齿效果的操作函数相关联。但是用户不能用这个函数来处理按键松开的动作。
另一个情形下,用户可能需要区分由单个按键的“按下”和“松开”事件产生的不同动作。例如控制第一人称视角的射击者动作。按下w键使模型加速向前。松开w键之后,运动模型逐渐停止。一种可行的设计方法是,为按下按键和松开按键分别设计不同的响应函数。两者中的一个用来实现按下按键的动作。
#ifndef KEYBOARD_HANDLER_H #define KEYBOARD_HANDLER_H #include <osgGA/GUIEventHandler> class keyboardEventHandler : public osgGA::GUIEventHandler { public: typedef void (*functionType) (); enum keyStatusType { KEY_UP, KEY_DOWN }; // 用于保存当前按键状态和执行函数的结构体。 // 记下当前按键状态的信息以避免重复的调用。 // (如果已经按下按键,则不必重复调用相应的方法) struct functionStatusType { functionStatusType() {keyState = KEY_UP; keyFunction = NULL;} functionType keyFunction; keyStatusType keyState; }; typedef std::map<int, functionStatusType > keyFunctionMap; // 这个函数用于关联键值和响应函数。如果键值在之前没有注册过,它和它的响应函数都会被添加到“按下按键”事件的映射中,并返回true。 ?// 否则,不进行操作并返回false。 bool addFunction(int whatKey, functionType newFunction); // 重载函数,允许用户指定函数是否与KEY_UP或者KEY_DOWN事件关联。 bool addFunction(int whatKey, keyStatusType keyPressStatus, functionType newFunction); // ?此方法将比较当前按下按键的状态以及注册键/状态的列表。如果条目吻合且事件较新(即,按键还未按下),则执行响应函数。 virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter&); //重载函数,用于实现GUI事件处理访问器的功能。 virtual void accept(osgGA::GUIEventHandlerVisitor& v) { v.visit(*this); }; protected: //保存已注册的“按下按键”方法及其键值。 keyFunctionMap keyFuncMap; // 保存已注册的“松开按键”方法及其键值。 keyFunctionMap keyUPFuncMap; }; #endif
使用键盘接口类:
下面的代码用于演示如何使用上面定义的类: // 建立场景和视口。 // ... // 声明响应函数: // ... // 声明并初始化键盘事件处理器的实例。 keyboardEventHandler* keh = new keyboardEventHandler(); //将事件处理器添加到视口的事件处理器列表。 // 如果使用push_front且列表第一项的handle方法返回true,则其它处理器 //将不会再响应GUI同一个GUI事件。我们也可以使用push_back,将事件的第一处理权交给其它的事件处理器;或者也可以设置handle方法的返回值 // 为false。OSG 2.x版还允许使用addEventHandler方法来加以替代。 viewer.getEventHandlerList().push_front(keh); // 注册键值,响应函数。 // 按下a键时,触发toggelSomething函数。 // (松开a键则没有效果) keh->addFunction('a',toggleSomething); // 按下j键时,触发startAction函数。(例如,加快模型运动速度) // 注意,也可以不添加第二个参数。 keh->addFunction('j',keyboardEventHandler::KEY_DOWN,startAction); // 松开j键时,触发stopAction函数。 keh->addFunction('j',keyboardEventHandler::KEY_UP,stopAction); // 进入仿真循环 // ...
进入下一个教程 Basic keyboard input