Click here to Skip to main content
14,977,821 members
Articles / Desktop Programming / Win32
Article
Posted 6 Jul 2014

Stats

54.8K views
2.7K downloads
62 bookmarked

DWinLib 6: Pretty WinAPI Incorporation

Rate me:
Please Sign up or sign in to vote.
4.94/5 (28 votes)
17 Jan 2021CPOL59 min read
My DWinLib Windows API wrapper combined with Francisco Campos's Pretty WinAPI framework
In this article, you will learn about my DWinLib Windows API wrapper combined with Francisco Campos's Pretty WinAPI framework.

Summary of Last Revision

January 16, 2021: DWinLib 6.04: Numerous changes made while adding a simple binaural beat creation mechanism to my MIDI sequencing program. Cleaned up code, added lambda functions to callback mechanism, improved menus, greatly simplified error string unit, added scrollbars that can have customized colors (not included in DWinLib directories, but can be found in the fractalBrowser example), and revamped dwl::ControlWin controls to all use same base winProc, which greatly simplified them and made it easy to add events if wanted. (For an example, see how onKillFocusC is used in EditBoxBase::wKillFocus.) Placed some swc items like DCs into global namespace to eliminate keystrokes in function signatures.

Index

Introduction

Throughout the last eight months, I was fortunate to be able to revisit DWinLib, the Windows wrapper I started working on many years ago. Like other wrappers, DWinLib makes coding stand-alone Windows programs much more pleasurable than creating them through pure Window's API calls.

There are many wrappers now available, and even though the subject is kind of old-school at this point, I figured I'd update this article and my other DWinLib writings to make them a cohesive whole for anyone who is curious about going down this type of rabbit hole. Also, DWinLib has some aspects that may be interesting to you.

To give a little background, I began seriously coding with Borland Builder 4.0, long ago, and when my project blew up, I spent many days (weeks?) trying to figure out why it was dying a cruel death. Even without that symptom, though, BCB often died under different circumstances, which perplexed me to no end. Eventually, I knew I had to change, and since Visual Studio Express was free back then, I went with it. Rather than try to learn MFC, or another framework, I decided to learn the basics, to ensure that I no longer had to wonder if I was doing something wrong or if the fundamental problem was with other people's code.

Along the way, I created some things that 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. (For example, try the DwlDockWork project in the example projects below, resize the window from the left-hand side, and notice how smooth the window resizes with the dockers on either the left or right. Then compare that to Visual Studio, which jiggles insanely. Even my more demanding projects are super smooth.)

But my original 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 downloads are 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

Image 1

Image 2

Image 3

Image 4

(The SwcRebarTest project (captioned "DWinLib Test") is included for anyone who wishes to challenge themselves. The 6.03 code version ran, although there was a bug in the code I never got around to fixing because I never used rebars in my own work. The current code compiles, but there is a new bug I never delved into. If you need a rebar control, maybe the code gets you started, but the rebar class would benefit by being rewritten from scratch.)

In addition to these examples, I refactored much of Francisco's codebase, and you might 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.

Image 5

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 SdiNoDocks 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 Visual Studio's configuration for the SdiNoDocks project, change the Configuration Properties -> C/C++ -> Preprocessor Preprocessor Definitions from 'DWL_SDI_APP' to 'DWL_MDI_APP'. Do the same for the Dwl_SDI_Unicode project.
  • Right-click the 'SdiNoDocks' 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 'SdiNoDocks' (2 projects)" line in the Solution Explorer, and make the SdiNoDocks 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 SdiNoDocks, 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.

One last thing to note about building DWinLib projects. I wasn't able to compile any project in the new default Visual Studio 'Conformance mode' setting (as of 2017) (Properties -> Configuration Properties -> C/C++ -> Language -> Conformance mode). The main reason is DWinLib allows you to use MCBS or Unicode, and there are many instances of passing TCHAR* around. In those locations, Visual Studio complains about not being able to convert to wchar_t*, especially when the string is being passed in on the fly as an array. Therefore, Conformance mode was changed back to 'No.'

Differences in Approach

Before beginning, if you wish to familiarize yourself with DWinLib's core design, it is outlined here. This article will concentrate on the revisions of DWinLib and SWC to work together, which 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.

C++
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:

