Click here to Skip to main content
Click here to Skip to main content

DWinLib 6: Pretty WinAPI Incorporation

, 7 Jul 2014
Rate this:
Please Sign up or sign in to vote.
My DWinLib Windows API wrapper combined with Francisco Campos's Pretty WinAPI framework

NOTE: The above programs, except the SWC refactoring, can be compiled as MDI or SDI via a macro define in PrecompiledHeaders.h, and they can also be compiled as UNICODE or Multi-Byte via the project options. The projects are Visual Studio 2013, but should be portable to any C++11 compiler. (Several C++11 features have been used, so rewriting will be required for older platforms.)

In the above zip files the DWinLib library has been replicated in each, to make compilation on your end easier. On my end everything is arranged in this structure:

To save space, and work from a common codebase for everything, you may want to replicate that structure on your end.

The library project does not contain compiled libs, as each one is about 7 to 10 MB, and MDI/SDI, Unicode, and Multi-Byte configurations would be required. To create them, modify the library appropriately and build the project. The main changes made in the files are commenting out the '#define COMPILE_FOR_PROGRAM" definition in PrecompiledHeaders.h, commenting or uncommenting the "#define DWL_MDI_APP" line, and setting the project properties appropriately.

Introduction

Throughout the last ten years or so one of my projects has been understanding the Windows API and improving DWinLib in order to make coding Windows programs with Visual Studio Express more pleasurable.

As mentioned in the DWinLib link, there are several other wrappers you can use if you wish, but none of them suited my fancy. For instance, one item I really like in DWinLib is the ability to never have to think about control IDs - they are wrapped up and unseen, unneeded even for header constant declarations.

Another item I was pleased with is the elimination of a LOT of jitter during window resizing. Since DWinLib included a docking framework, that was a big deal compared to other approaches everyone has experience with. (Unfortunately, the reduced jitter was best observed in Windows Classic desktop. Microsoft incorporated items when they skinned XP and newer versions that made the elimination of jitter almost impossible because they never fixed issues with some classic controls.)

But that docking framework had a problem: it did not draw the docker very well in Windows 7 when it was undocked and dragged.

While perusing CodeProject I came across Francisco Campos's Pretty WinAPI Class article. It has a docking framework I like, so I decided to see about incorporating that, and the best of the rest, into DWinLib. The attached code is the result, and the following gives an overview of DWinLib as it now stands.

Differences in Approach

