• 10/15/04 04:33 PM
    Sign in to follow this  

    Using the Windows Template Library Part 2

    General and Gameplay Programming

    Myopic Rhino

    Introduction

    Welcome back! In this, the second article in the series, we'll be covering the features of [tt]CWindowImpl[/tt] in much greater detail. As we cover each of the various topics we'll also be developing our own derivative of [tt]CWindowImpl[/tt] called [tt]CDxWindowImpl[/tt]. [tt]CDxWindowImpl[/tt] will be a reusable window implementation that will provide many of the features needed by DirectX application windows. Using an existing, tested, and feature rich framework will make it less buggy and more extendable than most alternatives. You may recall last time that I mentioned we'd be covering message loops and idle processing in this article. Well, I lied. Once I sat down and planned the content of this article I realized how poorly that topic fits in with the rest of this article, so I decided to bump it to part 3.

    Requirements

    I make many of the same assumptions in this article I made in the first. I assume that the reader has a basic understanding of Win32 programming and should be capable of creating a simple Win32 application, and of course, that he or she has read the first article. Some familiarity with using C++ templates would be helpful as well. I also assume that the reader is using VC 6.0 SP 5 or VC .NET. Other compilers may work but again, I cannot make any guarantees. You'll also want to make sure you have the WTL 7.0 or 7.1 libraries and have added the WTL\Include path to your list of include directories. This is a minor change from the first article which compiled with the 3.1 library. The code included in this article will likely compile with the older library but I've not tested against it. And last but not least, you'll want to make sure you're building against the latest Platform SDK. Below are links to both the Microsoft Download Center where the 7.1 libraries can be obtained, and the Platform SDK Update site. [url="http://www.microsoft.com/downloads/details.aspx?FamilyID=1be1eb52-aa96-4685-99a5-4256737781c5&DisplayLang=en"]WTL[/url] [url="http://www.microsoft.com/msdownload/platformsdk/sdkupdate/"]Platform SDK[/url]

    A New Project

    Before going any further, let's create a new project for this article. Make it an empty Win32 Application project. Add a new header file to the project called [tt]CDxWindowImpl.h[/tt] and a code file called [tt]main.cpp[/tt]. The source for these two files is listed below. [i][listing 2.1][/i] [b]CDxWindowImpl.h[/b] [code] #ifndef __ATLWIN_H__ #error CDxWindowImpl.h requires atlwin.h to be included first #endif template class CDxWindowImpl : public CWindowImpl { public: BEGIN_MSG_MAP(CDxWindowImpl) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) END_MSG_MAP() LRESULT OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled ) { PostQuitMessage(0); bHandled = FALSE; return 0; } }; [/code] [i][listing 2.2][/i] [b]Main.cpp[/b] [code] //#define _UNICODE #include #include #include #include CAppModule _Module; #include #include #include "CDxWindowImpl.h" class CDxAppWindow : public CDxWindowImpl { public: BEGIN_MSG_MAP(CDxAppWindow) CHAIN_MSG_MAP(CDxWindowImpl) END_MSG_MAP() }; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { CMessageLoop messageLoop; CDxAppWindow mainwnd; _Module.Init(NULL, hInstance); _Module.AddMessageLoop(&messageLoop); mainwnd.Create(NULL,CWindow::rcDefault,_T("Main Window")); if(mainwnd == NULL) return 0; mainwnd.ShowWindow(nCmdShow); mainwnd.UpdateWindow(); int nRet = messageLoop.Run(); _Module.RemoveMessageLoop(); _Module.Term(); return nRet; } [/code] The first thing that needs to be pointed out is that although this code will compile, it won't actually create a window. This is because I've not passed any window styles to the call to [tt]Create()[/tt]. I've done this on purpose and my reason for doing so will become clear in the next section. There's not much in these two files that's new. In [tt]CDxWindowImpl.h[/tt] we create our new template class called [tt]CDxWindowImpl[/tt] which derives directly from [tt]CWindowImpl[/tt] and adds a message handler for [tt]WM_DESTROY[/tt]. In [tt]Main.cpp[/tt] we create another class called [tt]CDxAppWindow[/tt] which derives from [tt]CDxWindowImpl[/tt] (similar to [tt]Mainwnd[/tt] and [tt]CWindowImpl[/tt] from the previous article). [tt]CDxAppWindow[/tt] is then instantiated, and a call to [tt]Create()[/tt] finishes the setup. What's different this time around is that [tt]CDxAppWindow[/tt]'s message map contains the [tt]CHAIN_MSG_MAP[/tt] macro. We'll cover this in more detail in the next article. All we really need to know at this point is that [tt]CHAIN_MSG_MAP[/tt] will make all the message handlers in the message map of the class passed to it available to this class's message map. Now, all message handlers created in [tt]CDxWindowImpl[/tt] will be available to [tt]CDxAppWindow[/tt].

    Window Traits

    In the previous article we specified window styles by passing the style and extended style information to [tt]CWindowImpl[/tt]'s [tt]Create()[/tt] function. [listing 2.3] [code] mainwnd.Create(NULL,CWindow::rcDefault,_T("Main Window"), WS_OVERLAPPEDWINDOW); [/code] The Windows Template Library provides another method of specifying these styles: window traits. Window traits work by taking style information and storing it in an instance of a template class. This is done though a template class called [tt]CWinTraits[/tt]. The template takes two [tt]DWORD[/tt] parameters, the style and extended style, and provides methods for accessing them. The definition of [tt]CWinTraits[/tt] is listed below. [listing 2.4] [code] template class CWinTraits { public: static DWORD GetWndStyle(DWORD dwStyle) { return dwStyle == 0 ? t_dwStyle : dwStyle; } static DWORD GetWndExStyle(DWORD dwExStyle) { return dwExStyle == 0 ? t_dwExStyle : dwExStyle; } }; [/code] The purpose of the [tt]GetWndStyle()[/tt] and [tt]GetWndExStyle()[/tt] functions is rather obvious but their implementation is a bit cryptic. If the value passed to [tt]GetWndStyle()[/tt] and [tt]GetWndExStyle()[/tt] is zero they return the stored style information. If the value passed is not zero, they return the passed value. Admittedly, this seems a little silly. Why not simply return the stored values, drop the parameter, and be done with it? The answer lies in the way the class is used in the WTL libraries. Below is [tt]CWindowImpl[/tt]'s [tt]Create[/tt] method. [listing 2.5] [code] HWND Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName = NULL, DWORD dwStyle = 0, DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL) { if (T::GetWndClassInfo().m_lpszOrigName == NULL) T::GetWndClassInfo().m_lpszOrigName = GetWndClassName(); ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc); dwStyle = T::GetWndStyle(dwStyle); dwExStyle = T::GetWndExStyle(dwExStyle); return CWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rcPos, szWindowName, dwStyle, dwExStyle, nID, atom, lpCreateParam); } [/code] Notice that the [tt]dwStyle[/tt] and [tt]dwExStyle[/tt] parameters have a default value: zero. Also notice that [tt]dwStyle[/tt] and [tt]dwExStyle[/tt] are passed to [tt]GetWndStyle()[/tt] and [tt]GetWndExStyle()[/tt], set to the value returned by [tt]GetWndStyle()[/tt] and [tt]GetWndExStyle()[/tt], and then passed on to [tt]CWindowImplBaseT[/tt]'s [tt]Create()[/tt] method. With this set up, if no styles are specified in the call to [tt]CWindowImpl::Create()[/tt] the styles stored in the trait class are used. If styles are passed to [tt]CWindowImpl::Create()[/tt] they override the traits. This useful feature allows us to attach a set of default window styles to our newly implemented window, while users of this new window retain the ability easily override these styles if they happen to not suit the particular instance he or she is creating. There are times, however, when you don't necessarily want users to replace the trait styles. You may wish to use the trait styles as a set of base styles and only give users of your window class the ability to add a style or two to this minimum set. The [tt]CWinTraitsOR[/tt] class provides this functionality. Simply use it in place of [tt]CWinTraits[/tt]. To sum up, [tt]CWindowImpl[/tt] derived classes that use a [tt]CWinTraits[/tt] instance for the [tt]TWinTraits[/tt] template parameter will override the stored styles if styles are passed to [tt]Create()[/tt]. [tt]CWindowImpl[/tt] derived classes that use a [tt]CWinTraitsOR[/tt] instance for the [tt]TWinTraits[/tt] template parameter will add any styles passed via [tt]Create()[/tt] to the stored styles and this combined set of styles will be used to create the window. The WTL library also provides a handful of predefined trait classes. You'll find them used by various window implementation classes provided by the library. For the sake of completeness I've listed their definition below. [listing 2.6] [code] typedef CWinTraits CControlWinTraits; typedef CWinTraits CFrameWinTraits; typedef CWinTraits CMDIChildWinTraits; typedef CWinTraits<0, 0> CNullTraits; [/code] Now that we have a thorough understanding of window traits, let's create a trait class for [tt]CDxWindowImpl[/tt]. Make the following changes to [tt]CDxWindowImpl.h[/tt]: [listing 2.7] [code] #ifndef __ATLWIN_H__ #error CDxWindowImpl.h requires atlwin.h to be included first #endif typedef CWinTraits CDxAppWinTraits; template class CDxWindowImpl : public CWindowImpl { public: BEGIN_MSG_MAP(CDxWindowImpl) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) END_MSG_MAP() LRESULT OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled ) { PostQuitMessage(0); bHandled = FALSE; return 0; } }; [/code] With these changes in place the call to [tt]Create()[/tt] in [tt]Main.cpp[/tt] no longer needs window styles passed to it. At this point rebuilding the project should produce an executable that creates and displays a window.

    Window Class Registration

    Window styles (and trait classes that store styles) are a means of customizing the appearance of an individual instance of a window class. Window styles can do a great deal to change the look of a given window, but we require more control over the look of the window than window styles alone provide. Specifically, we'd like to change the background color to black. To do this we need to customize the window class itself. So, what window class does an instance of our [tt]CDxWindowImpl[/tt] belong to, and more importantly, how can we change the class styles and attributes to suit our needs? If you'll recall from the previous article we could choose a specific name for the window class used by our implementation by including the [tt]DECLARE_WND_CLASS[/tt] macro in the class definition. Doing that gives us a definite name but doesn't tell us much else or give us any more control. Besides, [tt]CDxWindowImpl[/tt] doesn't yet include the [tt]DECLARE_WND_CLASS[/tt] macro. So what's the name of the class it's using? The answer to that last question can be found in a function called [tt]AtlModuleRegisterWndClassInfo()[/tt][sup]1[/sup] which is defined in [tt]atlwin.h[/tt]. A bit of code in that function assigns a (pseudo) random name to the window class if one is not specifically provided elsewhere[sup]2[/sup]. The answer to the first two questions lies in the [tt]DECLARE_WND_CLASS[/tt] macro itself, but first we need to cover a structure called [tt]CWndClassInfo[/tt].

    CWndClassInfo

    The [tt]CWndClassInfo[/tt] structure is used by the WTL libraries to store information needed to register a window class. Below are the members of [tt]CWndClassInfo[/tt][sup]1[/sup]. [listing 2.8] [code] WNDCLASSEX m_wc; LPCSTR m_lpszOrigName; WNDPROC pWndProc; LPCSTR m_lpszCursorID; BOOL m_bSystemCursor; ATOM m_atom; CHAR m_szAutoName[13]; ATOM Register(WNDPROC* p) { return AtlModuleRegisterWndClassInfo(&_Module, this, p); } [/code] As you can see the first member of [tt]CWndClassInfo[/tt] is a [tt]WNDCLASSEX[/tt] structure. This is the same structure passed to the Windows API function [tt]RegisterClassEx()[/tt]. The [tt]m_lpszOrigName[/tt] is used to support super classing. The [tt]pWndProc[/tt] is a pointer to the window procedure for the class. The [tt]m_lpszCursorID[/tt] and [tt]m_bSystemCursor[/tt] support the use of cursor resources. The [tt]m_atom[/tt] member stores the [tt]ATOM[/tt] value returned by [tt]RegisterClassEx()[/tt], and [tt]m_szAutoName[/tt] stores the pseudo random class name, if used. The sole method, [tt]Register()[/tt], registers the class defined by this structure by calling [tt]AtlModuleRegisterWndClassInfo()[/tt]. Super classing and using custom cursors won't be covered in this series[sup]3[/sup].

    DECLARE_WND_CLASS and DECLARE_WND_CLASS_EX

    The [tr]DECLARE_WND_CLASS[/tr] macro does nothing more than create a function that returns an instance of a [tt]CWndClassInfo[/tt] class [1]. Below is the definition of the [tt]DECLARE_WND_CLASS[/tt] macro. [listing 2.9] [code] #define DECLARE_WND_CLASS(WndClassName) \ static CWndClassInfo& GetWndClassInfo() \ { \ static CWndClassInfo wc = \ { \ { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, \ 0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL }, \ NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \ }; \ return wc; \ } [/code] As you can see the function created is a static one called [tt]GetWndClassInfo()[/tt]. The macro is a bit of a mess to look at but the class styles ([tt]CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS[/tt]) and background color ([tt]COLOR_WINDOW + 1[/tt]) jump right out. Since all the [tt]DECLARE_WND_CLASS[/tt] macro does is define a static function, we could omit the macro and define the function ourselves. By doing this we'd gain complete control over every item in the [tt]WNDCLASSEX[/tt] structure. Such a function might look like the one below, which changes the background color to black by using the helper function [tt]AtlGetStockBrush()[/tt][sup]4[/sup]. [listing 2.10] [code] static CWndClassInfo& GetWndClassInfo() { static CWndClassInfo wc = { {sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, 0, 0, NULL, NULL, NULL, AtlGetStockBrush(BLACK_BRUSH), NULL, GetWndClassName() , NULL}, NULL, NULL, IDC_ARROW, TRUE, 0, _T("") }; return wc; } [/code] But if all we'd like to do is change the background color and perhaps alter the class styles we may not need go though the trouble of creating our own [tt]GetWndClassInfo()[/tt] function. The WTL provides another macro called [tt]DECLARE_WND_CLASS_EX[/tt] which allows us to set these often customized properties. The definition of [tt]DECLARE_WND_CLASS_EX[/tt] is almost identical to [tt]DECLARE_WND_CLASS[/tt] except that is adds two additional parameters, [tt]style[/tt] and [tt]bkgnd[/tt], which as you might expect, set the class styles and background color. Unfortunately this macro, though worth mentioning, won't do. The value passed to [tt]bkgnd[/tt] must be one of the predefined brush constants like [tt]COLOR_WINDOW[/tt], [tt]COLOR_WINDOWTEXT[/tt], etc. Using [tt]AtlGetStockBrush()[/tt] here won't work, so we'll stick with defining our own [tt]GetWndClassInfo()[/tt] function. This has the added advantage of giving us complete control over the class.

    The GetWndClassName() Function

    One of the features I'd like for [tt]CDxWindowImpl[/tt] to have is the ability to detect and prevent multiple instances of itself. To do this we'll need to be able to easily change the window class name. Each project based on [tt]CDxWindowImpl[/tt] will need to use a unique class name. If two projects use the same class name there's a chance Game A would 'see' an instance of Game B as an instance of itself. We need a means of easily setting the class name, and there are a few options to choose from. We could simply require that each class that derives from [tt]CDxWindowImpl[/tt] define it's own [tt]GetWndClassInfo()[/tt] function. This would work well, but unless we need to change some item other than the class name it'd be nice to be able to use the [tt]GetWndClassInfo()[/tt] function inherited from [tt]CDxWindowImpl[/tt]. We could use [tt]#define[/tt] to assign a token to a string literal and use that inside [tt]GetWndClassInfo()[/tt], but I dislike this approach. Instead we'll override a static function inherited from [tt]CWindow[/tt] called [tt]GetWndClassName()[/tt]. [tt]CWindow[/tt]'s implementation of [tt]GetWndClassName()[/tt] simply returns [tt]NULL[/tt]. If this function is overridden to return a non-[tt]NULL[/tt] value that value will be used by the WTL library to name the window class. This approach also allows us to call [tt]GetWndClassName()[/tt] whenever we need the name of the window class. We can even use it in [tt]GetWndClassInfo()[/tt], and I do for the sake of clarity. Technically this isn't necessary so long as the class name used in [tt]GetWndClassInfo()[/tt] is [tt]NULL[/tt]. If not, the name set in [tt]GetWndClassInfo()[/tt] is used in place of the name returned by [tt]GetWndClassName()[/tt]. Let's give [tt]CDxWindowImpl[/tt] a definite class name and a black background color. We'll also add some code to prevent multiple instances of the same application from running on the same machine. Make the following changes to [tt]CDxWindowImpl.h[/tt]: [listing 2.11] [code] #ifndef __ATLWIN_H__ #error CDxWindowImpl.h requires atlwin.h to be included first #endif typedef CWinTraits CDxAppWinTraits; template class CDxWindowImpl : public CWindowImpl { public: static CWndClassInfo& GetWndClassInfo() { static CWndClassInfo wc = { { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc,0, 0, NULL, NULL, NULL, (AtlGetStockBrush(BLACK_BRUSH),NULL,GetWndClassName(), NULL }, NULL, NULL, IDC_ARROW, TRUE, 0, _T("") }; return wc; } static LPCTSTR GetWndClassName() { return _T("CDxWindowImpl"); } BOOL AllowMultipleInstances() { return false; } BOOL PreviousInstanceFound(LPCTSTR lpClassName, LPCTSTR lpWindowName) { HWND hwnd = FindWindow(lpClassName,lpWindowName); if(hwnd) { if(!T::AllowMultipleInstances()) { // Flash the existing window FLASHWINFO flashinfo; flashinfo.cbSize = sizeof(flashinfo); flashinfo.hwnd = hwnd; flashinfo.dwFlags = FLASHW_ALL; flashinfo.uCount = 2; flashinfo.dwTimeout = 0; FlashWindowEx(&flashinfo); } return true; } return false; } HWND Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName = NULL, DWORD dwStyle = 0, DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL) { if(PreviousInstanceFound(GetWndClassInfo().m_wc.lpszClassName,szWindowName) & !AllowMultipleInstances()) return NULL; HWND hwnd = CWindowImpl::Create(hWndParent, rcPos,szWindowName,dwStyle,dwExStyle,nID,lpCreateParam); return hwnd; } BEGIN_MSG_MAP(CDxAppWindow) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) END_MSG_MAP() LRESULT OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled ) { PostQuitMessage(0); bHandled = FALSE; return 0; } }; [/code] We now have functions for detecting and preventing multiple instances and a [tt]Create()[/tt] function to bring them all together. Each new project based on [tt]CDxWindowImpl[/tt] now just needs to return a different string from [tt]GetWndClassName()[/tt] to make the functionality work. And if you wish to allow multiple instances just change [tt]AllowMultipleInstances()[/tt] to return true. Let's add a [tt]GetWndClassName()[/tt] override to [tt]CDxAppWindow[/tt] in [tt]main.cpp[/tt]. [listing 2.12] [code] //... class CDxAppWindow : public CDxWindowImpl { public: static LPCTSTR GetWndClassName() { return _T("CDxAppWindow"); } BEGIN_MSG_MAP(CDxAppWindow) CHAIN_MSG_MAP(CDxWindowImpl) END_MSG_MAP() }; //... [/code]

    Full Screen and Back

    Another feature we'll want from our [tt]CDxWindowImpl[/tt] class is the ability to support both windowed and full screen applications. We'll also want the ability to switch between the two at runtime. I won't go into the details of how this is accomplished, it's just basic Windows API programming, but I'll list the functions here. Simply make the following changes to the [tt]CDxWindowImpl[/tt] class. [listing 2.13] [code] typedef CWinTraits CDxAppWinTraits; template class CDxWindowImpl : public CWindowImpl { public: // ... Some code left out to save space HWND Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName = NULL, DWORD dwStyle = 0, DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL) { if(PreviousInstanceFound(GetWndClassInfo().m_wc.lpszClassName,szWindowName) && !AllowMultipleInstances()) return NULL; HWND hwnd = CWindowImpl::Create(hWndParent, rcPos,szWindowName,dwStyle,dwExStyle,nID,lpCreateParam); if(!hwnd) return NULL; wndStyles = GetWindowLong(GWL_STYLE); wndExStyles = GetWindowLong(GWL_EXSTYLE); GetWindowRect(&wndRect); return hwnd; } BOOL FullScreen() { BOOL retval; // Save the styles and position of the window GetWindowRect(&wndRect); wndStyles = GetWindowLong(GWL_STYLE); wndExStyles = GetWindowLong(GWL_EXSTYLE); ShowWindow(SW_HIDE); SetWindowLong(GWL_STYLE,WS_POPUP); retval = SetWindowPos(HWND_TOPMOST,0,0,GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),SWP_SHOWWINDOW); ShowWindow(SW_SHOW); return retval; } BOOL Windowed() { BOOL retval; ShowWindow(SW_HIDE); SetWindowLong(GWL_STYLE,wndStyles); SetWindowLong(GWL_EXSTYLE,wndExStyles); retval = SetWindowPos(HWND_NOTOPMOST,&wndRect,SWP_DRAWFRAME); ShowWindow(SW_SHOW); return retval; } // ... More left out private: LONG wndStyles; LONG wndExStyles; RECT wndRect; }; [/code] It doesn't matter where in the class they're included so long as they're there. Basically [tt]FullScreen()[/tt] saves the styles and position of the window and [tt]Windowed()[/tt] restores them. If you need to reposition or resize the window use [tt]SetWindowPos()[/tt] or [tt]ResizeClient()[/tt]. The complete [tt]CDxWindowImpl[/tt] template class contains two message handlers not listed in this article. One stops screensavers and the other calls virtual functions when the application becomes active or inactive. The complete [tt]CDxWindowImpl[/tt] template class can be found in the accompanying source.

    Conclusion

    Well, that wraps up this installment. I expect we'll be able to finish up what's left in the next article, but it may stretch on to a fourth. You'll find in the source that accompanies this article two additional functions. They allow [tt]CDxWindowImpl[/tt] to provide window styles suitable for both windowed and full screen DirectX applications. You can even switch between the two at runtime. In the next article we'll finally get to message loops and idle processing, and also implement our own version of [tt]CAppModule[/tt] that'll initialize and create the Direct3D object. In the meantime I can be reached by emailing [email="rmack005@hotmail.com"]rmack005@hotmail.com[/email] or by posting a comment in this articles discussion forum.

    Acknowledgements

    I'd like to thank Ben "Kylotan" Sizer, Jim Nasche, and Ranthalion for the feedback they provided. Their comments were a great help. And again, I'd like to thank the staff at Gamedev.net for providing both a home and an audience for this article. Thanks again everyone.

    Notes

    [sup]1[/sup] Technically speaking, there are actually two version of a few of the WTL classes and functions, an ANSI version and a Unicode version. Here I remove the 'A' and 'W' suffixes for the sake of clarity since SomeClassOrFunction is SomeClassOrFunctionW if _UNICODE is defined or SomeClassOrFunctionA if it is not. [sup]2[/sup] For the curious: line 2933 for AtlModuleRegisterWndClassInfoA and line 2995 for AtlModuleRegisterWndClassInfoW. [sup]3[/sup] I may change my mind about super classing and cursors and write an article covering them. Their use would more likely be seen in game tools like map editors and such than in an actual game. If I get to them, they'll be in a supplemental article which would likely cover using the WTL to create game development tools. [sup]4[/sup] AtlGetStockBrush() checks to make sure the value passed is a valid brush type and casts the value returned by GetStockObject() to HBRUSH. You'll find it, along with a few other functions like it, in atlmisc.h.

    References

    [1] Michael Park, "ATL 3.0 Window Classes: An Introduction", MSDN. [url="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvc60/html/atlwindow.asp"]http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvc60/html/atlwindow.asp[/url]


      Report Article
    Sign in to follow this  


    User Feedback

    Create an account or sign in to leave a review

    You need to be a member in order to leave a review

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

    There are no reviews to display.