Click here to Skip to main content
12,401,324 members (49,870 online)
Click here to Skip to main content
Add your own
alternative version

Stats

20.7K views
1.6K downloads
36 bookmarked
Posted

DWinLib 6: Pretty WinAPI Incorporation

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

Summary of Last Revision

December 9, 2014: DWinLib 6.02: MDI and SDI libraries have been created that are easily used in projects in order to reduce compile times. '6.02' and any future version numbers are no longer reflected in subdirectory structure of working projects, and are for reference only. Also, the WinMainO unit was changed to MainAppWin, because the name is more descriptive, and doesn't have a backstory no one besides me will know. This also entailed changing the global main window pointer to gMainWin. A couple additional minor revisions were made.

Index

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 stand-alone Windows programs with Visual Studio Express Community Edition without proprietary frameworks 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 previous download is the result, and the remainder of this article describes DWinLib as it now stands.

To summarize why you may be interested in using DWinLib for a project:

  • DWinLib can be used in non-GPL programs for free.
  • It is a very thin wrapper, and will probably familiarize you with more aspects of the Windows' API than some other wrappers. That familiarity is kind of fun, once you get the hang of it.
  • It makes handling controls easier than many other wrappers.
  • Pens, Bitmaps, Fonts, and Brushes are easier to use in DWinLib.
  • It is a clean design, uses namespaces properly, and doesn't use Hungarian Notation.
  • The codebase has everything needed for a main menu system, previous files used, and MDI and SDI applications. It even contains the framework for a minimal-memory-usage Undo system.
  • Two different docking frameworks are included, and adding more is simple because of the namespace approach used.
  • The dwl docking framework is smoother than others I've experienced.

Downloads

In addition, I refactored much of Francisco's codebase, and you may find it useful:

Notes on Building and Laying Out Projects

The examples zip file uses a directory structure that reduces the search times for libs and include files more than any other approach I've found. My 'Programs' subdirectory contains everything needed for coding, and is arranged as shown in the following Explorer screengrab. It also contains a 'MyProgs' directory, 'OthersProgs' directory, and a 'TheoryAndExamples' directory, to keep things segregated in an easy-to-understand system. If you are new to programming, I hope this arrangement helps you focus on coding faster, and spend less time figuring out how to organize everything.

Using Libraries

All of the example programs are now set up to use libraries, for faster compilation in most cases. Two libraries have been created: MDI and SDI. They are located in the LibsAndUtils/DWinLibLibrary directory, and if you open up the main projects (not in that directory, but in the subdirectories off of DWinLibExamples) the libraries should compile automatically because project dependencies have been used, and relative paths were specified. (The library projects in the zip file don't contain compiled libs, as each one is about 7 to 13 MB, and there is no need for such big downloads since Visual Studio will create them.)

All projects were compiled for Unicode, because Multi-Byte has been deprecated by Microsoft, but you may create Multi-Byte libraries for yourself if you wish, and DWinLib will work fine. Just follow the examples of the existing libraries, and change the 'Properties -> Configuration Properties -> General -> Character Set' to 'Use Multi-Byte Character Set'. Of course, this means you will also need to change your main project that links to the library to use the same character set.

I should mention that tying the projects to libraries makes it harder to modify the projects to use another setup. Stepping through one scenario will give an idea what is needed for all.

Currently, the BareSwcDocks project is the only one to use an SDI interface. To change it to MDI, perform the following steps:

  • Remove the DWL_SDI_Unicode project from the solution.
  • Add the MDI_Unicode project to the solution by selecting 'File -> Open -> Project/Solution...', and make certain the 'Add to Solution' radio button in the Open Project dialog box is selected.
  • In the PrecompiledHeaders.h file for the BareSwcDocks, uncomment the #define DWL_MDI_APP line.
  • Right-click the 'BareSwcDocks' project in the Solution Explorer, and select 'Properties.' Then select the 'Configuration Properties -> Linker -> General', and change the Debug Additional Library Directories to "..\..\..\LibsAndUtils\DWinLibLibrary\MDI_Unicode\Debug;%(AdditionalLibraryDirectories)", and the Release version to "..\..\..\LibsAndUtils\DWinLibLibrary\MDI_Unicode\Release;%(AdditionalLibraryDirectories)".
  • Right-click on the top "Solution 'BareSwcDocks' (2 projects)" line in the Solution Explorer, and make the BareSwcDocks depend on the DWL_MDI_Unicode library project by selecting the 'Common Properties -> Project Dependencies,' and clicking the check box in the 'Depends on:' section.
  • In the 'Configuration Properties -> Linker -> Input' property page for BareSwcDocks, change the Additional Dependencies for all configurations to 'DWL_MDI_Unicode.lib' (so the line reads "DWL_MDI_Unicode.lib;%(AdditionalDependencies)".

If you execute the program, everything will rebuild, and when all is finished the program will open as an MDI application. It isn't the easiest routine in the world, but once you master the process involved another level of Visual Studio usefulness will open up for you.

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. (I also moved many implementations into the correct .cpp file - that aspect of Franciscos' code was terribly annoying.) 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 them 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 dwl::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 will crash the program
         auto it = gDwlApp->windowsC.find(window);
         if (it != gDwlApp->windowsC.end()) win = it->second;
         }
      if (win) return win->windowProc(window, msg, wParam, lParam);
      else {
         BaseWin * tempWin = static_cast<BaseWin*>(TlsGetValue(gDwlApp->tlsIndexC));
         gDwlApp->windowsC.insert(std::make_pair(window, tempWin));

         //#ifdef DWL_DO_LOGGING
         //   wStringStream str;
         //   str << _T("   Adding window to map.  Window HWND: ") << window <<
         //               _T(" BaseWin*: ") << tempWin<< _T(" MSG: ") << msg;
         //   gLogger->log(str);
         //   #endif

         return tempWin->windowProc(window, msg, wParam, lParam);
         }
      }

   catch (Exception & e) {
      Strings & strings = gDwlGlobals->stringsC;
      wString str = strings[Strings::Error_Programming] + e.strC;
      str += strings[Strings::Error_PleaseReport];
      if (e.continuableC == 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 == Continuable::False) exit(EXIT_FAILURE);
      }
   catch (std::exception & e) {
      Strings & strings = gDwlGlobals->stringsC;
      wString str = strings[Strings::Error_PleaseReport];
      str += _T("\r\n");
      str += strings[Strings::Error_StdExceptionCaught];
      str += _T("\r\nError: ");
      str += utils::strings::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) exit(EXIT_FAILURE);
      }
   catch (...) {
      Strings & strings = gDwlGlobals->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) exit(EXIT_FAILURE);
      }
   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 it contains a bunch of code dedicated to exception handling, which SWC doesn't include.

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->instantiate(/*other args*/);

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.

Regarding exceptions, it is worth knowing that until the main window is fully constructed, all exceptions are non-continuable. This is because the application's run() loop is not entered until that point, and I can't see an easy way to tie MainAppWin::instantiate into that routine.

One item this brings up is the use of WM_CREATE (handled by wCreate in DWinLib). In pure API programs, subwindows of windows are normally created in WM_CREATE handlers. Then the LRESULT is checked for errors, and handled that way.

DWinLib uses an instantiate method throughout its internals. But you can use the wCreate handler if you wish, and do things in a more 'direct API' way. Just keep in mind that a DWinLib program's run() procedure isn't operational until the main window is fully constructed.