Before beginning I will mention that revising DWinLib and SWC to work together was not a minor task. ('SWC' is Campos's name for his library, although he also indicates 'PWC' in a couple other places.) Francisco stated, "I don't guarantee that it is well written", and I have no arguments with his claim. His framework seems to have a long history into early Windows programming, and the naming style (and lack thereof in many places) - and the fact that magic numbers and other items were used throughout the code - made for several puzzlements. But despite those issues the scope of his accomplishment is jaw-dropping. It took two solid months, possibly a little more, to get the above results finished. I suspect that much more than a year was required on his part, and for his tenacity and willingness to make it public I am in deep appreciation.

If you are one who would like to dive into SWC itself, and be spared some of the difficulty, I've included my refactoring of Francisco's PwcStudio in the above zip files. I found it best to rename classes and variables so I could step through the equivalent code in both frameworks while hunting bugs, and that is the result. My memory may be incorrect, but it feels like I spent a week on refactoring, to make class and variable names better describe their intention. And that didn't include everything in SWC - just the stuff I had to understand to do my work!

One item that shocked me when I finally understood the mechanism behind Campos's approach is that it appears all Window messages are routed through 'SwcBaseWin::WndProc' (or 'CWin::WinProc' before refactoring). By this I mean that user created windows AND common controls are handled there, although they did call into the original procedure in a manner I never got into very deeply.

I kept my existing approach instead of adopting Francisco's. Common controls are derived from a base class that has its own Window procedure unrelated to the main application window procedure. That separation logically mirrors Microsoft's own handling of those window procedures, so it should be less confusing if you follow the class relationships. (It is probably also responsible for the brevity of my WindowProc compared to his, as you will see below.)

To give an idea of the difference in coding this enables, here is a copy/paste of Francisco's window procedure. I have no idea if all of the branches are really required, and reverse engineering the logic, and the reasons behind it is not something I relish.

static LRESULT CALLBACK WndProc(HWND hWnd, UINT uID, WPARAM wParam, LPARAM lParam) 
   { 
         
   CWin* pWnd=NULL; 
   BOOL  bClose=FALSE; 
   BOOL  bResult=FALSE;
   LRESULT lResult=0;
   if( uID == WM_INITDIALOG )   //is a dialog window
   { 
      if (pWnd== NULL)
      {
         pWnd =reinterpret_cast<CWin*>(lParam); 
         ::SetWindowLong(hWnd,GWL_USERDATA,reinterpret_cast<long> (pWnd));
         pWnd->SethWnd(hWnd);
      }
   }else if( uID == WM_NCCREATE) //is a normal windows
   { 
      pWnd =reinterpret_cast<CWin*> ((long)((LPCREATESTRUCT)lParam)->lpCreateParams); 
      BOOL res=pWnd->IsMDI();
      if (res == 0)
      {
         pWnd =reinterpret_cast<CWin*> ((long)((LPCREATESTRUCT)lParam)->lpCreateParams); 
      }
      else
      {
         LPMDICREATESTRUCT pmcs = ( LPMDICREATESTRUCT )(( LPCREATESTRUCT )lParam )->
                     lpCreateParams;
         pWnd =reinterpret_cast<CWin*>(pmcs->lParam); 
         pWnd->SethWnd(hWnd);
      }
      ::SetWindowLong(hWnd,GWL_USERDATA,reinterpret_cast<long>(pWnd));
      pWnd->SethWnd(hWnd);


   }
   pWnd= reinterpret_cast<CWin*>(::GetWindowLong(hWnd,GWL_USERDATA)); 
   if (pWnd!=NULL)
      pWnd->SaveMsg(hWnd,uID, wParam,lParam); //save the actually message, the idea is if
                                              //you need to call  the default message
                                              //[Default()]
   if (HIWORD(pWnd))
   {
      if(uID ==   WM_COMMAND)
      {
         CWin* pChild=reinterpret_cast<CWin*>((HWND)
               ::GetWindowLong(pWnd->GetDlgItem( LOWORD(wParam)),
                        GWL_USERDATA) ); 
         if (HIWORD(pChild))
         {
            int x=HIWORD(wParam);
            if (x == CBN_EDITCHANGE ) 
               pChild->OnCbnEditChange();
            if (x == CBN_KILLFOCUS ) 
               pChild->OnCbnKillFocus();
            if (x == CBN_EDITUPDATE       ) 
               pChild->OnCbnEditUpdate();
            if (x == CBN_CLOSEUP ) 
               pChild->OnCbnCloseUp();
            if (x == CBN_SELENDOK  ) 
               pChild->OnCbnSelendOk();
            if (x == CBN_SELENDCANCEL   ) 
               pChild->OnCbnSelendCandel();
            if (x == CBN_SELCHANGE ) 
               pChild->OnCbnSelChange();
            if (x == CBN_SETFOCUS ) 
               pChild->OnCbnSetFocus();
            if (x == CBN_DROPDOWN ) 
               pChild->OnCbnDropDown();
         }
         else
            pWnd->OnCommand(wParam,lParam);
                  
      }
      else if( uID ==  WM_DESTROY)
      {
         if(IsWindow(pWnd->GetSafeHwnd()) )
            pWnd->OnDestroy();
            return 0;
      }
      else if (uID ==  WM_NCDESTROY)
         return 0;
      else if(uID == WM_CLOSE)
      {
         bClose=TRUE;
         lResult=pWnd->OnClose();
      }
      else if(uID == WM_COMPAREITEM )
      {
         if(wParam != 0)
         {
            CWin* pChild=reinterpret_cast<CWin*>((HWND)
                  ::GetWindowLong(pWnd->GetDlgItem( ((( LPDRAWITEMSTRUCT )lParam)->CtlID) ),
                        GWL_USERDATA) ); 
            bResult=pChild->OnCompareItem((LPCOMPAREITEMSTRUCT) lParam  );
            if(bResult && pWnd->IsDialog()) 
               return ::SetWindowLong(pWnd->GetSafeHwnd(), DWL_MSGRESULT, ( LONG )bResult);
         }
      }   
      else if( uID ==   WM_MEASUREITEM) 
      {
         if(wParam != 0)
         {
            CWin* pChild=reinterpret_cast<CWin*>((HWND)
                  ::GetWindowLong(pWnd->GetDlgItem((((LPMEASUREITEMSTRUCT)lParam)->CtlID)),
                        GWL_USERDATA) ); 
            bResult=pChild->OnMeasureItem((LPMEASUREITEMSTRUCT) lParam  );
            if(bResult && pWnd->IsDialog()) 
               ::SetWindowLong(pWnd->GetSafeHwnd(), DWL_MSGRESULT, ( LONG )bResult);
         }
               
      }
      else if (uID ==   WM_DRAWITEM) 
      {               
         //el problema con estos mensajes es que nunca llegan al control directamente,
         //inicialmente el mensaje se envia al propietario del control,luego es labor
         //nuestra  enrutarlo desde aqui a quien debe manejarlo.
         //
         if(wParam != 0)
         {
            CWin* pChild=reinterpret_cast<CWin*>((HWND)
               ::GetWindowLong(pWnd->GetDlgItem( ((( LPDRAWITEMSTRUCT )lParam)->CtlID) ),
                        GWL_USERDATA) ); 
            bResult=pChild->OnDrawItem((LPDRAWITEMSTRUCT) lParam  );
            if(bResult && pWnd->IsDialog()) 
               ::SetWindowLong(pWnd->GetSafeHwnd(), DWL_MSGRESULT, ( LONG )bResult);
         }
               
      }
      else if(uID   ==   WM_NOTIFY)
      {
         LPNMHDR pNMHDR = ( LPNMHDR )lParam;

         CWin* pChild=reinterpret_cast<CWin*>((HWND)
               ::GetWindowLong(pNMHDR->hwndFrom,
                        GWL_USERDATA) ); 
         if ( pChild )
         {
            BOOL bNotify=TRUE;
            bResult = pChild->ReflectChildNotify( pNMHDR, bNotify);
            if ( pWnd->IsDialog())
               ::SetWindowLong(pWnd->GetSafeHwnd(), DWL_MSGRESULT, ( LONG )bResult);
                  
            if(bNotify)
               pWnd->OnNotify(wParam,pNMHDR);

            if (bResult != 0)
               return bResult;
         }

      }


      if( pWnd->IsDialog())
      {
         if (bClose)
            return 0;
               
         bResult=pWnd->NewMsgProc(hWnd,uID,wParam,lParam,lResult);
         if(!bResult) 
            return pWnd->DefWindowProc(pWnd->GetSafeHwnd(),uID,wParam,lParam); 
         return 0;
      }
      else
      {   
               
         if(bClose )
         {
            if(lResult) 
               return pWnd->DefWindowProc(pWnd->GetSafeHwnd(),uID,wParam,lParam); 
            return 0;
         }

         bResult=pWnd->NewMsgProc(hWnd,uID,wParam,lParam,lResult);
         if(!bResult) 
            return pWnd->DefWindowProc(hWnd,uID,wParam,lParam); 

         if (pWnd->IsMDI() )
            return pWnd->DefWindowProc(hWnd,uID,wParam,lParam); 
      }
            
   }
   return lResult;   
}

Compare that to the following:

LRESULT CALLBACK Application::WindowProc(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 becomes a BIG problem!
         auto it = gApplication->windowsC.find(window);
         if (it != gApplication->windowsC.end()) win = it->second;
         }
      if (win) return win->winProc(window, msg, wParam, lParam);
      else {
         //#ifdef DWL_DO_LOGGING
         //   wStringStream str;
         //   str << _T("Adding window to map.  Window HWND:  ") << window;
         //   gLogger->log(str);
         //   #endif
         BaseWin * tempWin = static_cast<BaseWin*>(TlsGetValue(gApplication->tlsIndexC));
         gApplication->windowsC.insert(std::make_pair(window, tempWin));
         return tempWin->winProc(window, msg, wParam, lParam);
         }
      }

   catch (Exception & e) {
      Strings & strings = gGlobals->stringsC;
      wString str = strings[Strings::Error_Programming] + e.strC;
      str += strings[Strings::Error_PleaseReport];
      if (e.continuableC == Exception::Continuable::True)
         str += strings[Strings::Error_WillAttemptContinue];
      else str += strings[Strings::Error_ProgramMustExit];
      MessageBox(gDwlMainWin->hwnd(), str.c_str(), strings[Strings::Error].c_str(),
                  MB_OK);
      if (e.continuableC == Exception::Continuable::False) exit(EXIT_FAILURE);
      }
   catch (std::exception & e) {
      Strings & strings = gGlobals->stringsC;
      wString str = strings[Strings::Error_PleaseReport];
      str += _T("\r\n");
      str += strings[Strings::Error_StdExceptionCaught];
      str += _T("\r\nError: ");
      str += dwl::convertToApiString(e.what());
      str += _T("\r\n");
      str += strings[Strings::Error_StdExceptionAbortQuery];
      int wish = MessageBox(gDwlMainWin->hwnd(),
                  str.c_str(), strings[Strings::Error].c_str(), MB_YESNO);
      if (wish == IDYES) abort();
      }
   catch (...) {
      Strings & strings = gGlobals->stringsC;
      wString str = strings[Strings::Error_UnknownException];
      str += dwl::lastSysError();
      str += strings[Strings::Error_UnknownExceptionAbortQuery];
      int wish = MessageBox(gDwlMainWin->hwnd(), str.c_str(), 
                  strings[Strings::Error].c_str(), MB_YESNO);
      if (wish == IDYES) abort();
      }
   return -1;
   }

