9 Replies - 392 Views - Last Post: 09 September 2019 - 08:55 PM Rate Topic: -----

#1 Dave89   User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 20
  • Joined: 25-July 19

Win32 Subclassing issues

Posted 05 September 2019 - 05:35 AM

Afternoon everyone,

I've been reading up on and tryingto implement subclassing using the Comctl32.lib and commctrl.h, I've been successful in getting it to work from the SubClassProc but it doesn't seem like it is working as it should.

On reading the msdn material on Subclassing controls is says

Quote

If a control does almost everything you want, but you need a few more features, you can change or add features to the original control by subclassing it. A subclass can have all the features of an existing class as well as any additional features you want to give it.
from this my understanding is that subclassing a control or window will deal with the messages in the subclass as well as messages sent in the main WndProc but it doesn't seem to work like that. Example code below (where in my main WndProc i check for WM_RBUTTONDOWN and other stuff in the SubClassProc):

// File: main.cpp

// Includes

// Pre-Processor defines
#ifndef UNICODE
#define UNICODE
#endif

#ifndef _UNICODE
#define _UNICODE
#endif

#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRALEAN
#define VC_EXTRALEAN

// Libraries
#pragma comment(lib, "windowsapp.lib")
#pragma comment(lib, "Comctl32.lib")

//Includes WinRT stuffs
#include <Unknwn.h>
#include <winrt/base.h>

#if defined(DEBUG) || defined(_DEBUG)
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif

// Includes
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>

// SubClassing functions
void DisplayError(LPCWSTR errCaption)
{
	DWORD err = GetLastError();
	LPTSTR error = 0;
	if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, err, 0, (LPTSTR)& error, 0, nullptr) != 0)
	{
		MessageBox(nullptr, error, errCaption, MB_OK | MB_IConerror);
	}

	if (error)
	{
		LocalFree(error);
		error = 0;
	}
}

void OnSubClass(HWND hwnd, SUBCLASSPROC scProc, int iControl)
{
	HWND control = GetDlgItem(hwnd, iControl);
	UINT_PTR uIdSubClass = 0;

	if (!SetWindowSubclass(control, scProc, uIdSubClass, 0))
	{
		DisplayError(L"OnSubClass");
		return;
	}

	RECT rc;
	if (GetClientRect(control, &rc))
		InvalidateRect(control, &rc, TRUE);
}

/* Unsubclass function */
void OnUnSubClass(HWND hwnd, SUBCLASSPROC scProc, int iControl)
{
	HWND control = GetDlgItem(hwnd, iControl);
	UINT_PTR uIdSubClass = 0;

	if (!RemoveWindowSubclass(control, scProc, uIdSubClass))
	{
		DisplayError(L"OnUnSubClass");
		return;
	}

	RECT rc;
	if (GetClientRect(control, &rc))
		InvalidateRect(control, &rc, TRUE);
}

// Class: Window
class WindowSubClass
{
public:
	WindowSubClass(HWND hWnd, SUBCLASSPROC proc) {
		m_hWnd = hWnd;
		m_scProc = proc;
		SetWindowSubclass(m_hWnd, m_scProc, 1, (DWORD_PTR)this);
	}

	~WindowSubClass() {
		RemoveWindowSubclass(m_hWnd, m_scProc, 1);
	}

protected:
	HWND m_hWnd{ nullptr };
	SUBCLASSPROC m_scProc{ nullptr };
};

// Forward Declarations
LRESULT CALLBACK WndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK ButtonProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR, DWORD_PTR);
LRESULT CALLBACK EditProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR, DWORD_PTR);

enum ControlIDs {
	button1 = 101,
	edit1 = 102,
	window1 = 103,
};

HWND m_hWnd{ nullptr };

