Skip navigation links
IT services and product development
Menu
TwoLogs
IT services and product development

Source code for C_SubclassedWindow

Download

The source files for this module are listed below.  You can also download the module C_SubclassedWindow as a zip archive; this archive contains all the source files and documentation.

Description

CSubclassedWindow allows easy window subclassing and message interception.

Information

To start subclassing windows, you first have to derive a class from CSubclassedWindow.  Also implement the virtual method ProcessMessage.

You can subclass one or more windows at the same time.  To start subclassing a window, you call the Subclass method with the handle of the window you want to subclass.  Call it again with another window's handle to also subclass that window.  If you want to stop subclassing a particular window, call the StopSubclassing window with the window's handle, or just call StopSubclassingAll to stop subclassing all windows in one go.  If your CSubclassedWindow-derived class instance goes out of scope, it will also automatically stop subclassing any window it subclasses.

Once you have started subclassing a window, your implementation of ProcessMessage will be called for each intercepted message.  You can process the message yourself or just ignore it, depending on the needs in your code.  The CMessage parameter contains all the message information in it's member variables.  If you decide to handle the message, you can set the bStop member of the message to stop further processing of the message by the original window procedure for the window.  In that case you can also specify a return value in the nResult member.  You can call DefaultResult if you want to let the default message handler handle the message first (so you can e.g. modify it's result).

Multiple subclassers can subclass the same window.  In this case, their ProcessMessage calls will be chained together; further processing stops when you indicate the message may not be processed any further.  The result of the default message handler is cached by the DefaultResult method, so the default message processing will only occur once, even when multiple chained subclassers call this method.

Files

Each file belonging to this source code module is listed below.

SubclassedWindow.h

/*******************************************************************************

  Version: 2
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  This code is freely distributable, as long as this comment remains intact.
  If you find this source useful, you may use this code in your own projects
  free of charge, but some acknowledgement to the author of this code is always
  appreciated :)
  The source is however distributed 'as is' without waranty and/or support, and
  may not be fit for each and every application.  Use it at your own discretion
  and at your own risk.
  The source already has undergone testing.  This doesn't mean however that all
  bugs are removed from this piece of code.  If you find one of them, please
  contact me about it.  I can however not guarantee when and if the bug will be
  fixed.

  More information about this module can be found in the accompanying HTML file.

*******************************************************************************/

#ifndef INCLUDE_TWOLOGS_COMMON_SUBCLASSEDWINDOW_H
#define INCLUDE_TWOLOGS_COMMON_SUBCLASSEDWINDOW_H

#include <windows.h>
#include <set>

// Single message
class CMessage {
public:
  // The message details
  const HWND hWindow;
  const UINT eCode;
  const WPARAM nWParam;
  const LPARAM nLParam;

  // The currently set result
  LRESULT nResult;

  // Whether further processing must stop
  bool bStop;

  // Con- & destructor
  CMessage(WNDPROC fOriginalProc, HWND hWindow, UINT eMsg, WPARAM nWParam, LPARAM nLParam);
  virtual ~CMessage();

  // Gets the (cached) result from the default window procedure handler
  LRESULT DefaultResult();

private:
  // The window's own window proc
  const WNDPROC m_fOriginalProc;

  // Whether default processing has already been done for the message
  bool m_bDefProcessed;

  // The result of the default processing
  LRESULT m_nDefResult;
};

class CSubclassedWindow {
public:
  // Con- & destructor
  CSubclassedWindow();
  virtual ~CSubclassedWindow();

  // Starts subclassing the given window
  bool Subclass(HWND hWindow);

  // Stops subclassing the given window
  void StopSubclassing(HWND hWindow);

  // Stops subclassing all windows
  void StopSubclassingAll();

  // Processes the given message
  virtual void ProcessMessage(CMessage& oMsg) = 0;

private:
  // The windows we handle
  typedef std::set<HWND> CWindowSet;
  CWindowSet m_ahWindows;
};

#endif // INCLUDE_TWOLOGS_COMMON_SUBCLASSEDWINDOW_H

SubclassedWindow.cpp

/*******************************************************************************

  Version: 2
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  This code is freely distributable, as long as this comment remains intact.
  If you find this source useful, you may use this code in your own projects
  free of charge, but some acknowledgement to the author of this code is always
  appreciated :)
  The source is however distributed 'as is' without waranty and/or support, and
  may not be fit for each and every application.  Use it at your own discretion
  and at your own risk.
  The source already has undergone testing.  This doesn't mean however that all
  bugs are removed from this piece of code.  If you find one of them, please
  contact me about it.  I can however not guarantee when and if the bug will be
  fixed.

  More information about this module can be found in the accompanying HTML file.

*******************************************************************************/

#include "SubclassedWindow.h"
#include <map>

// Controller for the subclassing
class CSubclassController {
public:
  // Con- & destructor
  CSubclassController();
  virtual ~CSubclassController();

  // Adds the given subclasser to the list for the given window
  bool Add(HWND hWindow, CSubclassedWindow* poSubclasser);

  // Removes the given subclasser from the list for the given window
  void Remove(HWND hWindow, CSubclassedWindow* poSubclasser);

private:
  // List of all subclassers
  typedef std::set<CSubclassedWindow*> CSubclassers;

  // Subclass info for a window
  struct CSubclassInfo {
    // The window that is subclassed
    HWND hWindow;

    // The window's own window proc
    WNDPROC fOriginalProc;

    // All registered subclassers for this window
    CSubclassers apoSubclassers;
  };

  // Subclass info's by window handle
  typedef std::map<HWND, CSubclassInfo*> CWindow2Info;
  static CWindow2Info g_apoInfos;

  // Window procedure for subclassing
  static LRESULT WINAPI WndProc(
    HWND hWnd,
    UINT eMsg,
    WPARAM nWParam,
    LPARAM nLParam
  );
};

// Subclass info's by window handle
CSubclassController::CWindow2Info CSubclassController::g_apoInfos;

// Con- & destructor
CSubclassController::CSubclassController() {
}
CSubclassController::~CSubclassController() {
  // Kill all subclass info's
  for (auto ppoNextInfo: g_apoInfos) {
    // Delete this subclass info
    delete ppoNextInfo.second;
  }
}

// Adds the given subclasser to the list for the given window
bool CSubclassController::Add(HWND hWindow, CSubclassedWindow* poSubclasser) {
  // Look if the given window already occurs in our lists
  CSubclassInfo* poInfo = nullptr;
  auto ppoFoundInfo = g_apoInfos.find(hWindow);
  if (ppoFoundInfo != g_apoInfos.end()) {
    // Yes -> re-use that one
    poInfo = ppoFoundInfo->second;
  } else {
    // No -> start subclassing this window
    poInfo = new CSubclassInfo;
    poInfo->hWindow = hWindow;
    poInfo->fOriginalProc = (WNDPROC)SetWindowLongPtr(
      hWindow,
      GWLP_WNDPROC,
      (LONG_PTR)WndProc
    );
    if (poInfo->fOriginalProc != nullptr) {
      // Done -> note it in the list
      g_apoInfos[hWindow] = poInfo;
    } else {
      // Couldn't -> kill the info again
      delete poInfo;
      poInfo = nullptr;
    }
  }

  // Look if an info could be found
  if (poInfo != nullptr) {
    // Yes -> add the subclasser to it
    poInfo->apoSubclassers.insert(poSubclasser);
  }

  // And return if the subclasser could be added
  return poInfo != nullptr;
}

// Removes the given subclasser from the list for the given window
void CSubclassController::Remove(HWND hWindow, CSubclassedWindow* poSubclasser) {
  // Look if the given window can be found in our lists
  auto ppoFoundInfo = g_apoInfos.find(hWindow);
  if (ppoFoundInfo != g_apoInfos.end()) {
    // Yes -> get the info for it
    CSubclassInfo* poInfo = ppoFoundInfo->second;

    // Remove the subclasser from it
    poInfo->apoSubclassers.erase(poSubclasser);

    // And look if this info is still needed
    if (poInfo->apoSubclassers.size() == 0) {
      // No -> stop subclassing the window
      SetWindowLongPtr(hWindow, GWLP_WNDPROC, (LONG_PTR)poInfo->fOriginalProc);

      // And kill the info
      delete poInfo;
      g_apoInfos.erase(ppoFoundInfo);
    }
  }
}

// Window procedure for the edit control
LRESULT WINAPI CSubclassController::WndProc(
  HWND hWnd,
  UINT eMsg,
  WPARAM nWParam,
  LPARAM nLParam) {
  // Find the associated subclassers for this window
  LRESULT nResult = 0;
  auto ppoFoundInfo = g_apoInfos.find(hWnd);
  if (ppoFoundInfo != g_apoInfos.end()) {
    // Done -> extract the info
    CSubclassInfo* poInfo = ppoFoundInfo->second;

    // Create the message
    CMessage oMsg(
      poInfo->fOriginalProc,
      hWnd,
      eMsg,
      nWParam,
      nLParam
    );

    // Let each of the subclassers process the message
    auto ppoNextSubclasser = poInfo->apoSubclassers.begin();
    auto ppoLastSubclasser = poInfo->apoSubclassers.end();
    for (; ppoNextSubclasser != ppoLastSubclasser && !oMsg.bStop; ++ppoNextSubclasser) {
      // Done -> get the subclasser
      CSubclassedWindow* poSubclasser = *ppoNextSubclasser;

      // And let it process the message
      poSubclasser->ProcessMessage(oMsg);
    }

    // And look if the original window procedure may still process it
    if (oMsg.bStop) {
      // No -> return the result from the last processing
      nResult = oMsg.nResult;
    } else {
      // Yes -> pass it on
      nResult = oMsg.DefaultResult();
    }
  }

  // And return the result
  return nResult;
}

// The subclass controller
CSubclassController g_oController;





// Con- & destructor
CMessage::CMessage(WNDPROC fOriginalProc, HWND hWindow, UINT eMsg, WPARAM nWParam, LPARAM nLParam):
 hWindow(hWindow),
 eCode(eMsg),
 nWParam(nWParam),
 nLParam(nLParam),
 nResult(0),
 bStop(false),
 m_fOriginalProc(fOriginalProc),
 m_bDefProcessed(false) {
}
CMessage::~CMessage() {
}

// Gets the (cached) result from the default window procedure handler
LRESULT CMessage::DefaultResult() {
  // Look if we have already processed the message
  if (!m_bDefProcessed) {
    // No -> do so now
    m_nDefResult = CallWindowProc(
      m_fOriginalProc,
      hWindow,
      eCode,
      nWParam,
      nLParam
    );
    m_bDefProcessed = true;
  }

  // And return the result
  return m_nDefResult;
}






// Con- & destructor
CSubclassedWindow::CSubclassedWindow() {
}
CSubclassedWindow::~CSubclassedWindow() {
  StopSubclassingAll();
}

// Starts subclassing the given window
bool CSubclassedWindow::Subclass(HWND hWindow) {
  // Subclass the window
  bool bSuccess = g_oController.Add(hWindow, this);
  if (bSuccess) {
    // Done -> note the window we subclass
    m_ahWindows.insert(hWindow);
  }

  // And return if successfull
  return bSuccess;
}

// Stops subclassing the given window
void CSubclassedWindow::StopSubclassing(HWND hWindow) {
  // Remove us from the controller's lists for this window
  g_oController.Remove(hWindow, this);

  // And remove this window from our own lists
  m_ahWindows.erase(hWindow);
}

// Stops subclassing all windows
void CSubclassedWindow::StopSubclassingAll() {
  // Process all windows we have registered
  for (HWND hNextWindow: m_ahWindows) {
    // Remove us from the controller's lists for this window
    g_oController.Remove(hNextWindow, this);
  }

  // And we're not controlling any windows anymore
  m_ahWindows.clear();
}