Creating a Window

Published April 05, 2012
Advertisement
Before we create our game, we first need to know how to create a window. Thanks to OOP's the code just needs to be written just once. Since we will want to create only one instance of this class for each application we will create it as singleton.
[subheading]IMainWindow Interface : MainWindow.hxx[/subheading]
Since we dont need to expose all the functions to the rest of the world, we will just include a small subset in the interface. We will come to the purpose of each function in just a bit.
[spoiler]

namespace GameBase
{
class IMainWindow
: public Base::cNonCopyable
{
public:
virtual ~IMainWindow() {}
GAMEBASE_API virtual HWND VOnInitialization( const HINSTANCE & hInstance, const int & nCmdShow, const bool bFullScreen, const int iFullScreenWidth, const int iFullScreenHeight) = 0;
GAMEBASE_API virtual void VOnDestroy() = 0;
GAMEBASE_API virtual void VToggleFullScreen() = 0;
GAMEBASE_API static IMainWindow * GetInstance();
};
}

[/spoiler]
[subheading]cMainWindow Class : MainWindow.h[/subheading]
The cMainWindow class is the main class that handles the window creation and controls the message loop.
[spoiler]

namespace GameBase
{
class cMainWindow
: public IMainWindow
{
public:
HWND VOnInitialization(const HINSTANCE& hInstance, const int& nCmdShow, const bool bFullScreen, const int iFullScreenWidth, const int iFullScreenHeight);
void VOnDestroy();
void VToggleFullScreen();
static cMainWindow * Create();

private:
cMainWindow();
~cMainWindow();
static LRESULT CALLBACK StaticWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
void RegisterWin() ;
void CreateMyWindow(const int& nCmdShow, const Base::cString& lpWindowTitle); LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
void OnWindowCreated();
void OnWindowDestroyed();
void SetDisplayResolution();

private:
const DWORD m_kdwFullScreenStyle;
const DWORD m_kdwWindowedStyle;
WINDOWPLACEMENT m_wp;
bool m_bFullScreen;
HWND m_Hwnd;
HINSTANCE m_hInstance;
int m_iFullScreenWidth;
int m_iFullScreenHeight;
};
}

[/spoiler]
[subheading]Implementation : MainWindow.cpp[/subheading]
Constructor
[spoiler]

cMainWindow::cMainWindow()
: m_bFullScreen(false)
, m_Hwnd(NULL)
, m_hInstance(NULL)
, m_iFullScreenHeight(0)
, m_iFullScreenWidth(0)
, m_pGame(NULL)
, m_kdwFullScreenStyle(WS_EX_TOPMOST | WS_POPUP | WS_VISIBLE)
, m_kdwWindowedStyle(WS_SYSMENU | WS_MINIMIZEBOX | WS_CAPTION)
{
ZeroMemory( &m_wp, sizeof( WINDOWPLACEMENT ) );
}

[/spoiler]
method VOnInitialization
[spoiler]

HWND cMainWindow::VOnInitialization( const HINSTANCE & hInstance, const int & nCmdShow, const bool bFullScreen, const int iFullScreenWidth, const int iFullScreenHeight )
{
m_hInstance = hInstance;
m_bFullScreen = bFullScreen;
m_iFullScreenWidth = iFullScreenWidth;
m_iFullScreenHeight = iFullScreenHeight;
if (m_iFullScreenWidth <= 0)
{
m_iFullScreenWidth = 1;
}
if (m_iFullScreenHeight <= 0)
{
m_iFullScreenHeight = 1;
}

RegisterWin();
SetDisplayResolution();
CreateMyWindow(nCmdShow, "MyWindow") ;
OnWindowCreated();
m_wp.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(m_Hwnd, &m_wp);
return m_Hwnd;
}

[/spoiler]
method VOnDestroy
[spoiler]

void cMainWindow::VOnDestroy()
{
DestroyWindow(m_Hwnd);

delete this;
s_pWindow = NULL;
}

[/spoiler]
method VToggleFullScreen - This method is used to toggle between windowed and full screen mode. We just need to set the appropriate style in the GWL_STYLE attribute of the window by calling SetWindowLongPtr.
[spoiler]