_Use_decl_annotations_
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);
	UNREFERENCED_PARAMETER(nCmdShow);

	// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	// Initialize the window class
	WNDCLASSEX WCEX = { 0 };
	WCEX.cbSize = sizeof(WNDCLASSEX);
	WCEX.style = CS_HREDRAW | CS_VREDRAW;
	WCEX.hbrBackground = (HBRUSH)(CreateSolidBrush(RGB(128, 128, 255)));
	WCEX.lpfnWndProc = WndProc;
	WCEX.hInstance = hInstance;
	WCEX.hCursor = LoadCursor(NULL, IDC_ARROW);
	WCEX.lpszClassName = L"Win32AppClass";
	RegisterClassEx(&WCEX);

	RECT windowRect = { 0, 0, static_cast<LONG>(1024), static_cast<LONG>(768) };
	AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, FALSE);

	long x = (GetSystemMetrics(SM_CXSCREEN) - 1024) / 2;
	long y = (GetSystemMetrics(SM_CYSCREEN) - 768) / 2;

	// Create the window and store a handle to it
	m_hWnd = CreateWindow(WCEX.lpszClassName, L"MainWindow", WS_OVERLAPPEDWINDOW, x, y, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, nullptr, nullptr, hInstance, nullptr/*pSample*/);

	DWORD error{};
	error = GetLastError();

	// Show the window
	ShowWindow(m_hWnd, nCmdShow);
	UpdateWindow(m_hWnd);

	// Create child windows / Controls
	// Create child windows / Controls
	HWND hwndWindow{ nullptr };

	hwndWindow = CreateWindowEx(0,                            //extended styles
		L"static",            //control 'class' name
		L"Child Window",              //control caption
		WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_BLACKRECT,                      //control style 
		200,                      //position: left
		200,                       //position: top
		300,                     //width
		300,                    //height
		m_hWnd,                      //parent window handle
		//control's ID
		reinterpret_cast<HMENU>(window1),
		hInstance,                        //application instance
		0);                           //user defined info


	HWND hwndButton{ nullptr };
	hwndButton = CreateWindowEx(0,                            //extended styles
		L"button",            //control 'class' name
		L"Press Me",              //control caption
		WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,                      //control style 
		0,                      //position: left
		0,                       //position: top
		200,                     //width
		40,                    //height
		m_hWnd,                      //parent window handle
		//control's ID
		reinterpret_cast<HMENU>(button1),
		hInstance,                        //application instance
		0);                           //user defined info

	HWND hwndEdit{ nullptr };
	hwndEdit = CreateWindowEx(0,                            //extended styles
		L"edit",            //control 'class' name
		L"Press Me",              //control caption
		WS_CHILD | WS_VISIBLE | ES_CENTER,                      //control style 
		0,                      //position: left
		70,                       //position: top
		200,                     //width
		200,                    //height
		m_hWnd,                      //parent window handle
		//control's ID
		reinterpret_cast<HMENU>(edit1),
		hInstance,                        //application instance
		0);                           //user defined info
		
	WindowSubClass Button(hwndButton, ButtonProc);
	WindowSubClass Edit(hwndEdit, ButtonProc);
	WindowSubClass Window(hwndWindow, EditProc);

	// Main Win32 Loop
	MSG msg = {};
	while (msg.message != WM_QUIT)
	{
		// Process any messages in the queue.
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		// Do other logic
	}

	// Return this part of the WM_QUIT message to Windows.
	return static_cast<char>(msg.wParam);
}

LRESULT CALLBACK WndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE:
		break;

	/*case WM_LBUTTONDOWN:
		OutputDebugString(L"Message from the WndProc\n");
		break;*/

	case WM_MBUTTONDOWN:
		MessageBox(nullptr, L"Weeeeeeeeeeeeeeeeee", L"Where ever we are", MB_ICONINFORMATION);
		break;

	case WM_RBUTTONDOWN:
		MessageBox(nullptr, L"Right Mouse CLick on mainwindow", L"mainwindow", MB_ICONINFORMATION);
		break;

	case WM_COMMAND:
	{
		switch (wParam)
		{
		case button1:
			MessageBox(nullptr, L"From main WNDPROC", L"YAY", MB_ICONINFORMATION);
			break;
		}
		break;
	}

	case WM_DESTROY:
		PostQuitMessage(0);

	}
	return DefWindowProc(window, message, wParam, lParam);
}

LRESULT CALLBACK ButtonProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR, DWORD_PTR dwRefData)
{
	WindowSubClass* pThis = reinterpret_cast<WindowSubClass*>(dwRefData);
	switch (message)
	{
	
	case WM_MBUTTONDOWN:
	{
		UINT id = GetWindowLong(window, GWL_ID);
		//if (id == edit1)
			OutputDebugString(L"Message from the ButtonProc\n");
		//DestroyWindow(GetParent(window));
			DefSubclassProc(window, message, wParam, lParam);
			return 0;;
	}

	case WM_LBUTTONUP:
		return  0;

	default: return DefSubclassProc(window, message, wParam, lParam);

	} 
}

