Torque3D Documentation / _generateds / winDispatch.cpp

winDispatch.cpp

Engine/source/windowManager/win32/winDispatch.cpp

More...

Classes:

Public Defines

define
_WIN32_WINNT() 0x0400
define
dIsStandardVK(c) 	(((0x08 <= (c)) && ((c) <= 0x12)) || \
	((c) == 0x1b) ||                    \
	((0x20 <= (c)) && ((c) <= 0x2e)) || \
	((0x30 <= (c)) && ((c) <= 0x39)) || \
	((0x41 <= (c)) && ((c) <= 0x5a)) || \
	((0x70 <= (c)) && ((c) <= 0x7B)))
define

Public Functions

bool
_dispatch(HWND hWnd, UINT message, WPARAM wParam, WPARAM lParam)
_keyboardEvent(Win32Window * window, UINT message, WPARAM wParam, WPARAM lParam)
Dispatch(DispatchType type, HWND hWnd, UINT message, WPARAM wparam, WPARAM lparam)

Dispatch the event into the journaling system.

bool

Dispatch the next event in the delayed dispatch queue.

DispatchRemove(HWND hWnd)

Remove events related to the window from the dispatch queue.

RemoveMessages(HWND hWnd, UINT msgBegin, UINT msgEnd)

Remove messages from the event queue, matching a msg value range or hWnd.

Detailed Description

Public Defines

_WIN32_WINNT() 0x0400
dIsStandardVK(c) 	(((0x08 <= (c)) && ((c) <= 0x12)) || \
	((c) == 0x1b) ||                    \
	((0x20 <= (c)) && ((c) <= 0x2e)) || \
	((0x30 <= (c)) && ((c) <= 0x39)) || \
	((0x41 <= (c)) && ((c) <= 0x5a)) || \
	((0x70 <= (c)) && ((c) <= 0x7B)))
NO_MINMAX() 
WIN32_LEAN_AND_MEAN() 

Public Variables

WinMessageQueue _MessageQueue 
U32 _ModifierKeys 
bool initKBState 
BYTE keyboardState [256]
bool sgDoubleByteEnabled 

Public Functions

_dispatch(HWND hWnd, UINT message, WPARAM wParam, WPARAM lParam)

_keyboardEvent(Win32Window * window, UINT message, WPARAM wParam, WPARAM lParam)

convertModifierBits(const U32 in)

DIK_to_Key(U8 dikCode)

Dispatch(DispatchType type, HWND hWnd, UINT message, WPARAM wparam, WPARAM lparam)

Dispatch the event into the journaling system.

Dispatch Win32 events into the journaling system. To avoid problems with journaling, events should normally use the DelayedDispatch type.

Delayed events are pushed onto a queue for later processing by DispatchNext().

DispatchNext()

Dispatch the next event in the delayed dispatch queue.

This function should be called outside of any journaled calls. Returns true if an event was dispatched.

DispatchRemove(HWND hWnd)

Remove events related to the window from the dispatch queue.

RemoveMessages(HWND hWnd, UINT msgBegin, UINT msgEnd)

Remove messages from the event queue, matching a msg value range or hWnd.

If no filter is specified, either HWND or MessageRange, nothing will be removed You may not match HWND and MsgRange both, currently.

Message Range is calculated as follows.

  • Both Begin and End are specified as message values, ex WM_MOUSEMOVE

  • Specifying an identical set of begin/end will remove all messages matching that message value (WM_MOUSEMOVE)

  • If you specify a range it will remove from that beginning value through the end value

note:

: The range is useful because on windows messages declared such that you can filter a block of messages just by specifying the beginning value and end.
ex. WM_MOUSEFIRST,WM_MOUSELAST range will match all mouse messages.

Parameters:
hWnd

The HWND to filter by, this cannot be combined with a msg range filter currently

msgBegin

The beginning msg value to filter from

msgEnd

The ending msg value to filter to