C++
LRESULT CALLBACK dwl::Application::winProc(HWND window, UINT msg, WPARAM wParam,
            LPARAM lParam) {

   try {
      BaseWin * win(nullptr);
      {  //Scope the iterator to eliminate the possibility that the windowproc
         //deletes the window and causes the iterator to become invalid,
         //which will crash the program
         auto it = gDwlApp->windowsC.find(window);
         if (it != gDwlApp->windowsC.end()) win = it->second;
         }
      if (win) return win->winProc(window, msg, wParam, lParam);
      else {
         BaseWin * tempWin = static_cast<BaseWin*>(TlsGetValue(gDwlApp->tlsIndexC));
         gDwlGlobals->dwlApp->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->winProc(window, msg, wParam, lParam);
         }
      }

   catch (Exception & e) {
      wString str = dwl::strings::msgProgramming() + e.strC;
      str += dwl::strings::msgPleaseReport();
      if (e.continuableC == Continuable::True)
         str += dwl::strings::msgWillAttemptContinue();
      else str += dwl::strings::msgProgramMustExit();
      MessageBox(gDwlGlobals->dwlMainWin->hwnd(), str.c_str(),
                  dwl::strings::msgError().c_str(), MB_OK);
      if (e.continuableC == Continuable::False) exit(EXIT_FAILURE);
      }
   catch (std::exception & e) {
      wString str = dwl::strings::msgPleaseReport();
      str += _T("\r\n");
      str += dwl::strings::stdException();
      str += _T("\r\nError: ");
      str += utils::strings::convertToApiString(e.what());
      str += _T("\r\n");
      str += dwl::strings::stdExceptionAbortQuery();
      int wish = MessageBox(gDwlGlobals->dwlMainWin->hwnd(),
                  str.c_str(), dwl::strings::msgError().c_str(), MB_YESNO);
      if (wish == IDYES) exit(EXIT_FAILURE);
      }
   catch (...) {
      wString str = dwl::strings::msgUnknownException();
      str += dwl::lastSysError();
      str += dwl::strings::msgUnknownExceptionAbortQuery();
      int wish = MessageBox(gDwlGlobals->dwlMainWin->hwnd(), str.c_str(), 
                  dwl::strings::msgError().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.

(In the 6.04 overview, I stated that the error string unit had been greatly simplified. Before, the exception wStrings had been kept in a class using a map with an enum as its base. Eliminating them for pure static functions was a refactoring well worth making.)

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 during the 6.03 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:

C++
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.

Above, I parenthesized a 6.04 error string comment, about how an ugly approach storing those strings had been eliminated. To add to that comment, I believe the original thought behind the enum and associated tom-foolery was to make it simple down the road to store all the error strings in a DLL, to simplify internationalization. It is still easy to transform them into a DLL - just change the DwlStrings unit into a DLL and change the function signatures via a find/replace in that unit. To give you an idea as to the work involved, here's a small snippet of the DwlStrings.h file:

C++
namespace dwl {

   namespace strings {
      static wString buttonCreationFailure() { return _T("Button: Creation failure"); }

      static wString checkBoxCreationFailure() { return _T("CheckBox: Creation failure"); }
      ...
      ...

(I should also mention you may be interested in Michael Haephrati's String Obfuscation System if you wish to extend this method and 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. DWinLib itself contains a global unit which has pointers to the MainWin and Application, and is also in charge of creating and destroying a wrapper for Window's dialogs. The MIDI program I developed has its own globals unit in another namespace which is 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 it used to. 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. After using it for awhile, a few of his creations were placed in the global namespace because it was tiring to type swc::DC all the time. 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 haven't had the time and inclination. My coding priority has always been my own project, and now that DWinLib is robust enough to take it on in its new form, I doubt I'll ever get to the other windows shown on the Pretty WinAPI Class page. Feel free to do so if you wish and either notify me or post the code to CodeProject.

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'. I've also started using unit-local variables more often, and I append 'U' to them. 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:
    C++
    SwcTabCtrl* tabControl = (SwcTabCtrl*) tabsC[selectedTabC];

    Compare that to:

    C++
    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:
    C++
    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:

C++
//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.  An old link for this statement is dead.
         //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. I have modified this in the 6.04 rewrite, by adding an Idler unit to the Application:

C++
void Idler::addToIdleList(dwl::BaseWin * win) {
   auto it = std::find(windowsToIdleC.begin(), windowsToIdleC.end(), win);
   if (it != windowsToIdleC.end()) return;   //Already in list, no need to insert again
   windowsToIdleC.push_back(win);
   }

void Idler::wIdle() {
   bool idleProcessingNeeded = true;
   auto it = windowsToIdleC.begin();
   std::list<std::list<dwl::BaseWin*>::iterator> itsToRemove;
   while (!windowsToIdleC.empty()) {
      while (it != windowsToIdleC.end()) {
         bool itHasMoreIdleProcessingToDo = (*it)->wIdle();
         if (!itHasMoreIdleProcessingToDo) itsToRemove.push_back(it);
         ++it;
         }
      while (!itsToRemove.empty()) {
         windowsToIdleC.erase(itsToRemove.back());
         itsToRemove.pop_back();
         }
      }
   }

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:

C++
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!

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 (it is now just DC as of 6.04), because that abbreviation better describes what the class is wrapping. 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:

C++
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.

C++
//DWinLib version of Dock Manager Window painting:
LRESULT swc::DockManagerWindow::wPaint(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:

C++
//Forward declare the DC class:
class DC;

namespace swc {
   enum DeleteAction { DoDelete, DontDelete };

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

C++
//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:

C++
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:

C++
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 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 dwl::Control::childControlsC vector.)

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:

XML
<!-- 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 existed in the 6.03 rebar test, so I suspect they also affect the 6.04 version, although, as I said earlier, the rebar example is not working, and was left in as an exercise for anyone who feels like playing.

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:

C++
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.

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

(Playing with this some more, creating a dialog through Visual Studio's dialog editor, and calling it in code is not a terribly big deal. Dialog windows get their own window procedure, so they don't rely on DWinLib's internals in any way. They are still a pain because you must do a lot of work to set up the IDs in the header files correctly. The earlier 'dialog box' article I mentioned has code showing how to accomplish this.)

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 winProc 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 winProc, and adding handlers for the additional messages you wish to respond to, and finally call the original winProc for the original message handling.

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

    C++
    LRESULT MainAppWin::winProc(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::winProc(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::winProc, 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 winProc, 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::winProc entirely overrides the dwl::BaseWin::winProc.)

    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. 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 winProcs, 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:
    C++
    #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:
    C++
    #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 winProc. (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:
    C++
    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:

    C++
    #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.

For a large part of my programming work, I used logging to figure out many things. Eventually, I realized that OutputDebugString is almost always a faster debugging technique. Here is a snippet that reduces the tedium of its calls:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
	<CodeSnippet Format="1.0.0">
		<Header>
			<Title>DebugOut</Title>
			<Shortcut>debugout</Shortcut>
			<Description>Send something to OutputDebugString</Description>
			<Author>Me, Myself, and I</Author>
			<SnippetTypes>
				<SnippetType>Expansion</SnippetType>
				<SnippetType>SurroundsWith</SnippetType>
			</SnippetTypes>
		</Header>
		<Snippet>
			<Declarations>
				<Literal>
					<ID>expression</ID>
					<ToolTip>Item to output</ToolTip>
					<Default></Default>
				</Literal>
			</Declarations>
			<Code Language="cpp">
				<![CDATA[std::wstringstream str;
				str << "$expression$" << std::endl;
				OutputDebugString(str.str().c_str());$end$
				]]>
			</Code>
		</Snippet>
	</CodeSnippet>
</CodeSnippets>;

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 gDwlGlobals->dwlApp->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!

DWinLib Alternatives

Windows Specific Products

  • .NET - Microsoft’s masterpiece, programs written in it are interpreted at runtime to execute. This requires .NET to be installed. As far as I know, for the greatest productivity with it you need Visual Studio, although Borland once had a programming environment oriented around it, which seems to have been inherited by Embarcadero Technologies. Mono is another option for creating and executing .NET code.
  • MFC - The grandmother of all frameworks (almost). To use MFC on a real-world app requires the (discontinued?) $300 version of VS, in order to get the resource editor. If you want to statically link MFC, you will need the $800 version of VS. (I’m uncertain about these prices, because Visual Studio’s price structure changed with the 2010 edition, and the standard edition no longer seems to be offered.)
  • Visual Class Library (VCL) - At one time, you could get Borland’s Personal Edition for $50, but no more. The lowest entry price seems to be $199. Borland left a bad taste in my mouth once (and is the reason for the birth of DWinLib). I won’t let it happen again. (Embarcadero seems to really care about their product now, so disregard my bitchiness if that route intrigues you. C++ Builder was very fun to program in, and I’m glad it was my first C++ tool.)

Windows Specific Frameworks

  • Visual Component Framework (VCF) - Jim Crafton and his crew put a phenomenal amount of work into this. Threading libraries, graphics libraries, and a whole slew of other stuff are included. There are even hints of a Rapid Application Development framework. It is also cross-platform compatible. But I couldn’t find much of an overview on its inner workings.
  • Relisoft’s Generic Windows Wrapper - One of the first places I learned about creating Windows API wrappers.
  • Oluseyi Sonaiya’s Window Wrapper - Another resource.
  • David Nash’s SDI Framework - Nash’s contribution to this field is impressive, since his creation now handles MDI! Also, David is a great guy, and I’m not saying that just because of my familiarity with the first name! (He helped me come to grips with the intricacies of Windows’ LRESULT data type, as well as pointing out in a big way what was needed to make DWinLib cross-compiler compatible. I.E., he did the initial port from Borland C++ Builder to Visual Studio, for which huge thanks are given.)
  • Win32 Generics wrapper - but the site doesn’t appear to show you how it is accomplished. In addition, high-level template magic is required to perform the vast majority of its tricks.
  • WTL - For those who can’t get enough of templates. Its supporters sing great praises.

(I believe all of the Windows specific frameworks listed require you to assign and process control IDs yourself if you want to handle child controls. Or you will need to develop your own solution to do this in an easier manner. DWinLib does not have that requirement. I may be wrong regarding WTL.)

Cross-Platform

  • GTK - This drastically evolved since my first perusal, because I once said their example is not ‘objectified’ in the OOP meaning of the word. I can’t say that any longer.
  • wxWidgets - I like this better than most, as the design appears to be a fairly clean. Unfortunately, I ran some programs based upon this long ago, and a few of the ‘controls’ did not work as I expected them to. When I contacted the author, I was told the problem was with wxWidgets which couldn’t be overcome. I do not know if those quirks have been fixed, but due to the time that has passed, I suspect they have been.)
  • Qt - From my reading, this is one of the cleanest alternatives, but you must link to Qt DLLs in order to use the LGPL license. All other licensing options are costly, unless you make your program entirely open-source.

History

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

6.04 - January 16, 2021

  • Numerous changes made while adding a simple binaural beat creation mechanism to my MIDI sequencing program. Cleaned up code, added lambda functions to callback mechanism, improved menus, greatly simplified error string unit, added scrollbars that can have customized colors (not included in DWinLib directories, but can be found in the fractalBrowser example), and revamped dwl::ControlWin controls to all use same base winProc, which greatly simplified them and made it easy to add events if wanted. (For an example, see how onKillFocusC is used in EditBoxBase::wKillFocus.) Placed some swc items like DCs into global namespace to eliminate keystrokes in function signatures.

6.03 - Dec 5, 2017

  • Minor bug update. Did not keep DWinLib changelog while updating MEdit, but I know several small bugs were eliminated. Was able to recompile all example programs without any changes.

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:

      C++
      //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 winProc 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:
    C++
    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:

    C++
    //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::winProc fails when trying to call it at:

    C++
    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:

    C++
    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. If you want to understand the astronomic investigations of our priests 3,000 years ago, LATD is the book to turn to. It opens up the thoughts that pushed them away from their earlier polytheism and towards our current definition of God.

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

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

Comments and Discussions

 
Questionx64 compilation error Pin
Buddhadeva Das28-Jul-21 7:34
MemberBuddhadeva Das28-Jul-21 7:34 
AnswerRe: x64 compilation error Pin
David O'Neil28-Jul-21 14:12
professionalDavid O'Neil28-Jul-21 14:12 
QuestionVB.net use Pin
gwittlock18-Jan-21 10:18
Membergwittlock18-Jan-21 10:18 
AnswerRe: VB.net use Pin
David O'Neil22-Jan-21 8:43
professionalDavid O'Neil22-Jan-21 8:43 
Questionhm, .... Pin
TomBengsch7-Dec-17 14:46
MemberTomBengsch7-Dec-17 14:46 
AnswerRe: hm, .... Pin
Rick York7-Dec-17 16:40
mveRick York7-Dec-17 16:40 
GeneralVery impressive Pin
Satervalley6-Dec-17 15:37
MemberSatervalley6-Dec-17 15:37 
GeneralRe: Very impressive Pin
David O'Neil6-Dec-17 18:43
professionalDavid O'Neil6-Dec-17 18:43 
GeneralRe: Very impressive Pin
Satervalley6-Dec-17 22:13
MemberSatervalley6-Dec-17 22:13 
GeneralRe: Very impressive Pin
Rick York7-Dec-17 7:19
mveRick York7-Dec-17 7:19 
QuestionDerivation Paradigm Pin
Rick York6-Dec-17 11:51
mveRick York6-Dec-17 11:51 
AnswerRe: Derivation Paradigm Pin
David O'Neil6-Dec-17 13:05
professionalDavid O'Neil6-Dec-17 13:05 
GeneralMy vote of 1 Pin
bvbfan25-Aug-14 7:10
Memberbvbfan25-Aug-14 7:10 
GeneralRe: My vote of 1 PinPopular
David O'Neil29-Oct-14 10:39
professionalDavid O'Neil29-Oct-14 10:39 
GeneralRe: My vote of 1 PinPopular
Cristian Amarie21-May-17 21:18
MemberCristian Amarie21-May-17 21:18 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA13-Aug-14 1:40
professionalȘtefan-Mihai MOGA13-Aug-14 1:40 
GeneralRe: My vote of 5 Pin
David O'Neil13-Aug-14 17:23
professionalDavid 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
professionalDavid 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.