LRESULT CALLBACK EditProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR, DWORD_PTR dwRefData)
{
	WindowSubClass* pThis = reinterpret_cast<WindowSubClass*>(dwRefData);
	switch (message)
	{

	case WM_LBUTTONDOWN:
	{
		static bool max = false;

		if (max)
		{
			ShowWindow(window, SW_MAXIMIZE);
			max = !max;
		}
		else
		{
			ShowWindow(window, SW_RESTORE);
			max = !max;
		}
		OutputDebugString(L"Test From EditProc\n");
		return 0;
	}
	}

	return DefSubclassProc(window, message, wParam, lParam);
}


When i right click on any of the controls it does not respond as I would of expected it too from what the msdn document was saying and only responds to the SubClassProc it's been attached too, I'm unsure if I've missed something out or if this is the expected behavior of subclassing and I've just not understood what they meant.

Anyone able to give some insight and assistance into this please

Kind Regards
Dave

Is This A Good Question/Topic? 0
  • +

Replies To: Win32 Subclassing issues

#2 Dave89   User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 20
  • Joined: 25-July 19

Re: Win32 Subclassing issues

Posted 05 September 2019 - 05:52 AM

A Cleaner example from the msdn tutorial is as follows and does the same thing:

#pragma once

// Pre-Processor defines
#ifndef UNICODE
#define UNICODE
#endif

#ifndef _UNICODE
#define _UNICODE
#endif

#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRALEAN
#define VC_EXTRALEAN

// Libraries
#pragma comment(lib, "windowsapp.lib")
#pragma comment(lib, "Comctl32.lib")

//Includes WinRT stuffs
#include <Unknwn.h>
#include <winrt/base.h>

#if defined(DEBUG) || defined(_DEBUG)
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif

// Includes
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>

#include "resource.h"

/* Function Definitions */
INT_PTR CALLBACK DialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

/* Subclass Callback Button */
LRESULT CALLBACK ButtonProc(HWND, UINT, WPARAM, LPARAM, UINT_PTR, DWORD_PTR);
LRESULT CALLBACK TextBoxProc(HWND, UINT, WPARAM, LPARAM, UINT_PTR, DWORD_PTR);

class Dialog
{
public:

	/* Function to display error message */
	void DisplayError(LPCWSTR errCaption)
	{
		DWORD err = GetLastError();
		LPTSTR error = 0;
		if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, err, 0, (LPTSTR)& error, 0, nullptr) != 0)
		{
			MessageBox(nullptr, error, errCaption, MB_OK | MB_IConerror);
		}

		if (error)
		{
			LocalFree(error);
			error = 0;
		}
	}

	/* function OnSubclass*/
	void OnSubClass(HWND hwnd, SUBCLASSPROC scProc, int iControl)
	{
		HWND control = GetDlgItem(hwnd, iControl);
		UINT_PTR uIdSubClass = 0;

		if (!SetWindowSubclass(control, scProc, uIdSubClass, 0))
		{
			this->DisplayError(L"OnSubClass");
			return;
		}

		RECT rc;
		if (GetClientRect(control, &rc))
			InvalidateRect(control, &rc, TRUE);
	}

	/* Unsubclass function */
	void OnUnSubClass(HWND hwnd, SUBCLASSPROC scProc, int iControl)
	{
		HWND control = GetDlgItem(hwnd, iControl);
		UINT_PTR uIdSubClass = 0;

		if (!RemoveWindowSubclass(control, scProc, uIdSubClass))
		{
			this->DisplayError(L"OnUnSubClass");
			return;
		}

		RECT rc;
		if (GetClientRect(control, &rc))
			InvalidateRect(control, &rc, TRUE);
	}

	void OnClose(HWND hWnd)
	{
		this->OnUnSubClass(hWnd, ButtonProc, IDC_BUTTON2);
		this->OnUnSubClass(hWnd, TextBoxProc, IDC_EDIT1);
		EndDialog(hWnd, 0);
	}

	void OnCommand(HWND hWnd, int id, HWND hWndCtrl, UINT codeNotify)
	{
		UNREFERENCED_PARAMETER(hWndCtrl);
		UNREFERENCED_PARAMETER(codeNotify);
		switch (id)
		{
		case IDC_BUTTON1:
			OnClose(hWnd);
			break;
		}
	}
};