As you can see, DWinLib's method doesn't require any of the logic checks - just find the window corresponding to the HWND in the application map and shoot the message off to the receiver. Or add it to the map if it doesn't exist. The function is still shorter than Campos's, even though two-thirds of it is dedicated to exception handling. SWC doesn't include exception handling.

Another item worth mentioning is the thread local storage approach to temporarily storing the window class is much safer than the GetWindowLong/SetWindowLong approach used in SWC. DWinLib did use GetWindowLong/SetWindowLong at one time, and I must thank David Nash for his contributions in that, and other areas of DWinLib long ago.

Error Processing, Globals, and Design

Speaking of exception handling brings me to a topic I learned a lot about this go around, and wish to mention. A major change was implemented in DWinLib because of Campos's code, and an issue I had noticed in earlier versions of DWinLib. Even though exception handling was in place, the handlers were never triggered properly during a program crash.

The reason was DWinLib created everything in constructors. I have almost forgotten the step-by-step reason for the problem, but I'm pretty certain the initial exception caused more exceptions to be thrown during stack unwinding, and the additional errors wreaked havoc with my method.

SWC appears to be modeled on MFC, and approaches window creation in a two-step manner. In it, constructors don't do much, if anything that could throw. Mostly they are just used for variable initialization. Window creation is handled AFTER the constructor finishes its task. In other words creation looks something like:

   Window * win = new Window(/*args*/);
   win->createWindow();

