Click here to Skip to main content
15,029,333 members
Articles / Desktop Programming / Win32
Article
Posted 14 Feb 2013

Tagged as

Stats

14.9K views
334 downloads
15 bookmarked

DWinLib - The Guts

Rate me:
Please Sign up or sign in to vote.
4.94/5 (7 votes)
17 Jan 2021CPOL28 min read
A little about how things work behind the scenes in DWinLib!
In this article, you will get an overview of the internal workings of DWinLib, a semi-simple wrapper for the Windows API.

Index

  1. Introduction
  2. Simplest Window Wrapper
  3. Design Issues/Solutions
  4. Building DWinLib Programs
  5. Closing Thoughts
  6. History

Introduction

The following is an overview of the internal workings of DWinLib, a semi-simple wrapper for the Windows API. If you would like a higher-level overview, read DWinLib 6: Pretty WinAPI Incorporation.

DWinLib started life long ago because of two issues. First, my programming tool at the time (Borland Builder 4.0) became unstable as my project became bigger. Browsing forums, others had the same problem. Additionally, there was a bug somewhere in my code I couldn't figure out. I didn't know whether the framework or my programming was the issue. So I had to make a change, and the flakiness reported by others pushed me towards Visual Studio Express, which seemed to be the best free option at the time.

I don't believe that version of VS contained MFC, but even if it did, looking at MFC examples was frustrating. They were far from the clean code of Borland's VCL. I also didn't know whether I could fix my bug with that framework, so I decided to learn the underlying Window's API and overcome this doubt.

Happily, I succeeded in my quest, and the following is an overview of the framework I created along the way. I hope you find this writeup useful, even if only for intellectual curiousity.

Simplest Window Wrapper

The first item which had to be overcome is figuring out how to change a procedural Windows API program into an object-oriented one. Here's an example simple API program, with various steps pointed out:

C++
#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE hInst;

// **************************************
// Step 1: Create main program entrance:
// **************************************
int WINAPI WinMain(HINSTANCE instance, HINSTANCE , PSTR , int show) {
   // ***********************************
   // Step 2: Register the Window Class:
   // ***********************************
   static TCHAR appName[] = TEXT("MinApplication");
   hInst = instance;
   WNDCLASS wndclass;
   wndclass.style         = CS_HREDRAW | CS_VREDRAW;
   wndclass.lpfnWndProc   = WndProc;
   wndclass.cbClsExtra    = 0;
   wndclass.cbWndExtra    = 0;
   wndclass.hInstance     = instance;
   wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
   wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wndclass.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH);
   wndclass.lpszMenuName  = NULL;
   wndclass.lpszClassName = appName;

   if (!RegisterClass(&wndclass)) {
      MessageBox(NULL, TEXT("Oh no, Mr. Bill...."), appName, MB_ICONERROR);
      return 0;
      }

   // ***************************
   // Step 3: Create the window:
   // ***************************
   HWND hwnd = CreateWindowEx(WS_EX_APPWINDOW, appName, TEXT("MinApplication"),
               WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_OVERLAPPEDWINDOW | WS_BORDER,
               CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
               NULL, NULL, instance, NULL);

   ShowWindow(hwnd, show);
   UpdateWindow(hwnd);

   // ********************************
   // Step 4: Enter the message loop:
   // ********************************
   MSG msg;
   while (GetMessage(&msg, NULL, 0, 0)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
      }
   return msg.wParam;
   }


// ******************************************
// Step 5: Handle the messages from Windows:
// ******************************************
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
   switch (message) {
      case WM_DESTROY :
         PostQuitMessage(0);
         return 0;
      }
   return DefWindowProc(hwnd, message, wParam, lParam);
   }

One of the difficulties in programming a non-trivial application in the above manner is that a great deal of time is spent creating and managing the static WndProc procedures. It is also necessary to tie your application's logic and data into the WndProc procedures, and this can be quite a task for a non-trivial application.

Below is the previous program re-written using the same basic mechanism that DWinLib uses. It is almost four times larger than the preceding, code-wise, although it compiled to 52 KB vs. 48 KB for the non-wrapped version. It can be cut-and-pasted into a blank project and compiled as-is, although I believe you will have to change the Configuration Properties -> System -> Linker to 'Windows' in Visual Studio.

(Although it doesn't apply here, You will also have to change Configuration Properties -> C/C++ -> Language -> Conformance mode to 'No' to compile DWinLib programs.)

As you peruse the code, keep the following in mind:

The code works by creating an Application unit to hold the main static callback. A BaseWin unit is defined as a virtual base class from which to derive actual windows. (No member function screams out to be made into a pure virtual, therefore only common sense is to keep you from instantiating a BaseWin object.)

When a BaseWin object is instantiated, the BaseWin constructor calls the Application to registers itself. The classes derived from BaseWin override any BaseWin functions required, in order to handle the Windows specific messages (WM_PAINT, etc.) in a manner you desire. You can also add functions of your own to the derived classes, to handle your own logic unrelated to the window logic if you need to.

In this example, only a MainWin unit is derived from BaseWin. A MainWin equivalent named MainAppWin is a required unit within DWinLib applications, and contains the main window to your application. In a non-trivial application, you may have many more windows derived from BaseWin.

All in all, this is a simple approach, and it works well.

You will note that the same five steps have been commented as were previously pointed out, although there are a couple more comments thrown in.

You will also note that the fifth step has been broken into three parts. The first part (5 - just as in the previous code) is a static message handler that is part of the Application unit. The second part (5a) is a non-static, member function of the BaseWin unit, used to define default handlers for the Window messages. The third part (5b) overrides any window message handlers for specific use by your window, when the default handler will not do the task you want it to.

The other comments are thrown in to indicate program structure unrelated to the outlined steps.

C++
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0500
#define _WIN32_IE 0x0400

#include <tchar.h>
#include <windows.h>
#include <stdlib.h>

class BaseWin;

// *************************************************************************
// Define an 'Application' class to handle the main static window procedure.
// There will only be one of these instantiated in the program as a global.
// *************************************************************************
class Application {
   private:
      static BaseWin * winBeingCreatedC;
      HINSTANCE hInstC;
   public:
      Application(HINSTANCE hInst_, HINSTANCE, LPSTR, int);
 
      LRESULT static CALLBACK WindowProc(HWND window, unsigned int msg,
                  unsigned int wParam, long lParam);
      HINSTANCE hInst() { return hInstC; }
      void   setWinBeingCreated(BaseWin * win) { winBeingCreatedC = win; }
      WPARAM run();
   };


// **********************************************************
// Define a base window class for deriving real windows from
// **********************************************************
class BaseWin {
   //For dealing with the window handle and window procedure
      protected:
         HWND hwndC;
      public:
         BaseWin();
         ~BaseWin();
         
         //winProc can be overridden for each window, but it is easiest to just
         //override the individual functions.
         virtual LRESULT winProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam);
         HWND hwnd() { return hwndC; }
         
         void hwnd(HWND hwnd) { hwndC = hwnd; } //Only needed by Application::WindowProc

   //Routines to be inherited by derived windows
      public:
         virtual LRESULT wClose() { return DefWindowProc(hwndC, WM_CLOSE, 0, 0); }
   };


