Creating a Window Wrapper Class

From Scriptionary.com

Jump to: navigation, search
Author Eddy Luten
Author Link Scriptionary.com
Contributors none yet
Notes
A supply to a common demand

One of the most common requests that I encounter on online forums is the Window Class, which doesn't exist in the Windows API since the API itself is written in C, not C++. In fact, it isn't too easy to create a wrapper if you're not familiar with the Windows API at all. This article will go over the basics on Creating a Window Wrapper Class for Microsoft Windows.

Contents


Prototyping our Class

The idea behind this class will be to make it easier for us to create Windows in the future. So a task to become easy, we'll need a simple interface to work with. In this section we'll determine what we'll need our Window class to do and outline some basic (initial) functionality.

  • Methods
    • Create a Window
    • Show and Hide a Window
    • Dispose of a Window properly
  • Properties
    • Get and set the Title
    • Return the window's Handle to allow window manipulation

This will not be all of the functionality included but that's where we'll start off from. Looking at the functionality described above, we can already create a skeleton prototype based on those bare essentials:

// FILE: Window.h
#ifndef __WINDOW_H__
#define __WINDOW_H__
 
#include <iostream>
#include <string>
#ifndef WIN32_LEAN_AND_MEAN
	#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
 
// A general-purpose Windowing Class
class Window
{
private:
	Window(void){}; // can't touch this, does nothing
protected:
	int width;
	int height;
	std::string title;
	HWND hWND;
	HINSTANCE hInst;
public:
 
	Window(
		int Width, 
		int Height, 
		std::string Title, 
		HINSTANCE hInstance
	);
	~Window();
 
	bool Create(void);
	bool Show(void);
	bool Hide(void);
 
	std::string GetTitle(void) const;
	void SetTitle(std::string);
 
	HWND GetHandle(void);
}; // class Window
 
#endif

Some explanations for those new to Windows programming:

  • The windows.h file itself is the header file for the Windows API.
  • Defining WIN32_LEAN_AND_MEAN tells the windows.h file not to add any extra stuff we don't need.
  • The HINSTANCE datatype is a pointer to the Handle of your application.
  • The HWND datatype is a pointer to the Handle of your window.

Of course, this is not the final structure but it's a start to what will become out Windows Wrapper Class. The next step will be to create the actual window but to do that we'll need to apply some tricks.

The WndProc Problem

Anyone who has ever had to create a window knows that you need a WndProc parameter at some point. Those who have attempted to create a Window Wrapper Class before, know that this function is usually the end of an attempt. Normally, when you are programming in a procedural fashion you can pass functions to functions as a parameter like so:

int giveInt(){return 123;}
 
void XYZ(){
	callSomething(giveInt);
	// prototype: callSomething(void (*func)(void));
}

Which would result in a so called 'callback' to the giveInt() function. But because we're are dealing with classes, and our functions are working in the scope of our current class (not globally), we can't pass along our member functions, e.g. if giveInt() was in the same class as XYZ() you'd get a compiler error. The WndProc needs to be defined somewhere only once, like a regular old C function, it can't be a dynamic function as found within a class.

This is where we face our problem. The above functionality is exactly what the Windows API wants us to do in order for us to call our WndProc. But since our WndProc will be part of our class, we can't call it.

To resolve this issue, let's add two more lines to the public part of our class prototype:

static LRESULT CALLBACK stWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

The names are a bit cryptic but translated they say static Window Procedure(parameter list) and Window Procedure(parameter list). The functions will be explained as we head into the source code later on.

Starting Our Source Code

The entire source code for the .CPP file will be posted below but first there are three specific areas of interest. Firstly, what we do in the Create function.

bool Window::Create()
{
	WNDCLASSEX WndCls;
	WndCls.cbSize        = sizeof(WNDCLASSEX);
	// if we were to use this window for painting,
	// we'd set the following line to:
	// CS_HREDRAW | CS_VREDRAW | CS_OWNDC instead of 0
	WndCls.style = 0;
	WndCls.lpfnWndProc = this->stWndProc;
	WndCls.cbClsExtra = 0;
	WndCls.cbWndExtra = 0;
	WndCls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	WndCls.hCursor = LoadCursor(NULL, IDC_ARROW);
	WndCls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	WndCls.lpszMenuName = NULL;
	WndCls.lpszClassName = "ScriptionaryClass";
	WndCls.hInstance = this->hInst;
	WndCls.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
 
	RegisterClassEx(&WndCls);
 
	this->hWND = CreateWindowEx(
		WS_EX_CLIENTEDGE,
		WndCls.lpszClassName,
		this->title.c_str(),
		WS_OVERLAPPEDWINDOW,
		10, 10, // position: left 10, top 10
		this->width,
		this->height,
		NULL, NULL,
		this->hInst,
		(void *)this
	); // createwindowex
 
	return bool(this->hWND != NULL);
}; // Create

Everything seems like a regular Registration and Creation until we get to the last parameter to go into CreateWindowEx. (void *)this will provide a pointer back to the calling object, in this case the initialized Window object. And instead of calling WndProc in the WndCls.lpfnWndProc we call its static counterpart. So let's have a look at what this static function does:

LRESULT Window::stWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	Window* pWnd;
 
	if (uMsg == WM_NCCREATE) 
		SetWindowLongPtr(hWnd, GWL_USERDATA, (LONG_PTR)((LPCREATESTRUCT(lParam))->lpCreateParams));
 
	pWnd = (Window *)GetWindowLongPtr(hWnd, GWL_USERDATA);
 
	return LRESULT (
		(pWnd) ? 
		pWnd->WndProc(hWnd, uMsg, wParam, lParam) : 
		DefWindowProc(hWnd, uMsg, wParam, lParam)
	);
}; //stWndProc

stWndProc is a very simple function which simply re-routes back to the calling object and gets the real result from the WndProc if the Window-handle is not NULL. The WndProc itself is a regular WndProc like in any Windows program:

LRESULT Window::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg)
	{
	case WM_DESTROY:
		PostQuitMessage(WM_QUIT);
		break;
	default:
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}
	return 0;
}; //WndProc

Now the entire .CPP file including the extra properties:

// FILE: Window.cpp
#include "Window.h"
 
/*******************/
 
Window::Window(int Width, int Height, std::string Title, HINSTANCE hInstance)
{
	// Set some variables and move along
	this->width = Width;
	this->height = Height;
	this->title = Title;
	this->hWND = NULL;
	this->hInst = hInstance;
}; // constructor
 
/*******************/
 
Window::~Window()
{
	if(this->hWND && !DestroyWindow(this->hWND))
	{
		MessageBox(this->hWND, "Could not destroy Window!", "Destruction Error", NULL);
		return;
	}
	this->hWND = NULL;
}; // destructor
 
/*******************/
 
inline HWND Window::GetHandle()
{ return this->hWND; }; // GetHandle
 
/*******************/
 
inline std::string Window::GetTitle() const
{ return this->title; }; // GetTitle
 
/*******************/
 
bool Window::Show()
{
	if (this->hWND == NULL) return false;
	ShowWindow(this->hWND, SW_SHOW);
	UpdateWindow(this->hWND);
	return true;
}; // Show
 
/*******************/
 
bool Window::Hide()
{
	if (this->hWND == NULL) return false;
	ShowWindow(this->hWND, SW_HIDE);
	UpdateWindow(this->hWND);
	return true;
}; // Hide
 
/*******************/
 
bool Window::Create()
{
	WNDCLASSEX WndCls;
	WndCls.cbSize        = sizeof(WNDCLASSEX);
	// if we were to use this window for painting,
	// we'd set the following line to:
	// CS_HREDRAW | CS_VREDRAW | CS_OWNDC instead of 0
	WndCls.style = 0;
	WndCls.lpfnWndProc = this->stWndProc;
	WndCls.cbClsExtra = 0;
	WndCls.cbWndExtra = 0;
	WndCls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	WndCls.hCursor = LoadCursor(NULL, IDC_ARROW);
	WndCls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	WndCls.lpszMenuName = NULL;
	WndCls.lpszClassName = "ScriptionaryClass";
	WndCls.hInstance = this->hInst;
	WndCls.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
 
	RegisterClassEx(&WndCls);
 
	this->hWND = CreateWindowEx(
		WS_EX_CLIENTEDGE,
		WndCls.lpszClassName,
		this->title.c_str(),
		WS_OVERLAPPEDWINDOW,
		10, 10, // position: left 10, top 10
		this->width,
		this->height,
		NULL, NULL,
		this->hInst,
		(void *)this
	); // createwindowex
 
	return bool(this->hWND != NULL);
}; // Create
 
/*******************/
 
LRESULT Window::stWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	Window* pWnd;
 
	if (uMsg == WM_NCCREATE) 
		SetWindowLongPtr(hWnd, GWL_USERDATA, (LONG_PTR)((LPCREATESTRUCT(lParam))->lpCreateParams));
 
	pWnd = (Window *)GetWindowLongPtr(hWnd, GWL_USERDATA);
 
	return LRESULT (
		(pWnd) ? 
		pWnd->WndProc(hWnd, uMsg, wParam, lParam) : 
		DefWindowProc(hWnd, uMsg, wParam, lParam)
	);
}; //stWndProc
 
/*******************/
 
LRESULT Window::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg)
	{
	case WM_DESTROY:
		PostQuitMessage(WM_QUIT);
		break;
	default:
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}
	return 0;
}; //WndProc

Usage Example

Now that the class has been finished up let's have a look at a simple usage example:

// FILE: TestWindow.cpp
#include "Window.h"
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	Window myWindow = Window(800,600,"Hello, World!", hInstance);
 
	if(myWindow.Create()) {
		MSG messages;
		myWindow.Show();
 
		do{
			if (PeekMessage(&messages, NULL, 0, 0, PM_REMOVE))
			{
				TranslateMessage(&messages);
				DispatchMessage(&messages);
			}
		} while (WM_QUIT != messages.message);
 
		return EXIT_SUCCESS;
	}
	return EXIT_FAILURE;
};  //  main

While this class is not the holy grail, it should get you started by setting up a Window rather quickly.

Personal tools