With this in place, instead of everything being done in 'new', the exception handlers work correctly. You can see my approach does show the exception text to the user if it isn't handled in code, but you can easily modify it to a logging system if you wish.

Another item you might find interesting is I've implemented a global 'Strings' class that contains error messages for various items. If you haven't done a lot of programming, the interesting aspect is it shows a technique that would not need to be changed much to make it into a DLL. That will ease the burden of internationalization.

The trick isn't very hard:

namespace dwl {

   //This is meant to be singly instantiated as a global, or in a globals unit,
   //or in the main window.  Your choice, although it would be crazy to create more than
   //one as it is a waste of program space.
   class Strings {
      public:
         enum StringEnum {
            Error = 0,
            Error_InvalidNullParent,
            Error_PleaseReport,
            Error_Programming,
            ...
            TreeView_InsertItemInvalidWindow,
            Tls_AllocationFailure,
            Window_OnlyOneAllowed,

            DwlLastString
            };
      private:
         StringEnum stringEnumC;
         std::vector<wString> stringsC;
      public:
         Strings();
         const wString & dwlString(StringEnum stringNumber);
         const wString & operator[](StringEnum stringNumber);
      };

   }

   
//And in the .cpp file:
Strings::Strings() {
   stringsC.resize(DwlLastString + 2); //The '+2' is just in case I do something stupid.
                                       //(although it won't help much if that happens)

   stringsC[Error]                              = _T("Error");
   stringsC[Error_InvalidNullParent]            = _T("Improper NULL parent specified");
   stringsC[Error_PleaseReport]                 =
               _T("Please report the following error and the steps taken to\nobtain ")
               _T("it to the program's author.");
   stringsC[Error_Programming]                  = _T("Programming Error: ");
   ...
   stringsC[TreeView_InsertItemInvalidWindow]   =
               _T("Tree View: Invalid window in insertItem");
   stringsC[Tls_AllocationFailure]              = _T("Failed to allocate TLS Index");
   stringsC[Window_OnlyOneAllowed]              =
               _T("Trying to create an instantiated window type");

   }