BaseWin * Application::winBeingCreatedC = NULL;
Application * gApplication; //A Global Application object for the entire program


// ************************************
// Now fill in the BaseWin definition
// ************************************
BaseWin::BaseWin() : hwndC(NULL) {
   gApplication->setWinBeingCreated(this);
   }


BaseWin::~BaseWin() {
   if (IsWindow(hwndC) == TRUE) DestroyWindow(hwndC);
   }
   
   
// ***************************************************************************
// Step 5a: Handle the messages from Windows in a non-static member function:
// ***************************************************************************
LRESULT BaseWin::winProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) {
   switch (msg) {
      case WM_CLOSE:
         wClose();
         return 0;
      }
   return DefWindowProc(window, msg, wParam, lParam);
   }


// ***************************************************
// Now define and fill in the guts to a 'real' window
// that uses the above BaseWin definitions.
// ***************************************************
class MainWin : public BaseWin {
   //Regular class stuff
      private:
         void registerWinClass();

      public:
         MainWin();
         
   //BaseWin overrides:
      public:
         virtual LRESULT wClose();
   };


namespace { //An unnamed namespace for this unit to use
   const TCHAR * winCaption = _T("Bare Windows Wrapper Example");
   const TCHAR * winClassName = _T("BareWindowApp");
   }


// ***********************************
// Step 2: Register the Window Class:
// ***********************************
void MainWin::registerWinClass() {
   WNDCLASS wc;
   wc.style         = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc   = gApplication->WindowProc;
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = 0;
   wc.hInstance     = gApplication->hInst();
   wc.hIcon         = NULL;
   wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH)GetStockObject (GRAY_BRUSH);
   wc.lpszMenuName  = NULL;
   wc.lpszClassName = winClassName;
   if (!RegisterClass (&wc)) abort();
   }
 

MainWin::MainWin() {
   static bool registered = false;
   if (!registered) {
      registerWinClass();
      registered = true;
      }

   // ***************************
   // Step 3: Create the window:
   // ***************************
   hwndC = CreateWindow(winClassName, winCaption, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
               CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, gApplication->hInst(), 
               NULL);
   if (!hwndC) abort();

   ShowWindow(hwndC, SW_SHOW);
   UpdateWindow(hwndC);
   }


// *************************************************
// Step 5b: Override any message handlers you want
// to handle differently than the BaseWin handler:
// *************************************************
LRESULT MainWin::wClose() {
   PostQuitMessage(0);
   return DefWindowProc(hwndC, WM_CLOSE, 0, 0);
   }
 

// **********************************
// Now fill in the Application definition
// **********************************
Application::Application(HINSTANCE hInst_, HINSTANCE, LPSTR, int) :
            hInstC(hInst_) {

   gApplication = this;
   }
 

WPARAM Application::run() {
   MainWin win;
   // ********************************
   // Step 4: Enter the message loop:
   // ********************************
   MSG msg;
   while (GetMessage(&msg, NULL, 0, 0)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
      }
   return msg.wParam;
   } 


// ******************************************
// Step 5: Handle the messages from Windows:
// ******************************************
LRESULT CALLBACK Application::WindowProc(HWND window, unsigned int msg, unsigned int wParam,
            long lParam) {

   BaseWin * win = reinterpret_cast<BaseWin*>(GetWindowLong(window, GWL_USERDATA));
   if (win) return win->winProc(window, msg, wParam, lParam);
   SetWindowLong(window, GWL_USERDATA, reinterpret_cast<long>(winBeingCreatedC));
   winBeingCreatedC->hwnd(window);
   return winBeingCreatedC->winProc(window, msg, wParam, lParam);
   }


// ***************************************************************
// And finally, a WinMain that takes advantage of the above work:
// ***************************************************************
// **************************************
// Step 1: Create main program entrance:
// **************************************
int WINAPI WinMain(HINSTANCE inst1, HINSTANCE inst2, LPSTR str, int show) {
   Application app(inst1, inst2, str, show);
   return app.run();
   }

The above code is skeletal; error handling is very minimal and all extraneous messages are ignored. The previous logic is just enough to show how a basic Windows wrapper can be created.

One item to pay attention to is how the window class instance is stored during window creation. As I stated before the second code block, the BaseWin's constructor tells the global Application object that it is being constructed. If you examine the Application::WindowProc function, you will see how this is actually accomplished, and how the incoming Window messages are routed to the appropriate member function. DWinLib handles this slightly differently, as will be seen below.

Without a wrapper of some nature, it can be quite painful to tie your application's logic and data into the WndProc procedures of a pure API program. Using the previous framework, it is no longer difficult: just put it in the class definition of the window derived from BaseWin and use it as you would use data in any C++ class. The application logic also flows much easier in a program written using a wrapper, if you are conversant in object oriented techniques.

If you want to see the above method in action, you can cut-and-paste the previous code into a blank project. If you would like to see an example that is more fun, here is a Tic-Tac-Toe example you can tear apart. The program is significantly flushed out from the above code, in that many more messages are handled, exception handling has been included, and it is not as trivial as the above code. It also wraps a couple of Windows Common Controls in a very clean manner:

Image 1

Even though this is the framework that DWinLib is based upon at its core, DWinLib is far advanced from the preceding code. For example, here is the current run() function:

C++
WPARAM dwl::Application::run() {
   MSG msg;
   int var;
   try {
      #ifdef DWL_MDI_APP
         HWND mdiClient = gDwlGlobals->dwlMainWin->mdiClientHwnd();
         HWND hwnd = gDwlGlobals->dwlMainWin->hwnd();

         //Per Microsoft, all exceptions must be handled before returning control back to
         //this message pump.  For more info, read the last part of the article:
         //http://www.microsoft.com/msj/archive/S204D.aspx.  See DwlBaseApp::WndProc for the
         //exception handler implemented for DWinLib.
      
         accelTableC = accelC.table(); //The app can change this while running by calling
         while ((var = GetMessage(&msg, NULL, 0, 0)) != 0) {      //changeAccelTable(...)
            if (var == -1) return var;
            if (!TranslateMDISysAccel(mdiClient, &msg) &&
                        !TranslateAccelerator(hwnd, accelTableC, &msg)) {
               TranslateMessage(&msg);
               DispatchMessage(&msg);
               if (!::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE)) idlerC.wIdle();
               }
            }
      #else
         accelTableC = accelC.table();
         while ((var = GetMessage(&msg, NULL, 0, 0)) != 0) {
            if (var == -1) return var;
            if (!TranslateAccelerator(gDwlGlobals->dwlMainWin->hwnd(), accelTableC, &msg)) {
               TranslateMessage(&msg);
               DispatchMessage(&msg);
               //Now check if we should go into idle processing:
               if (!::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE)) {
                  idleC = true;
                  //Do the idle processing:
                  while (idleC) idleC = wIdle();
                  }
               }
            }
         #endif
      }
   catch (Exception & e) {
      wString str = dwl::strings::msgPleaseReport();
      str += L"\n" + dwl::strings::msgProgramming() + L"\n" + e.strC;
      if (e.continuableC == Continuable::True)
         str += L"\n" + dwl::strings::msgWillAttemptContinue();
      else str += L"\n" + dwl::strings::msgProgramMustExit();
      MessageBox(gDwlGlobals->dwlMainWin->hwnd(), str.c_str(),
                  dwl::strings::msgError().c_str(), MB_OK);
      if (e.continuableC == Continuable::False) exit(EXIT_FAILURE);
      }
   catch (std::exception & e) {
      wString str = dwl::strings::msgPleaseReport();
      str += _T("\r\n");
      str += dwl::strings::stdException();
      str += _T("\r\n");
      str += dwl::strings::msgError();
      str += utils::strings::convertToApiString(e.what());
      str += _T("\r\n");
      str += dwl::strings::stdExceptionAbortQuery();
      int wish = MessageBox(gDwlGlobals->dwlMainWin->hwnd(),
                  str.c_str(), dwl::strings::msgError().c_str(), MB_YESNO);
      if (wish == IDYES) exit(EXIT_FAILURE);
      }
   catch (...) {
      wString str = dwl::strings::msgUnknownException();
      str += dwl::lastSysError();
      str += dwl::strings::msgUnknownExceptionAbortQuery();
      HWND parent = gDwlGlobals->dwlMainWin ? gDwlGlobals->dwlMainWin->hwnd() : NULL;
      int wish = MessageBox(gDwlGlobals->dwlMainWin->hwnd(), str.c_str(), 
                  dwl::strings::msgError().c_str(), MB_YESNO);
      if (wish == IDYES) exit(EXIT_FAILURE);
      }
   return msg.wParam;
   }

As you can see, it handles Single Document Interface (SDI) and Multiple Document Interface (MDI) programs via preprocessor macros. It also interacts with a program-wide Accelerator Table unit (which responds to things like 'Ctrl + O' for the 'O'pen command). An 'idle' mechanism is built in, error handling is included, and it uses namespaces.

Design Issues/Solutions

For reference, here is an abbreviated illustration of the bigger design, if you want to refer back to it. All of these objects except MainAppWin are in the dwl namespace:

DWinLib Class View

Window Lookup

The first issue worth mentioning is thanks to David Nash's great Win32++ article, DWinLib eliminated the GetWindowLong/SetWindowLong approach laid out above and replaced it with a Threaded Local Storage index approach, in conjunction with an application map to store and lookup the individual windows. Of course, such an endeavor is never straightforward, and if you compare his to the following, you will notice some changes.

C++
LRESULT CALLBACK dwl::Application::winProc(HWND window, UINT msg, WPARAM wParam,
            LPARAM lParam) {
   try {
      BaseWin * win(nullptr);
      {  //Scope the iterator to eliminate the possibility that the windowproc
         //deletes the window and causes the iterator to become invalid,
         //which will crash the program.
         auto it = gDwlGlobals->dwlApp->windowsC.find(window);
         if (it != gDwlGlobals->dwlApp->windowsC.end()) win = it->second;
         }
      if (win) return win->winProc(window, msg, wParam, lParam);
      else {
         BaseWin * tempWin =
                     static_cast<BaseWin*>(TlsGetValue(gDwlGlobals->dwlApp->tlsIndexC));
         gDwlGlobals->dwlApp->windowsC.insert(std::make_pair(window, tempWin));
         return tempWin->winProc(window, msg, wParam, lParam);
         }
      }
   //exception handling...
   }

The TLS storage is set up in the createWindow calls, which all DWinLib windows should be created through unless you make certain the logic is somehow replicated in your own code:

C++
HWND dwl::Application::createWindow(BaseWin * winBeingCreated, const CREATESTRUCT & cs) {
   TlsSetValue(tlsIndexC, reinterpret_cast<LPVOID>(winBeingCreated));
   HWND ret = CreateWindowEx(cs.dwExStyle, cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y,
                         cs.cx, cs.cy, cs.hwndParent, cs.hMenu, cs.hInstance,
                         cs.lpCreateParams);
   if (ret == NULL) {
      MessageBox(NULL, dwl::lastSysError().c_str(), L"Error to report:", MB_OK);
      }
   return ret;
   }


HWND dwl::Application::createMdiWindow(BaseWin * winBeingCreated, const CREATESTRUCT & cs) {
   TlsSetValue(tlsIndexC, reinterpret_cast<LPVOID>(winBeingCreated));
   HWND ret = CreateMDIWindow(cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
               cs.hwndParent, cs.hInstance, NULL);
   if (ret == NULL) {
      MessageBox(NULL, dwl::lastSysError().c_str(), L"Error to report:", MB_OK);
      }
   return ret;
   }

Menus

One of the biggest pains in creating DWinLib was wrapping the Window's menu API. Microsoft's engineers created a recursive monstrosity which nobody in their right mind would want to delve into. But they work, and work better than the 'ribbon' system, which I don't care for. (Ribbons make things harder to find, not easier, unless you only have one ‘ribbon’ of features for your entire program. The background code is probably much cleaner, though, which may be the real reason MS deprecated menus.)

Basically, DwlMenu is a thin wrapper around the Windows’ menu functions. If you are interested in that awkwardness, feel free to poke inside ‘DwlMenu.cpp’ and its header. You can also see it in action by digging into the StupidSquares example code, down below.

Exceptions

Before the class overview diagram, I mentioned error handling, and this is a good point to repeat some information given in another article. Initially, DWinLib created everything in constructors, because of my C++ experiences. But they never triggered correctly.

During my incorporation of Francisco Campos SWC framework I noticed that he used a two-step creation process. Constructors never did anything that threw, and the actual creation step was performed in an instantiate method, or equivalent. This triggered the realization that my original method was causing more exceptions to occur during the stack unwinding process, which blew things to kingdom come. Looking at MFC, I believe it also used a two-step process. Because of my experiences I finally realized the reason for that arrangement, and modified DWinLib to do the same.

Another item to mention about exceptions is they don't cross the barrier between the WindowProc and the Application::run() function. DispatchMesage will not 'catch' exceptions in the message handler. You must catch them there, although I believe I've kept a try ... catch block in the run() function, which is probably unnecessary.

SDI/MDI Issues

There aren't really any 'issues,' per se, just an item to be aware of.

If you create an SDI application, you have direct control of the main application window via the hwndC of the main window. For MDI programs, DWinLib instantiates an MDI client window which you will need to paint, instead of the main application hwndC. You can step through the logic in the example MainAppWin unit.

Associating Windows Messages to Specific Windows

The next obstacle to overcome is linking messages to the correct window. DWinLib’s solution is slightly different than other wrappers I've seen.

As witnessed above, the actual forwarding occurs in the static Application::WndProc (or winProc in DWinLib as it currently stands). This is the main callback for all your program’s windows. (Remember: Standard Windows controls possess their own procedure, so they don’t call into Application::winProc without additional work. And creating such code isn’t recommended.) The magic line in the previous winProc function, which routes the message to the appropriate window within the program, is “if (win) return win->winProc(window, msg, wParam, lParam);”.

At that point, the executable accesses the vtable for the correct dwl::BaseWin object and executes its winProc procedure, sending along the message parameters.

Here is one area where DWinLib shines in comparison to some wrappers. Those messages are ‘unwrapped’ in the winProc procedures, making the code to handle them much simpler.

The trick behind this is simply a bunch of tedium. This snippet illustrates the technique:

C++
LRESULT BaseWin::winProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) {
   //Per Microsoft, all exceptions must be handled before returning control back to
   //the message pump.  For some information on this, see the bottom part of the article:
   //http://www.microsoft.com/msj/archive/S204D.aspx
   try {
      wParamC = wParam; //The windows derived from BaseWin can access the most current
      lParamC = lParam; //wParam and lParam variables through these class members
      msgC = msg;

      switch (msg) {
         case WM_ACTIVATE:
            return wActivate(LOWORD(wParam), HIWORD(wParam), (HWND)lParam);
         case WM_CHAR:
            return wChar(wParam, lParam);
         case WM_CLOSE:
            return wClose();
         case WM_COMMAND:
            return wCommand(HIWORD(wParam), LOWORD(wParam), (HWND)lParam);
         //...
         case WM_LBUTTONDOWN:
            return wMouseDown(Button::Left, wParam, Point(GET_X_LPARAM(lParam),
                        GET_Y_LPARAM(lParam)));
         //...

Then, wMouseDown is simply defined as:

C++
virtual LRESULT wMouseDown(Button button, WPARAM flags, Point p);

Eureka! You no longer need to write your handlers using the arcane ‘LOWORD’ and ‘HIWORD’ syntax. Yay! ‘LeftButton’ sure beats ‘wParam’!

I suppose I should say a brief word (or three) about Hungarian notation. I hate it.

You might be getting a different impression from the previous code. But those who understand Hungarian know the ‘w’ in front of ‘MouseDown’ isn’t being used in a Hungarian manner.

Instead, it is only stating the function handles a ‘Windows’ message. The letter can also indicate the attached variable is tightly linked to Windows. To see the reason why, take a look at all the messages handled in dwl::BaseWin. Try to imagine the appearance of your own code without a convention to set those functions apart. You would have an ‘enable’ routine floating alone in space, with very little information to convey purpose. Heaven forbid you want to later use a logical name like ‘enable’ for something else.

Even though DWinLib is wrapped in a ‘dwl’ namespace, placing a 'using' statement at the top of the .cpp file once again obfuscates things, so I kept the prepended 'w's for clarity.

My other standard is capital ‘C’s at the end of class member variable names. I agree with many coders that pointing these out is a necessity, but the prepended ‘m_’ is just an atrocious-looking monstrosity, and more difficult to type than a ‘C.’ Also, the member variableness isn’t so important it must come before the meaning. In other words, my mind groks “I encapsulate the idea of wParam, and I just happen to be a member variable” better then “I’m a member variable! which encapsulates the idea of wParam.” (The bolding is an attempt to show the ‘m_’ jumps out that way to me, and drowns out the primary information.) But maybe my grey cells work differently other people’s. Regardless, the main item is the logic gets written, and is functional.

Of course, then the question becomes “Why doesn’t the ‘w’ come after the variable name?” That is probably a relic of Hungarian notation influence. When I adopted this convention, I hadn’t thought through the issue. I also use a ‘g’ to indicate ‘globals,’ and with only those two prefixes, the code seems clean to me. Perhaps someday, I will move all these to the end of the word, but I must admit I like the alphabetization aspect to the current scheme, so maybe I won’t.

Responding to User Input - Callbacks

Let us return to code, rather than theory. We have seen how messages get dispatched to the appropriate classes, and the mechanism used to unpack WPARAM and LPARAM, to simplify the programming task. The next item to peruse is DWinLib’s callback feature. When you press a button in your program, this is the engine which routes the event to the approprate handler.

With or without a wrapper, the button’s parent window receives a WM_COMMAND message containing the ID of the control. In plain API code, that number must be assigned by you or your resource editor. DWinLib handles them behind the scenes, and you seldom have to think about IDs.

In Win API code, just test for the ID value in the WM_COMMAND logic and respond appropriately. Pretty simple, except when the codebase gets big, the entire process is orders of magnitude beyond cumbersome.

DWinLib vastly simplifies this part of the task. Buttons and other control items are wrapped in classes deriving from dwl::ControlWin. That class contains a dwl::CallbackItem as a member. Whenever one of these objects is created, it registers itself with a program-wide CallbackList instantiated in the DwlBaseApp unit. Upon doing so, it receives a control ID. You don’t need to think about these numbers any more.

The solution to the next part of the problem is heavily influenced by Borland Builder. In it, to make a button call into a class instance, just assign an ‘OnClick’ handler and you are done. Except for the actual coding of the function.

When I originally ported my program to resolve the bug I witnessed, I wanted something similar to Builder’s simple and easy-to-use callback mechanism so I didn’t have to totally redo this aspect of the processing along with everything else. Non-portable extensions, such as their ‘__closure’ (or whatever the term was) were unacceptable.

My first solution involved static functions and base class pointers, and I’m proud to say I got the design working, but I’m honest enough to admit it wasn’t very pretty. I’ve forgotten the details, but the method of non-static lookup, although doable, was not something I will expose your innocent eyes to.

Actually, pulling up some old code, it’s not quite as bad as I’m acting. I’ve changed my mind - your eyes can handle it. Here’s the beginning of the ‘redo’ process in some code from long ago:

C++
void MainWin::redo(WinObject * ) {  //This is a static function
   Performance * perf(gWinMain->perfKeeperC->activePerf());
   if (perf) perf->redo();
   }

If a button press was being responded to, the ‘WinObject’ pointer could be dynamic_casted to a DwlButton, and if I needed additional items the button had a void * ‘user’ member variable to hold or point to whatever I wanted. I never liked the void * and static_casting aspects which were involved, but the combination solved my problems in the shortest amount of coding.

Eventually, I discovered Sergey Ryazanov’s Impossibly Fast C++ Delegates, and after pondering them for a bit, figured out they would at last allow users to directly call back into an instantiated class without going through static functions and accessing a global pointer to initiate the actual function lookup. Three days later, and one false start, and this change worked on BCB, Dev-C++, and VS.

The solution involved creating a delegate which looked like the following:

C++
namespace sr { //'sr' for 'Sergey Ryazanov', of course!  (Even though I've made a few
               //modifications to the class)

   //There is also a plain 'Dwlegate' 
   //in DWinLib that doesn't require a <code>dwl::Object*</code>
   //as an argument, which is why I've kept the 'Dwl' prefix here.
   class DwlDelegate {
      private:
         #ifdef DWINLIB_C11_ENABLED
            std::function<void (dwl::Object *)> funcC;
            #endif
         typedef void (*MemberFunctionAddress)(void* instanceOfObject, dwl::Object * arg);
         MemberFunctionAddress functionAddressC;

         void        * instantiatedObjectC;
         dwl::Object * userDwlObjC;

         template <class T, void (T::*Method)(dwl::Object*)>
         static void delegateFor(void* instantiatedObject, dwl::Object * userDwlVariable) {
            T* p = static_cast<T*>(instantiatedObject);
            return (p->*Method)(userDwlVariable);
            }
      public:
         DwlDelegate() : instantiatedObjectC(0), userDwlObjC(0), functionAddressC(0), 
                     #ifdef DWINLIB_C11_ENABLED
                        funcC(nullptr)
                        #endif
                     { }
         #ifdef DWINLIB_C11_ENABLED
            DwlDelegate(const std::function<void (dwl::Object *)> & func, dwl::Object * obj) :
                        funcC(func), userDwlObjC(obj), functionAddressC(0),
                        instantiatedObjectC(nullptr) { }
            #endif

         ~DwlDelegate() { }

         template <class T, void (T::*Method)(dwl::Object*)>
         static DwlDelegate create(T* instantiatedObject, dwl::Object * userDwlVariable) {
            DwlDelegate d;
            d.functionAddressC = &delegateFor<T, Method>;
            d.instantiatedObjectC = instantiatedObject;
            d.userDwlObjC = userDwlVariable;
            return d;
            }

         void operator()() const {
            #ifdef DWINLIB_C11_ENABLED
               if (funcC) return funcC(userDwlObjC);
               else return (*functionAddressC)(instantiatedObjectC, userDwlObjC);
            #else
               return (*functionAddressC)(instantiatedObjectC, userDwlObjC);
               #endif
            }

         void object(dwl::Object * p) { userDwlObjC = p; }
         void func(const std::function<void(dwl::Object*)> & func, dwl::Object * obj) {
            funcC = func;
            userDwlObjC = obj;
            }
         bool callable() { return instantiatedObjectC || funcC; }
      };

   }

This code is fairly close to Sergey’s original design stripped to its bare essentials. It is far from Sergey’s complete work, though, which allows multi-signature delegates to be created without change to the codebase. BCB 4.0 could not compile the entirety of Sergey’s masterful creation. The modifications to the above allow you to make a delegate to a function with a signature of "void SomeClass::Function(dwl::Object * sender);".

In addition to the Delegate class, it was necessary to create a CallbackList, which stores a list of control IDs and the delegate associated with each of them.

When a control is created, it contains a CallbackItem, which ‘gets’ the next available ID number from the CallbackList. The CallbackItem also handles registering and unregistering the control from the CallbackList.

As somewhat of a side note, if you ever need an algorithm to dole out numbers, keep track of the assigned ones, and reassign those which are no longer in use, take a look at the processing behind CallbackList::insert and CallbackList::remove. I don’t guarantee that is the fastest or best method available, but it has not given me any problems so far.

In addition to this, BaseWin windows received the following wCommand processor:

C++
LRESULT BaseWin::wCommand(WORD notifyCode, WORD ctrlId, HWND hwnd) {   //ctrlHWND
   //Sometimes, during creation of a control, Windows sends WM_COMMANDS with 'notifyCode'
   //== 1024 and other values.  What these are doing is unknown, although it is suspected
   //that they are 'commanding' the parent window to register this control in it's list
   //of children inside of Windows itself.
   if ((notifyCode == 0 || notifyCode == 1) && ctrlId < DWL_MDI_FIRSTWIN) {
      if (gDwlGlobals->dwlApp->winCallBackListC.hasHandlerFor(ctrlId)) {
         gDwlGlobals->dwlApp->winCallBackListC.performCallBack(ctrlId);
         return 0;
         }
      return DefWindowProc(hwndC, WM_COMMAND, (notifyCode<<16)|ctrlId, (LRESULT)hwnd);
      }
   else {
      #ifdef DWL_MDI_APP
         if (wUseDefProcInMdiC) return DefWindowProc(hwndC, msgC, wParamC, lParamC);
         return DefFrameProc(hwndC, gDwlGlobals->dwlMainWin->mdiClientHwnd(), msgC, wParamC,
                     lParamC);
      #else
         return 0;
         #endif
      }
   }

You can see that when one of your windows derived from this class is called by Windows, the WinCallbackList is automatically called. As you may surmise, it is not a good idea to override or change this wCommand procedure until you know what you are doing.

Delving deeper into the design, the CallbackList::performCallback looks up the Delegate associated to the incoming control ID, and tells the delegate to execute itself.

The sample application shows how all of this works, but let me take a brief moment to pull some of the code pertaining to buttons into this write-up. Hopefully, what the programmer needs to do will become clear.

[Realizes the program doesn’t contain a button, so works furiously, fixing the issue. And, just for good measure, the button’s ‘user’ variable has been ‘examplified’.]

C++
//First, add a button to ObjViewWin.h, and add a function matching the default
//callback signature to the class.  Technically, the callback can be in another class,
//although you need to ensure an instantiated object pointer to that class is passed
//into the Delegate.

class ObjViewWin : public dwl::DockWindow {
   private:
      //...
      std::unique_ptr<dwl::Button>  buttonC;

   public:
      //...
      void onButtonClick(dwl::Object * obj);
      //...

//Then, in the .cpp file, initialize the button in the constructor with:

   //...
   buttonC.reset(new dwl::Button
   (this, CreateDwlDelegate(ObjViewWin, onButtonClick, this, this)));
   //...

//And instantiate it in 'instantiate':
void ObjViewWin::instantiate(dwl::DockManager * dockManager, 
     dwl::DockState state, dwl::DockEdge edge,
            int dockDim, int minDockDim, int maxDockDim) {
   //...
   buttonC->instantiate(38, 174, 100, 24, _T("Button Test"));
   buttonC->user = wString(_T("Howdy!"));
   //...

//And the callback:
void ObjViewWin::onButtonClick(dwl::Object * obj) {
   dwl::Button * test = d_cast<dwl::Button*>(obj);
   //Set a breakpoint below here to see that 'test' is not nullptr.
   //I could have used it below instead of the 'buttonC' (ie., test->user.isA<wString...)
   //and got the same thing (just move the comment to try it).
   dwl::msgBox(_T("Button Test is a success!"));
   if (buttonC->user.isA<wString>()) {
   //if (test->user.isA<wString>()) {
      dwl::msgBox(buttonC->user.cast<wString>());
      }
   }

I believe the previous snippets are fairly self explanatory. A button is derived from a dwl::Control, and all dwl::Controls have an ‘any’ object called ‘user.’ 'user' holds a wString in this case, which is is a wrapper around std::string or std::wstring depending upon Unicode’s definition. ‘ObjViewWin::onButtonClick’ is set to handle the button click, and displays the wString in a MessageBox. The CreateDwlDelegate portion is the aspect diverging the most from Borland’s VCL, but all things considered, the code is still clean and easy to grasp.

An interesting alternative to this approach is Sarah Thompson’s Signal and Slot implementation. I have never used her work, or any other signal/slot library, but they sound interesting, although I don't know if they are any cleaner than the end result of the previous approach. From my readings, it is fairly easy to chain signals together if you want, which may be one advantage. But the same thing can probably be accomplished with callbacks by calling into a function that in turn calls all of the functions that would be chained.

Another option is using Lambda functions. For fun, I added them to the Delegate class as seen above, so you can replace the CreateDwlDelegate line with the following if you wish:

C++
buttonC.reset(new dwl::Button(this, [&](dwl::Object *) { onButtonClick(this); }));

Docking Toolbars

The next item worthy of a brief illumination is Docking Toolbars. I’m not going to go into the mechanism behind them, as they were originally developed from an excellent C tutorial available at Catch22’s website, and my code is fairly self-explanatory. When I first wrote this page, I came across Jeff Glatt’s Docking Toolbars in Plain C. Due to its wording, I thought it was a straightforward adaptation of James’ work, but while editing this for a revision back on 8/1/05, I had reason to delve into it more deeply, and found that Jeff made some substantial changes to the code itself (all of them quite good), and I adopted his method of making docked windows resizable as a result.

I will mention that I vastly improved the workings of the docking toolbars, and polished them to the point where very little (to no) flicker or jumping occurs while the window is being resized (in the older Window’s themes). As far as I know, no other library has accomplished this feat.

Briefly, the secret is the handling of the WM_ERASEBKGND and WM_PAINT messages, and parenting items to the MDI client rather than the main window HWND. Do the same when you create controls from scratch (except the parenting, of course), and do all your painting double-buffered in the WM_PAINT handler.

The process of figuring out everything it took to eliminate these flicker sources was not one of the most fun experiences of my life, and made me pretty tired of looking at docking window code. That section went through *four* major edits, in order to overcome all of the difficulties involved.

Do not let the above scare you, though. If you decide to create a flicker-free control (not a docking toolbar, just a regular control) from scratch, it is not that painful of an event, as long as you are not wrapping a Windows control. (But if you are, the ControlWin class will considerably ease your task.) You can see an example of a non-WCC (non-Wrapped Child Control) in the StringGrid unit in the DWinLib subdirectory.

Getting back to docking toolbars, I should mention that although I’m proud of the work I’ve done, they could still be improved. It would be nice if the toolbars automatically docked themselves rather than waiting for you to release the mouse button when they are in the hot zone. Also, the mechanism in David Nash's framework would be a good addition.

If you play with the docking toolbar implementation on your own, a little complexity worth being aware of: there are three ways to initiate a docking window destruction. The first is to manually call the destructor of the appropriate docker. The second is to close the docker while it is floating by using its ‘close’ button (which calls the docker’s ‘wClose’ routine). And the third is to terminate the program when the docker is visible. All of these have different control flows (the last one starting at FloatingWindow::wNcDestroy, or your overridden version if you have done so).

The attached executable (and those in the other projects) show these methods in action, and the dockers destroy themselves correctly. A word of warning, though. I have not tested manually ‘deleting’ the toolbar window when it is floating. Instead, I recommend calling the wClose routine, which I know will initiate the appropriate chain of events:

C++
void MainAppWin::toggleToolbar() {
   if (toolbarC) {
      toolbarC->wSendMessage(WM_CLOSE, 0, 0);
      //Did not notice same problem as with objViewBar, but to ensure the MDI
      //client gets refreshed correctly:
      mdiClientC->invalidateRect();
      }
   else {
      toolbarC = new ToolbarWin(this, _T("Toolbar"));
      toolbarC->instantiate(&dockManagerC, DockState::Docked, dwl::DockEdge::Top, 40, 30, 100);
      }
   }

And with that, our docking window discussion will now close!

Child Controls And Events

Wrapped Child Controls (WCC - the Windows standards, such as edit boxes, combo boxes, etc.), posed their own problems, and only became simple at the end of my design expedition.

When I started, it was natural to wrap each type of control on its own just because that was the fast way to do things. As the design progressed, it became awkward and more than a little disconcerting to remember the details of the individual designs, and the disgust pushed me to consolidate everything into a common winProc. The first couple iterations were annoying because they somehow littered themselves with special cases and continued making my head hurt.

This last iteration (6.04, January, 2021) finally overcame the design blues with an approach that is centralized and simple, without the edge case mismanagement of the past.

In it, all WCC derive from a ControlWin class. That class has its own winProc method which is actually pretty simple. And the overridden controls handle their own special needs without requiring gunking up that winProc.

To instantiate a control, create a class that derives from ControlWin. Use the constructor to set up its width, height, and location, and anything else that can be done without throwing. Then, in its 'instantiate' method, make a CREATESTRUCT that would create the type of control you want. Pass that CREATESTRUCT by reference into ControlWin::instantiate with an error string to use in case creation fails. And that is it!

ControlWin::instantiate takes care of setting up the subclassed window procedure. You can see how this works by examining the ControlWin code, and the Button, CheckBox, edit boxes, and other classes in DWinLib that derive from ControlWin.

You can also examine those derived classes to see how to interact with those controls. Some actions will need to be handled through the parent window's wCommand function, because Windows normally notifies them in that manner.

If you find the need to create event handlers, there is one example in the StupidSquares codebase to model your own from.

By 'event handler,' I am not talking about normal routines like wPaint and other handlers for Windows messages. Although they truly are 'event handlers,' instead, I'm talking about user-settable functions you can have the window procedure hook into on the fly. Like event handlers in Microsoft Access.

StupidSquares has a textbox in its ObjViewWin which displays the text associated to the currently selected stupid square. You can edit that text, and when the textbox loses focus the textBoxKillFocusProc routine is triggered. textBoxKillFocusProc is user-settable on the fly, and you could have five or more different void someFuncName(dwl::Object* arg) functions selectable by program logic if you wanted.

This is accomplished in the ControlWin::winProc procedure through a Delegate, which is different than a DwlDelegate in that it doesn't take any arguments whereas the DwlDelegate takes a dwl::Object*. (To overcome this pollution of Delegate names, you could replace my overwrite of Sergey Ryazanov's work with his original, or use Sergey Alexandrovich Kryukov's work which adds several features.)

C++
class ControlWin : public Control {
   //...
   protected:
      sr::DwlDelegate onKillFocusC;
   public:
      void onKillFocus(const sr::DwlDelegate & del) { onKillFocusC = del; }

   protected:
      std::function<LRESULT(WORD, WORD, HWND)> onCommandC = nullptr;
   public:
      void onCommand(const std::function<LRESULT(WORD, WORD, HWND)> & func) {
         onCommandC = func;
         }
      //AN EXAMPLE USAGE:
      //AFTER CONSTRUCTING A COMBOBOX:
      //comboBoxC->onCommand([&](WORD notifyCode, WORD ctrlId, HWND hwnd) {
      //            return comboCommands(notifyCode, ctrlId, hwnd);
      //            } );

   };

Notice that there are actually two different event handlers in the code, as there is also an onCommand handler which I will leave out of the rest of this overview (since I've already illuminated a lambda function usage, and the code comment clarifies this case).

This onKillFocus is triggered in the wKillFocus procedure (which handles the WM_KILLFOCUS message):

C++
LRESULT ControlWin::wKillFocus(HWND receiveFocusWin) {
   if (onKillFocusC.callable()) {
      onKillFocusC();
      return 0;
      }
   return CallWindowProc(origProcC, hwndC, WM_KILLFOCUS, (WPARAM)receiveFocusWin, 0);
   }

And that, in turn, calls the function which was set in ObjViewWin's constructor:

C++
textBoxC->onKillFocus(CreateDwlDelegate
                     (ObjViewWin, textBoxKillFocusProc, this, textBoxC.get()));

Pretty simple! One item to be aware of is you might need to add a SetFocus command to the application window's wMouseDown handler, as I did in this example, in order to get the onKillFocus event to fire for the edit box.

I hope that gives you enough to go on if you want to handle any other events.

Idle Processing

If you find the need to do something when your program goes idle, you can add a BaseWin* to DWinLib's idle processing list:

C++
gDwlGlobals->dwlApp->idler().addToIdleList(this);

Once that is done, DWinLib will call the wIdle process for that BaseWin. Currently, this is set up as a one-shot event - DWinLib will continue to call that wIdle function until it returns false. Your program will become unresponsive until that occurs.

To see how this works, peruse the Application::run listing at the top of this writing. The dwl::Idler in the DwlApplication.cpp and header is the small class that takes care of that processing.

Behind the scenes, DWinLib uses this to handle docking toolbar deletion, so you can see usage examples within DWinLib itself.

Building DWinLib Programs

If you are creating a new DWinLib program in Visual Studio, perhaps the simplest way to do so is to copy one of the example program directories to a new name. Opening the project, you will need to delete all of the files in the non-DWinLib-library project and re-add them by dragging them from the new directory into the Solution Explorer heirarchy. If you haven't placed the new directory in the same parent directory as the sample program, you will have to change the relative directory locations in Configuration Properties -> C/C++ -> General -> Additional Include Directories. Then right-click on the "PrecompiledHeader.cpp" file and change its Configuration Properties -> C/C++ -> Precompiled Headers property to "Create (/Yc)". You should then be able to compile it and start the modification process.

If you want to start from complete scratch, you can create a blank C++ project. If you don't want to use DWinLib libraries, you can drag all of the DWinLib files from the DWinLib directory (and not the ReducedFlickerControlTests subdirectory) onto the Solution Explorer. Also, find the C++ files from the example most resembling your desired program type and copy them to a new working directory, then drag them into the Solution Explorer, preferrably onto a new filter in order to keep yourself organized.

Next, you will need to change the following properties:

  • The Configuration Properties -> C/C++ -> General -> Additional Include Directories
  • The Configuration Properties -> C/C++ -> Preprocessor -> Preprocessor Definitions will need to be modified as per the example programs.
  • The Configuration Properties -> C/C++ -> Language -> Conformance mode will need to be set to 'No.'
  • The Configuration Properties -> C/C++ -> Precompiled Headers will need to be set up to 'Use' for the project, and 'Create' for the PrecompiledHeaders.cpp file.
  • The Configuration Properties -> Linker -> System -> SubSystem will need to be changed to "Windows (/SUBSYSTEM:WINDOWS)."
  • You can also change Unicode / MBCS character set if you want, in Configuration Properties -> Advanced -> Character Set" setting.

And anything else you wish.

If you want to use libraries, follow the examples. Many of the previous settings will need to be set on the non-library project. Make certain the values for the library and the main project end up being the same, or you will experience some very difficult debugging problems. (The library binary layout will not line up with the expected binary layout of the main project because of macro expansion differences. You will swear you 'newed' something somewhere and got one pointer value for its location, but in the library, it will have a different pointer value, or NULL. Extremely frustrating to figure out!)

These steps have been laid out in a longer article, for anyone who wishes for more explicitness.

Here is the StupidSquares example mentioned earlier:

Image 3

Closing Thoughts

The previous items are the important ones which come to mind when looking back on DWinLib’s development. I hope you found this write-up useful for understanding the inner workings of Windows better, and/or improving your own code in some way, and/or as an intellectual curiousity.

As always, happy coding!

History

1/16/2021

Revamped dwl::ControlWin controls and improved many facets of DWinLib.

2/13/13

Completely revamped the MDI routines to the standard Windows’ method. Improved Unicode capabilities with units such as CharArrayWrappers.cpp, so wStrings can be easily converted from/to string/wstring if they are mostly American English, and locales are not an issue. (Also see DwlAsciiAndUnicodeFileWrapper.cpp for file reading/writing capabilities.) Rewrote entire DWinLib article series to logically flow from one topic to the next.

8/1/05

A huge ‘Thank You’ goes to David Nash. He took the time to get DWinLib working in Visual Studio .NET 2003, and sent a copy back. It was a major job, and I appreciate it, as well as the great feedback he gave me, helping me further improve DWinLib and this article. Due to his efforts, I was able to compile DWinLib on Dev-C++, so if you don’t have access to VS, you can still play with the code on a great free compiler.

(Having played with both Eclipse and Dev-C++, I am much more impressed by Dev-C++, as its project management seems to be a lot simpler than Eclipse’s, as well as easier to intuit. You don’t have to work in a ‘workspace’, and Eclipse’s ‘workspace’ caused me about two hours of cussing, due to either my feeble brain or the fact that Eclipse’s workspaces are as bad as I got the impression they are. Additionally, I beat my head into some other issues which are not easy-to-grasp for a beginner. After my attempt back in about 2002, I gave up.)

David also indicated he tried to get it working on VC6, but because of standards incompliance with that compiler (not due to non-standard code), he was unable to do so. I am guessing it has to do with using "for (int i=0; i<something..." everywhere, and I refuse to go back to improperly-scoped variables.

Another major change is DWinLib is now ‘Unicode’ enabled. Again, thanks goes to David for starting me down that road.

The last modification was to the callback mechanism outlined in the Callbacks section.

There are also various other minor changes made to DWinLib and this article which I won’t go into details on.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

David O'Neil
Software Developer www.randommonkeyworks.com
United States United States
I am the author of Laughing at the Devil: One Man’s Religious Discoveries. If you want to understand the astronomic investigations of our priests 3,000 years ago, LATD is the book to turn to. It opens up the thoughts that pushed them away from their earlier polytheism and towards our current definition of God.

Trained as a mechanical engineer, I have been involved with design, supervision, and project management. I taught myself C++ programming in order to play around with binaural beats more easily. I've also created various databases to help with project management and personal tasks.

Databases are cool and extremely useful! Happy coding, everybody!

Comments and Discussions

 
GeneralMy vote of 5 Pin
Espen Harlinn14-Feb-13 6:07
mvaEspen Harlinn14-Feb-13 6:07 
GeneralRe: My vote of 5 Pin
David O'Neil14-Feb-13 14:42
professionalDavid O'Neil14-Feb-13 14:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.