void cMainWindow::VToggleFullScreen()
{
m_bFullScreen = !m_bFullScreen;

SetDisplayResolution();

if (m_bFullScreen)
{
// Save Current location/size
GetWindowPlacement(m_Hwnd, &m_wp);

//Set style for Full Screen mode
SetWindowLongPtr(m_Hwnd, GWL_STYLE, m_kdwFullScreenStyle);

// hide the window
ShowWindow(m_Hwnd, SW_HIDE);
}
else
{
//Set style for Windowed mode
SetWindowLongPtr(m_Hwnd, GWL_STYLE, m_kdwWindowedStyle);

// reset the window location and size
SetWindowPlacement(m_Hwnd, &m_wp);

// allow other windows to come in front when we lose focus in windowed mode
SetWindowPos(m_Hwnd, HWND_NOTOPMOST, 0, 0,0, 0, SWP_NOMOVE | SWP_NOSIZE);
}

if (!IsWindowVisible(m_Hwnd))
{
ShowWindow(m_Hwnd, SW_SHOW);
}
}

[/spoiler]
method Create
[spoiler]

cMainWindow * cMainWindow::Create()
{
return(DEBUG_NEW cMainWindow());
}

[/spoiler]
method RegisterWin - Here, we need to define the window class by filling out a WNDCLASSEX structure. This structure tells Windows all the properties of the window that we want to create. The lpfnWndProc member is set to a function pointer that we will write later on. that has a specific function declaration:
LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
When the program is compiled, another parameter is added to all non-static member functions, a this pointer, which changes the function declaration so it is incompatible with what is required for the lpfnWndProc member. Static functions on the other hand, do not receive this extra parameter, which is why we set the lpfnWndProc member to StaticWndProc. We register the window class for subsequent window class by calling RegisterClassEx
[spoiler]

void cMainWindow::RegisterWin()
{
WNDCLASSEX wc ;

wc.cbSize = sizeof(WNDCLASSEX) ;
wc.style = 0 ;
wc.lpfnWndProc = (WNDPROC)cMainWindow::StaticWndProc ;
wc.cbClsExtra = 0 ;
wc.cbWndExtra = 0 ;
wc.hInstance = m_hInstance ;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION) ;
wc.hCursor = LoadCursor(NULL, IDC_ARROW) ;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1) ;
wc.lpszMenuName = NULL ;
wc.lpszClassName = "Window" ;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION) ;
if(!RegisterClassEx(&wc))
{
//error
exit(0) ;
}
}

[/spoiler]
method CreateMyWindow - This function is responsible for creating the window. In the CreateWindow function, we can store any type of pointer we want in the last parameter(lpParam), such as a this pointer. Since our static window procedure does not have access to the "this" pointer, the "this" pointer which will be accessible during a WM_CREATE message, could be used to send messages meant for our application to a non-static window procedure, which would allow us to access the non-static data of our class. One of the attributes of all windows is a user-defined attribute, where we can store our this pointer in the user-defined attribute using the SetWindowLongPtr function with the GWLP_USERDATA offset flag
[spoiler]

void cMainWindow::CreateMyWindow( const int &nCmdShow, const cString & lpWindowTitle)
{
DWORD dwStyle;
if(m_bFullScreen)
{
dwStyle = m_kdwFullScreenStyle;
}
else
{
dwStyle = m_kdwWindowedStyle;
}

RECT windowRect;
windowRect.left = 0;
windowRect.top = 0;
windowRect.right = m_iFullScreenWidth;
windowRect.bottom = m_iFullScreenHeight;

//get the required size of the window rectangle, based on the desired size of the client rectangle
AdjustWindowRectEx(&windowRect, dwStyle, false, 0);

m_Hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
"Window",
lpWindowTitle.GetData(),
dwStyle,
CW_USEDEFAULT, 0,
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top,
NULL,
NULL,
m_hInstance,
this) ; // pass "this" so that staticwndproc can access the non static data

if(m_Hwnd == NULL)
{
//error
PostQuitMessage(0);
}

ShowWindow(m_Hwnd, nCmdShow) ;
}

[/spoiler]
method StaticWndProc - With the this pointer stored with our window, we can access it in all subsequent messages with the GetWindowLongPtr. Once the pointer is retreived, we can cast the pointer to a cMainWindow pointer and access all the non-static functions of the class, such as the non-static window procedure. Using this, we route all messages to their corresponding non-static window procedure.
[spoiler]

LRESULT CALLBACK cMainWindow::StaticWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
if ( msg == WM_CREATE )
{
// store the this pointer which is passed when we create the window in lparam
// lpCreateParams contains the value of the lpParam parameter specified in the function call.
SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG)((CREATESTRUCT *)lParam)->lpCreateParams );
}

// get the this pointer for this class using GWLP_USERDATA
cMainWindow *targetApp = (cMainWindow*)GetWindowLongPtr( hwnd, GWLP_USERDATA );

if ( targetApp )
{
// let our window handle the msg
return targetApp->WndProc( hwnd, msg, wParam, lParam );
}

return DefWindowProc( hwnd, msg, wParam, lParam );
}

[/spoiler]
method OnWindowCreated - This function will be called after the window is created. All our resources will be created here
[spoiler]

LRESULT CALLBACK cMainWindow::WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
PAINTSTRUCT ps ;
HDC hdc ;

switch(uMsg)
{
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;

case WM_CLOSE:
OnWindowDestroyed();
return 0 ;

case WM_DESTROY:
OnWindowDestroyed();
return 0 ;

case WM_ACTIVATE:
{
if (!HIWORD(wParam))
{
//active
}
else
{
//inactive
}
return 0;
}
//other messages
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam) ;
}
}

[/spoiler]
method WndProc - This function is used to process all our messages
[spoiler]

void cMainWindow::OnWindowCreated()
{
//Bring the window into the foreground and activates the window
SetForegroundWindow(m_Hwnd);
//Set the keyboard focus
SetFocus(m_Hwnd);
}

[/spoiler]
method OnWindowDestroyed - This function will be called after the window is destroyed. All our resources will be released here
[spoiler]

void cMainWindow::OnWindowDestroyed()
{
// return to the default mode
ChangeDisplaySettings(NULL, 0);

ReleaseCapture() ;
PostQuitMessage(0) ;
}

[/spoiler]
method SetDisplayResolution - This function sets the display settings of the monitor to the fullscreen width and height that has already been specified by filling the DEVMODE structure. ChangeDisplaySettings(NULL, 0) resets the display to the registry stored values
[spoiler]

void cMainWindow::SetDisplayResolution()
{
if (m_bFullScreen)
{
DEVMODE dmScreenSettings;
SecureZeroMemory(&dmScreenSettings, sizeof(dmScreenSettings));
if (!EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dmScreenSettings))
{
//error
return;
}

// set the full screen height and width
dmScreenSettings.dmPelsHeight = m_iFullScreenHeight;
dmScreenSettings.dmPelsWidth = m_iFullScreenWidth;
dmScreenSettings.dmFields = (DM_PELSWIDTH | DM_PELSHEIGHT);

// Test if the requested graphics mode could be set.
if (ChangeDisplaySettings(&dmScreenSettings, CDS_TEST) == DISP_CHANGE_SUCCESSFUL)
{
// Set the requested graphics mode.
if(ChangeDisplaySettings(&dmScreenSettings, 0) == DISP_CHANGE_SUCCESSFUL)
return;
}
//error
}

// return to the default mode
ChangeDisplaySettings(NULL, 0);
return;
}

[/spoiler]
method GetInstance _ this function will create a cMainWindow object if it is not yet created and return a pointer to it
[spoiler]

IMainWindow * IMainWindow::GetInstance()
{
if(!s_pWindow)
s_pWindow = cMainWindow::Create();
return s_pWindow;
}

[/spoiler]
[subheading]Running the code[/subheading]
The WinMain function is the entry point of all Windows programs. Here we initialize the window and on quit destroy the window
[spoiler]

int WINAPI WinMain(const HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd = IMainWindow::GetInstance()->VOnInitialization(hInstance, nCmdShow, false, 640,480);

MSG Msg;
PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE) ;
// run till completed
while (Msg.message!=WM_QUIT)
{
// is there a message to process?
if (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
{
// dispatch the message
TranslateMessage(&Msg) ;
DispatchMessage(&Msg) ;
}
else
{
//No message to process?
// Then do your game stuff here
}
}

if (IMainWindow::GetInstance())
{
IMainWindow::GetInstance()-> VOnDestroy();
}
}

[/spoiler]
[subheading]Updates[/subheading]
Edit 14 Apr 2012 : Added a fix in VToggleFullScreen so that other windows can come in front when we lose focus in windowed mode.
Previous Entry NonCopyable objects
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement