[C++] Console App - Child Process (Win32 GUI & Message Loops Event Handling)

AceInfinity

Emeritus, Contributor
Joined
Feb 21, 2012
Posts
1,728
Location
Canada
Preview

MSDN Links:

Using Messages and Message Queues -- Creating a Message Loop: Using Messages and Message Queues (Windows)

WinMain Entry Point: WinMain entry point (Windows)

CreateWindow Function: CreateWindow function (Windows)
Window Styles Reference: Window Styles (Windows)
ShowWindow Function: ShowWindow function (Windows)
GetMessage Function: GetMessage function (Windows)

TranslateMessage Function: TranslateMessage function (Windows)
DispatchMessage Function: DispatchMessage function (Windows)

PostQuitMessage Function: PostQuitMessage function (Windows)

Alright, so creating a GUI and not having to jump into the .NET framework was my main goal here. In order to create a GUI Window, you have to use the Win32 API's, which is essentially what the .NET framework does for you in the background, only it helps you easily manage events and all of that. It's also a bit more refined than this code here, but this is a good starting point.

What the following code below does, is essentially oriented around a message loop with a child window as a subprocess for the main parent process (being the console app that is initially our main process for this executable). In this message loop we check the Windows messages, translate and dispatch of them to turn them into windows procedures that call predefined event methods for each specific message being read... Up until the GetMessage() function reads the WM_QUIT Windows message, which is essentially the last message before we return the error code posted by the PostQuitMessage() function in our WM_DESTROY Message Event, contained in the wParam for the WM_QUIT message as detailed in the MSDN docs for that specific message.

I would suggest, before looking at the source code below that i'm providing, that you review some of the links above, as these are the main ones that I used to understand how a message loop is created.

source.cpp:
Code:
#include "stdafx.h"
#include "Form1.h"

int main()
{
	// Create a new child process that creates a GUI utilizing the Win32 API's.
	HINSTANCE pHinst = (HINSTANCE)GetCurrentProcess();
	char* cmdLine = "";
	WinMain(pHinst, NULL, cmdLine, SW_SHOW);

	// This program will not terminate until the main console window is closed,
	// or the created window is closed, and you complete the main() function
	// because we are in a message loop with the created window for the GUI.
	//
	// The created GUI is a child process however for the main console process,
	// so if the main console window is exited, the program is terminated along
	// with the visible GUI.

	printf("\nPress enter to continue...\n");
	getchar();
	return 0;
}

Form1.h:
Code:
// Form1.h
#include "stdafx.h"
#include <windows.h>
#include <tchar.h>

#include <climits>

#ifndef Form_h
#define Form_h

//Control ID's.
#define IDC_BUTTON1 1600
#define IDC_BUTTON2 1605
#define IDC_EDIT1 1610
#define IDC_STATIC1 1615

// Define The # Of Event Handlers.
#define handlerCount(x) (sizeof(x) / sizeof(x[0]))

// Windows Message Structure
struct WndMsg
{
	HWND hWnd;
	WPARAM wParam;
	LPARAM lParam;
	HINSTANCE hIns;
};

// Various event methods for handling each Windows Message.
long WndProc_OnCreate (WndMsg &wm);
long WndProc_OnCommand (WndMsg &wm);
long WndProc_OnMove (WndMsg &wm);
long WndProc_OnDestroy (WndMsg &wm);

// Event Handler Structure.
struct EVENTHANDLER
{
	unsigned int msg;
	long (*fPtr)(WndMsg&);
};

// Event Handler Structure Array.
const EVENTHANDLER EventHandler[] =
{
	{ WM_CREATE, WndProc_OnCreate },
	{ WM_COMMAND, WndProc_OnCommand },
	{ WM_MOVE, WndProc_OnMove },
	{ WM_DESTROY, WndProc_OnDestroy }
};
#endif

Form1.cpp:
Code:
// Form1.cpp
#include "stdafx.h"
#include "Form1.h"

#pragma region Windows Message Event Handling

// Handles the WM_CREATE Windows Message.
// Initializes the form with all the child controls.
long WndProc_OnCreate(WndMsg &wm)
{
	wm.hIns = ((LPCREATESTRUCT)wm.lParam)->hInstance;
	CreateWindow(_T("BUTTON"), _T("OK"), WS_CHILD | WS_VISIBLE, 80, 30, 150, 30, wm.hWnd, (HMENU)IDC_BUTTON1, wm.hIns, 0);
	CreateWindow(_T("BUTTON"), _T("CANCEL"), WS_CHILD | WS_VISIBLE, 250, 30, 150, 30, wm.hWnd, (HMENU)IDC_BUTTON2, wm.hIns, 0);

	CreateWindow(_T("EDIT"), _T("Enter your text here..."), WS_CHILD | WS_VISIBLE, 45, 100, 400, 200, wm.hWnd, (HMENU)IDC_EDIT1, wm.hIns, 0);
	CreateWindow(_T("STATIC"), _T("Developed by AceInfinity - Tech.Reboot.Pro 2013"), WS_CHILD | WS_VISIBLE, 45, 310, 400, 30, wm.hWnd, (HMENU)IDC_STATIC1, wm.hIns, 0);

	return 0;
}

