Creating a Window Wrapper Class
From Scriptionary.com
| 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.
BlogMarks
del.icio.us
digg
Fark
Furl
Newsvine
reddit
Segnalo
Simpy
Slashdot
smarking
Spurl
Wists
