基于OpenGL编写一个简易的2D渲染框架-07 鼠标事件和键盘事件
這次為程序添加鼠標事件和鍵盤事件
?
當檢測到鼠標事件和鍵盤事件的信息時,捕獲其信息并將信息傳送到需要信息的對象處理。為此,需要一個可以分派信息的對象,這個對象能夠正確的把信息交到正確的對象。
?
實現思路:
要實現以上的功能,需要幾個對象:
事件分派器:EventDispatcher,負責將 BaseEvent 分派給 EventListener 對象
事件監聽器:EventListener,這只是一個接口類,接受 BaseEvent 的對象,真正的處理在它的子類中實現
事件:BaseEvent,儲存用戶數據,事件信息載體
假設我要分派一個?BaseEvent, 那么我應該將?BaseEvent?分派給哪個監聽器 EventListener ?可以在 BaseEvent 上添加一個 ID,通過這個 ID 將 BaseEvent 分派到對應 ID 的監聽器。
有這樣一個場景,有 A、B、C、D 四個監聽器,需要把 ID 為 5 的 BaseEvent 分派給 A、B 監聽器,而 C、D 監聽器不需要接受這個 BaseEvent。
這時可以創建一個映射表,存儲有 ID 和 監聽器之間的聯系信息
typedef std::map<int, std::list<EventListener*>> ListenerGroup;A、B 需要監聽 ID 為 5 的 BaseEvent,就把 A、B 注冊到這個表中,表中就有了 5-A、B 這樣的信息。事件分派器就能根據這個表將 ID 為 5 的 BaseEvent 分派到需要監聽這個 BaseEvent 的監聽器 A 和 B。對于 C、D 監聽器,只能監聽到對應 ID 的 BaseEvent,實現思路就這樣。
代碼實現:
BaseEvent 結構如下
struct BaseEvent{int nEventID; /* 事件 ID */int nParams[MAX_EVENT_PARAM]; /* 自定義參數 */void* pUserData; /* 用戶數據 */};nParams 用來儲存幾個自定義參數,對于其他數據就用 void 指針儲存,需要時轉換一下就可以了。
?
事件分派器有兩個屬性,分別是 事件池 和 ID-監聽器表,事件池主要是用來儲存所有要分派的事件
std::list<BaseEvent> vEventPool;ListenerGroup listenerGroup;接下來是監聽器的實現
class DLL_export EventListener{friend class EventDispatcher;public:EventListener();virtual ~EventListener() {}protected:void appendListener(int eventID, EventListener* listener);void removeListener(int eventID, EventListener* listener);virtual void handleEvent(const BaseEvent& event) = 0;private:static unsigned int nIDCounter;unsigned int nID;};主要有三個函數,用于將監聽器注冊到?ID-監聽器表和從?ID-監聽器表中移除監聽器,最后一個是處理 BaseEvent 的函數,這是一個抽象函數,表示在子類中實現處理函數。
將監聽器注冊到表中,需要一個監聽器要監聽的 BaseEvent ID 以及監聽器本身
void EventListener::appendListener(int eventID, EventListener* new_listener){auto listenerList = pDispatcher->listenerGroup.find(eventID);/* 事件 ID 沒有監聽列表?為 ID 創建監聽列表,添加 eListener */if ( listenerList == pDispatcher->listenerGroup.end() ) {std::list<EventListener*> newListenerList;newListenerList.push_back(new_listener);pDispatcher->listenerGroup.insert(std::make_pair(eventID, newListenerList));}else {/* 如果監聽列表中沒有監聽器,添加監聽器到列表中 */std::list<EventListener*>::iterator listener_it;for ( listener_it = listenerList->second.begin(); listener_it != listenerList->second.end(); ++listener_it ) {if ( (*listener_it)->nID == new_listener->nID ) return;}if ( listener_it == listenerList->second.end() ) {listenerList->second.push_back(new_listener);}}}先判斷該 ID 的 BaseEvent 是否有一張表了,如果沒有就新建表,然后將監聽器添加到表中。
將監聽器中表中移除
void EventListener::removeListener(int eventID, EventListener* listener){auto listenerList = pDispatcher->listenerGroup.find(eventID);if ( listenerList == pDispatcher->listenerGroup.end() ) return;/* 從監聽列表中移除監聽器 */for ( auto it = listenerList->second.begin(); it != listenerList->second.end(); ++it ) {if ( (*it)->nID == listener->nID ) {listenerList->second.erase(it);break;}}/* 移除空監聽列表 */if ( listenerList->second.empty() ) {pDispatcher->listenerGroup.erase(listenerList);}}?
如果要分派一個 BaseEvent,先將其添加到分派器中
void EventDispatcher::dispatchEvent(const BaseEvent& event){/* 只是暫時添加事件到事件池中,并沒有立即分派事件,避免遞歸分派錯誤 */vEventPool.push_back(event);}這里沒有立即將 BaseEvent 交給對應的監聽器處理,是因為如果處理函數中有將 BaseEvent 添加到事件分派器中的操作,會發生遞歸錯誤。所以就將 BaseEvent 添加到一個事件池中,稍后在函數 flushEvent 中統一分派
void EventDispatcher::flushEvent(){if ( vEventPool.empty() ) return;/* 分派事件池中的所有事件 */for ( auto& event : vEventPool ) {this->realDispatchEvent(event);}vEventPool.clear();}分派每一個 BaseEvent,需要找到其對應的監聽表,再交給表中的監聽器處理
void EventDispatcher::realDispatchEvent(const BaseEvent& event){auto listenerList_it = listenerGroup.find(event.nEventID);if ( listenerList_it != listenerGroup.end() ) {std::list<EventListener*>& listenerList = listenerList_it->second;for ( auto listener_it : listenerList ) {listener_it->handleEvent(event);}}}以上就實現了一個事件分派模塊,費如此大的一番功夫,是為了讓它不僅僅分派鼠標和鍵盤事件,還可以分派其他需要的事件。
?
鼠標事件和鍵盤事件處理
為鼠標事件和鍵盤事件分別定義事件 ID
enum EventType { ET_UNKNOWN, /* 未知事件 */ET_MOUSE, /* 鼠標事件 */ET_KEY /* 按鍵事件 */};
先實現鼠標事件的處理,定義一個鼠標監聽器類,繼承于事件監聽器
class DLL_export MouseEventListener : public EventListener{public:MouseEventListener();virtual ~MouseEventListener();virtual void mouseMove(const MouseEvent& event) {}virtual void mousePress(const MouseEvent& event) {}virtual void mouseRelease(const MouseEvent& event) {}virtual void mouseDoubleClick(const MouseEvent& event) {}virtual void mouseWheel(const MouseEvent& event) {}void handleEvent(const BaseEvent& event);};在構造函數和析構函數中,主要是注冊監聽器到事件分派器和從事件分派器中移除監聽器
MouseEventListener::MouseEventListener(){this->appendListener(EventType::ET_MOUSE, this);}MouseEventListener::~MouseEventListener(){this->removeListener(EventType::ET_MOUSE, this);}?
鼠標事件分別有按鍵按下、釋放、雙擊、鼠標移動和滾輪滑動等動作
enum EventAction{ACT_MOVE, /* 移動 */ACT_PRESS, /* 按壓 */ACT_RELAESE, /* 釋放 */ACT_DUBBLE_CLICK, /* 雙擊 */ACT_SCROLL /* 滾動 */};?
以及按鈕類型,左鍵、右鍵和中鍵
enum ButtonType { LEFT_BUTTON, /* 鼠標左鍵 */RIGHT_BUTTON, /* 鼠標右鍵 */MIDDLE_BUTTON /* 鼠標中鍵 */};?
對于一個鼠標事件,需要的數據信息如下
/* 鼠標事件 */struct MouseEvent{EventAction eventAction;ButtonType buttonType;int nDelta;int nX, nY;};動作類型、按鈕類型、滾輪滾動數據和坐標數據。
?
為了捕捉窗口程序的鼠標信息,定義一個窗口信息處理類
//------------------------------------------------------------------// WinMsgHandle// 窗口信息處理//------------------------------------------------------------------class WinMsgHandle{public:WinMsgHandle();void handleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);private:BaseEvent baseEvent;KeyEvent keyEvent;MouseEvent mouseEvent;};函數 handleMessage 主要捕捉窗口信息
void WinMsgHandle::handleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){baseEvent.nEventID = ET_UNKNOWN;/* 鼠標事件信息 */if ( msg >= WM_MOUSEMOVE && msg <= WM_MBUTTONDBLCLK || msg == WM_MOUSEWHEEL ) {switch ( msg ) {case WM_LBUTTONDOWN:mouseEvent.buttonType = ButtonType::LEFT_BUTTON;mouseEvent.eventAction = EventAction::ACT_PRESS;break;case WM_LBUTTONUP:mouseEvent.buttonType = ButtonType::LEFT_BUTTON;mouseEvent.eventAction = EventAction::ACT_RELAESE;break;case WM_LBUTTONDBLCLK:mouseEvent.buttonType = ButtonType::LEFT_BUTTON;mouseEvent.eventAction = EventAction::ACT_DUBBLE_CLICK;break;case WM_MBUTTONDOWN:mouseEvent.buttonType = ButtonType::MIDDLE_BUTTON;mouseEvent.eventAction = EventAction::ACT_PRESS;break;case WM_MBUTTONUP:mouseEvent.buttonType = ButtonType::MIDDLE_BUTTON;mouseEvent.eventAction = EventAction::ACT_RELAESE;break;case WM_MBUTTONDBLCLK:mouseEvent.buttonType = ButtonType::MIDDLE_BUTTON;mouseEvent.eventAction = EventAction::ACT_DUBBLE_CLICK;break;case WM_RBUTTONDOWN:mouseEvent.buttonType = ButtonType::RIGHT_BUTTON;mouseEvent.eventAction = EventAction::ACT_PRESS;break;case WM_RBUTTONUP:mouseEvent.buttonType = ButtonType::RIGHT_BUTTON;mouseEvent.eventAction = EventAction::ACT_RELAESE;break;case WM_RBUTTONDBLCLK:mouseEvent.buttonType = ButtonType::RIGHT_BUTTON;mouseEvent.eventAction = EventAction::ACT_DUBBLE_CLICK;break;case WM_MOUSEMOVE:mouseEvent.eventAction = EventAction::ACT_MOVE;break;case WM_MOUSEWHEEL:mouseEvent.eventAction = EventAction::ACT_SCROLL;mouseEvent.nDelta = ( short ) HIWORD(wParam);break;}mouseEvent.nX = ( short ) LOWORD(lParam);mouseEvent.nY = ( short ) HIWORD(lParam);baseEvent.nEventID = ET_MOUSE;baseEvent.pUserData = &mouseEvent;EventDispatcher::getInstance()->dispatchEvent(baseEvent);}}主要是獲取鼠標事件數據 MouseEvent,然后將數據附加到 BaseEvent 上,設置其 ID 為 鼠標事件ID——ET_MOUSE,最后由事件分派器分派 BaseEvent。
當鼠標事件監聽器處理 BaseEvent 時,需要獲取 MouseEvent 數據,然后根據按鈕類型和動作類型調用相應函數
void MouseEventListener::handleEvent(const BaseEvent& event){if ( event.nEventID != EventType::ET_MOUSE && event.pUserData ) return;MouseEvent* mouseEvent = static_cast<MouseEvent*>(event.pUserData);switch ( mouseEvent->eventAction ) {case Simple2D::ACT_MOVE: this->mouseMove(*mouseEvent); break;case Simple2D::ACT_PRESS: this->mousePress(*mouseEvent); break;case Simple2D::ACT_RELAESE: this->mouseRelease(*mouseEvent); break;case Simple2D::ACT_SCROLL: this->mouseWheel(*mouseEvent); break;case Simple2D::ACT_DUBBLE_CLICK: this->mouseDoubleClick(*mouseEvent); break;}}當然這些函數都沒有具體的實現,具體的實現由子類完成。
?
對于鍵盤事件,只有兩個按鍵動作按壓和釋放,及事件的數據結構體
/* 按鍵事件 */struct KeyEvent{EventAction eventAction;bool keys[256];KeyType keyType;};bool 類型的按鍵數組 keys 儲存哪一個按鍵被按下的信息,當同時有多個按鍵按壓時也可以檢測。而 KeyType 就記錄了當前按壓的按鍵類型,這里并不包括鍵盤上的所有按鍵,只包含字母鍵、數字鍵和其它常用按鍵。
/** VK_0 - VK_9 are the same as ASCII '0' - '9' (0x30 - 0x39)* 0x40 : unassigned* VK_A - VK_Z are the same as ASCII 'A' - 'Z' (0x41 - 0x5A)*/enum KeyType{Key_Unknown,Key_Space = 0x20,Key_Prior,Key_Next,Key_End,Key_Home,Key_Left,Key_Up,Key_Right,Key_Down,Key_Select,Key_Print,Key_Execute,Key_Snapshot,Key_Insert,Key_Delete,Key_Help,/* 主鍵盤上的數字鍵 */Key_0 = 0x30,Key_1,Key_2,Key_3,Key_4,Key_5,Key_6,Key_7,Key_8,Key_9,Key_A = 0x41,Key_B,Key_C,Key_D,Key_E,Key_F,Key_G,Key_H,Key_I,Key_J,Key_K,Key_L,Key_M,Key_N,Key_O,Key_P,Key_Q,Key_R,Key_S,Key_T,Key_U,Key_V,Key_W,Key_X,Key_Y,Key_Z,/* 小鍵盤上的數字 */Key_NumPad_0 = 0x60,Key_NumPad_1,Key_NumPad_2,Key_NumPad_3,Key_NumPad_4,Key_NumPad_5,Key_NumPad_6,Key_NumPad_7,Key_NumPad_8,Key_NumPad_9,Key_F1 = 0x70,Key_F2,Key_F3,Key_F4,Key_F5,Key_F6,Key_F7,Key_F8,Key_F9,Key_F10,Key_F11,Key_F12,Key_F13,Key_F14,Key_F15,Key_F16,Key_F17,Key_F18,Key_F19,Key_F20,Key_F21,Key_F22,Key_F23,Key_F24,};?
鍵盤事件監聽器定義
class DLL_export KeyEventListener : public EventListener{public:KeyEventListener();virtual ~KeyEventListener();virtual void keyPress(const KeyEvent& event) {}virtual void keyRelease(const KeyEvent& event) {}void handleEvent(const BaseEvent& event);};?
對于按鍵信息的捕捉,和鼠標事件一樣在 handleMessage 函數中,這里只截取了鍵盤事件
void WinMsgHandle::handleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){baseEvent.nEventID = ET_UNKNOWN;/* 鍵盤按鍵事件信息 */if ( msg == WM_KEYDOWN || msg == WM_KEYUP ) {keyEvent.eventAction = (msg == WM_KEYDOWN) ? EventAction::ACT_PRESS : EventAction::ACT_RELAESE;keyEvent.keyType = keyMap(( UINT ) wParam);keyEvent.keys[( UINT ) wParam] = (msg == WM_KEYDOWN) ? true : false;baseEvent.nEventID = ET_KEY;baseEvent.pUserData = &keyEvent;EventDispatcher::getInstance()->dispatchEvent(baseEvent);}}和鼠標事件一樣,獲取按鍵數據 KeyEvent,然后附加到 BaseEvent 中,設置其 ID 為 ET_KEY,最后由分派器分派事件。按鍵事件監聽器處理 BaseEvent 時,根據動作類型調用相應函數,其函數有子類實現。
void KeyEventListener::handleEvent(const BaseEvent& event){if ( event.nEventID != EventType::ET_KEY && event.pUserData ) return;KeyEvent* keyEvent = static_cast<KeyEvent*>(event.pUserData);switch ( keyEvent->eventAction ) {case Simple2D::ACT_PRESS: this->keyPress(*keyEvent); break;case Simple2D::ACT_RELAESE: this->keyRelease(*keyEvent); break;}}?
最后在窗口的 proc 函數中
/* 處理鼠標和按鍵事件 */if ( self ) {self->winMsgHandle.handleMessage(wnd, msg, wParam, lParam);}?
主循環中分派所有事件
if ( PeekMessage(&msg, 0, 0, 0, PM_REMOVE) ) {TranslateMessage(&msg);DispatchMessage(&msg);EventDispatcher::getInstance()->flushEvent();}?
新建一個測試類,繼承與鼠標事件監聽器和按鍵事件監聽器,實現監聽器中的函數,輸出到輸出窗口
class EventTest : public MouseEventListener, public KeyEventListener { public://void mouseMove(const MouseEvent& event)//{// log("mouse move");// log("x:%d - y:%d", event.nX, event.nY);//}void mousePress(const MouseEvent& event){if ( event.buttonType == ButtonType::LEFT_BUTTON ) {log("left button press");}else if ( event.buttonType == ButtonType::MIDDLE_BUTTON ) {log("middle button press");}else if ( event.buttonType == ButtonType::RIGHT_BUTTON ) {log("right button press");}log("x:%d - y:%d", event.nX, event.nY);}void mouseRelease(const MouseEvent& event){log("mouse release");log("x:%d - y:%d", event.nX, event.nY);}void mouseDoubleClick(const MouseEvent& event){log("mouse double click");log("x:%d - y:%d", event.nX, event.nY);}void mouseWheel(const MouseEvent& event){log("mouse wheel");log("delta: %d", event.nDelta);}void keyPress(const KeyEvent& event){if ( event.keys[KeyType::Key_A] && event.keys[KeyType::Key_S] ) {log("同時按下 AS");}}void keyRelease(const KeyEvent& event){if ( event.keyType == KeyType::Key_NumPad_1 ) {log("釋放鍵 1");}} };?
運行程序的結果
?
源碼下載:http://pan.baidu.com/s/1skOmP21
轉載于:https://www.cnblogs.com/ForEmail5/p/6882998.html
總結
以上是生活随笔為你收集整理的基于OpenGL编写一个简易的2D渲染框架-07 鼠标事件和键盘事件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jsp常用动作
- 下一篇: JavaScript Math 对象