Subclass.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. ////////////////////////////////////////////////////////////////
  2. // Copyright 1998 Paul DiLascia
  3. // If this code works, it was written by Paul DiLascia.
  4. // If not, I don't know who wrote it.
  5. //
  6. // CSubclassWnd is a generic class for hooking another window's messages.
  7. //
  8. /////////////////////////////////////////////////////////////////////////////
  9. /****************************************************************************
  10. *
  11. * $Date: 10/14/99 12:41p $
  12. * $Revision: 2 $
  13. * $Archive: /CodeJock/CJLibrary/Subclass.cpp $
  14. *
  15. * $History: Subclass.cpp $
  16. *
  17. * ***************** Version 2 *****************
  18. * User: Kirk Stowell Date: 10/14/99 Time: 12:41p
  19. * Updated in $/CodeJock/CJLibrary
  20. * Added source control history to file header.
  21. *
  22. ***************************************************************************/
  23. /////////////////////////////////////////////////////////////////////////////
  24. #include "StdAfx.h"
  25. #include "Subclass.h"
  26. #ifdef _DEBUG
  27. #define new DEBUG_NEW
  28. #undef THIS_FILE
  29. static char THIS_FILE[] = __FILE__;
  30. #endif
  31. //////////////////
  32. // The message hook map is derived from CMapPtrToPtr, which associates
  33. // a pointer with another pointer. It maps an HWND to a CSubclassWnd, like
  34. // the way MFC's internal maps map HWND's to CWnd's. The first CSubclassWnd
  35. // attached to a window is stored in the map; all other CSubclassWnd's for that
  36. // window are then chained via CSubclassWnd::m_pNext.
  37. //
  38. class CSubclassWndMap : private CMapPtrToPtr {
  39. public:
  40. CSubclassWndMap();
  41. ~CSubclassWndMap();
  42. static CSubclassWndMap& GetHookMap();
  43. void Add(HWND hwnd, CSubclassWnd* pSubclassWnd);
  44. void Remove(CSubclassWnd* pSubclassWnd);
  45. void RemoveAll(HWND hwnd);
  46. CSubclassWnd* Lookup(HWND hwnd);
  47. };
  48. // This trick is used so the hook map isn't
  49. // instantiated until someone actually requests it.
  50. //
  51. #define theHookMap (CSubclassWndMap::GetHookMap())
  52. IMPLEMENT_DYNAMIC(CSubclassWnd, CWnd);
  53. CSubclassWnd::CSubclassWnd()
  54. {
  55. m_pNext = NULL;
  56. m_pOldWndProc = NULL;
  57. m_hWnd = NULL;
  58. }
  59. CSubclassWnd::~CSubclassWnd()
  60. {
  61. if (m_hWnd)
  62. HookWindow((HWND)NULL); // unhook window
  63. }
  64. //////////////////
  65. // Hook a window.
  66. // This installs a new window proc that directs messages to the CSubclassWnd.
  67. // pWnd=NULL to remove.
  68. //
  69. BOOL CSubclassWnd::HookWindow(HWND hwnd)
  70. {
  71. ASSERT_VALID(this);
  72. if (hwnd) {
  73. // Hook the window
  74. ASSERT(m_hWnd==NULL);
  75. ASSERT(::IsWindow(hwnd));
  76. theHookMap.Add(hwnd, this); // Add to map of hooks
  77. } else if (m_hWnd) {
  78. // Unhook the window
  79. theHookMap.Remove(this); // Remove from map
  80. m_pOldWndProc = NULL;
  81. }
  82. m_hWnd = hwnd;
  83. return TRUE;
  84. }
  85. //////////////////
  86. // Window proc-like virtual function which specific CSubclassWnds will
  87. // override to do stuff. Default passes the message to the next hook;
  88. // the last hook passes the message to the original window.
  89. // You MUST call this at the end of your WindowProc if you want the real
  90. // window to get the message. This is just like CWnd::WindowProc, except that
  91. // a CSubclassWnd is not a window.
  92. //
  93. LRESULT CSubclassWnd::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
  94. {
  95. // ASSERT_VALID(this); // removed for speed
  96. ASSERT(m_pOldWndProc);
  97. return m_pNext ? m_pNext->WindowProc(msg, wp, lp) :
  98. ::CallWindowProc(m_pOldWndProc, m_hWnd, msg, wp, lp);
  99. }
  100. //////////////////
  101. // Like calling base class WindowProc, but with no args, so individual
  102. // message handlers can do the default thing. Like CWnd::Default
  103. //
  104. LRESULT CSubclassWnd::Default()
  105. {
  106. // MFC stores current MSG in thread state
  107. MSG& curMsg = AfxGetThreadState()->m_lastSentMsg;
  108. // Note: must explicitly call CSubclassWnd::WindowProc to avoid infinte
  109. // recursion on virtual function
  110. return CSubclassWnd::WindowProc(curMsg.message, curMsg.wParam, curMsg.lParam);
  111. }
  112. #ifdef _DEBUG
  113. void CSubclassWnd::AssertValid() const
  114. {
  115. CObject::AssertValid();
  116. CSubclassWnd *p = NULL;
  117. ASSERT(m_hWnd==NULL || ::IsWindow(m_hWnd));
  118. if (m_hWnd) {
  119. for ( p = theHookMap.Lookup(m_hWnd); p; p=p->m_pNext) {
  120. if (p==this)
  121. break;
  122. }
  123. ASSERT(p); // should have found it!
  124. }
  125. }
  126. void CSubclassWnd::Dump(CDumpContext& dc) const
  127. {
  128. CObject::Dump(dc);
  129. }
  130. #endif
  131. //////////////////
  132. // Subclassed window proc for message hooks. Replaces AfxWndProc (or whatever
  133. // else was there before.)
  134. //
  135. LRESULT CALLBACK
  136. HookWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
  137. {
  138. #ifdef _USRDLL
  139. // If this is a DLL, need to set up MFC state
  140. AFX_MANAGE_STATE(AfxGetStaticModuleState());
  141. #endif
  142. // Set up MFC message state just in case anyone wants it
  143. // This is just like AfxCallWindowProc, but we can't use that because
  144. // a CSubclassWnd is not a CWnd.
  145. //
  146. MSG& curMsg = AfxGetThreadState()->m_lastSentMsg;
  147. MSG oldMsg = curMsg; // save for nesting
  148. curMsg.hwnd = hwnd;
  149. curMsg.message = msg;
  150. curMsg.wParam = wp;
  151. curMsg.lParam = lp;
  152. // Get hook object for this window. Get from hook map
  153. CSubclassWnd* pSubclassWnd = theHookMap.Lookup(hwnd);
  154. ASSERT(pSubclassWnd);
  155. LRESULT lr;
  156. if (msg==WM_NCDESTROY) {
  157. // Window is being destroyed: unhook all hooks (for this window)
  158. // and pass msg to orginal window proc
  159. //
  160. WNDPROC wndproc = pSubclassWnd->m_pOldWndProc;
  161. theHookMap.RemoveAll(hwnd);
  162. lr = ::CallWindowProc(wndproc, hwnd, msg, wp, lp);
  163. } else {
  164. // pass to msg hook
  165. lr = pSubclassWnd->WindowProc(msg, wp, lp);
  166. }
  167. curMsg = oldMsg; // pop state
  168. return lr;
  169. }
  170. ////////////////////////////////////////////////////////////////
  171. // CSubclassWndMap implementation
  172. //
  173. CSubclassWndMap::CSubclassWndMap()
  174. {
  175. }
  176. CSubclassWndMap::~CSubclassWndMap()
  177. {
  178. // This assert bombs when posting WM_QUIT, so I've deleted it.
  179. // ASSERT(IsEmpty()); // all hooks should be removed!
  180. }
  181. //////////////////
  182. // Get the one and only global hook map
  183. //
  184. CSubclassWndMap& CSubclassWndMap::GetHookMap()
  185. {
  186. // By creating theMap here, C++ doesn't instantiate it until/unless
  187. // it's ever used! This is a good trick to use in C++, to
  188. // instantiate/initialize a static object the first time it's used.
  189. //
  190. static CSubclassWndMap theMap;
  191. return theMap;
  192. }
  193. /////////////////
  194. // Add hook to map; i.e., associate hook with window
  195. //
  196. void CSubclassWndMap::Add(HWND hwnd, CSubclassWnd* pSubclassWnd)
  197. {
  198. ASSERT(hwnd && ::IsWindow(hwnd));
  199. // Add to front of list
  200. pSubclassWnd->m_pNext = Lookup(hwnd);
  201. SetAt(hwnd, pSubclassWnd);
  202. if (pSubclassWnd->m_pNext==NULL) {
  203. // If this is the first hook added, subclass the window
  204. pSubclassWnd->m_pOldWndProc =
  205. (WNDPROC)SetWindowLong(hwnd, GWL_WNDPROC, (DWORD)HookWndProc);
  206. } else {
  207. // just copy wndproc from next hook
  208. pSubclassWnd->m_pOldWndProc = pSubclassWnd->m_pNext->m_pOldWndProc;
  209. }
  210. ASSERT(pSubclassWnd->m_pOldWndProc);
  211. }
  212. //////////////////
  213. // Remove hook from map
  214. //
  215. void CSubclassWndMap::Remove(CSubclassWnd* pUnHook)
  216. {
  217. HWND hwnd = pUnHook->m_hWnd;
  218. ASSERT(hwnd && ::IsWindow(hwnd));
  219. CSubclassWnd* pHook = Lookup(hwnd);
  220. ASSERT(pHook);
  221. if (pHook==pUnHook) {
  222. // hook to remove is the one in the hash table: replace w/next
  223. if (pHook->m_pNext)
  224. SetAt(hwnd, pHook->m_pNext);
  225. else {
  226. // This is the last hook for this window: restore wnd proc
  227. RemoveKey(hwnd);
  228. SetWindowLong(hwnd, GWL_WNDPROC, (DWORD)pHook->m_pOldWndProc);
  229. }
  230. } else {
  231. // Hook to remove is in the middle: just remove from linked list
  232. while (pHook->m_pNext!=pUnHook)
  233. pHook = pHook->m_pNext;
  234. ASSERT(pHook && pHook->m_pNext==pUnHook);
  235. pHook->m_pNext = pUnHook->m_pNext;
  236. }
  237. }
  238. //////////////////
  239. // Remove all the hooks for a window
  240. //
  241. void CSubclassWndMap::RemoveAll(HWND hwnd)
  242. {
  243. CSubclassWnd* pSubclassWnd;
  244. while ((pSubclassWnd = Lookup(hwnd))!=NULL)
  245. pSubclassWnd->HookWindow((HWND)NULL); // (unhook)
  246. }
  247. /////////////////
  248. // Find first hook associate with window
  249. //
  250. CSubclassWnd* CSubclassWndMap::Lookup(HWND hwnd)
  251. {
  252. CSubclassWnd* pFound = NULL;
  253. if (!CMapPtrToPtr::Lookup(hwnd, (void*&)pFound))
  254. return NULL;
  255. ASSERT_KINDOF(CSubclassWnd, pFound);
  256. return pFound;
  257. }