// File: main.cpp
// https://www.youtube.com/watch?v=4Jjyu4u7saQ

// Includes
#include "main.h"

Dialog testDialog;

_Use_decl_annotations_
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);
	UNREFERENCED_PARAMETER(nCmdShow);

	// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
	return DialogBox(hInstance, MAKEINTRESOURCE( MyDialog ), HWND_DESKTOP, DialogProc);;
}

INT_PTR CALLBACK DialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);
	switch (msg)
	{
	case WM_INITDIALOG:
		testDialog.OnSubClass(hwnd, ButtonProc, IDC_BUTTON2);
		testDialog.OnSubClass(hwnd, TextBoxProc, IDC_EDIT1);
		return TRUE;


	HANDLE_MSG(hwnd, WM_COMMAND, testDialog.OnCommand);

	case WM_MBUTTONDOWN:
		MessageBox(nullptr, L"CLickidy click", L"Hello", MB_ICONINFORMATION);
		break;

	case WM_CLOSE:
		testDialog.OnUnSubClass(hwnd, ButtonProc, IDC_BUTTON2);
		testDialog.OnUnSubClass(hwnd, TextBoxProc, IDC_EDIT1);
		EndDialog(hwnd,0);
		break;

	default:
		return FALSE;
	}

	return FALSE;
}

LRESULT CALLBACK ButtonProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubClass, DWORD_PTR dwRefData)
{
	UNREFERENCED_PARAMETER(uIdSubClass);
	UNREFERENCED_PARAMETER(dwRefData);

	switch (message)
	{
	case WM_RBUTTONDOWN:
		MessageBox(nullptr, L"Clicked Right Mouse in Button2", L"Click", MB_ICONINFORMATION);
		return TRUE;
	}

	return DefSubclassProc(hwnd, message, wParam, lParam);
}

LRESULT CALLBACK TextBoxProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubClass, DWORD_PTR dwRefData)
{
	UNREFERENCED_PARAMETER(uIdSubClass);
	UNREFERENCED_PARAMETER(dwRefData);

	switch (message)
	{
	case WM_GETDLGCODE:
	{
		if (wParam == VK_RETURN)
		{
			MessageBox(nullptr, L"Enter from edit", L"Edit", MB_ICONINFORMATION);
			return TRUE;
		}
	}
	}

	return DefSubclassProc(hwnd, message, wParam, lParam);
}


//{{NO_DEPENDENCIES}} Resource.h
// Microsoft Visual C++ generated include file.
// Used by Resource.rc
//
#define MyDialog                        105
#define IDC_BUTTON1                     1001
#define IDC_BUTTON2                     1002
#define IDC_EDIT1                       1003

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        106
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1004
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif



// Microsoft Visual C++ generated resource script.
// resource.rc
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// English (United Kingdom) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

MyDialog DIALOGEX 0, 0, 309, 176
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    PUSHBUTTON      "Close",IDC_BUTTON1,259,162,50,14
    PUSHBUTTON      "SubClassed",IDC_BUTTON2,7,6,121,162
    EDITTEXT        IDC_EDIT1,143,15,154,14,ES_AUTOHSCROLL
END


/////////////////////////////////////////////////////////////////////////////
//
// AFX_DIALOG_LAYOUT
//

MyDialog AFX_DIALOG_LAYOUT
BEGIN
    0
END


/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    MyDialog, DIALOG
    BEGIN
    END
END
#endif    // APSTUDIO_INVOKED

#endif    // English (United Kingdom) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED



Was This Post Helpful? 0
  • +
  • -

#3 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7020
  • View blog
  • Posts: 23,840
  • Joined: 05-May 12

Re: Win32 Subclassing issues

Posted 05 September 2019 - 07:52 PM

View PostDave89, on 05 September 2019 - 08:35 AM, said:

On reading the msdn material on Subclassing controls is says

Quote

If a control does almost everything you want, but you need a few more features, you can change or add features to the original control by subclassing it. A subclass can have all the features of an existing class as well as any additional features you want to give it.
from this my understanding is that subclassing a control or window will deal with the messages in the subclass as well as messages sent in the main WndProc but it doesn't seem to work like that. Example code below (where in my main WndProc i check for WM_RBUTTONDOWN and other stuff in the SubClassProc):

You misunderstood. Every window has window procedure, but they don't necessarily all lead back to the main parent window WndProc. From the code you posted, it looks like you were assuming that if ButtonProc() or EditProc() didn't handle a message, the call to DefSubclassProc() would call your WndProc(). As you've discovered, that assumption is incorrect.