const wString & Strings::dwlString(StringEnum pos) {
   return stringsC[pos];
   }


const wString & Strings::operator[](StringEnum pos) {
   return stringsC[pos];
   }

(If you are adamantly against globals, you could unglobalize it and pass a reference around, but that is FAR more work than a global approach for something that the whole program really does access.)

I have read some of the opinions on exceptions and asserts, and have concluded that exceptions ARE things that should rarely, if ever happen. Code I've seen, including SWC, has used asserts liberally throughout, and nothing else. (SWC's asserts contained a bug when expanded, so they didn't work the way they were assumed to, which I found to be interesting.)

Therefore stack dumps are the only tool left for debugging when the asserts are turned off in production mode. Because of that thought I've inserted 'throw's in place of asserts, to give a faster clue as to the cause of underlying problems. When I'm debugging a routine I use asserts in order to bring VS to the proper line, but that is the only time asserts seem useful to me.

To work with Windows, my 'Exception' class contains either a std::wstring or a std::string, depending upon the UNICODE macro. It also contains a 'continuableC' class boolean, to try to allow the program to continue operating in the case of lesser errors.

As far as globals themselves, I'm not as against them as some people are, but I do use them sparingly. A pointer to the main window and the DWinLib slice of it is a global, as is a pointer to the Application, and a Globals unit. In the MIDI program I developed, the globals are handled as I outline in "Two-thirds of a pimpl and a grin".

(If you are new to programming, one reason to shy away from globals like this is because when your codebase gets big, calls to the globals will likely incur cache misses, and take more time. That and the fact that once you pass a certain number of globals your code becomes ugly as sin, and prone to global instantiation order issues. In my 'String' case, once exceptions are encountered time is the least of your worries.)

I also kept a lot of the rest of DWinLib intact, because the SWC approach did not make immediate sense to me. For instance, CMsg is a base class used by CWin in SWC. CMsg encapsulates the window procedure. The statement "CWin derives from CMsg" sounds illogical. (And thinking about the fact that CWin has a 'WndProc' inside itself leads to some perplexion!)

In DWinLib the Application 'has' a message procedure; it doesn't 'derive' from a message procedure. That procedure routes the messages to the appropriate handlers in the windows themselves. Nothing 'derives' from a window procedure! And Campos's window procedure was a long macro that evidently did not work on MinGW. I believe (but haven't tested) that the virtual route DWinLib uses will work correctly on that platform. (The virtual method is outlined in my earlier article: DWinLib - The Guts).

Another difference is I've slightly restructured DWinLib's class hierarchy. It no longer contains a DwlMdiFrame, as outlined in DWinLib - The Guts. The BaseWin automatically becomes an MDI container when DWL_MDI_APP is defined in either the PrecompiledHeader.h or FinishedProgramHeaders.h file. As can be seen in the examples, there are some items in the main window you will need to change depending upon whether your program is MDI or SDI.

And the last item to touch upon is DWinLib now uses namespaces throughout the code, to make things simpler, and allow you to use names more freely. Do you want to have a class called "Object"? You are free to do so at the global namespace level, or in a namespace other than my wittily named 'dwl.' I've also put much of Francisco's code into the 'swc' namespace, as a reminder of his work. I don't guarantee that some of his code isn't in 'dwl', because I retrofitted those parts into existing DWinLib classes, but most of his work is given a properly labeled home. Christopher Diggin's 'any' class has also been put in the 'cd' namespace, as a reminder of his work. (Sorry, Christopher - I don't like typing more than three letter acronyms for namespaces.) His 'any' class is a very handy template I've used in a couple situations!

