Pages

Sunday, November 1, 2009

ATL Window

From my point of view, MFC is just a wrapper for Windows API. I think, it is a heavy and slow wrapper. I cannot say that if I need to test something simple, I always create an MFC dialog-based application as it used to be few years ago. The Visual Studio wizards create a lot of code, many files, add stdafx.h that connects my code with a lot of headers and libraries, and I still need to edit something in the resources. So, whenever possible, I use just the following simple shablon:


#define WIN32_LEAN_AND_MEAN

#include <windows.h>


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
return 0;
}
It is not the Win32 console application, I can test many new for me things from the Platform SDK and do not see the annoying console window.


I have shown how to add a window to this code - Самая простая Win32 программа-шаблон для тестов. - it is more C-style, than C++. I'd say the ATL can bring a very modern C++ style into this simple test application.


For the beginning let's modify the first example from this article and add the ATL support:


#define WIN32_LEAN_AND_MEAN
#include <atlbase.h>

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
return 0;
}
It's compiled successfully and works, but does nothing.
Let's add an ATL Window:
#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <atlbase.h>
#include <atlwin.h>

typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CMyWindowTraits;

class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
{
public:
DECLARE_WND_CLASS(L"My Window")

BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()

LRESULT OnClose(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DestroyWindow();
return 0;
}

LRESULT OnDestroy(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
return 0;
}
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
CMyWindow wnd;
MSG msg;

HWND hWnd = wnd.Create(NULL, CWindow::rcDefault, L"ATL Window");
if (hWnd == NULL)
return 0;

wnd.ShowWindow(nShowCmd);
wnd.UpdateWindow();
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
This time the application window is an object of CMyWindow class. WinMain function has common things: creates the window, show it, launch the message loop.
CMyWindow class is simple too. It contains:
  1. Window class definition (DECLARE_WND_CLASS).
  2. Message map. (BEGIN_MSG_MAP.. END_MSG_MAP with two message handlers for WM_CLOSE and WM_DESTROY)
  3. Window styles (CMyWindowTraits).

That'all:


Why I like this style?

Mainly, because I see and control each message my window receives. Nothing is hidden from me, I have to write with my hands everything I want to happen in my application. This style allows me to learn more about Win32, understand better the Windows.

Let's add one more message handler into the CMyWindow class - for example, let's handle WM_ERASEBKGND. We need to update the message map:
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
and add the method that will be called on the message arriving:

LRESULT OnEraseBkgnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
return 1;
}

We got a transparent window:


The window is transparent, because the background eraser is empty, but return 1 - meaning we have painted the background.

Why I like ATL?

Because I can use the templates and real Object-Oriented programming for my window classes. For example, ATL allows me to split the message map in the CMyWindow class between many classes and do not have a long and heavy window class. Let's move the backround painter to a seperate class like the following:

template <class T, COLORREF color>
class CBackground
{
HBRUSH m_hBrush;

public:
CBackground()
{
m_hBrush = CreateSolidBrush(color);
}

~CBackground()
{
DeleteObject(m_hBrush);
}

BEGIN_MSG_MAP(CBackground)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
END_MSG_MAP()

LRESULT OnEraseBkgnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
T* pT = static_cast<T*>(this);
HDC hDC = (HDC)wParam;
RECT rect = { 0 };
pT->GetClientRect(&rect);
FillRect(hDC, &rect, m_hBrush);
return 1;
}
};

This time I created a brush with the predefined color in the class contstructor and delete it in the destructor. Please pay attention that we do not need to check any pointer in OnEraseBkgnd method - no one of them can be NULL.

We need to modify the CMyWindow:
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>,
public CBackground<CMyWindow, RGB(128, 128, 128)>
{
public:

typedef CBackground<CMyWindow, RGB(128, 128, 128)> CBackgroundEraser;
DECLARE_WND_CLASS(L"My Window")

BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
CHAIN_MSG_MAP(CBackgroundEraser)
END_MSG_MAP()

LRESULT OnClose(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DestroyWindow();
return 0;
}

LRESULT OnDestroy(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
return 0;
}
};
CBackground class was added into the inheritance list. and CHAIN_MSG_MAP was added into the map (I used typedef to use a simple name in the message map macro).
And here is the application:



Now we have ATL included and so can use other cool stuff. Let's put an image onto the background of our window:
#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <atlbase.h>
#include <atlwin.h>
#include <atlimage.h>

template <class T, WCHAR* lpszFileName>
class CImageBackground
{
CImage m_Image;

public:
CImageBackground()
{
m_Image.Load(lpszFileName);
}

BEGIN_MSG_MAP(CBackground)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
END_MSG_MAP()

LRESULT OnEraseBkgnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
T* pT = static_cast<T*>(this);
HDC hDC = (HDC)wParam;
RECT rect = { 0 };
pT->GetClientRect(&rect);
m_Image.AlphaBlend(hDC, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
0, 0, m_Image.GetWidth(), m_Image.GetHeight());
return 1;
}
};

typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CMyWindowTraits;

WCHAR s_szFile[] = L"image.jpg";

class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>,
public CImageBackground<CMyWindow, s_szFile>
{
public:

typedef CImageBackground<CMyWindow, s_szFile> CBackgroundEraser;
DECLARE_WND_CLASS(L"My Window")

BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
CHAIN_MSG_MAP(CBackgroundEraser)
END_MSG_MAP()

LRESULT OnClose(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DestroyWindow();
return 0;
}

LRESULT OnDestroy(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
return 0;
}
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
CMyWindow wnd;
MSG msg;

HWND hWnd = wnd.Create(NULL, CWindow::rcDefault, L"ATL Window");
if (hWnd == NULL)
return 0;

wnd.ShowWindow(nShowCmd);
wnd.UpdateWindow();
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}


This image has the alpha and it is drawn with the AlphaBlend.

No comments:

Post a Comment