What is actually happening is that the call to DefSubclassProc() will try to call all the other registered subclasses for that window, and if there are no more registered subclasses, it will call the original window procedure that was registered for that window class when the window was first registered by RegisterClassEx(). Yes, even all the built in Windows controls are registered via RegisterClassEx(), albeit, in a more optimized internal matter.

If you think about it, since those built-in Windows controls, how can Windows possibly have known 30 years ago about the Window procedure that you just wrote today. It can't. It can only register window procedures for what what is needed to drive those particular controls.

Another way to think about it is this: When you first call RegisterClassEx(), you say pass in a structure that associates the window class "Foo" with some window procedure (which for the sake of argument) we'll call FooWndProc(). When CreateWindowEx() is called to instantiate a "Foo" class window, the GWLP_WNDPROC for that instance points to FooWndProc().

To subclass that instance of the "Foo" class window the old fashioned way, you would have to manually make the GWLP_WNDPROC point to your MyFooWndProc(). Your MyFooWndProc() needs to remember what the old pointer value was both for restoring the old value, as well as for being able to call CallWindowProc() for the messages that you want the original "Foo" behaviors. As noted in that same link, managing things the old-fashioned way gets harder and harder if the subclass chain gets longer.

(Please forgive all the handwaving here, but I'm trying to stick with high level concepts.) When you subclass the new way, what happens is that Windows also replaces the GWLP_WNDPROC, but instead of directly pointing to your SubclassFooWndProc(), it actually points to a Windows internal window procedure which we'll call SuperWndProc() for convenience. Furthermore, a data structure is setup which keeps track of the pointer to your SubclassFooWndProc(), an id of your choosing, as well as a "cookie" (e.g. the dwRefData). Every time the SuperWndProc() gets called it iterates over that data structure and calls each of the subclass window procedure and passing back in the id and the cookie. It maintains internal state so that if your SubclassFooWndProc() calls DefSubclassProc() it would correctly call the next stored subclass window procedure in that data structure. Basically, it tries to let you focus on what you need the window to do, and let Windows take care of the boilerplate code that every subclassing window procedure would have had to do if they wrote code the old-fashioned way.

Windows API is different from WPF. Where WPF will bubble up events up the tree, the Windows API in general does not. Occasionally you'll get lucky for some controls that send WM_NOTIFY messages that send notifications to their immediate parent, but there is no guarantee that message will be forward up to the next parent.
Was This Post Helpful? 1
  • +
  • -

#4 Dave89   User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 20
  • Joined: 25-July 19

Re: Win32 Subclassing issues

Posted 06 September 2019 - 05:16 AM

Thank you again SkyDiver, From my experimenting with the code I got the impression I misunderstood something so thought to ask to get better insight. I've never touched WPF so don't really know what goes on in that (I'm sort of a die hard win32 programmer and now starting to think about changing to UWP c++/WinRT, trying to weigh the positives and negatives of both).

So simply speaking the only real advantage I can see of using sub classing is more to readability and clean coding (instead of having everything in the main WNDPROC, you split it up through subclassing for controls and windows that you want to behave differently on the message calls making the code cleaner rather than doing checks in the main WNDPROC like GetWindowText etc to check if you have the correct window to call a certain functions). I can't understand quite what scenario you would require the need to have multiple sub classes for a single window (or is that more in reference of chaining the WNDPROC with the subclasses of controls that that window is the parent of?)

I'm also wandering if over use of Sub classing would have a potential performance hit?

Sorry for all the questions and misunderstanding, it was a concept that i never knew about till you mentioned it in my previous post.

Kind Regards
Dave
Was This Post Helpful? 0
  • +
  • -

#5 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7020
  • View blog
  • Posts: 23,840
  • Joined: 05-May 12

Re: Win32 Subclassing issues

Posted 06 September 2019 - 05:34 AM

The reason for multiple subclasses is you start designing each window to be a one big monolithic window, you can instead build up the window using parts and behaviors. If you have a copy of the Design Patterns book, look at their example of the Decorator pattern. They describe how one system starts out with just a plain rectangular window, and then adding a pair of scrollbar decorators to give the window scrolling horizontal and vertical capabilities, and then adding a caption decorator to end up with a window that has scrollbars and a caption. The code for each of the decorators follows the SRP and encapsulated in its own class, and is reusable. If you were writing code using the classic monolithic style you would put all that code in a single window procedure, and you need to create another window which just needs a horizontal scrollbar, you would need to copy the plain window code and the horizontal window code into the window procedure for that other window.

(Of course, the example above I gave about adding scrollbars and captions is kind of foreign for a Windows programmer, because to use Windows programmers, we tend think of "I just need to add a WS_* style to get scrollbars and captions". But think of it as how would you create extended window capabilities/functionality in a readily usable way.)
Was This Post Helpful? 0
  • +
  • -

#6 Dave89   User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 20
  • Joined: 25-July 19

Re: Win32 Subclassing issues

Posted 07 September 2019 - 04:03 AM

I went to read up on the Decorator design pattern as you mentioned but I'm still having trouble relating that to this particular situation with regards the WNDPROC (most likely due to that fact that i'm only dealing with the high level concepts of win32 and not acquainted with the happenings fully under the hood). I'm trying to think of a decorator design in terms of win32 that would help me understand it for this situation.

Idea that i have is that like above you have a window (with it's own WNDPROC) and a decorator class, from the decorator class you may derive a child window class or control class that would allow you to extend the basic window, each of these would have their own WNDPROC (or subclass but this is what i'm not quite getting) and so externds from the basic window: I written code like version of my thoughts on this but I did not write this to actually run (might try so later):

class Window
{
public:
	virtual void Show() = 0;
	virtual string GetTitle() = 0;
	virtual LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM) = 0
	virtual ~Window(){}
};

class SimpleWindow : public Window
{
public:
	SimpleWindow( HINSTANCE hInstance, POINT position, SIZE size){}
	void Show(){ ::ShowWindow(m_hWnd, SW_SHOW);}
	void String GetTitle() { return m_Title; }
	LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
	{
		switch(msg)
		{
			...
		default: return DefWindowProc(hwnd, msg, wp, lp);
		}
	}
private:
	HWND m_hWnd{nullptr};
	String m_Title{};
};

class WindowDecorator : public Window
{
protected:
	Window *m_decorateWindow{nullptr};

public:
	WindowDecorator (Window *decoratedWindow) : m_decorateWindow(decorateWindow){}
};

class StaticWindow : public WindowDecorator
{
public:
	StaticWindow(Window *decoratedWindow) : WindowDecorator(decoratedWindow){}
	LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
	{
		MessageHandler(hwnd, msg, wp, lp);
		m_decoratedWindow->WndProc(hwnd, msg, wp, lp);
	}
	~StaticWindow(){}

private:
	LRESULT MessageHandler(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
	{
		switch(msg)
		{
			...
		case WM_KEYDOWN: MessageBox(hwnd, L"You Clicked a static window", L"Event", MB_OK); return 0;
		}
	}
}

class ButtonControl: public WindowDecorator
{
public:
	ButtonControl(Window *decoratedWindow) : WindowDecorator(decoratedWindow){}
	LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
	{
		MessageHandler(hwnd, msg, wp, lp);
		m_decoratedWindow->WndProc(hwnd, msg, wp, lp);
	}
	~ButtonControl(){}
private:
	LRESULT MessageHandler(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
	{
		switch(msg)
		{
			...
		case WM_KEYDOWN: MessageBox(hwnd, L"You Clicked a static window", L"Event", MB_OK); return 0;
		}
	}
}

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPCSTR, INT)
{
	Window wnd( hInstance, {0, 0}, {640, 480});
	StaticWindow child01(Wnd); 
	StaticWindow child02(Wnd);
	ButtonControl Button1(Wnd);
	ButtonControl Button2(Child01);
	ButtonControl Button3(Child02);

	// DO MESSAGE LOOP STUFFS
	...

	return 0;
}


I have no idea if this is sort of what is happening under the hood with the SetWindowSubclass etc, hence why I'm going to try make a working version of the above as it would allow me to debug the code and see what happens where and when to stop any confusion I'm having grasping this concept but not sure if this appraoch will help.

Regards
Dave
Was This Post Helpful? 0
  • +
  • -

#7 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7020
  • View blog
  • Posts: 23,840
  • Joined: 05-May 12

Re: Win32 Subclassing issues

Posted 07 September 2019 - 07:15 AM

Slightly wandering off:

In general, the use of subclassing in Windows is to change the appearance or behavior of an existing window -- often a window for you don't have source code access to be able to simply modify the code, or setup the chain of calls like you did with your m_decoratedWindow->WndProc(hwnd, msg, wp, lp);. The subclassing happens at runtime.

The Decorator design pattern on the other hand transcends the specific Windows implementation, and is more of a concept. Conceptually, it is meant to change the behavior of the item being decorated, but since code is so malleable, the behavior change could be a change in appearance to the user. When the decorator pattern is usually first taught to students, it is first presented via inheritance while coding and is fixed at compile time. Later teachers will also show how this can be done at runtime.

Coming back to your code above:

It looks like you are trying to decorate that single static window wnd by adding three child windows child01, child02, and a Button1. And then you are trying to decorate the child windows child01 and child02 with buttons Button2 and Button3. Although this could work, you have to have a compelling reason why you are decorating the windows instead of simply adding child windows.

It looks like you are trying to teach yourself Windows. I highly recommend reading Petzold's Programming Windows, 5th edition. The 5th edition is the last version that deals with Win32, the later editions delve into the .NET Framework's WinForms and later frameworks. Although the book is dated in terms of being very Win95 and WinXP era, it teaches a lot about how Windows works, as well as how to think in "the Windows way" without going into the depths of how the OS works like Richter's books.
Was This Post Helpful? 1
  • +
  • -

#8 Dave89   User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 20
  • Joined: 25-July 19

Re: Win32 Subclassing issues

Posted 09 September 2019 - 02:10 PM

Sorry for not adding a reply sooner from my +1 to your reply, for some reason I was unable to post replies (just went back to index.php with a messed up layout.)

Thank you SkyDiver, I've started reading though your recommended book, Though teaching myself Windows is not my end goal it certainly will help. I'm more interested in Game development from a Game Engine/Tools perspective primarily then games second. I understand that Win32 may not be the best choice for games but I like to understand what is happening on a lower level than newer frameworks etc like UWP and XAML and hope this will help before upgrading to using more up-to-date frameworks.

Use of the decorator pattern seems like a good option for the projects i'm aiming for, starting off with a minimal Win32 project (for projects including graphics simulators/games) and then decorating the project with extra features as required like child windows, menu's, controls etc (if i wanted to create a editor for a game or graphic simulator). From what you said above:

Quote

the use of sub classing in Windows is to change the appearance or behavior of an existing window -- often a window for you don't have source code access to be able to simply modify the code

Give the use of sub classing in my case more sense as, though I would have access to edit the code in the minimal application I would want to keep the base functionality untouched, and just extend (or decorate) it with the extra functionality based on the type of application being developed.

I will definitely be referring back to this post from time to time if I become unsure again.
Thank you again
Kind Regards
Dave

This post has been edited by Dave89: 09 September 2019 - 02:10 PM

Was This Post Helpful? 0
  • +
  • -

#9 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7020
  • View blog
  • Posts: 23,840
  • Joined: 05-May 12

Re: Win32 Subclassing issues

Posted 09 September 2019 - 02:18 PM

If game programming is your end goal, and the types of games you intend to create a more twitchy games, then you just need the basics of how the Windows window life cycle works, and then after that immerse yourself in your game framework of choice: DirectX, SFML, Unity, etc. Most game frameworks try to abstract away the OS most of the time. On the other hand, if your goal is to make games that are more like board games/card games where you'll simply use Windows GDI, then yes, a deeper understanding of Windows will likely benefit you.
Was This Post Helpful? 0
  • +
  • -

#10 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7020
  • View blog
  • Posts: 23,840
  • Joined: 05-May 12

Re: Win32 Subclassing issues

Posted 09 September 2019 - 08:55 PM

I hope the following code gives you something to play with with regards to subclassing. A window is created with 4 edit controls. The top left edit control is left alone and not subclassed. The top right control is subclassed to prevent letters from being typed in. The bottom left is subclassed to prevent numbers from being typed in. The bottom right is subclassed to prevent letters and numbers from being typed in. Notice that the bottom right subclass makes use of the two existing SUBCLASSPROC's instead of creating a third monolithic SUBCLASSPROC.

// Including SDKDDKVer.h defines the highest available Windows platform.
// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
#include <SDKDDKVer.h>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <CommCtrl.h>
#include <tchar.h>
#include <vector>

#pragma comment(lib, "Comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' \
    name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
    processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

HWND AddEdit(HWND hwndParent, int id, const RECT & rc)
{
    DWORD dwStyle = WS_CHILD |
                    WS_VISIBLE |
                    WS_BORDER |
                    WS_VSCROLL |
                    ES_LEFT |
                    ES_MULTILINE |
                    ES_AUTOVSCROLL;
    HWND hwnd = CreateWindow(TEXT("EDIT"),
                            nullptr,
                            dwStyle,
                            rc.left, rc.top,
                            rc.right - rc.left, rc.bottom - rc.top,
                            hwndParent,
                            HMENU(id),
                            GetWindowInstance(hwndParent),
                            nullptr);                      // lpParam

    SetWindowFont(hwnd, GetStockObject(DEFAULT_GUI_FONT), false);
    return hwnd;
}

std::vector<HWND> CreateQuandrantEdits(HWND hwndParent)
{
    std::vector<HWND> vechwnd;

    RECT rc = { 0 };
    GetClientRect(hwndParent, &rc);
    auto width = rc.right - rc.left;
    auto height = rc.bottom - rc.top;
    width /= 2;
    height /= 2;

    int id = 101;
    for (int y = 0; y < 2; y++)
    {
        for (int x = 0; x < 2; x++)
        {
            rc.left = x * width;
            rc.right = rc.left + width;
            rc.top = y * height;
            rc.bottom = rc.top + height;

            InflateRect(&rc, -5, -5);
            HWND hwnd = AddEdit(hwndParent, id++, rc);
            vechwnd.push_back(hwnd);
        }
    }
    return vechwnd;
}

LRESULT CALLBACK NoLettersProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (message)
    {
    case WM_CHAR:
        TCHAR ch = GET_WM_CHARTOITEM_CHAR(wParam, lParam);
        if (IsCharAlpha(ch))
            return 0;
        break;
    }

