5,692,513 members and growing! (16,057 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Win32/64 SDK & OS » General     Intermediate License: The MIT License

Win32++: A Simple Alternative to MFC

By David Nash

A simple Windows Framework that beginners and intermediate programmers can use as an alternative to MFC. It makes learning to program for Windows easier.
VC6, VC7, VC7.1, VC8.0, C++Windows, .NET, .NET 1.1, NT4, Win2K, WinXP, Win2003VS.NET2003, VS6, Visual Studio, Dev

Posted: 3 Mar 2005
Updated: 7 Mar 2008
Views: 268,558
Bookmarked: 243 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
128 votes for this Article.
Popularity: 9.86 Rating: 4.68 out of 5
4 votes, 3.1%
1
4 votes, 3.1%
2
4 votes, 3.1%
3
10 votes, 7.9%
4
105 votes, 82.7%
5
Screenshot - Browser.gif

Introduction

Win32++ has been designed to make life a little easier for those learning to use C++ to program using the Win32 API directly. It is a simple alternative to MFC, but has the added advantage of running on a wide range of free compilers. Win32++ doesn't attempt to hide the Win32 API. On the contrary, it exposes the Win32 API, allowing it to be more easily learnt and understood. You have all the source code right there in front of you, so there is no mystery about how the Framework actually works.

The code has been designed to run on a wide range of C++ compilers, including those from Microsoft, Borland and the free MinGW compiler from GNU. Win32++ supports all Win32 operating systems, from Windows 95 through to Windows XP and Vista.

Win32++ also directly supports the Windows CE operating system. Windows CE is the operating system which runs on the various Pocket PCs, Smartphones, as well as industrial devices and embedded systems. The Windows CE API is a subset of the Win32 API. It also includes some new common controls tailored for the smaller computers and devices it runs on.

Win32++ brings an object oriented approach to programming directly with the Win32 API. Each window created is a C++ class object capable of having its own window procedure for routing messages.

Background

When I first approached the task of teaching myself to program Windows using C++, I took a brief look at some of the Win32 tutorials on the Web and then jumped straight into using MFC. I hoped that using MFC might make the task of learning Windows programming easier.

With the benefit of hindsight, I now realize that this approach was a mistake. I should have taken the time to study the Win32 API more thoroughly before moving on to MFC. It would have been far easier (and faster) to approach these two topics one at a time, instead of trying to learn both of them at once. In a sense, I should have learned to walk before trying to run. The two main challenges I faced when writing Win32 applications were:

  • Bringing an object-oriented approach to Win32 programs
  • Building Win32 API programs with a professional-looking user interface

With this in mind, I decided to revisit my Win32 API programming and develop a generic Framework for my applications that could be used as an alternative to MFC. My goal was to produce a Framework that was robust, object-oriented and that produced professional-looking results.

Framework Overview

The following diagram illustrates the classes used in Win32++:

ClassView.gif

The classes which define the Framework itself are contained within the Win32xx namespace. These classes are as follows:

  • CCmdbar: A class used on Windows CE to provide a CommandBar. It is used by CFrame on Windows CE.
  • CCriticalSection: This class provides for thread synchronization for multi-threaded applications.
  • CDC: A class which represents a Device Context. It simplifies working the Windows GDI.
  • CDialog: The class responsible for creating modal and modeless dialogs. It is used by CFrame, and can also be used to create dialog applications.
  • CFrame: This class produces a frame window which has a rebar, menubar, toolbar, and a status bar. The client area of the frame window should be occupied by a separate CWnd object.
  • CMDIApp: This class is inherited from CWinApp. You should inherit from this class to start an MDI frame application.
  • CMDIChild: This is the class to be used for MDI children. Each MDI child should be inherited from this class.
  • CMDIClient: This is a class used internally by Win32++ as the view window for the MDI frame.
  • CMDIFrame: This class is responsible for creating the MDI frame window. It is inherited from CFrame.
  • CMenubar: This class is responsible for creating the menubar. A menubar is a menu housed inside a rebar control.
  • CPropertyPage: This class adds support for property pages to Win32++. A property page has one or more property sheets.
  • CPropertySheet: This class represents a property page. It is used by CPropertySheet.
  • CRebar: This class is responsible for creating the rebar. It is used by CFrame.
  • CSocket: This class adds network support to Win32++.
  • CSplitter: This class can be used to provide a splitter window.
  • CStatusBar: The class responsible for creating the status bar. It is used by CFrame.
  • CToolBar: The class responsible for creating the toolbar. It is used by CFrame.
  • CWceFrame: A class which provides a simple frame for Pocket PCs. It utilises a Commandbar to display the menu and toolbar buttons.
  • CWinApp: The class responsible for initializing the Framework, and also provides our message loop. You should inherit from this class to start the Framework.
  • CWinException: A class which handles exceptions.
  • CWnd: The class responsible for the window objects. It is the base class for the more specialized window objects such as CDialog, CFrame, CToolbar etc.

About the File Downloads

Each of the files available for download contain an identical copy of the Win32++ Framework. The Generics.zip download is intended as the starting point for your own applications. This file contains all five generic start-ups:

  • Simple window
  • Dialog
  • Form
  • Frame
  • MDI frame

The Samples.zip download contains a collection of simple examples of how Win32++ might be used. It includes:

  • Browser - An Internet browser application based on ActiveX controls.
  • DlgSubclass - A dialog application with a sub-classed button and hyperlink.
  • DX - A simple DirectX application.
  • Explorer - A Windows Explorer-like application.
  • MDIDemo - Demonstrates some of the features of MDI frames.
  • Networking - Demonstrates the use of networking.
  • Notepad - A simple text editor with printing.
  • Performance - Measures Win32++'s message handling speed.
  • Picture - A simple picture rendering application.
  • PropertySheets - A demonstration of property sheets.
  • Scribble - A simple drawing application.
  • Splitter - A demo of the CSplitter class.
  • Subclass - Demonstrates sub-classing.
  • TabDialogDemo - A dialog with a tab control.
  • Themes - Demonstrates how to customise the colours for rebar and toolbar controls.
  • Threads - Demonstrates multi-threaded Windows.

The WceSamples.zip is a collection of programs for Windows CE.

Using the Framework

The code which forms the basis of the Framework is located in the Win32++ directory. You shouldn't need to modify these files, but rather inherit from Win32++ and add any additional code in your derived classes. To use the Framework to create an SDI frame window, for example, you would typically derive your own class from CFrame and place any modifications to the standard frame there. You can override the WndProc member function to include any additional messages you would like to handle.

A separate view window is placed over the client area of the frame window. Typically, this view window is created by inheriting a class from CWnd. The CFrame::SetView function is used to assign the view window to the frame. For MDI frames however, the CMDIFrame already uses CMDIClient as the view window, and you would use CMDIFrame::AddMDIChild to create a new instance of an MDI child window.

One of the important advantages of programming directly with the Win32 API is that the code produced is portable, which is to say that it can be compiled on different compilers. The code in this Framework has been checked for compatibility with Visual C++ 6.0, Visual Studio .NET 2003, Visual C++ 2008 Express Edition, and also Dev-C++ version 4.9.9.2. Dev-C++ is a free C++ compiler and Integrated Development Environment available for download from here. The Framework is also compatible with Visual C++ Toolkit 2003 (a free compiler from Microsoft) and Borland's free Turbo C++ 2006.

A tutorial which provides step by step instructions for using the Framework is available here.

Object-oriented Approach

The key to bringing an object-oriented approach to programming directly with the Win32 API is to have a C++ class that can create a window and which includes its own window procedure as a member function. Once we have this class, we can inherit from it and override the window procedure member function to handle messages the way we want for each derived window type.

Creating a class like this is not trivial and I suspect that's one of the reasons why MFC was created in the first place. The problem stems from the way a "window class" is registered before the window can be created. The term "class" here refers to the Win32 API "window class," which is not the same thing as a C++ class. The following code snippet shows how a window class might be registered using the API:

WNDCLASSEX wc = {0};

wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WindowProc;    //Window procedure function

wc.hInstance = hInstance;
wc.lpszClassName = "TEST";
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

//Register the window class
::RegisterClassEx(&wc);

Note that we need to supply the function name of our window procedure. The window procedure is where we control what is to be done when a window message is received. This function must conform precisely to the predefined standards required by the Win32 API. A typical declaration of the callback function looks like this:

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg,
    WPARAM wParam, LPARAM lParam);