Improvements to SWC

Not all of Francisco's library has been incorporated into DWinLib, as you will see if you compare the examples to his work. But the important core has, so the remainder will be much easier to code than the part that has been done. I simply don't have the time right now, and must focus on making money for a bit, so I may be unable to return to that aspect for a while. In fact, my coding priority will be my own project, now that DWinLib is robust enough to take it on in its new form, so the other windows shown on the Pretty WinAPI Class page may not be incorporated by me; feel free to do so if you wish.

Offsetting this negative, I've made several improvements, and many important bug fixes in my rework of SWC.

In his write up Francisco wondered about adding a top docker to the framework. I have done so. He also asked for a CString replacement. Although I haven't added one (Joe O'Leary's CStdString could easily be used if you wanted), I have incorporated my older 'wString' type that expands to either 'std::string' or 'std::wstring', depending upon the UNICODE macro. (I originally did this through a typedef that expanded std::basic_string, but have revamped it to use PJ Arends's work.) You will find 'wString' in many function signatures and return definitions throughout DWinLib.

I will call the removal of all Hungarian notation an improvement, although some might argue that point. As mentioned in an earlier article, I do prefix globals with 'g', and append class variables with 'C'. Windows items, like mouse callbacks, are usually prepended with a 'w', like 'wMouseDown'. I make no apologies for any naming conventions I've overridden in DWinLib - my goal was to make something that was simple and consistent throughout, and if I had to touch someone else's code, I usually made it look like the rest of my work in order to speed up understanding in future reviews, and to minimize the number of styles found in the codebase.

Two other major improvements have been mentioned below the zip files:

  • First, all of the examples can be compiled as either UNICODE or Multi-Byte programs. SWC could only be compiled as a Multi-Byte program.
  • Second, if you want an MDI program instead of an SDI program, simply uncomment the DWL_MDI_APP definition in either 'PrecompiledHeaders.h' or 'FinishedProgramHeaders.h'. It is the simplest solution I could figure out.

I don't believe many of the bugfixes made it to the SWC refactoring example. For instance, the mouse could be way beyond the right side of the main window and the window would still be attempting to dock, as indicated by the window outline on the main window. In fact, it would dock when the mouse was released. I've modified this so the mouse must be close to the window edge for docking to occur.

Another, more sinister bug is seen if you delete all the dockers along one edge of the window using the 'X' buttons. Then move another docker over that edge: there won't be any more dock action there until the program is rebooted. I've fixed this in my examples.

One last bugfix is all the dockers were leaked at program termination. No destructors were ever called. That isn't a critical bug, as Windows will reclaim the memory at that point anyway, but it is not a good coding habit to get into. There were additional resource leaks I squashed, although I don't guarantee I got them all.

Speaking of leaking dockers, an interesting construct was used to overcome the issue.

During the rewrite I adopted the SWC method of having an 'idle' loop in the program. Of course, such endeavors are never perfectly straightforward (although it wasn't very difficult, either). Here's the pertinent part of the code:

//DWinLib:
      #ifdef DWL_MDI_APP
         HWND mdiClient = gDwlMainWin->mdiClientHwnd();
         HWND hwnd = gDwlMainWin->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)) {
                  idleC = true;
                  //Do the idle processing:
                  while (idleC) idleC = wIdle();
                  }
               }
            }
      #else
         while ((var = GetMessage(&msg, NULL, 0, 0)) != 0) {
            if (var == -1) return var;
            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
         
//...
bool Application::wIdle() {
   bool result = false;
   iteratorInvalidatedC = false; //Set this up so only changes made in 'wIdle' affect it.
   auto it = windowsC.begin();
   while (it != windowsC.end()) {
      if (it->second->wIdle() == true) result = true;
      if (iteratorInvalidatedC == true) {
         iteratorInvalidatedC = false;
         it = gApplication->windowsC.begin();
         if (it == windowsC.end()) return result;
         //The code will now go through all the windows again (except the first window)
         //and redo the 'wIdle' processing, but that is better than blowing up!
         }
      ++it;
      }
   return result;
   }
         
         
//SWC:
	MSG msg;
	BOOL bresult;
	BOOL bPeekMsg=TRUE;
	while (bPeekMsg || GetMessage(&msg, NULL, 0, 0)) 
	{
		
		if (bPeekMsg)
		{
			if(!PeekMessage(&msg,NULL,0,0,PM_REMOVE))
				bPeekMsg=mainWinC->OnIdle();
			continue;
		}

		if (bMDI)
		{
			bresult=(
					(!TranslateMDISysAccel (mainWinC->GetSafeClientHwnd(), &msg)) 
					&&  	(!TranslateAccelerator (msg.hwnd,hAccelTable, &msg)));
		}
		else
			bresult=(!TranslateAccelerator (msg.hwnd, hAccelTable, &msg));
	
		CWin* pActive= reinterpret_cast<CWin*>((HWND)::GetWindowLong(msg.hwnd,GWL_USERDATA)); //CWin::GetUserPointerWindow(msg.hwnd); 
		BOOL bPre=TRUE;
		//if (pActive)
		//	bPre=pActive->PreTranslateMessage(&msg);
		if (bresult && bPre) 
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		
	}
	return  msg.wParam;
	

}

