Иногда бывает необходимо привязаться к окну и добавить логики по обработке оконных сообщений. Механизм имеет всем известное название сабклассинг. 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.