We might be tempted to set the WindowProc function as a member of the class. Unfortunately, each class member function has an implicit this pointer as one of its arguments and therefore cannot be used as the callback function for a window. If we did this, our WindowProc function would no longer conform to the predefined standards and the program would fail to compile.

We can make the WindowProc function a static member function of the class. There is no implicit this in a static function and this will compile correctly. Unfortunately, a static member function doesn't have access to the class object (i.e. it doesn't have a this pointer) and it cannot access other members of the class. It is this that prevents the static member function from being used in an object-oriented way. The following code demonstrates the limitations of a static member function approach:

class TestStatic
{
public:
  int member;

  void NormalFunction()
  {
    //We can access member variables in a normal
    //member function
    member = 5;

    //The following line is equivalent to the one above
    this->member = 5;
  }

  void static StaticFunction()
  {
    //We cannot access member variables
    //in a static member function

    //The following line will give a compile error
    member = 5;

    //This will give an error too
    this->member = 5;
  }
};

A static member function for the window procedure would be useful if we could just get our hands on a pointer to the window class object (our this pointer). There are a number of techniques that we can use to get access to our pointer as the window is being created. The one I have chosen takes advantage of Thread Local Storage to store our pointer, which is later inserted into an STL map. This is how it's done:

Step 1: Set up the Thread Local Storage to store our this pointer. This is done in the CWinApp class:

CWinApp::CWinApp(HINSTANCE hInstance) : m_hInstance(hInstance)
{
    if (GetApp() == 0)
    {
        st_dwTlsIndex = ::TlsAlloc();

        //snip
    }
}

Step 2: Store our this pointer in the Thread Local Storage when we use CreateEx to create the window:

// Ensure this thread has the TLS index set
TLSData* pTLSData = GetApp()->SetTlsIndex();

// Store the CWnd pointer in thread local storage
pTLSData->pCWnd = this;

Step 3: Extract the pointer from Thread Local Storage and add it to the STL map during the initial creation of the window:

// Retrieve the pointer to the TLS Data
TLSData* pTLSData = (TLSData*)TlsGetValue(GetApp()->GetTlsIndex());

// Retrieve pointer to CWnd object from Thread Local Storage TLS
w = pTLSData->pCWnd;

// Store the CWnd pointer in the HWND map
GetApp()->AddToMap(hWnd, w);

return w->WndProc(hWnd, uMsg, wParam, lParam);

Step 4: For each subsequent window message, we extract the pointer from the STL map and use it to redirect the message handling to the appropriate WndProc function:

CWnd* w = GetApp()->GetCWndFromMap(hWnd);
return w->WndProc(hWnd, uMsg, wParam, lParam);

Window Creation in Detail

Now that we've had a look at the window procedure and CBT hooks, it is time to see how these fit together as we create the window. This is the code that creates the window:

HWND CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName,
                 DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hParent,
                 HMENU hMenu, LPVOID lpParam /*= NULL*/)
{
  try
  {
    // Test if Win32++ has been started
    if (0 == GetApp())
      throw CWinException(_T("Win32++ has not been initialised properly.\n
                   Start the Win32++ by inheriting from CWinApp."));

    // Only one window per CWnd instance allowed
    if (::IsWindow(m_hWnd))
      throw CWinException(_T("CWnd::CreateEx ... Window already exists"));

    // Ensure a window class is registered
    TCHAR ClassName[MAX_STRING_SIZE] = _T("");
    if (0 == lstrlen(lpszClassName) )
      lstrcpyn (ClassName, _T("Win32++ Window"), MAX_STRING_SIZE);
    else
      // Create our own local copy of szClassName.
      lstrcpyn(ClassName, lpszClassName, MAX_STRING_SIZE);

    WNDCLASS wc = {0};
    wc.lpszClassName = ClassName;
    wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
    wc.hCursor         = ::LoadCursor(NULL, IDC_ARROW);
    if (!RegisterClass(wc))    // Register the window class (if not already registered)
      throw CWinException(_T("CWnd::CreateEx  Failed to register window class"));

    // Ensure this thread has the TLS index set
    TLSData* pTLSData = GetApp()->SetTlsIndex();

    // Store the CWnd pointer in thread local storage
    pTLSData->pCWnd = this;

    // Create window
    m_hWnd = ::CreateWindowEx
                       (dwExStyle, ClassName, lpszWindowName, dwStyle, x, y, nWidth,
                       nHeight, hParent, hMenu, GetApp()->GetInstanceHandle(), lpParam);

    // Now handle window creation failure
    if (!m_hWnd)
      throw CWinException(_T("CWnd::CreateEx ... Failed to Create Window"));

    m_hWndParent = hParent;

    // Automatically subclass predefined window class types
    ::GetClassInfo(GetApp()->GetInstanceHandle(), lpszClassName, &wc);
    if (wc.lpfnWndProc != st_pfnWndProc)
    {
      Subclass();

      // Send a message to force the HWND to be added to the map
      ::SendMessage(m_hWnd, WM_NULL, 0, 0);

      OnCreate(); // We missed the WM_CREATE message, so call OnCreate now
    }

    // Clear the CWnd pointer from TLS
    pTLSData->pCWnd = NULL;

    // Window creation is complete. Now call OnInitialUpdate
    OnInitialUpdate();
  }

  catch (const CWinException &e)
  {
    e.MessageBox();
  }

  return m_hWnd;

} // HWND CWnd::CreateEx()

The next code segment handles the window procedure which first receives the messages. We extract the pointer to the CWnd object from the map, and use it to redirect the handling of the window messages to the appropriate WndProc function:

LRESULT CALLBACK CWnd::StaticWindowProc
    (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  try
  {
    CWnd* w = GetApp()->GetCWndFromMap(hWnd);
    if (0 != w)
    {
      // CWnd pointer found, so call the CWnd's WndProc
      return w->WndProc(hWnd, uMsg, wParam, lParam);
    }
    else
    {
      // The CWnd pointer wasn't found in the map, so add it now

      // Retrieve the pointer to the TLS Data
      TLSData* pTLSData = (TLSData*)TlsGetValue(GetApp()->GetTlsIndex());
      if (NULL == pTLSData)
        throw CWinException(_T("CWnd::StaticCBTProc ... Unable to get TLS"));

      // Retrieve pointer to CWnd object from Thread Local Storage TLS
      w = pTLSData->pCWnd;
      if (NULL == w)
        throw CWinException(_T("CWnd::StaticWindowProc .. Failed to route message"));

      pTLSData->pCWnd = NULL;

      // Store the CWnd pointer in the HWND map
      GetApp()->AddToMap(hWnd, w);

      // Store the HWND in the CWnd object early
      w->m_hWnd = hWnd;

      return w->WndProc(hWnd, uMsg, wParam, lParam);
    }
  }
//snip

Finally, the next code segment shows the function called by StaticWindowProc. Typically, when we derive a new class from CWnd, we would override this function to control the way various window messages are handled:

LRESULT CWnd::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  // Override this function in your class derived from CWnd to handle
  // window messages. A typical function might look like this:

  //  switch (uMsg)
  //  {
  //  case MESSAGE1:    // Some Win32 API message
  //    OnMessage1();   // A user defined function
  //    break;          // Also do default processing
  //  case MESSAGE2:
  //    OnMessage2();
  //    return x;       // Don't do default processing, but instead return
  //                    //  a value recommended by the Win32 API documentation
  //  }

  // Always pass unhandled messages on to WndProcDefault
  return WndProcDefault(hWnd, uMsg, wParam, lParam);
}

History

  • Mar, 2005: Version 1.0
    • Initial release
  • Apr, 2005: Version 2.0
    • Added CDialog
    • Added CWinApp
  • Dec, 2005: Version 3.0
    • Added tracing
    • Added support for Windows in different threads
  • Apr, 2006: Version 4.0
    • Used a map rather than the window's user data for storing the window's CWnd pointer. This allows the lpParam parameter to be used for user data when creating a window.
  • Dec, 2006: Version 5.0
    • Added CRebar, CMenubar, CSplitter
    • Added MDI frames support
    • Added Property Sheet support
    • Added message reflection
    • Added automatic subclassing
    • Added multilingual support
    • Added icons to menu icons
    • Added skinning for rebars, toolbars and menus
  • March, 2008: Version 6.0
    • Added Windows CE support
    • Added CSocket for network support
    • Added CDC to simplify using Windows Graphics Device Interface (GDI)
    • Enhanced the tutorial. It now also demonstrates file operations and printing.

    The latest version of Win32++ can be downloaded from the Win32++ Website.

What's New

The Windows CE operating system is now supported by Win32++. Windows CE is the operating system which runs on the various Pocket PCs, Smartphones, as well as industrial devices and embedded systems. The Windows CE API is a subset of the Win32 API, but also includes some new common controls tailored for the smaller computers and devices it runs on.

Win32++ now includes a CSocket class which encapsulates much of the Winsock API. This class is capable of monitoring network events, such as connection requests from clients, and notification that the socket has data ready to be received. Command line and dialog samples are provided to demonstrate how the class can be used to create TCP/IP (and UDP/IP) servers and clients.

Refer here for a detailed list of revision changes.

Reference Material

License

This article, along with any associated source code and files, is licensed under The MIT License

About the Author

David Nash


David worked in the computer industry for 20 years as a hardware and network specialist. He taught himself to program C++ in 2002, and continues to dabble in C++ as a hobbie.
Location: Australia Australia

Other popular Win32/64 SDK & OS articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 25 of 223 (Total in Forum: 223) (Refresh)FirstPrevNext
General[Message Removed]memberKatekortez9:57 25 Oct '08  
GeneralRe: [Message Removed]memberDavid Nash23:39 25 Oct '08  
QuestionChanging Theme/Visual Style on a Modal Dialog to Match MainFrame window which uses 2008 Feature Pack.memberkaren hinds2:10 17 Jul '08  
AnswerRe: Changing Theme/Visual Style on a Modal Dialog to Match MainFrame window which uses 2008 Feature Pack.memberDavid Nash15:18 17 Jul '08  
GeneralSplitter windowsmemberleonhorne22:15 14 Jul '08  
GeneralRe: Splitter windowsmemberDavid Nash15:46 15 Jul '08  
GeneralRe: Splitter windowsmemberDavid Nash3:31 25 Oct '08  
GeneralMaximising a window at start-upmemberleonhorne5:33 10 Jul '08  
GeneralRe: Maximising a window at start-upmemberDavid Nash15:59 10 Jul '08  
GeneralRe: Maximising a window at start-upmemberleonhorne0:47 11 Jul '08  
GeneralCan't link :-( [modified]membermaxmeiermintraching7:58 5 Jul '08  
GeneralRe: Can't link :-(memberDavid Nash15:28 5 Jul '08  
GeneralRe: Can't link :-(membermaxmeiermintraching23:34 5 Jul '08  
GeneralWindows themesmemberatali14:00 3 Jun '08  
GeneralRe: Windows themesmemberDavid Nash18:20 3 Jun '08  
GeneralRe: Windows themesmemberatali5:02 4 Jun '08  
GeneralRe: Windows themesmemberDavid Nash17:58 4 Jun '08  
GeneralExample of Tabbed Property Sheets?memberl_d_allan8:10 25 Mar '08  
GeneralRe: Example of Tabbed Property Sheets?memberDavid Nash1:39 27 Mar '08  
GeneralRe: Example of Tabbed Property Sheets?memberl_d_allan15:04 31 Mar '08  
QuestionHow to Create a Popup Menu with Themes?memberY___Y0:01 10 Jan '08  
GeneralFixed in later versions of Win32++memberDavid Nash