• 基于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

  • 相关阅读:
    洛谷 P2807 三角形计数
    洛谷 P1727 计算π
    洛谷 P1595 信封问题
    洛谷 P3131 [USACO16JAN]子共七Subsequences Summing to Sevens
    3.1、spark集群运行应用
    移动端自适应
    【Flex布局】
    【pm2】
    【安全】
    【Bower】
  • 原文地址:https://www.cnblogs.com/ForEmail5/p/6882998.html
Copyright © 2020-2023  润新知