Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

How to subclass a window multiple times

Иногда бывает необходимо привязаться к окну и добавить логики по обработке оконных сообщений. Механизм имеет всем известное название сабклассинг. MFC, как и многие библиотеки, поддерживает сабклассинг окон. Проблема лишь в том, что сделать это можно лишь один раз. А если надо много раз? А если к нам попадает хэндл окна и мы понятия не имеем кем оно было создано? Проблемка, однако. Но на помощь как всегда приходит могучий ATL.

#pragma once
 
#include <atlwin.h>
 
class CWindowSubclass
{
 
public:
   CWindowSubclass(CMessageMap *pObject, DWORD msgMapID = 0);
   CWindowSubclass(HWND hWnd, CMessageMap *pObject, DWORD msgMapID = 0);
   ~CWindowSubclass();
 
public:   
   void SwitchMessageMap(DWORD msgMapID);
   BOOL SubclassWindow(HWND hWnd);
   HWND UnsubclassWindow(bool force = false);
   HWND GetHWnd() const;
   operator bool() const;
 
private:   
   static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
   LRESULT DefWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);   
 
private:
   HWND m_hWnd;
   CWndProcThunk m_thunk;
   WNDPROC m_pfnSuperWindowProc;
   CMessageMap *m_pObject;
   DWORD m_msgMapID;
};
#include "stdafx.h"
#include "WindowSubclass.h"
 
CWindowSubclass::CWindowSubclass(CMessageMap *pObject,  DWORD msgMapID/* = 0*/)
   :  m_hWnd(NULL),         
      m_pObject(pObject),
      m_msgMapID(msgMapID),
      m_pfnSuperWindowProc(NULL)
{
}
 
CWindowSubclass::CWindowSubclass(HWND hWnd,  CMessageMap *pObject, DWORD msgMapID/* = 0*/)
   :  m_hWnd(NULL),
      m_pObject(pObject),
      m_msgMapID(msgMapID),
      m_pfnSuperWindowProc(NULL)
{
   SubclassWindow(hWnd);
}
 
CWindowSubclass::~CWindowSubclass()
{
   if (m_hWnd) {
      UnsubclassWindow(TRUE);
   }
}
 
HWND CWindowSubclass::GetHWnd() const
{
   return(m_hWnd);
}
 
CWindowSubclass::operator bool() const
{
   return m_hWnd != NULL;
}
 
void CWindowSubclass::SwitchMessageMap(DWORD msgMapID)
{
   m_msgMapID = msgMapID;
}
 
LRESULT CWindowSubclass::DefWindowProc(UINT uMsg,  WPARAM wParam,  LPARAM lParam)
{
#ifdef STRICT
   return ::CallWindowProc(m_pfnSuperWindowProc, m_hWnd, uMsg, wParam, lParam);
#else
   return ::CallWindowProc(reinterpret_cast<FARPROC>(m_pfnSuperWindowProc), m_hWnd, uMsg, wParam, lParam);
#endif
}
 
LRESULT CWindowSubclass::WindowProc(HWND hWnd,  UINT uMsg,  WPARAM wParam,  LPARAM lParam)
{
   CWindowSubclass * const pThis = reinterpret_cast<CWindowSubclass *>(hWnd);
 
   ATLASSERT(pThis);
   if (!pThis) {
      return 0;
   }
 
   ATLASSERT(pThis->m_hWnd);
   ATLASSERT(pThis->m_pObject);
   if (!pThis->m_hWnd || !pThis->m_pObject) {
      return 0;
   }
 
   LRESULT lRes = 0;
   BOOL bRet = pThis->m_pObject->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, pThis->m_msgMapID);
   if (!bRet) {
      if (uMsg != WM_NCDESTROY)
         lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
      else
      {
         const LONG_PTR pfnWndProc = ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC);
         lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
         if (pThis->m_pfnSuperWindowProc && ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC) == pfnWndProc) {
            ::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(pThis->m_pfnSuperWindowProc));
         }
         pThis->m_hWnd = NULL;
      }
   }
   return lRes;
}
 
 
BOOL CWindowSubclass::SubclassWindow(HWND hWnd)
{      
   ATLASSUME(m_hWnd == NULL);
   ATLASSERT(::IsWindow(hWnd));
 
   const BOOL result = m_thunk.Init(WindowProc, this);
   if (result == FALSE)
      return result;
 
   const WNDPROC pProc = m_thunk.GetWNDPROC();
   const WNDPROC pfnWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(pProc)));
   if (pfnWndProc == NULL)
      return FALSE;
   m_pfnSuperWindowProc = pfnWndProc;
   m_hWnd = hWnd;
   return TRUE;
}
 
HWND CWindowSubclass::UnsubclassWindow(bool force/* = false*/)
{
   ATLASSUME(m_hWnd != NULL);
 
   const WNDPROC pOurProc = m_thunk.GetWNDPROC();
   const WNDPROC pActiveProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtr(m_hWnd, GWLP_WNDPROC));
 
   HWND hWnd = NULL;
   if (force || pOurProc == pActiveProc) {
      if (!::SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(m_pfnSuperWindowProc)))
         return NULL;
 
      m_pfnSuperWindowProc = NULL;
      hWnd = m_hWnd;
      m_hWnd = NULL;
   }
   return hWnd;
}

Использовать этот класс проще простого, достаточно передать наследника ATL::CMessageMap в качестве параметра конструктора вместе с дескриптором окна. Плюс привычная для ATL конструкция:

BEGIN_MSG_MAP(MyCMessageMapDerivedClass)
   MESSAGE_HANDLER(WM_MESSAGE, OnMessage)
END_MSG_MAP()

Класс можно использовать множество раз для одного окна. За это спасибо механизму переходников ATL.

0 коммент.:

Отправить комментарий

Copyright 2007-2011 Chabster