zoukankan      html  css  js  c++  java
  • 基于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

  • 相关阅读:
    eclipse下c/cpp " undefined reference to " or "launch failed binary not found"问题
    blockdev 设置文件预读大小
    宝宝语录
    CentOS修改主机名(hostname)
    subprocess报No such file or directory
    用ldap方式访问AD域的的错误解释
    英特尔的VTd技术是什么?
    This virtual machine requires the VMware keyboard support driver which is not installed
    Linux内核的文件预读详细详解
    UNP总结 Chapter 26~29 线程、IP选项、原始套接字、数据链路访问
  • 原文地址:https://www.cnblogs.com/ForEmail5/p/6882998.html
Copyright © 2011-2022 走看看