Introduction
Win32++ is a simple and easy to understand library for creating Windows applications. It runs on the commonly available free compilers, making it a free alternative to MFC.
Win32++ has been designed to make life a little easier for those learning to use C++ to program using the Windows API directly. Win32++ doesn't attempt to hide the Windows API. On the contrary, it exposes the Windows API, allowing it to be more easily learnt and understood. Win32++ is also a good choice for those professional programmers looking for a simple, robust and efficient framework.
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 Windows operating systems, from Windows 95 through to Windows 7. It can be used to create both 32 bit and 64 bit applications.
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 Windows 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 Windows 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 Windows 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 Windows API programs with a professional-looking user interface
With this in mind, I decided to revisit my Windows 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++:
The classes which define the Framework itself are contained within the Win32xx
namespace. These classes are as follows:
CBitmapInfoPt
: A class used to simplify the creation and use if the BITMAPINFO
structure for use with GDI graphics. CCmdbar
: A class used on Windows CE to provide a CommandBar
. It is used by CFrame
on Windows CE. CContainer
: A specialised view window for dockers. It has tabs and an optional toolbar. 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. CDocker
: A class which provides support for docking and splitter windows. 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. CListView
: A class used to create a list-view control. 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. CPoint
: This class can be used in place of a POINT structure. 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
. CRect
: This class can be used in place of a RECT structure. CSize
: This class can be used in place of a SIZE structure. CSocket
: This class adds network support to Win32++. CStatusBar
: The class responsible for creating the status bar. It is used by CFrame
. CTab
: A class used to create a tab control. CToolBar
: The class responsible for creating the toolbar. It is used by CFrame
. CTreeView
: A class used to create a tree-view control. 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 specialised window objects such as CDialog
, CFrame
, CToolbar
etc.
About the File Downloads
The file download from Sourceforge includes the following:
- The Win32++ library itself
- Help for the library
- A set of tutorials
- A collection of sample applications
The sample applications include:
Browser
- An Internet browser application based on ActiveX controls. Dialog
- An example of a simple dialog application. DialogDemo
- An interactive dialog application demonstrating slider controls and progress bars. DialogTab
- A dialog application with a tab control. DirectX
- A simple DirectX application. Dock
- An example of a simple docking application. DockContainer
- An example of a docking application which incorporates containers. DockTabbedMDI
- An example of a docking application with containers and a tabbed MDI. Explorer
- A Windows Explorer-like application. FastGDI
- An application which demonstrates direct manipulation of a bitmap's colour. FormDemo
- An example of a modeless dialog within a frame. MDIFrame
- A simple MDI frame applications MDIFrameDemo
- Demonstrates some additional 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. Simple
- Creates a simple window. Splitter
- A demo of the CSplitter
class. StaticLibrary
- Builds the Win32++ framework into a static library. TabDemo
- Demonstrates the use of a CTab
control in a frame. Themes
- Demonstrates how to customise the colours for rebar and toolbar controls. Threads
- Demonstrates multi-threaded Windows. WinCE samples
- A small collection of samples 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 Windows 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 included with the framework.
Object-oriented Approach
The key to bringing an object-oriented approach to programming directly with the Windows 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 Windows 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;
wc.hInstance = hInstance;
wc.lpszClassName = "TEST";
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
::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 Windows 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()
{
member = 5;
this->member = 5;
}
void static StaticFunction()
{
member = 5;
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();
}
}
Step 2: Store our this
pointer in the Thread Local Storage when we use CreateEx
to create the window:
TLSData* pTLSData = GetApp()->SetTlsIndex();
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:
TLSData* pTLSData = (TLSData*)TlsGetValue(GetApp()->GetTlsIndex());
w = pTLSData->pCWnd;
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 thread local storage and the window procedure, 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 )
{
try
{
if (0 == GetApp())
throw CWinException(_T("Win32++ has not been initialised properly.\n
Start the Win32++ by inheriting from CWinApp."));
if (::IsWindow(m_hWnd))
throw CWinException(_T("CWnd::CreateEx ... Window already exists"));
TCHAR ClassName[MAX_STRING_SIZE] = _T("");
if (0 == lstrlen(lpszClassName) )
lstrcpyn (ClassName, _T("Win32++ Window"), MAX_STRING_SIZE);
else
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)) throw CWinException(_T("CWnd::CreateEx Failed to register window class"));
TLSData* pTLSData = GetApp()->SetTlsIndex();
pTLSData->pCWnd = this;
m_hWnd = ::CreateWindowEx
(dwExStyle, ClassName, lpszWindowName, dwStyle, x, y, nWidth,
nHeight, hParent, hMenu, GetApp()->GetInstanceHandle(), lpParam);
if (!m_hWnd)
throw CWinException(_T("CWnd::CreateEx ... Failed to Create Window"));
m_hWndParent = hParent;
::GetClassInfo(GetApp()->GetInstanceHandle(), lpszClassName, &wc);
if (wc.lpfnWndProc != st_pfnWndProc)
{
Subclass();
::SendMessage(m_hWnd, WM_NULL, 0, 0);
OnCreate(); }
pTLSData->pCWnd = NULL;
OnInitialUpdate();
}
catch (const CWinException &e)
{
e.MessageBox();
}
return m_hWnd;
}
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)
{
return w->WndProc(hWnd, uMsg, wParam, lParam);
}
else
{
TLSData* pTLSData = (TLSData*)TlsGetValue(GetApp()->GetTlsIndex());
if (NULL == pTLSData)
throw CWinException(_T("CWnd::StaticCBTProc ... Unable to get TLS"));
w = pTLSData->pCWnd;
if (NULL == w)
throw CWinException(_T("CWnd::StaticWindowProc .. Failed to route message"));
pTLSData->pCWnd = NULL;
GetApp()->AddToMap(hWnd, w);
w->m_hWnd = hWnd;
return w->WndProc(hWnd, uMsg, wParam, lParam);
}
}
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)
{
return WndProcDefault(hWnd, uMsg, wParam, lParam);
}
The method described here uses a global map to associate a windows handle (HWND
) with a CWnd
object. This map uses Thread Local Storage (TLS) to ensure that the creation of the windows thread safe. If TLS wasn't used, attempts to create multiple windows simultaneously in different threads could fail. Using a map to associate a windows handle (HWND
) with a CWnd
object also allows every message for the window to be processed. There is no need, for example, to discard window messages prior to WM_NCCREATE
when using this method.
History
What's New
Win32++ uses CDocker to support docking and splitter windows. When docking, the undocked docker is dragged over another docker. Various visual clues such has the dock targeting (small arrow-like images), and the dock hinting (where a portion of the destination window turns blue) provide a hint as to where the docker will be docked. To facilitate undocking, the caption of the docked window is dragged and dropped.
Virtually any child window can be used as the view window for a docker. The CContainer
class provides a specialised view for dockers which has tabs, and also has an optional toolbar.
The CTabbedMDI
class adds support for tabbed MDIs. These behave much like a traditional Multiple Document Interface, but uses a tab control to manage the various child MDI windows.
Refer here for a detailed list of revision changes.
Reference Material