    return DefSubclassProc(hwnd, message, wParam, lParam);
}

LRESULT CALLBACK NoNumbersProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (message)
    {
    case WM_CHAR:
        TCHAR ch = GET_WM_CHARTOITEM_CHAR(wParam, lParam);
        if (IsCharAlphaNumeric(ch) && !IsCharAlpha(ch))
            return 0;
        break;
    }

    return DefSubclassProc(hwnd, message, wParam, lParam);
}

bool OnWmCreate(HWND hwnd, LPCREATESTRUCT pCreateStruct)
{
    auto vechwnd = CreateQuandrantEdits(hwnd);

    // top right
    SetWindowSubclass(vechwnd[1], NoLettersProc, 1, 0);

    // bottom left
    SetWindowSubclass(vechwnd[2], NoNumbersProc, 2, 0);

    // bottom right
    SetWindowSubclass(vechwnd[3], NoLettersProc, 3, 0);
    SetWindowSubclass(vechwnd[3], NoNumbersProc, 4, 0);

    return true;
}

void OnWmClose(HWND hwnd)
{
    PostQuitMessage(0);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        HANDLE_MSG(hwnd, WM_CREATE,  OnWmCreate);
        HANDLE_MSG(hwnd, WM_DESTROY, OnWmClose);
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

int APIENTRY _tWinMain(_In_      HINSTANCE hInstance,
                       _In_opt_  HINSTANCE hPrevInstance,
                       _In_      LPTSTR    lpCmdLine,
                       _In_      int       nCmdShow)
{
    INITCOMMONCONTROLSEX icce = { 0 };
    icce.dwSize = sizeof(icce);
    icce.dwICC = ICC_STANDARD_CLASSES;
    if (!InitCommonControlsEx(&icce))
        return FALSE;

    TCHAR szWindowClass[] = TEXT("WINDOWSUBCLASS");

    WNDCLASSEX wcex = { 0 };
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.hInstance = hInstance;
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    wcex.lpszClassName = szWindowClass;

    if (!RegisterClassEx(&wcex))
        return FALSE;

    HWND hwnd = CreateWindow(szWindowClass,
                             TEXT("Windows Subclass"),
                             WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT, CW_USEDEFAULT,  // x, y
                             CW_USEDEFAULT, CW_USEDEFAULT,  // cx, cy
                             nullptr,                       // hwndParent
                             nullptr,                       // hMenu
                             hInstance,
                             nullptr);                      // lpParam

    if (!hwnd)
        return FALSE;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}


Was This Post Helpful? 0
  • +
  • -

Page 1 of 1