TranslateOSKeyCode(U8 vcode)

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#define NO_MINMAX
 25#define WIN32_LEAN_AND_MEAN
 26
 27// This is a fix for mouse wheel support on 
 28// older versions of VC++.
 29#if _MSC_VER < 1500
 30#define _WIN32_WINNT 0x0400
 31#endif
 32
 33#include <windows.h>
 34
 35#include "platform/platformInput.h"
 36#include "windowManager/win32/winDispatch.h"
 37#include "windowManager/win32/win32Window.h"
 38#include "windowManager/win32/win32CursorController.h"
 39#include "platformWin32/winDirectInput.h"
 40#include "core/util/journal/process.h"
 41#include "core/util/journal/journaledSignal.h"
 42
 43#if !defined( TORQUE_SDL )
 44
 45static U32 _ModifierKeys=0;
 46static BYTE keyboardState[256];
 47static bool initKBState = false;
 48static bool sgDoubleByteEnabled = false;
 49
 50// is keyboard input a standard (non-changing) VK keycode
 51#define dIsStandardVK(c) (((0x08 <= (c)) && ((c) <= 0x12)) || \
 52   ((c) == 0x1b) ||                    \
 53   ((0x20 <= (c)) && ((c) <= 0x2e)) || \
 54   ((0x30 <= (c)) && ((c) <= 0x39)) || \
 55   ((0x41 <= (c)) && ((c) <= 0x5a)) || \
 56   ((0x70 <= (c)) && ((c) <= 0x7B)))
 57
 58extern InputObjectInstances DIK_to_Key( U8 dikCode );
 59
 60extern U8 TranslateOSKeyCode(U8 vcode );
 61
 62extern InputModifiers convertModifierBits(const U32 in);
 63
 64static void _keyboardEvent(Win32Window* window,UINT message, WPARAM wParam, WPARAM lParam)
 65{
 66   if(!initKBState)
 67   {
 68      dMemset(keyboardState, 0, sizeof(keyboardState));
 69      initKBState = true;
 70   }
 71
 72   // Extract windows key info:
 73   // S32 repeatCount = (lParam & 0xffff);
 74   U32 scanCode    = (lParam >> 16) & 0xff;
 75   bool extended   = lParam & (1 << 24);       // Enhanced keyboard key
 76   bool previous   = lParam & (1 << 30);       // Previously down
 77   bool make       = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN);
 78
 79   // Translate the OS virtual key code to a Torque KEY_XXXX.   
 80   S32 nVirtkey = TranslateOSKeyCode( wParam );
 81
 82   S32 keyCode;
 83   if ( wParam == VK_PROCESSKEY && sgDoubleByteEnabled )
 84      keyCode = MapVirtualKey( scanCode, 1 );   // This is the REAL virtual key...
 85   else
 86      keyCode = wParam;
 87
 88   // Convert alt/shift/ctrl to left or right variant if needed.
 89   S32 newVirtKey = nVirtkey;
 90   switch(nVirtkey)
 91   {
 92   case KEY_ALT:
 93      newVirtKey = extended ? KEY_RALT : KEY_LALT;
 94      break;
 95   case KEY_CONTROL:
 96      newVirtKey = extended ? KEY_RCONTROL : KEY_LCONTROL;
 97      break;
 98   case KEY_SHIFT:
 99      newVirtKey = (scanCode == 54) ? KEY_RSHIFT : KEY_LSHIFT;
100      break;
101   case KEY_RETURN:
102      if ( extended )
103         newVirtKey = KEY_NUMPADENTER;
104      break;
105   }
106
107   // Track modifier keys
108   U32 modifier = 0;
109   switch (newVirtKey) 
110   {
111   case KEY_LALT:     modifier = IM_LALT;   break;
112   case KEY_RALT:     modifier = IM_RALT;   break;
113   case KEY_LSHIFT:   modifier = IM_LSHIFT; break;
114   case KEY_RSHIFT:   modifier = IM_RSHIFT; break;
115   case KEY_LCONTROL: modifier = IM_LCTRL;  break;
116   case KEY_RCONTROL: modifier = IM_RCTRL;  break;
117   }
118
119   if (make)
120   {
121      _ModifierKeys |= modifier;
122      keyboardState[keyCode] |= 0x80;
123   }
124   else
125   {
126      _ModifierKeys &= ~modifier;
127      keyboardState[keyCode] &= 0x7f;
128   }
129
130   U32 torqueMods = convertModifierBits( _ModifierKeys );
131   Input::setModifierKeys( torqueMods );
132   
133   // If character event translation is active and this isn't a key
134   // mapped in the global action map, try converting the event into
135   // a character event first.
136   
137   if(    make
138       && window->getKeyboardTranslation()
139       && !window->shouldNotTranslate( torqueMods, newVirtKey ) )
140   {
141      wchar_t chars[ 64 ];
142      dMemset( chars, 0, sizeof( chars ) );
143
144      S32 res = ToUnicode( keyCode, scanCode, keyboardState, chars, sizeof( chars ) / sizeof( chars[ 0 ] ), 0 );
145
146      // This should only happen on Window 9x/ME systems
147      if( res == 0 )
148         res = ToAscii( keyCode, scanCode, keyboardState, (LPWORD)chars, 0 );
149
150      if( res >= 1 )
151      {
152         // Post chars, but filter them to not be control codes... this is a bit hacky.
153
154         bool handledCharEvent = false;
155         for( S32 i=0; i< res; i ++ )
156            if( chars[i] >= 32)
157            {
158               window->charEvent.trigger(window->getWindowId(),_ModifierKeys,chars[i]);
159               handledCharEvent = true;
160            }
161            
162         if( handledCharEvent )
163            return;
164      }
165   }
166
167   // Produce a key event.
168   
169   U32 action = make ? (previous ? IA_REPEAT : IA_MAKE ) : IA_BREAK;   
170   window->keyEvent.trigger(window->getWindowId(),_ModifierKeys,action,newVirtKey);
171}
172
173
174//-----------------------------------------------------------------------------
175
176static bool _dispatch(HWND hWnd,UINT message,WPARAM wParam,WPARAM lParam)
177{
178   static bool button[3] = {false,false,false};
179   static S32 mouseNCState = -1; // -1 denotes unchanged, 
180   // 0  denotes changed but was hidden
181   // 1  denotes changed but was visible
182   Win32Window* window = hWnd?(Win32Window*)GetWindowLongPtr(hWnd, GWLP_USERDATA): 0;
183   const WindowId devId = window ? window->getWindowId() : 0;
184
185   // State tracking for focus/lose focus cursor management
186   static bool cursorLocked = false;
187   static bool cursorVisible = true;
188
189   switch(message) 
190   {
191   case WM_MOUSEMOVE:
192      {
193         // Skip it if we have no window!
194         if (!window || !window->getCursorController())
195            break;
196
197
198         PlatformCursorController *pController = window->getCursorController();
199
200         // If we're locked and unfocused, ignore it.
201         if(window->shouldLockMouse() && !window->isFocused())
202            break;
203
204         // If the mouse was shown to accommodate a NC mouse move
205         //  we need to change it back to what it was
206         if( mouseNCState != -1 )
207         {
208            pController->setCursorVisible( mouseNCState );
209            mouseNCState = -1; // reset to unchanged
210         }
211
212         // Let the cursor manager update the native cursor.
213         pController->refreshCursor();
214
215         // Grab the mouse pos so we can modify it.
216         S32 mouseX = S16(LOWORD(lParam));
217         S32 mouseY = S16(HIWORD(lParam));
218
219         // Ensure mouse lock when appropriate
220         window->setMouseLocked( window->shouldLockMouse() );
221
222         // Are we locked?
223         if(window->isMouseLocked())
224         {
225            // Always invisible when locked.
226            if( window->isCursorVisible() )
227               window->setCursorVisible( false );
228
229            RECT r;
230            GetWindowRect(window->getHWND(), &r);
231
232            // See Win32Window::setMouseLocked for explanation
233            RECT rCopy = r;
234            rCopy.top  += 32; rCopy.bottom -= 64;
235            rCopy.left += 32; rCopy.right  -= 64;
236            ClipCursor(&rCopy);
237
238            // Recenter the mouse if necessary (don't flood the message pump)
239            Point2I curPos;
240            pController->getCursorPosition( curPos );
241
242            const S32 centerX = (r.right + r.left) / 2;
243            const S32 centerY = (r.bottom + r.top) / 2;
244
245            if( curPos.x != centerX || curPos.y != centerY )
246               pController->setCursorPosition(centerX, centerY);
247
248            // Convert the incoming client pos into a screen pos, so we can
249            // accurately convert to relative coordinates.
250            POINT mousePos;
251            mousePos.x = mouseX;
252            mousePos.y = mouseY;
253
254            ClientToScreen(window->getHWND(), &mousePos);
255
256            // Now we can calculate the position relative to the center we set.
257            mouseX = mousePos.x - centerX;
258            mouseY = mousePos.y - centerY;
259         }
260         else
261         {
262            // Probably don't need to call this all the time but better
263            // safe than sorry...
264            ClipCursor(NULL);
265         }
266
267         window->mouseEvent.trigger(devId,_ModifierKeys,mouseX,mouseY,window->isMouseLocked());
268         break;
269      }
270
271      // We want to show the system cursor whenever we leave
272      // our window, and it'd be simple, except for one problem:
273      // showcursor isn't a toggle.  so, keep hammering it until
274      // the cursor is *actually* going to be shown.
275   case WM_NCMOUSEMOVE:
276      {
277         if( window )
278         {
279            mouseNCState = ( window->isCursorVisible() ? 1 : 0);
280            window->setCursorVisible( true );
281         }
282
283         break;
284      }
285
286   case WM_LBUTTONDOWN:
287   case WM_MBUTTONDOWN:
288   case WM_RBUTTONDOWN: {
289      S32 index = (message - WM_LBUTTONDOWN) / 3;
290      button[index] = true;
291
292      // Capture the mouse on button down to allow dragging outside
293      // of the window boundary.
294      if (GetCapture() != hWnd)
295         SetCapture(hWnd);
296
297      if (window)
298         window->buttonEvent.trigger(devId,_ModifierKeys,IA_MAKE,index);
299      break;
300                   }
301
302   case WM_LBUTTONUP:
303   case WM_MBUTTONUP:
304   case WM_RBUTTONUP: {
305      S32 index = (message - WM_LBUTTONUP) / 3;
306      button[index] = false;
307
308      // Release mouse capture from button down.
309      if (!button[0] && !button[1] && !button[2])
310         ReleaseCapture();
311
312      if (window)
313         window->buttonEvent.trigger(devId,_ModifierKeys,IA_BREAK,index);
314      break;
315                  }
316
317   case WM_MOUSEWHEEL: // Vertical wheel.
318      if (window)
319         window->wheelEvent.trigger(devId,_ModifierKeys,0,GET_WHEEL_DELTA_WPARAM(wParam));
320      break;
321
322#ifdef WM_MOUSEHWHEEL // Vista
323   case WM_MOUSEHWHEEL: // Horizontal wheel.
324      if( window )
325         window->wheelEvent.trigger( devId, _ModifierKeys, GET_WHEEL_DELTA_WPARAM( wParam ), 0 );
326      break;
327#endif
328
329   case WM_KEYUP:
330   case WM_SYSKEYUP:
331   case WM_KEYDOWN:
332   case WM_SYSKEYDOWN:
333      if (window)
334         _keyboardEvent(window,message,wParam,lParam);
335      break;
336
337      // NOTE: if wParam is NOT equal to our window handle then we are GAINING focus
338   case WM_SETFOCUS:
339
340      // clear any key states 
341      _ModifierKeys = 0;
342      dMemset(keyboardState, 0, 256);
343      Input::setModifierKeys(0);
344
345      // We must have a window present; otherwise there's nothing further
346      // we can do about this event.
347      if (window && window->getHWND() != (HWND)wParam)
348      {
349         if (cursorVisible == false)
350            window->setCursorVisible(false);
351
352         if (cursorLocked == true)
353            window->setMouseLocked(true);
354
355         // Update window state.
356         window->setBackground(false);
357
358         // Fire event.
359         window->appEvent.trigger(devId, GainFocus);
360
361         if (!Input::isActive())
362            Input::activate();
363      }
364      break;
365
366      // NOTE: if wParam is NOT equal to our window handle then we are LOSING focus
367   case WM_KILLFOCUS:
368
369      // clear any key states 
370      _ModifierKeys = 0;
371      dMemset(keyboardState, 0, 256);
372      Input::setModifierKeys(0);
373
374      // We must have a window present; otherwise there's nothing further
375      // we can do about this event.
376      if (window && window->getHWND() != (HWND)wParam)
377      {
378         HWND hwnd = (HWND)wParam;
379         UTF16 classBuf[256];
380
381         if (hwnd)
382            GetClassName(hwnd, classBuf, sizeof(classBuf));
383
384         // We toggle the mouse lock when we become inactive 
385         // causing the subsequent lock call to defer itself
386         // until the window becomes active again.
387         if (window && window->isMouseLocked())
388         {
389            window->setMouseLocked( false );
390            window->setMouseLocked( true );
391         }
392
393         // FIXME [tom, 5/1/2007] Hard coding this is lame since there's a const in win32Window.cpp
394         // CodeReview - this fails if there is a second jug app in the arena.
395         if (hwnd == NULL || String::compare(classBuf, L"TorqueJuggernaughtWindow") != 0)
396         {
397            // We are being made inactive and the window being made active isn't
398            // a jugg window. Thus, we need to deactivate input.
399            if (Input::isActive())
400               Input::deactivate();
401         }
402
403         cursorVisible = window->isCursorVisible();
404         if (!cursorVisible)
405            window->setCursorVisible(true);
406
407         cursorLocked = window->isMouseLocked();
408         if (cursorLocked)
409            window->setMouseLocked(false);
410
411         // Update window state.
412         window->setBackground(true);
413
414         // Fire event.
415         window->appEvent.trigger(devId, LoseFocus);
416      }
417      break;      
418
419   case WM_ACTIVATEAPP:
420      if (wParam) 
421      {
422         // Could extract current modifier state from windows.
423         _ModifierKeys = 0;
424         Input::setModifierKeys(_ModifierKeys);
425      }
426      break;
427
428   case WM_CLOSE:
429      if (window)
430         window->appEvent.trigger(devId,WindowClose);
431
432      // Force a quit if we're in play mode, otherwise there would be
433      // no way to stop a journal playback.(
434      if (Journal::IsPlaying())
435         Process::requestShutdown();
436      break;
437
438   case WM_TIMER: {
439      if (window)
440         window->appEvent.trigger(devId,Timer);
441      break;
442               }
443
444   case WM_DESTROY:{
445      // Only people who care about this currently are web plugins, because
446      // everyone else will just handle the WM_CLOSE app event.
447      if(window)
448         window->appEvent.trigger(devId,WindowDestroy);
449      break;
450               }
451
452   case WM_QUIT: {
453      // Quit indicates that we're not going to receive anymore Win32 messages.
454      // Therefore, it's appropriate to flag our event loop for exit as well,
455      // since we won't be getting any more messages.
456      Process::requestShutdown((S32)wParam);
457      break;
458              }
459
460              // CodeReview - This is not used now and will incur an overhead for rendering 
461              //              since the renderThreadBlocked fix requires handling WM_PAINT and
462              //              triggering the displayEvent.  May need to revisit this at a later
463              //              time if we want event driven rendering.
464              //case WM_PAINT: {
465              //   // Checking for isOpen will keep us from generating an event
466              //   // during the window creation process, which can cause problems
467              //   // with the journaling.
468              //   if (window && window->isOpen() && !winState.renderThreadBlocked )
469              //      window->displayEvent.trigger(devId);
470              //}
471   case WM_SIZE: {
472      if (window && wParam != SIZE_MINIMIZED && !Journal::IsPlaying())
473      {
474         window->resizeEvent.trigger(window->getWindowId(), LOWORD(lParam),HIWORD(lParam));
475
476         // Consume all existing mouse events and those posted to our own dispatch queue
477         MSG msg;
478         PeekMessage( &msg, 0,WM_MOUSEFIRST,WM_MOUSELAST , PM_QS_POSTMESSAGE | PM_NOYIELD | PM_REMOVE );
479         RemoveMessages( NULL, WM_MOUSEMOVE, WM_MOUSEMOVE );
480
481         if( window->isMouseLocked())
482         {
483            RECT r;
484            GetWindowRect(window->getHWND(), &r);
485
486            S32 centerX = (r.right + r.left) >> 1;
487            S32 centerY = ((r.bottom + r.top) >> 1);
488            window->setCursorPosition( centerX, centerY );
489
490            // Set the CursorPos
491            SetCursorPos(centerX, centerY);
492         }
493      }
494
495              }
496
497   }
498   return true;
499}
500
501
502//-----------------------------------------------------------------------------
503
504// Structure used to store Windows events for delayed dispatching
505struct WinMessageQueue
506{
507public:
508   struct Message {
509      HWND hWnd;
510      UINT message;
511      WPARAM wparam;
512      WPARAM lparam;
513   };
514
515   WinMessageQueue()
516   {
517      VECTOR_SET_ASSOCIATION( _messageList );
518   }
519   bool isEmpty() {
520      return !_messageList.size();
521   }
522   void post(HWND hWnd,UINT message,WPARAM wparam,WPARAM lparam) {
523      Message msg;
524      msg.hWnd = hWnd;
525      msg.message = message;
526      msg.wparam = wparam;
527      msg.lparam = lparam;
528      _messageList.push_back(msg);
529   }
530   bool next(Message* msg) {
531      if (!_messageList.size())
532         return false;
533      *msg = _messageList.first();
534      _messageList.pop_front();
535      return true;
536   }
537   void remove(HWND hWnd, UINT msgBegin = -1, UINT msgEnd = -1) {
538      for (S32 i = 0; i < _messageList.size(); i++)
539      {
540         // Match Window
541         if( hWnd != NULL && _messageList[i].hWnd == hWnd)
542            _messageList.erase_fast(i--);
543         else if( msgBegin != -1 && msgEnd != -1 )
544         {
545            // CodeReview - Match Message Range [6/30/2007 justind]
546            //
547            // Word of caution : Use this only if you know what you're doing.
548            //  I cannot be responsible for you blowing your leg off destroying
549            //  a bunch of messages you didn't intend to if you specify a ridiculous
550            //  range of messages values.
551            //
552            // To filter a single message, pass the message as the begin and end.
553            if( _messageList[i].message >= msgBegin && _messageList[i].message <= msgEnd )
554               _messageList.erase_fast(i--);
555         }
556      }
557   }
558
559private:
560   Vector<Message> _messageList;
561};
562
563static WinMessageQueue _MessageQueue;
564
565
566void RemoveMessages(HWND hWnd,UINT msgBegin,UINT msgEnd )
567{
568   _MessageQueue.remove( hWnd, msgBegin, msgEnd );
569}
570
571// Dispatch the window event, or queue up for later
572void Dispatch(DispatchType type,HWND hWnd,UINT message,WPARAM wparam,WPARAM lparam)
573{
574   // If the message queue is not empty, then we'll need to delay
575   // this dispatch in order to preserve message order.
576   if (type == DelayedDispatch || !_MessageQueue.isEmpty())
577      _MessageQueue.post(hWnd,message,wparam,lparam);
578   else
579      _dispatch(hWnd,message,wparam,lparam);
580}
581
582// Dispatch next even in the queue
583bool DispatchNext()
584{
585   WinMessageQueue::Message msg;
586   if (!_MessageQueue.next(&msg))
587      return false;
588   _dispatch(msg.hWnd,msg.message,msg.wparam,msg.lparam);
589   return true;
590}
591
592// Remove events from the queue
593void DispatchRemove(HWND hWnd)
594{
595   _MessageQueue.remove(hWnd);
596}
597
598
599#endif
600