//SWC only queries the main window for idle conditions, unlike my rewrite:

   virtual BOOL OnIdle()   //Main window
   {
      return FALSE; 
   }

You will note that when a DWinLib program gains many windows, the idle loop querying all of them may be a little overkill if you know some of them will never do idle processing. You can modify it if necessary.

You will also note that the two lines after "BOOL bPre=TRUE;" have been commented out if you deeply dig into Campos's original source code. On my machine the example died in the PreTranslateMessage portion, and commenting averted the issue, although the coolbar no longer appeared.

Getting to the interesting case, I actually used wIdle to destroy the floating docking windows.

In the past I've seen cases where WM_NCDESTROY wasn't the last message a window received. I don't recall the details, but because of those experiences I shy away from assuming WM_NCDESTROY is a useful tool. When searching for a way to kill the floating windows at the appropriate time the only method I could see was to use the last window message. But to make it safer I used the following construct:

LRESULT swc::FloatingWindow::wNcDestroy() {
   if (!beingDestroyed()) {
      needToDestroyC = true;
      }
   return 0;
   }


bool swc::FloatingWindow::wIdle() {
   if (needToDestroyC) {
      delete this;
      }
   return false; //If you return true, wIdle will continually reprocess and take up 100% of processor.
   }

It worked!, although I don't know if it is overkill or not. When WM_NCDESTROY is followed by another message, will the second one be immediately queued? I never tested. If you ever come across a case where that occurs, and this approach blows up you can make fun of me for designing a non-solution!

Fun Stuff

SWC was an interesting framework to play with, even though its coding practices had me muttering some inanities from time to time - quite a lot actually. For instance, it isn't really 'plug and play' ready. The rebar and docking windows are designed and implemented in the 'SwcBaseSdiMdiFrame' unit (in my refactoring). The main window is 'PwcStudioMainWin', and it derives from the the 'SwcBaseSdiMdiFrame'. If you want to plug another docking framework in, or eliminate the rebar, you must rework both objects, not just one.

Because of issues like that I redesigned DWinLib to be more friendly for plug and play. In the examples the dockers are 'plugged into' the main window. They aren't contained in a base class.

By combining this with namespaces you can much more easily use another docking framework - just plug it into the main window (WinMainO). To test it out, I copied all of the SWC docking framework files, gave them another namespace, and changed the program to use the copies by including the new files and modifying the namespace in WinMainO.cpp and WinMainO.h. The final part of actually modifying the program to use the new files was less than a minute of work, and the results were what I expected.

While refactoring SWC I came across something I would never have thought of doing. In the DWinLib and refactored files there is a SwcPrimitives.h. In it a 'Size' is derived from 'SIZE', and by doing so it captures all the Windows 'SIZE' characteristics. Such a paradigm had never entered my mind before, and I thought that was rather slick!

The combined DWinLib/SWC codebase has some items you might find useful. Campos's GDI unit (swc::Gdi) has a gradient class I thought was pretty neat. It doesn't seem to currently be optimized for bitmaps less than 256 pixels in width (or height), as it will always iterate over 256 steps when drawing the bitmap, but that can be changed when there is time.