Sometimes it is necessary to put some code you would automatically associate with the instantiate method into the wCreate handler. If a window or other resource is created that relies on the HWND of the creating window, and has a wPaint or other handler that relies on that HWND, you will need to construct it in the wCreate routine in order to force it to occur before the paint handler is called.

I don't believe any of the samples showed that type of design constraint. In them all the subwindows are created in instantiate methods, and errors are handled via exceptions. Since exceptions are something that shouldn't happen, and the main program shouldn't NOT start up, that makes logical sense, even though it seems counterintuitive from an error-handling mindset. You could change everything back to wCreate and error codes instead of exceptions, since the zip files contain all the source code, but that would be a lot of work for very little, if any benefit that I can see.

Another item non-related to exceptions 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 wouldn't 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 the whole program really does access.)

(I should also mention you may be interested in Michael Haephrati's String Obfuscation System if you wish to make your executables harder to reverse engineer.)

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 throws in place of asserts, to give a faster clue as to the cause of underlying problems. When I'm debugging a routine I sometimes use asserts in order to bring VS to the proper line, but that is the only time they seem useful to me. (The proper Visual Studio assert to use is _ASSERT, which doesn't need a header inclusion.)

To better interact with Windows, my Exception class contains either a std::wstring or a std::string, depending upon the UNICODE macro. It also contains a continuableC enum variable, to indicate if the program can continue operating in the case of lesser errors. (After instantiation is complete, that is. Before then you can specify Continuable::True, but the program will still terminate.)

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 dwl::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 talk a bit more about overcoming the instantiation issues in the previously mentioned Two-thirds of a pimpl and a grin article, and cache misses are a small price to pay for the organizational features of globals. Just be aware that you might not want to use globals in a highly-called routine that could be a bottleneck.)

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 the PrecompiledHeader.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 unless I have to.) 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.
  • Another improvement, in my mind, is a reduction in the number of Window classes registered in DWinLib. Francisco created a new window class for each non-native-control window SWC made. As an example all of the docking windows were different classes, even though they had the exact same properties as the other ones. I modified this so docking windows derive from one WNDCLASSEX, (in the dwl::DockWindow unit) and other windows derive from other classes. Therefore, only one window class will be registered for each distinct type. (To see this better, place a breakpoint in SwcBaseWin::RegisterDefaultClass in the SWC Refactored project.)
  • Also, I revamped everything to use the standard library. It made some of the code much easier to read. For instance, Francisco used a swc::Array (in my refactoring) throughout his codebase, and it harks back to old-fashioned C usage:
    SwcTabCtrl* tabControl = (SwcTabCtrl*) tabsC[selectedTabC];

    Compare that to:

    SwcTabCtrl * tabControl = tabsC[selectedTabC];

    Eliminating the redundancy of the necessary casts in the first approach made it easier to swim through the code, because casts always interrupt my train of thought and make me investigate them in a skeptical light. They are dangerous, and demand attention.

  • I eliminated all if (HIWORD(someWindowClassPointer) == NULL) testing (which DWinLib never had). For instance, SwcTabbedWindowContainer::GetNumWnd contains these two lines:
    SwcBaseWin* pw=((SwcTabCtrl*) tabCtrlPtrArrayC [tabNumber])->parentC;
    if (HIWORD(pw)== NULL) return NULL;
    

    Similar testing was used throughout SWC, and I never understood why he didn't just do a if (!pw) return NULL; in its place. (Maybe it had something to do with the LOWORD memory being reserved by the system? But even in that case the HIWORD check is unnecessary, because Windows should never send messages that cause you to test for system logic in areas such as this, to my knowledge.)

  • Two other major improvements have been mentioned below the zip files:
    • All of the examples can be compiled as either UNICODE or Multi-Byte programs. SWC could only be compiled as a Multi-Byte program.
    • If you want an MDI program instead of an SDI program, perform actions like those given in Using Libraries.
  • I don't believe many of the bugfixes made it to the SWC refactoring example. For instance, the mouse could be dragging a docker 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.
  • You should be able to easily find any function body in DWinLib. As an experiment in the original SWC codebase, try to determine where CMiniDock::OnLButtonUp is implemented without using the Class Explorer or a global search. Not so easy! And that is only one example of many where definitions are not where you would expect them to be.
  • 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. (As an example, the CReBarCtrlEx::OnPaint is crafted in a way that never terminates the BeginPaint call in CPaintDC's constructor.)

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 believe a WM_UAHDESTROYWINDOW message was involved, but I don't recall more than that. 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 a bit 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!

=>6.01 Update: I guess I get to laugh at myself. Even though the previous approach worked for all of the 6.00 examples, it failed when things became more complicated. The final solution involved calling DestroyWindow before delete, like this:

LRESULT ModalBaseForm::wClose() {
   //The 'EnableWindow' is irrelevant to this problem, but needed for ModalBaseForm:
   EnableWindow(parentC->hwnd(), TRUE);
   DestroyWindow(hwndC);
   delete this;
   return 0;
   }

That means DestroyWindow will be called twice, with the final one in the BaseWin destructor, but the redundancy is OK because the second of them has an IsWindow check before it, and Windows probably repeats that check internally in its own processing.

Without the previous approach, relying on the DestroyWindow to occur in the destructor will trigger an exception when DestroyWindow initiates events that call into the class being deleted, whether or not wIdle is used. It does work in some circumstances, though, and DWinLib still uses it for the floating dockers because that did work and didn't give problems, and I'm too lazy to change it.

As a point of semi-interest, while working on DWinLib 6.01 I witnessed a bug that seemed to trace back to wIdle, and I immediately remembered the previous paragraph. But I also recalled that the day before WinAmp reported a serious error which probably occurred while coming out of hibernation. A complete reboot fixed the problem, indicating Windows 7 can still get internally corrupted. It has been a long time since I've seen that type of situation, so I was somewhat surprised.

GDI Objects

One frustrating aspect of Windows programming is keeping track of Graphics Device Interface objects. These are pens, brushes, bitmaps, and fonts. When you select one to use in a Device Context (DC) you must usually remember to select the previous object back into the DC when you are finished with your processing, and perform a DeleteObject.

Earlier versions of DWinLib had a mechanism where DwlDC's (which were my wrapper of a Windows HDC) had a brush, font, bitmap, and pen pointer in it, so when the DwlDC went out of scope the appropriate processing was taken care of, and the 'remembering tedium' was reduced.

SWC did not have anything equivalent, and in DWinLib 6.00 the impetus was to get it working and not worry about the inconvenience. But actually using those methods brought the old frustrations to the surface, and made me modify DWinLib one more time.

The new framework is not a straight translation of my earlier techniques, because I was unhappy with some aspects, and SWC is a different paradigm. So I went back to the drawing board.

In DWinLib 6.01 the earlier swc::Gdi has been renamed swc::DC, because that abbreviation better describes what the class is wrapping. Swc::DC now has four std::unique_ptrs, one for each GDI object. The pertinent part of the code will give you a good idea of how it is used, but I will be more specific below this snippet:

namepace swc {

   class DC  {
      private:
         enum Type { UseBeginPaint, UseGetDC, UseCreateCompatibleDC, Unspecified };
         Type typeC = Unspecified;
         HWND hwndC; //Must make hwndC be initialized before dcC for BeginPaint to work.
         PAINTSTRUCT * psC;   //Must also be before dcC.
         HDC dcC;
      
         std::unique_ptr<swc::Bitmap> bitmapC;
         std::unique_ptr<swc::Pen>    penC;
         std::unique_ptr<swc::Font>   fontC;
         std::unique_ptr<swc::Brush>  brushC;

      public:
         DC(HDC dc=NULL) : dcC(dc), typeC(Unspecified) {
         
            }


         DC::DC(HDC dc, HWND hwnd) : dcC(dc), hwndC(hwnd), typeC(UseGetDC) {

            }


         DC::DC(HWND hwnd, PAINTSTRUCT * ps) : hwndC(hwnd), psC(ps),
                     dcC(BeginPaint(hwndC, psC)), typeC(UseBeginPaint) {
            
            }


         DC::DC(HWND hwnd) : hwndC(hwnd), dcC(GetDC(hwnd)), typeC(UseGetDC) {
         
            }


         DC::DC(DC & dc) : dcC(CreateCompatibleDC(dc.dcC)), typeC(UseCreateCompatibleDC) {
            
            }


         ~DC() {
            if (bitmapC.get()) {
               SelectObject(dcC, bitmapC->oldBitmapC);
               }
            if (fontC.get()) {
               SelectObject(dcC, fontC->oldFontC);
               }
            if (penC.get()) {
               SelectObject(dcC, penC->oldPenC);
               }
            if (brushC.get()) {
               SelectObject(dcC, brushC->oldBrushC);
               }

            if (typeC == UseBeginPaint) EndPaint(hwndC, psC);
            else if (typeC == UseGetDC) ReleaseDC(hwndC, dcC);
            else if (typeC == UseCreateCompatibleDC) DeleteDC(dcC);
            else if (typeC == Unspecified) {
               //Do nothing, and let the caller manage the DC
               }
            }


         HDC operator()() { return dcC; }


         HFONT setFont(HFONT font, DeleteAction deleteAction) {
            if (fontC.get()) {
               //First, remove the font from the dc:
               HFONT oldFont = (HFONT) SelectObject(dcC, fontC->oldFontC);
               }
            //The following will DeleteObject on the HFONT if 'deleteActionC == DoDelete'
            fontC.reset(new swc::Font(font, deleteAction));
            fontC->oldFontC = (HFONT) SelectObject(dcC, fontC->fontC);
            return fontC->oldFontC;
            }

         HPEN setPen(HPEN pen, DeleteAction deleteAction) {
            if (penC.get()) {
               //First, remove the pen from the dc:
               HPEN oldPen = (HPEN) SelectObject(dcC, penC->oldPenC);
               }
            //The following will DeleteObject on the HPEN if 'deleteActionC == DoDelete'
            penC.reset(new swc::Pen(pen, deleteAction));
            //And finally select the pen:
            penC->oldPenC = (HPEN) SelectObject(dcC, penC->penC);
            return penC->oldPenC;
            }

         HBRUSH setBrush(HBRUSH brush, DeleteAction deleteAction) {
            if (brushC.get()) {
               //First, remove the brush from the dc:
               HBRUSH oldBrush = (HBRUSH) SelectObject(dcC, brushC->oldBrushC);
               }
            //The following will DeleteObject on the HBRUSH if 'deleteActionC == DoDelete'
            brushC.reset(new swc::Brush(brush, deleteAction));
            //And finally select the brush:
            brushC->oldBrushC = (HBRUSH) SelectObject(dcC, brushC->brushC);
            return brushC->oldBrushC;
            }

         HBITMAP setBitmap(HBITMAP bitmap, DeleteAction deleteAction) {
            if (bitmapC.get()) {
               //First, remove the bitmap from the dc:
               HBITMAP oldBitmap = (HBITMAP) SelectObject(dcC, bitmapC->oldBitmapC);
               }
            //The following will DeleteObject on the HBITMAP if 'deleteActionC == DoDelete'
            bitmapC.reset(new swc::Bitmap(bitmap, deleteAction));
            //And finally select the bitmap:
            bitmapC->oldBitmapC = (HBITMAP) SelectObject(dcC, bitmapC->bitmapC);
            return bitmapC->oldBitmapC;
            }
            
            //...

As can be seen, depending upon which DC constructor is used, the appropriate action will be taken when the DC is destroyed. You no longer need to remember to call ReleaseDC when an HDC and an HWND are passed into the DC.

And, if you pass in an HPEN, HBRUSH, or other GDI object via a 'setXXX' call, you must specify whether that object should be DeleteObjected at the end of its cycle. That extra step of specification may seem like an inconvenience, but it makes me remember how I want the object used, and allows me to have a font or other object as a class member, and not be destroyed when the DC goes out of scope.

An example will get our feet wet, and illuminate the details.

Francisco's SWC code had several resource issues that boiled down to management. This isn't one of them as far as I remember, but it does show the difference in approach, and the simplification my revision enables. Why Francisco used new and delete in the original code is unknown. I don't think it was required, but I only looked hard enough to tell that I didn't need to.

//DWinLib version of Dock Manager Window painting:
LRESULT swc::DockManagerWindow::wPaint(swc::DC & dc) {
   Brush brush(CreateSolidBrush(dwl::colors::windowFace()), DoDelete);
   //The following is lazy coding, because the 'getClientRect()' routine makes a copy,
   //whereas if I'd "Rect r; getClientRect(r);", no copy would have been made.
   //Lazy, lazy, lazy!  But I never claimed not to be, even though it required
   //this long comment to point out the extent of my laziness.
   Rect  clientRect = getClientRect();
   DC    memDC(dc);
   Bitmap memDcBitmap(CreateCompatibleBitmap(dc(), clientRect.width(), clientRect.height()),
                      DoDelete);
   
   //Note that the underlying HBITMAP is being passed in the following call, NOT a
   //swc::Bitmap:
   memDC.setBitmap(memDcBitmap(), DontDelete);
   memDC.fillRect(&clientRect, &brush);
      
   dc.bitBlt(0, 0, clientRect.width(), clientRect.height(), memDC(), clientRect.left,
             clientRect.top, SRCCOPY);
   return TRUE;
   }


//Francisco's original code:
BOOL DockManager::OnPaint(HDC hDC) {
   CRect rcClient;
   CPaintDC dc(GetSafeHwnd()); // device context for painting
   CBrush cbr;
   CRect m_rectDraw;
   cbr.CreateSolidBrush(CDrawLayer::GetRGBColorFace());
   GetClientRect(rcClient);
   CGDI    MemDC;
   CBitmap m_BitmapMemDC;
   MemDC.CreateCompatibleDC(dc.m_hDC);
   m_BitmapMemDC.CreateCompatibleBitmap(dc.m_hDC,rcClient.Width(),rcClient.Height());   
   
   CBitmap *m_bitmapOld=new CBitmap(MemDC.SelectObject(&m_BitmapMemDC));
   MemDC.FillRect(&rcClient,&cbr);
      
   //paint routines
   dc.BitBlt(0,0,rcClient.Width(),rcClient.Height(),MemDC.m_hDC,
            rcClient.left,rcClient.top,SRCCOPY);
   MemDC.SelectObject(m_bitmapOld);
   m_BitmapMemDC.DeleteObject();
   MemDC.DeleteDC();
   cbr.DeleteObject();
   m_bitmapOld->DeleteObject();
   delete m_bitmapOld;
   return TRUE;
   }

Note that the DeleteObjects are no longer needed, although if you really wanted to use the old methods with DWinLib you could. In other words you could do all the resource management yourself via SelectObject and DeleteObject but why?

Also note that in my rewrite the bitmap is coded to be DeleteObjected when it goes out of scope, and not when the memory DC is destroyed. This allows the bitmap to stick around longer if you need it for other purposes.

The GDI and DC objects interact, and to better understand that interaction here is a paste of the appropriate part of a swc::Font object:

namespace swc {
   enum DeleteAction { DoDelete, DontDelete };

   //Forward declare the DC class:
   class DC;

   //-------------------------------
   //Font
   //-------------------------------
   class Font {
      friend class DC;

      private:
         HFONT fontC;
      
      //Housekeeping items for swc::DC to use if needed:
      private:
         HFONT oldFontC;
         DeleteAction deleteActionC;


      public:
         Font(HFONT font, DeleteAction deleteAction) :
                     fontC(font), oldFontC(NULL), deleteActionC(deleteAction) {
         
            }

         ~Font() {
            if (deleteActionC==DoDelete && fontC!=NULL && fontC!=oldFontC) 
                        DeleteObject(fontC);
            }
      
      //...

I felt it was better to place the oldFontC member into the font itself, rather than polluting the DC with those details. Even though the DC is logically responsible for keeping track of the old fonts, pens, and such, the code is much messier with all of those items placed in that class. If you are interested in why I say this, the following is code from the previous version of DWinLib. Compare the DwlDC constructors and destructor to the previous code.

//Used when painting from a WM_PAINT MESSAGE
DwlDC::DwlDC(HWND hwnd, PAINTSTRUCT * ps) : hwndC(hwnd), typeC(UseBeginPaint), psC(ps),
            penC(NULL), brushC(NULL), bmpC(NULL), fontC(NULL),
            deletePenWhenDoneC(false), deleteBrushWhenDoneC(false),
            deleteBmpWhenDoneC(false), deleteFontWhenDoneC(false) {

   dcC = BeginPaint(hwndC, psC);
   initObjects();
   }


//Used when painting from a non-WM_PAINT message
DwlDC::DwlDC(HWND hwnd) : hwndC(hwnd), typeC(UseGetDC),
            penC(NULL), brushC(NULL), bmpC(NULL), fontC(NULL),
            deletePenWhenDoneC(false), deleteBrushWhenDoneC(false),
            deleteBmpWhenDoneC(false), deleteFontWhenDoneC(false) {

   dcC = GetDC(hwnd);         
   initObjects();
   }


//Used when creating a compatible DC from another dc
DwlDC::DwlDC(DwlDC & wdc) : hwndC(NULL), typeC(UseCreateCompatibleDC),
            penC(NULL), brushC(NULL), bmpC(NULL), fontC(NULL),
            deletePenWhenDoneC(false), deleteBrushWhenDoneC(false),
            deleteBmpWhenDoneC(false), deleteFontWhenDoneC(false) {

   dcC = CreateCompatibleDC(wdc());
   initObjects();
   }


DwlDC::~DwlDC() {
   SelectObject(dcC, origBrushC);
   SelectObject(dcC, origPenC);
   SelectObject(dcC, origBmpC);
   SelectObject(dcC, origFontC);

   if (penC   &&   penC != origPenC   && deletePenWhenDoneC)   DeleteObject(penC);
   if (brushC && brushC != origBrushC && deleteBrushWhenDoneC) DeleteObject(brushC);
   if (bmpC   &&   bmpC != origBmpC   && deleteBmpWhenDoneC)   DeleteObject(bmpC);
   if (fontC  &&  fontC != origFontC  && deleteFontWhenDoneC)  DeleteObject(fontC);

   if (typeC == UseBeginPaint) EndPaint(hwndC, psC);
   else if (typeC == UseGetDC) ReleaseDC(hwndC, dcC);
   else if (typeC == UseCreateCompatibleDC) DeleteDC(dcC);
   }


void DwlDC::initObjects() {
   origPenC   = (HPEN)    GetCurrentObject(dcC, OBJ_PEN);
   origBrushC = (HBRUSH)  GetCurrentObject(dcC, OBJ_BRUSH);
   origFontC  = (HFONT)   GetCurrentObject(dcC, OBJ_FONT);
   origBmpC   = (HBITMAP) GetCurrentObject(dcC, OBJ_BITMAP);
   }


void DwlDC::font(HFONT newFont, bool deleteFontWhenDone) {
   //This will return the original font if the user wants to do something with it
   HFONT oldFont = (HFONT)SelectObject(dcC, newFont);
   if (deleteFontWhenDoneC && oldFont != fontC) DeleteObject(oldFont);
   fontC = newFont;
   deleteFontWhenDoneC = deleteFontWhenDone;
   }
   
   //...

I could have also eliminated the friend declaration in my redesign, and added getters and setters for the fontC and doDeleteC members (even though the doDeleteC will probably never be directly changed by a DC), but I felt the friendship was a cleaner solution. This is probably the third time I've ever used friends in any production code, which tells you how seldom I find the construct useful.

One last item to point out in this regard is I used operator() to return the items in the fonts, pens, DC, and such. Going through Francisco's code I found an implicit conversion operator I'd never seen before. For the case of implicitly converting an instantiation into its HPEN member it looks like this:

operator HPEN() { return penC; }

Even though it provides a convenience, I shy away from implicit conversions of any type. They have bitten me before. I want to be able to tell when functions are being called by looking at the screen:

HPEN pen = theSwcPen();
//rather than:
HPEN pen = theSwcPen;

I also dislike typing getPen everywhere because the conversion is plain enough from the operator() usage. Repeating a comment from some earlier code, I'm lazy! And I try to make my code as simple as possible to satisfy my laziness, while being descriptive enough to keep understandability high! I hope the above inspires some great, lazy creations in the future, and if you have any suggestions for improvement, please post them below!

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 (MainAppWin). 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 MainAppWin.cpp and MainAppWin.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 Rect is derived from RECT, etc...), 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 Utilities subdirectory to be useful. For instance, if you ever need to enumerate a window's children using Windows' methods, the dwl::ChildEnumerator takes the drudge work out of the task. (I haven't needed DWinLib children exposed, so I haven't coded that into DWinLib at this point, although doing so would be simple. Just enumerate over the the dwl::Control::childControlsC vector.)

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.)

Visual Studio Tricks Learned

While performing these modifications my investigations somehow led to the 'Tools -> Code Snippets Manager' menu items. If you've never played with that utility, I suggest you do as it is a nice time saver.

One routine I always trip over is entering _T("some string"). The keystrokes have always felt awkward because of the rocking 'Shift' key usage. Using code snippets, I now just press "T" (with 'Shift', for capitalization, of course), then the Tab key, and a generic string appears that I type over and press 'Enter' to take me to the end of the ")". Super sweet! The following is the code for that action:

<!-- T.snippet -->
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
   <CodeSnippet Format="1.0.0">
      <Header>
         <Title>_T</Title>
         <Shortcut>T</Shortcut>
         <Description>Code snippet for _T statement</Description>
         <Author>Me, Myself, and I</Author>
         <SnippetTypes>
            <SnippetType>Expansion</SnippetType>
            <SnippetType>SurroundsWith</SnippetType>
         </SnippetTypes>
      </Header>
      <Snippet>
         <Declarations>
            <Literal>
               <ID>expression</ID>
               <ToolTip>Expression to evaluate</ToolTip>
               <Default>string</Default>
            </Literal>
         </Declarations>
         <Code Language="cpp"><![CDATA[_T("$expression$")$end$]]>
         </Code>
      </Snippet>
   </CodeSnippet>
</CodeSnippets>

If you want this for yourself, all you have to do is copy it into a text file in some dedicated directory, and then point VS to it by using the 'Tools -> Code Snippets Manager' menu item.

I also coded "td" to fill in a generic //TODO: string, "throw" expands to a dwl::Exception with a generic _T macro written, and "sup" begins defining a std::unique_ptr. From the previous example you can probably figure out how to add them to your system if you wish.

It did take a minute to realize that the "$end$" in the snippet was the location VS takes you to when 'Enter' is pressed while in the highlighted area of the snippet. Sometimes that is the only way to get rid of that highlighting.

Another slight annoyance is that using the snippet sometimes messes up your indentation if you don't use the standard style imposed by Visual Studio. Oh well.

In spite of those issues, I never came across use of code snippets before and figured I'd mention it here. If you haven't been introduced, I hope this saves you some time.

There is one more trick I'll document here, since I had to google it three times during the revision process. To make the Class View go to the currently selected class, in Tools -> Options -> Keyboard, enter "SynchronizeClassView" in the "Show commands containing" box, and then enter your desired shortcut in the "Press shortcut keys" box, and finally press "Assign". I use Ctrl + F1, and it is a slick way to quickly view the rest of the items in a class.

To Do

I am aware of four issues in the example programs. Three of them are in the rebar test.

First, if you compile the rebar project in release mode the rebar is about 150 pixels high instead of 20. This existed in Francisco's code, and I can't quickly see the reason for the problem.

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.

Also, the rebars don't use Campos's gradient drawing method because I never overrode the wPaint routine in ControlWin::classWinProc. Somehow, Francisco's 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. When I did override WM_PAINT something seemed to be covering up the rebar, and I couldn't get the rebar to paint because the HREGION was invalid, although the HDC was valid! It was a perplexion I didn't get to the bottom of.

The final issue is in DwlPwcStudio (and Francisco's original version). Inside splitters in the docking windows are incorrectly calculated and drawn under various circumstances. I believe it has something to do with the non-client area being taken into account wrong, but I haven't looked into that. It was not a priority in my MEdit work. If you do solve the problem, post the solution and I'll incorporate it into DWinLib.

Regarding the last item (splitters being drawn incorrectly): a week or so has passed since writing the previous paragraph, and I'm now pretty certain the quirk is due to a weirdness in Windows, where a shift is taking place between client and window coordinates. The dwl::DockWindow unit brought this to my attention, as I tried to get splitters to work correctly there. I haven't back-ported the change into the SWC code, but the necessary revision probably involves something like:

Rect mainWin;
gDwlMainWin->getWindowRect(mainWin);
Rect clientRect;
getWindowRect(clientRect);
blitRect.offsetRect(Point(clientRect.left-mainWin.left, clientRect.top-mainWin.top));

See dwl::DockWindow::drawWindowResizeBar for the dwl version.

There is one additional item I could put in here. Now that DWinLib is stable again, it could be made into a header-only library. I believe that would further improve compile times over the library approach, if you wanted to do away with libraries and tie the compilation of everything into one cycle.

But my #include memories are saying there may be cyclic dependencies that will be difficult to fix if such a road is taken. And I don't like looking for stuff in header-only arrangements. It becomes a real pain. So I am going to leave it as is. If reduced compile times are a priority, the compiled library approach is the preferred method.

As I finished up the 6.01 MEdit rework I realized the menu callbacks could be improved, so that should be mentioned before I close since I'm not going to tackle it right now.

Currently, MEdit has a bunch of dwl::CallbackItems in the main window for handling things like opening and closing files. The menu logic requires delegates to be passed into it, and this is done via CreateDelegate macro instantiations in the menu creation logic.

All the menus really need is an integer id, to pass back into the WM_COMMAND handler. The dwl::CallbackItems have the necessary id, which can be accessed via id(), but changing the menu logic to obtain that id and use it instead of delegates is not a trivial task. (The logic was laid out with the sole idea of getting it working, and menu logic is a convoluted, twisted beast given to us by Microsoft, as you will see if you peruse the DwlMenu files.) It will take a day or more to implement the necessary changes (probably more, the way things always seem to go for me), and I will simply make due for now, since it does work, even though my approach of creating toolbar and menu wrapper classes which contain their own callbacks ends up creating delegates with new numbers that handle the exact same items as existing numbers.

One last item to be aware of is during the 6.01 revision I ran into a bug that was caused by the merger of DWinLib and SWC's control window logic. It didn't show itself during the 6.00 edition, because I never used checkboxes in the original examples.

For now I've created a new, vastly stripped down type called dwl::ControlWinBare and reimplemented the items which gave me troubles from a clean slate, only using the minimum necessary code. Eventually all common controls may be reimplemented from that new base, and then the old code will be eliminated and the new stuff renamed without the Bare at the back. But everything works for now, so I'm happy for now!

Usage Pointers

My main goal has always been my MIDI program, MEdit, which gives finer control over MIDI events than other sequencers I'm aware of. DWinLib came about because of a bug with Borland Builder, and circumstances allowed me to reinvent the wheel in order to fix that bug. As you may have gleaned in the GDI Objects section and the rest of this writing, my sight was set on making DWinLib simple and powerful, while being very close to the bare metal of the API. Unlike David Nash's approach, you don't have to think about control IDs. And I don't believe his, or other wrappers, make using GDI objects as easy as DWinLib does (but I haven't looked into that in detail so if I'm wrong let me know).

Another item is DWinLib doesn't have DialogProc oriented windows, and all the background work that goes into them. But it does include a fairly simple mechanism to make a window and use it as a dialog box. The fundamentals of the window creation process are the same, so you don't need to remember two different methods. All that is different is how the parents are handled when the selected window becomes modal, and the mechanism I've used to return a value (as discussed in the linked article).

Some might consider the flip side of this to be a detriment, because DWinLib is not oriented towards creating forms through resources. Doing so would require much more work to hide the control IDs, and I haven't needed that for my projects. In other words, laying out an input form for a complicated financial application is not something I would enjoy doing in DWinLib. But for a free wrapper it does have a lot of power. (You could revise the wCommand routine for a specific window to handle control IDs specific to that window itself, so adding the ability may not be very difficult if you want to go down that road.)

As I reworked MEdit some useful knowledge pointed itself out to me:

  • MainAppWin is created on the stack. Almost all other windows are created via new, and DWinLib takes care of deleting the child windows. (Once, long ago, I found that some windows, like modal dialog boxes, could be safely created on the stack. It may still be possible, but I haven't attempted it in a while.)
  • If you are concerned about the number of virtual functions each window has, some of which you may never need, it is possible to override the virtual windowProc per window type, and only define the virtual methods you want that type to have. I see no reason to, especially with the power of today's computers, but you can if you wish.
  • Regarding the last point, it is also possible to easily extend a class by overriding the same windowProc, and adding handlers for the additional messages you wish to respond to, and finally call the original windowProc for the original message handling.

    For example, here's a modified version of MEdit's MainAppWin::windowProc:

    LRESULT MainAppWin::windowProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) {
       //This is a small aspect of the single-instance logic:
       if (msg == UWM_YOU_ARE_ME) return UWM_YOU_ARE_ME;
       
       //And continue on to other stuff:
       else if (msg == WM_COPYDATA) {
          COPYDATASTRUCT *cds = (COPYDATASTRUCT*)lParam;
          if(cds->dwData == UWM_YOU_ARE_ME) {  //Constrained to items coming from this program
             openMultiFiles(wString((TCHAR*)cds->lpData));
             return 0;
             }
          }
       //handle other stuff...
       
       //and finally call the original proc:
       return BaseWin::windowProc(window, msg, wParam, lParam);
       }
  • In some instances such as the dwl::ModalBase logic, it is necessary to make the window procedure return DefWindowProc results instead of DefFrameProc for an MDI build. If you create a class that needs the former, simply add a wUseDefProcInMdiC = true; line to the appropriate constructor.
  • As DWinLib stands, two window procedures are worth being aware of, because I have not guaranteed both of them have all of the same functions defined. I (re)discovered this while working on the scroll code for MEdit. Scrollbars in the docking windows worked correctly, but those in the main window wouldn't.

    The issue was MDI children have their own window procedure, dwl::MdiBase::windowProc, because they have to often call and return DefMDIChildProc instead of DefWindowProc. My issue was I hadn't enabled the WM_HSCROLL and WM_VSCROLL in the windowProc, but uncommenting old code in there fixed the issue. (I didn't have to define the procedure in the header, because dwl::MdiBase inherits from dwl::BaseWin. But the procedures won't be enabled because dwl::MdiBase::windowProc entirely overrides the dwl::BaseWin::windowProc.)

    As per questions about which windows require MDI processing, dockers don't fall into this category because they are connected to the main window's logic, not the MDI client window. Items like rebars and status bars are also exempt from MDI processing.

  • The menu skinning does not work for the system menu, unless I overlooked something major. In my testing I could not gain any control of that menu using the CMenuSpawn approach Francisco's code derives from.

    (As far as I can tell, the menu code was originally created by Iuri Apollonio. A 1998 precursor article is on CodeGuru, and a (newer?) version is at this SVN repository. If I am wrong in the fundamental sourcing of that code, or you find a better link to Apollonio's work, please leave a message.)

    By the way, in order to get popup menus to be skinned, call changeToOwnerDrawn before calling popupAtMouse in response to the mouse down message handling.

  • You can safely use multiple inheritance with DWinLib windows, as long as such an approach makes sense. For example, you wouldn't want to create a window with two windowProcs, because that will mess up the internals in DWinLib itself. But creating a window with Undo will work as expected.

Logging

If you need to troubleshoot something, and want to capture a bunch of stuff without the tedium of breakpoints, DWinLib has a rudimentary logging system. To use it,

  • Uncomment the #define DWL_DO_LOGGING line in PrecompiledHeaders.h for both the library and the main project. (In the future this can be modified to only affect the main program if desired, but the logger is instantiated in dwl::Application as it currently stands, and this allows logging within DWinLib itself if you need to chase items down in it.)
  • At the top of the .cpp file(s) you wish to capture things in, add the following:
    #if defined (DWL_DO_LOGGING)
       #include "DwlLogger.h"
       extern dwl::Logger * gLogger;
       #endif
  • At the point(s) logging is required, place code similar to this:
    #ifdef DWL_DO_LOGGING
       std::tstringstream str;
       wString space = _T(" ");
       str << _T("Msg: ") << msg;
       gLogger->padStream(str, 10);
       str << space << gLogger->crack(msg);
       gLogger->padStream(str, 34);
       str <<  _T("hwnd: ") << (win) << _T(", wParam: ") << wParam;
       gLogger->padStream(str, 70);
       str << _T("lParam: ") << lParam;
       gLogger->log(str);
       #endif
    

    As can be guessed, the previous will log all of the messages sent to a windowProc. (The padStream simply lines up the columns, because ragged text makes those very hard to parse by eye.) A search for #ifdef DWL_DO_LOGGING throughout the example projects will reveal commented areas in DWinLib where other example usages have been commented out. If you think they were originally created for my own bug-hunting endeavors, you are correct.

  • Change the location/filename of the log file in DwlApplication.cpp in dwl::Application::Application. It is the line that probably reads:
    gLogger =
       new Logger(_T("C:\\Users\\David\\Documents\\Programs\\MyProgs\\curLog.txt"));
    

    You can also change the log location at runtime by using the following code in MainAppWin, after the dwl::MainWin::instantiate method has been called:

    #if defined DWL_DO_LOGGING
       if (!gDialogs->openDialog(wString(_T("Log File:\0*.txt\0\0"), 18),
                   _T("txt"), OFN_HIDEREADONLY)) return;
       gLogger->changeFile(gDialogs->fileName());
       #endif
    
  • Then run the program and recreate the circumstances you wish to test. If you do multiple runs and a viewing after each one, I recommend Notepad++ because it will prompt you that the file changed. That saves a lot of reloading tedium. And, in my opinion, Notepad++ is an indispensable tool you should know about anyway.

That's all that was triggered in my work. If you play around with DWinLib I hope these shorten your learning time. If you have other questions that would be addressable here, let me know and I'll add the pointers.

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 PWC Studio example, which I haven't said before, so if you drag an undocked one onto a docked one, 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 the feature either, so I might attack that if I ever use the SWC dockers.

Oh yeah. When creating windows, make the final creation occur through gApplication->createWindow(BaseWin * winBeingCreated, 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!

History

For anyone interested in a changelog, the following is a somewhat comprehensive list of modifications:

6.02 - Dec. 9, 2014:

  • As stated earlier, MDI and SDI libraries have been created that are easily used in projects in order to reduce compile times.
  • From here on out, '6.02' and any future version numbers will no longer be reflected in subdirectory structure of working projects, and are for reference only.
  • Changed WinMainO unit to MainAppWin, because the name is more descriptive, and doesn't have a backstory no one besides me will know. gWinMain was changed to gMainWin to supplement this modification.
  • Added the Notes on Building and Laying Out Projects section.
  • Placed an old class that was called Timer, but is now called Timer_Cpp in order to not clash with the Windows timer class I've created, into the Timer file, to give it a proper home. It is in the utils namespace.
  • Changed the previously mentioned Timer class to Timer_Win, so the Timer_Cpp would be a logical name with regard to Timer_Win.
  • Added helpTopic int argument to FloatWindow constructors, and DockWindow::instantiate. Also added wUseDefProcInMdiC = true to DockWindow constructor, and with a couple other modifications the dockers now respond correctly to the F1 key.

6.01 - Oct. 27, 2014:

Major:

  1. GDI objects can now be managed more easily as discussed in the GDI Objects section.
  2. Changed dwl::DockWindows from being a copy of SWC dockers to using a non-tabbable approach where each window is its own container. (This is how MEdit behaved, and now continues to behave, with some improvements!)

Minor:

If you extensively played with an earlier version of DWinLib, the following may interest you. I don't guarantee I documented everything, but the list certainly gives an idea of the effort required to get things working, and the nooks and crannies a good refactoring can require.

  1. Some resource management items were fixed, such as swc::FloatingWindow::drawFrame had an extra delete brush line for some reason.
  2. Removed an extemporaneous stringEnumC from dwl::Strings.
  3. Added older classes that existed in earlier versions. Many were placed into the dwl namespace:
    • dwl::WinDialogs
    • dwl::Scrollbar
    • dwl::IniFile
    • dwl::ModalBase
    • dwl::Button
    • dwl::ComboBox
    • dwl::EditBoxBase
    • dwl::EditIntBox
    • dwl::TextBox
    • dwl::RadioButton
    • dwl::CallbackForwarder
    • dwl::CallbackWin
    • dwl::ProgressBar
    • dwl::WinCriticalSection
    • dwl::RegistryManipulator
    • dwl::ToolTip - This class holds integer commands and strings for tooltip processing.
    • dwl::ToolTips - This class holds a map of integers and strings, for handling tooltip notifications.

      The previous two are used together, to handle tooltip processing for the application. In MEdit, the gDwlMainWin has a ToolTips object, and the toolbar has a vector of ToolTip (no 's') unique_ptrs that populate the ToolTips object. That way, when the tooltips go out of scope (by destroying the toolbar, for instance), the IDs are removed from the processing. Here is a quick bit of code for an example:

      //In the toolbar definition:
         
         class Toolbar : public dwl::ToolbarControl {
         private:
            //...
            std::vector<std::unique_ptr<dwl::ToolTip>> tooltipsC;
            //...
            
            
      //In the corresponding cpp file:
         
            tooltipsC.push_back(make_unique<tooltip>(gWinMain->newPerfCB.id(),
                     _T("Create a new composition")));</tooltip>

      By poking through the code, you can see how this interacts with dwl::BaseWin::wNotify, and dwl::Application::tooltipsC.

    • dwl::Undoer (was 'DwlUndo') - Worth knowing: this is set up for an ::Application unit you must define yourself, and not dwl::Application. (All the core non-UI logic in MEdit is contained in that unit, and it makes sense to me to use the same approach in other applications, but I don't force you to.)
    • utils::Timer
    • utils::DllWrap
  4. Changed all procedures named WindowProc and winProc to windowProc for consistency.
  5. Renamed the following, so the original names could be used by user code:
    • gGlobals to gDwlGlobals
    • gApplication to gDwlApp
  6. Changed MdiWindow to AppWindow. This is a non-DWL core file. In other words, it is application specific for MDI programs. The only example program it affected was the SwcRebarTest, since the other MDI examples used the MdiBaseWin class directly, instead of deriving an AppWindow from it.
  7. Eliminated updateWindow static function approach in MainAppWin/dwl::MainWin. It was a relic of doing the dockers through the DWinLib main window instead of MainAppWin. Added virtual update to dwl::MainWin in its place. Even though the dockers are tied to the main window in the design, they are part of the DWinLib library, as far as the static libs are concerned. In order to keep MainAppWin out of the library dependencies, the virtual function was necessary.
  8. Modified framework so docking containers are passed a pointer to the dock manager, so the dwl::MainWin doesn't have to hold a cd::any dockManagerC member. dwl::MainWin, TabbedWindowContainer and FloatingWindow are only units this affected.
  9. Changed WinMain to _tWinMain, with appropriate LPSTR/LPTSTR changes. (This was an oversight in earlier version.) I believe this makes DWinLib non-compilable as-is under MinW, but am not certain. It shouldn't be difficult to modify for TCHAR usage in that environment?
  10. Changed example projects entry point filenames to reflect project, instead of being the same file as the DwlPwcStudio (which they were pure copies of).
  11. Fixed miscellaneous items, like eliminating 'forcing X to bool' performance warnings that somehow never appeared when compiling before.
  12. Additional minor revisions to swc::DC:
    • Made dcC private, and accessible through operator().
    • swc::DC used to contain a static halfGrayBrush. I eliminated it and created a Brush constructor that takes a Brush::Pattern enum to do the trick. The reason for that is using the old static method required remembering to use delete on the created brush.
  13. Added methods to insert and retrieve images in ImageControls by string instead of by number or resource. These shouldn't be used in an imagelist that was initially set up without strings, although you may be able to if you are VERY CAREFUL. The routines are void ImageControl::addImage(wString str) and int ImageControl::imagePos(wString str).
  14. Modified program entry try/catch blocks to work in an expected manner. There is logically no way to re-enter the app->run() routine at that point, and initial examples didn't reflect that. Also, testing indicated that abort() hung the program unexpectedly for the end user, but exit(EXIT_FAILURE); had an expected abortion result.
  15. Changed tabbed window containers to take vector of ids in constructor instead of allocating in instantiate. This involved a couple changes in the class and at least one example: DwlPwcStudio.
  16. Got the icons straightened out in the examples.
  17. Made it so the project was responsible for naming all resources. Now must pass resource IDs into DWL classes, instead of the DWL classes having a #include 'DwlResources.h in them. Hopefully this makes using them less confusing in the end because the signatures indicate what the class needs.
  18. Used namespaces to their limit, once I figured out how they eliminated the need for awkward names in places. The best example of this is in the MainMenu code: there is a dwl::MainMenu from which the application MainMenu derives from, i.e.: ui::MainMenu. (This may be overkill, as it is not necessary to keep the global namespace so unpolluted, but it is just an example. I have adopted the approach, though, because it makes finding things easier in the Class View for me.)
  19. Also, regarding namespaces, I've taken stuff out of the DwlUtilities file and put them into their own logical files (which are in the utils namespace, such as all the string functions, which are now in StringUtils.h, and placed in the utils::strings namespace.)
  20. The ChildrenEnumerator class was renamed ChildEnumerator.
  21. Made the createWindow calls (now called instantiate) void instead of bool, because as far as I can see, all problems will result in exceptions of some nature, and result in a MessageBox showing the error.
  22. DWinLib originally had height() and width() functions in dwl::Control, but I modified them to be winHeight(), and winWidth(), and added clientHeight() and clientWidth() to be more unambiguous. I suspect that in many cases they are equivalent, for borderless windows, but reading some code made me scratch my head, and then change things so I hopefully won't scratch it again.

    Regarding the last point, when working with scrollbars in MEdit, the easiest way I found in certain cases was to deal with the WINDOWPOS dimensions passed into the window procedure through WM_WINDOWPOSCHANGED, so I added some variables to dwl::BaseWin to hold them. They are wpTopC, wpLeftC, wpWidthC, and wpHeightC. They are accessed through wpTop(), wpLeft(), ..., although they are also protected members so you may use them that way. There were other instances in which they came in helpful, like tooltip sizing, but there are probably other ways to do the same thing as I did in MEdit.

  23. dwl::ControlWin was slightly revamped due to confusion when reading, and trying to fix a bug that appeared when my dwl::MinWin was incorporated to get MEdit working. I believe this was the result of trying to better merge the SWC and DWinLib approaches in control windows, but I've forgotten the details except for it being a few painful hours.
  24. Renamed a vast majority of the createWindow routines instantiate, because it seemed to better describe the situation, and was more applicable to those cases. In other words, while revamping MEdit I modified the program so that the constructors didn't have much that could throw in them, as mentioned in the main article text, and that caused me to call instantiate after those cases, even when they weren't windows. So instantiate became a standard function in my vocabulary to describe what happens after a constructor is called. (dwl::Application still has a createWindow routine in it, which actually takes care of calling CreateWindow.)
  25. In the SWC Refactoring project, I renamed the SwcTabbedContainer class to swc::TabbedMainWindowContainer. It didn't make sense to have SwcTabbedContainer derive from SwcTabbedWindowContainer, and this change better described the situation. That led to less confusion, which is always good. The class was added to DWinLib, and the DwlPwcStudio example was modified to use it correctly for SDI applications. (I used a blank window instead, due to lack of time.)
  26. I broke apart a DockEnum enum that was inherited from SWC, and refactored it into logical enumerations with names other than styleC. My first attempt involved the following, and I thought I was successful:
    enum DrawGripperWhen : uint32_t {
       Docked = 1,
       Floating = 2,
       };
    
    enum DrawBorder : uint32_t {
       //The following must be set to the same as BF_TOP, BF_LEFT, BF_BOTTOM, & BF_RIGHT
       //in order to call ::DrawEdge successfully:
       OnLeft   = 1,
       OnTop    = 2,
       OnRight  = 4,
       OnBottom = 8,
       };
    
       
    //In DwlDockWindow.cpp, constructor:
    //...
                drawBorderC(s_cast<DrawBorder>(OnTop | OnBottom)),
                drawGripperWhenC(s_cast<DrawGripperWhen>(Docked | Floating)),
    //...

    But the logical OR in combination with the static_cast dropped the ball as far as bit twiddling was involved. So the current solution is the following construct:

    //In 'DwlSwcEnums.h':
    
       class EnumBitFieldBase {
          //It is assumed that derived classes will have a public enum in them, that
          //user code can use. For an example, see the following 'DrawGripperWhen'
          //and 'DrawBorder' derivations.
    
          protected:
             DWORD bitFieldC;
    
          public:
             EnumBitFieldBase() : bitFieldC(0) { }
    
             EnumBitFieldBase(DWORD value) : bitFieldC(value) { }
    
             void value(DWORD val) {
                bitFieldC = val;
                }
    
             DWORD value() {
                return bitFieldC;
                }
    
             void operator=(DWORD val) {
                bitFieldC = val;
                }
    
             bool operator|(DWORD val) {
                return (bitFieldC | val) ? true : false;
                }
    
             bool operator&(DWORD val) {
                return (bitFieldC & val) ? true : false;
                }
    
             DWORD operator()() {
                return bitFieldC;
                }
          
             //If the following is uncommented, it will clash with 'bool operator&(DWORD val)'
             //Therefore use the 'operator()()' to compare bit fields, like
             //"drawBorderC() | BF_ADJUST" to send a bit pattern to a function,
             //or "if (drawBorderC & DrawBorder::OnTop)" to do a comparison.
    
             //operator bool() {
             //   return bitFieldC ? true : false;
             //   }
    
          };
    
    
       class DrawGripperWhen : public EnumBitFieldBase {
          public:
             enum {
                Docked   = 0x1,
                Floating = 0x2,
                Force32  = 0x7FFFFFFF
                };
          
             DrawGripperWhen(DWORD val) : EnumBitFieldBase(val) { }
          };
    
    
       class DrawBorder : public EnumBitFieldBase {
          public:
             enum {
                //The following must be set to the same as BF_TOP, BF_LEFT, BF_BOTTOM, & BF_RIGHT
                //in order to call ::DrawEdge correctly:
                OnLeft   = 1,
                OnTop    = 2,
                OnRight  = 4,
                OnBottom = 8,
                Force32  = 0x7FFFFFFF
                };
    
             DrawBorder(DWORD val) : EnumBitFieldBase(val) { }
    
          };
       
       
    //And use it like:
    
       DrawBorder drawBorder(DrawBorder::OnTop | DrawBorder::OnBottom);
       
       ...
       
       if (drawBorder & DrawBorder::OnTop) doSomething();

    It can be a little tedious to use OnTop, OnBottom, Docked, and Floating, but it works.

  27. Fixed a fifth and sixth issue with the rebars that I failed to mention in the text. Actually, the problem was in the image list painting, which didn't react properly to reflect mouse focus, and disabled buttons. I may have introduced them in my recreation of SWC's original logic - I don't know. But they appear to be fixed!
  28. Towards the end of redeveloping DWinLib I noticed the SWC refactored example has a bug where if the Help and Resource containers are added together, then floated (so both of them are in one window), then re-docked to the left side (or any other side, probably), the program will eventually crash.

    Briefly delving into it, the issue is connected to the SWC tooltips. Somehow origProcC becomes corrupted, and ControlWin::windowProc fails when trying to call it at

    return CallWindowProc(win->origProcC, hwnd, msg, wParam, lParam);

    For now I've simply commented out the three lines that instantiate the tooltip. They are in swc::TabbedWindowContainer::wCreate.

    DWinLib had a tooltip mechanism in place before SWC was incorporated, and I am using it in my work. The dwl::Application::tooltipsC, Tooltip and Tooltips classes, and BaseWin::wNotify procedure will give you an idea of how it operates, but basically, the program as a whole has a vector of Tooltips in the Application unit that keeps the necessary strings, and when Windows sends a WM_NOTIFY message requesting the tooltip text, the BaseWin::wNotify procedure initiates the process of returning the string.

    The sub items, such as the toolbar, contain their own vectors of Tooltip (without an 's' at the end), and those tooltip items take care of registering the text with the Tooltips when they are created, and unregistering it at their destruction. As an example, here is the line that handles the 'New File' button in MEdit:

    tooltipsC.push_back(make_unique<ToolTip>(gWinMain->newPerfCB.id(),
                _T("Create a new composition")));
    

    When I have more time I may look into the original issue a bit deeper.

  29. When all was said and done, it wasn't. Everything in MEdit worked fine in debug mode, and the earlier release mode testing I'd done, but when the final touches were in place MEdit triggered a breakpoint after performing some (not all) menu callbacks in release mode (but not debug mode). They weren't breakpoints I'd set; they evidently had something to do with Windows itself.

    After a couple hours of debugging, the problem was discovered to be that I returned DefWindowProc (or DefFrameProc) calls after WM_COMMAND processing. Changing the return value to '0' solved the issue, as Microsoft documented. Why it never showed up in debug mode is perplexing, but I will leave the explanation as 'magic voodoo calls.'

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. 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!

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 1 Pin
bvbfan25-Aug-14 7:10
memberbvbfan25-Aug-14 7:10 
GeneralRe: My vote of 1 Pin
David O'Neil29-Oct-14 10:39
memberDavid O'Neil29-Oct-14 10:39 
GeneralMy vote of 5 Pin
Mihai MOGA13-Aug-14 1:40
professionalMihai MOGA13-Aug-14 1:40 
GeneralRe: My vote of 5 Pin
David O'Neil13-Aug-14 17:23
memberDavid O'Neil13-Aug-14 17:23 
GeneralDWinLib6.zip not available Pin
uniskz6-Jul-14 12:48
memberuniskz6-Jul-14 12:48 
GeneralRe: DWinLib6.zip not available Pin
David O'Neil6-Jul-14 19:16
memberDavid O'Neil6-Jul-14 19:16 

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.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160721.1 | Last Updated 9 Dec 2014
Article Copyright 2014 by David O'Neil
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid