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 TMessage {
public:
  // The message details
  const HWND window;
  const UINT code;
  const WPARAM wParam;
  const LPARAM lParam;

  // The currently set result
  LRESULT result;

  // Whether further processing must stop
  bool stop;

  // Con- & destructor
  TMessage(WNDPROC OriginalProc, HWND window, UINT msgCode, WPARAM wParam, LPARAM lParam);
  virtual ~TMessage();

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

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

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

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

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

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

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

  // Stops subclassing all windows
  void StopSubclassingAll();

  // Processes the given message
  virtual void ProcessMessage(TMessage& msg) = 0;

private:
  // The windows we handle
  typedef std::set<HWND> TWindows;
  TWindows m_windows;
};

#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 TSubclassController {
public:
  // Con- & destructor
  TSubclassController();
  virtual ~TSubclassController();

  // Adds the given subclasser to the list for the given window
  bool Add(HWND window, TSubclassedWindow* subclasserPtr);

  // Removes the given subclasser from the list for the given window
  void Remove(HWND window, TSubclassedWindow* subclasserPtr);

private:
  // List of all subclassers
  typedef std::set<TSubclassedWindow*> TSubclasserPtrs;

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

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

    // All registered subclassers for this window
    TSubclasserPtrs subclasserPtrs;
  };

  // Subclass info's by window handle
  typedef std::map<HWND, TSubclassInfo*> TInfoPtrsByWindow;
  static TInfoPtrsByWindow g_infoPtrsByWindow;

  // Window procedure for subclassing
  static LRESULT WINAPI WndProc(
    HWND window,
    UINT msgCode,
    WPARAM wParam,
    LPARAM lParam
  );
};

// Subclass info's by window handle
TSubclassController::TInfoPtrsByWindow TSubclassController::g_infoPtrsByWindow;

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

// Adds the given subclasser to the list for the given window
bool TSubclassController::Add(HWND window, TSubclassedWindow* subclasserPtr) {
  // Look if the given window already occurs in our lists
  TSubclassInfo* infoPtr = nullptr;
  auto foundInfoPtrPtr = g_infoPtrsByWindow.find(window);
  if (foundInfoPtrPtr != g_infoPtrsByWindow.end()) {
    // Yes -> re-use that one
    infoPtr = foundInfoPtrPtr->second;
  } else {
    // No -> start subclassing this window
    infoPtr = new TSubclassInfo;
    infoPtr->window = window;
    infoPtr->OriginalProc = (WNDPROC)SetWindowLongPtr(
      window,
      GWLP_WNDPROC,
      (LONG_PTR)WndProc
    );
    if (infoPtr->OriginalProc != nullptr) {
      // Done -> note it in the list
      g_infoPtrsByWindow[window] = infoPtr;
    } else {
      // Couldn't -> kill the info again
      delete infoPtr;
      infoPtr = nullptr;
    }
  }

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

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

// Removes the given subclasser from the list for the given window
void TSubclassController::Remove(HWND window, TSubclassedWindow* subclasserPtr) {
  // Look if the given window can be found in our lists
  auto foundInfoPtrPtr = g_infoPtrsByWindow.find(window);
  if (foundInfoPtrPtr != g_infoPtrsByWindow.end()) {
    // Yes -> get the info for it
    TSubclassInfo* infoPtr = foundInfoPtrPtr->second;

    // Remove the subclasser from it
    infoPtr->subclasserPtrs.erase(subclasserPtr);

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

      // And kill the info
      delete infoPtr;
      g_infoPtrsByWindow.erase(foundInfoPtrPtr);
    }
  }
}

// Window procedure for the edit control
LRESULT WINAPI TSubclassController::WndProc(
  HWND window,
  UINT msgCode,
  WPARAM wParam,
  LPARAM lParam) {
  // Find the associated subclassers for this window
  LRESULT result = 0;
  auto foundInfoPtrPtr = g_infoPtrsByWindow.find(window);
  if (foundInfoPtrPtr != g_infoPtrsByWindow.end()) {
    // Done -> extract the info
    TSubclassInfo* infoPtr = foundInfoPtrPtr->second;

    // Create the message
    TMessage msg(
      infoPtr->OriginalProc,
      window,
      msgCode,
      wParam,
      lParam
    );

    // Let each of the subclassers process the message
    auto nextSubclasserPtrPtr = infoPtr->subclasserPtrs.begin();
    auto lastSubclasserPtrPtr = infoPtr->subclasserPtrs.end();
    for (; nextSubclasserPtrPtr != lastSubclasserPtrPtr && !msg.stop; ++nextSubclasserPtrPtr) {
      // Done -> get the subclasser
      TSubclassedWindow* subclasserPtr = *nextSubclasserPtrPtr;

      // And let it process the message
      subclasserPtr->ProcessMessage(msg);
    }

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

  // And return the result
  return result;
}

// The subclass controller
TSubclassController g_controller;





// Con- & destructor
TMessage::TMessage(WNDPROC OriginalProc, HWND window, UINT msgCode, WPARAM wParam, LPARAM lParam):
 window(window),
 code(msgCode),
 wParam(wParam),
 lParam(lParam),
 result(0),
 stop(false),
 m_OriginalProc(OriginalProc),
 m_defProcessed(false) {
}
TMessage::~TMessage() {
}

// Gets the (cached) result from the default window procedure handler
LRESULT TMessage::DefaultResult() {
  // Look if we have already processed the message
  if (!m_defProcessed) {
    // No -> do so now
    m_defResult = CallWindowProc(
      m_OriginalProc,
      window,
      code,
      wParam,
      lParam
    );
    m_defProcessed = true;
  }

  // And return the result
  return m_defResult;
}






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

// Starts subclassing the given window
bool TSubclassedWindow::Subclass(HWND window) {
  // Subclass the window
  bool allOK = g_controller.Add(window, this);
  if (allOK) {
    // Done -> note the window we subclass
    m_windows.insert(window);
  }

  // And return if successful
  return allOK;
}

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

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

// Stops subclassing all windows
void TSubclassedWindow::StopSubclassingAll() {
  // Process all windows we have registered
  for (HWND nextWindow: m_windows) {
    // Remove us from the controller's lists for this window
    g_controller.Remove(nextWindow, this);
  }

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