// Handles the WM_COMMAND Windows Message.
// Retrieves the ID and Notification code from the Lo and Hiword of the wParam
// for the control that had raised this event.
long WndProc_OnCommand(WndMsg &wm)
{
	unsigned short btnID = LOWORD(wm.wParam);
	unsigned short notifyCode = HIWORD(wm.wParam);
	printf("WM_COMMAND:\n    ID: %d\n    Notification Code: %d\n", btnID, notifyCode);
	return 0;
}

// Handles the WM_MOVE Windows Message.
// Updates the console buffer with the current coordinates of the GUI Window as it's child process.
// These are taken from the Lo and Hiword of the lParam from the event args for this Windows Message.
long WndProc_OnMove(WndMsg &wm)
{
	unsigned short x = LOWORD(wm.lParam);
	unsigned short y = HIWORD(wm.lParam);
	
	// If the x coordinate is greater than 10000, then we've probably reached outside of the screenboundaries with the top left of the form.
	// I've implemented a function to check and subtract the max value of an unsigned short from the location it provides for the x component,
	// because it seems to want to go to that max value as soon as we exceed the bounds of the screen on the left.
	// (For readability, i've negated this value to show that we are to the left of 0, in terms of the x component.)
	printf("WM_MOVE: Coordinates (%d, %d)\n", x > 10000 ? -(USHRT_MAX - x) : x, y);
	return 0;
}

// Handles the WM_DESTROY Windows Message.
// Indicate that this thread has made a request to destroy the window: (PostQuitMessage). 
long WndProc_OnDestroy(WndMsg &wm)
{
	PostQuitMessage(0);
	printf("WM_DESTROY: Window Destroyed\n");
	return 0;
}

#pragma endregion

// Function to handle and call each event method for a specific Windows Message
LRESULT CALLBACK WndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
	// Windows Message Sturcture that we'll be assigning.
	WndMsg wm;

	// Loop through each EventHandler in the array
	for(unsigned int i = 0; i < handlerCount(EventHandler); i++)
	{
		// If the currently indexed EventHandler's msg is equal to the const int defined by our msg param...
		if(EventHandler[i].msg == msg)
		{
			// Assign our WndMsg to contain the values of this Windows Message arguments.
			wm.hWnd = hwnd,
			wm.lParam = lParam,
			wm.wParam = wParam;

			// Return to the caller with a pointer to this WndMsg structure.
			return (*EventHandler[i].fPtr)(wm);
		}
	}

	// No recognizable Windows Message EventHandler was found from the array.
	// Call the default Window processing for this Windows Message, and return to the caller.
	return (DefWindowProc(hwnd, msg, wParam, lParam));
}

// Entry point for the GUI Window child process to be called from the parent process.
int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int swFlag)
{
	TCHAR szClassName[] = _T("Form1");
	WNDCLASSEX wc;
	MSG msg;
	HWND hWnd;

	// Set our Window class information struct values
	wc.lpszClassName = szClassName;
	wc.lpfnWndProc = WndProc;
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.style = CS_DROPSHADOW | CS_DBLCLKS;
	wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);
	wc.hInstance = hIns;
	wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor = LoadCursor(NULL,IDC_ARROW);
	wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
	wc.cbWndExtra = 0;
	wc.lpszMenuName = NULL;
	wc.cbClsExtra = 0;

	// Need to register the Window class so we can use it to create our Window in a child process.
	RegisterClassEx(&wc);
	hWnd = CreateWindowEx(0, szClassName, szClassName, WS_TILEDWINDOW, 75, 75, 500, 400, HWND_DESKTOP, 0, hIns, 0);
	
	// Call ShowWindow function to display our newly created window.
	ShowWindow(hWnd, swFlag);

	// This is our message loop for handling the Windows Messages.
	// Note: This will continue to loop until the function see's that the WM being passed
	// is the WM_QUIT message.

	bool ret;
	while((ret = GetMessage( &msg, NULL, 0, 0 )) != 0)
	{
		switch (ret)
		{
		case -1:
			return ERROR_INVALID_HANDLE;
		default:
			// Translate and post the Windows Mesage to the message queue to be read next time the GetMessage function is called.
		TranslateMessage(&msg);
		// Dispatch previous translated message to a windows procedure.
		DispatchMessage(&msg);
			break;
		}
	}

	// Return with the wParam for this Windows Message.
	// Note: This is the WM_QUIT Windows Message, the wParam for this message holds the value 
	// from the error code posted with the PostQuitMessage function.
	return msg.wParam;
}

I've heavily commented the code to help you guys understand, but it took a little while to get some of this stuff working. Haven't gotten into any GDI with this yet.

edit: Just realized I guess I could use the secure version of some of the functions such as prinf(), since I am using the Win32 API's for this (there's no way around that here). It's not going to be a disadvantage for me...

:thumbsup2:
 
Last edited:
Found a good link on GDI+ libraries for C++, might be good to try 1 + 1 at some point down the road with GDI and this GUI, but right now i'm already swarmed with enough projects. Finished a big development push for my current work, now I can start again, working on all the projects of my own ! :)
 
I've been meaning to run this since I first saw it - I'll even get round to it at some point. :lol:

I'll definitely go through the code and links, looks like it'll be useful for me to learn from it.
 

Has Sysnative Forums helped you? Please consider donating to help us support the site!

Back
Top