You may find some of the items in the DwlUtilities files to be useful. For instance, if you ever need to enumerate a window's children, the dwl::ChildrenEnumerator takes the drudge work out of the task.

One class in DwlControl.h was fun to put together, although I may never use it because it swaps one type of drudgery for another. DwlWndClassEx is a simple wrapper over a WNDCLASSEX structure. It allows you to get rid of the Hungarian notation, but because it is a union over the data, the access mode is more verbose:

      DwlWndClassEx wc;
      wc.u.dwl.cursor = NULL;

In it, the 'u' stands for 'union', and the 'dwl' just means the DWinLib view of the union. That wrapper also automatically sets the cbSize portion up correctly, and zeros out all of the members initially. As I said, it was fun, although maybe not of much use. (I suppose you could add functions to eliminate the overhead, so you could use it like: "wc.cursor(NULL)". Maybe someday I'll be in the mood for more fun, but part of me balks at adding accessors for such usage, even if it is a trivial task.)

To Do

I am aware of two three issues in the example programs. They are all in the rebar test.

First, if you compile the project in release mode the rebar is about 150 pixels high instead of 20. (I just found out that this existed in Francisco's code.) Second, there is a weird interaction between the docking code and the rebar sizing in both release and debug mode. If you continually press 'Ctrl + N' to create new windows, the MDI workspace flickers onto the rebar space.

I haven't looked deeply into either of these issues as I don't have plans to use rebars, but they are worth being aware of. If you do play with this and find the problem(s) feel free to post it below.

It just came to my attention that the rebars don't use Campos's gradient drawing method because I never overrode the wPaint routine in 'ControlWin::classWinProc'. Somehow, the code in the lengthy cut and paste at the top of this writing called the paint procedure that used the gradient, but my rework ended up calling the default gradient supplied by Windows.

Closing Thoughts

Other than the above, nothing immediately comes to mind, so I will wrap this up. I just remembered that the dockers are tabbed in the rework, which I haven't said before, so if you drag an undocked one onto one that's docked, you can pick which tab you want from the bottom. That reminds me that when dragging an undocked window over a docker, there isn't any feedback when the mouse is over the non-client area of the docker. Campos's code didn't have that either, so I'll attack that as I rework MEdit.

Oh yeah. When creating windows, make the final creation occur through 'gApplication->createWindow(BaseWin * win, const CREATESTRUCT & cs)'. The examples use the technique, so you can follow them to see what I'm saying. That will take care of setting the Thread Local Storage up correctly. I dealt with a two-step process for a while, where I had to pre-register the being-created window with the application, and then create it, and I found myself forgetting to do both steps. The new procedure solved my poor memory problem.

And with that, I will say no more about DWinLib for now!

Hopefully the above transmits some of the frustrations and exultations of working on a project of this nature. Would I do it again? Yes, if I had to go back and start over. But if Microsoft had finished their work on the C# native code compiler I'd undertake the task in C# (if the compiler makes it to the Express editions). Having a library of that nature at your fingertips makes working in that framework a no-brainer. But what is done is done, and I'll settle for this. As far as the flat Metro interface - I'm certain this could be retrofitted for that style if desired, but I've never been fond of their flatness. Maybe someday I'll change my mind, but if you take it upon yourself to add the feature please post the modification!

Happy Coding!

License

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

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. For those who are “ready to look at the world - religion, science, spirituality - differently,” LATD is the book to turn to.
 
In about 1994 I began studying and documenting the astronomy of our ancestors. A hint lead to many years of partial understanding, before a profound breakthrough occurred and some old myths finally made sense.
 
The greatest of my discoveries is the celestial observations behind the biblical tale of Samson, which was created 3,000 years ago. That find casts a profound new light on the roots of Western religion, as well as the foundation of modern science. To learn more, visit my website.
 
Trained as a mechanical engineer, I learned C++ programming on my own in order to create a MIDI program. I am delighted to say I also succeeded in that goal. Happy coding, everybody!

Comments and Discussions

 
GeneralDWinLib6.zip not available Pinmemberuniskz6-Jul-14 12:48 
GeneralRe: DWinLib6.zip not available PinpremiumDavid O'Neil6-Jul-14 19:16 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140718.1 | Last Updated 7 Jul 2014
Article Copyright 2014 by David O'Neil
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid