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 DC
s 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
(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.
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.
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 ) {
if (pWnd== NULL)
{
pWnd =reinterpret_cast<CWin*>(lParam);
::SetWindowLong(hWnd,GWL_USERDATA,reinterpret_cast<long> (pWnd));
pWnd->SethWnd(hWnd);
}
}else if( uID == WM_NCCREATE) {
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); 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)
{
if(wParam != 0)
{
CWin* pChild=reinterpret_cast<CWin*>((HWND)
::GetWindowLong(pWnd->GetDlgItem( ((( LPDRAWITEMSTRUCT )lParam)->CtlID) ),
GWL_USERDATA) );
bResult=pChild->OnDrawItem((LPDRAWITEMSTRUCT) lParam );
if(bResult && pWnd->IsDialog())
::SetWindowLong(pWnd->GetSafeHwnd(), DWL_MSGRESULT, ( LONG )bResult);
}
}
else if(uID == WM_NOTIFY)
{
LPNMHDR pNMHDR = ( LPNMHDR )lParam;
CWin* pChild=reinterpret_cast<CWin*>((HWND)
::GetWindowLong(pNMHDR->hwndFrom,
GWL_USERDATA) );
if ( pChild )
{
BOOL bNotify=TRUE;
bResult = pChild->ReflectChildNotify( pNMHDR, bNotify);
if ( pWnd->IsDialog())
::SetWindowLong(pWnd->GetSafeHwnd(), DWL_MSGRESULT, ( LONG )bResult);
if(bNotify)
pWnd->OnNotify(wParam,pNMHDR);
if (bResult != 0)
return bResult;
}
}
if( pWnd->IsDialog())
{
if (bClose)
return 0;
bResult=pWnd->NewMsgProc(hWnd,uID,wParam,lParam,lResult);
if(!bResult)
return pWnd->DefWindowProc(pWnd->GetSafeHwnd(),uID,wParam,lParam);
return 0;
}
else
{
if(bClose )
{
if(lResult)
return pWnd->DefWindowProc(pWnd->GetSafeHwnd(),uID,wParam,lParam);
return 0;
}
bResult=pWnd->NewMsgProc(hWnd,uID,wParam,lParam,lResult);
if(!bResult)
return pWnd->DefWindowProc(hWnd,uID,wParam,lParam);
if (pWnd->IsMDI() )
return pWnd->DefWindowProc(hWnd,uID,wParam,lParam);
}
}
return lResult;
}
Compare that to the following:
LRESULT CALLBACK dwl::Application::winProc(HWND window, UINT msg, WPARAM wParam,
LPARAM lParam) {
try {
BaseWin * win(nullptr);
{ 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));
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 wString
s 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:
Window * win = new Window();
win->instantiate();
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 string
s 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:
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 throw
s in place of asserts, to give a faster clue as to the cause of underlying problems. When I'm debugging a routine I 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:
SwcTabCtrl* tabControl = (SwcTabCtrl*) tabsC[selectedTabC];
Compare that to:
SwcTabCtrl * tabControl = tabsC[selectedTabC];
Eliminating the redundancy of the necessary casts in the first approach made it easier to swim through the code, because casts always interrupt my train of thought and make me investigate them in a skeptical light. They are dangerous, and demand attention.
- I eliminated all
if (HIWORD(someWindowClassPointer) == NULL)
testing (which DWinLib
never had). For instance, SwcTabbedWindowContainer::GetNumWnd
contains these two lines:
SwcBaseWin* pw=((SwcTabCtrl*) tabCtrlPtrArrayC [tabNumber])->parentC;
if (HIWORD(pw)== NULL) return NULL;
Similar testing was used throughout SWC, and I never understood why he didn't just do a if (!pw) return NULL;
in its place. (Maybe it had something to do with the LOWORD
memory being reserved by the system? But even in that case the HIWORD
check is unnecessary, because Windows should never send messages that cause you to test for system logic in areas such as this, to my knowledge.)
- Two other major improvements have been mentioned below the zip files:
- All of the examples can be compiled as either
UNICODE
or Multi-Byte
programs. SWC could only be compiled as a Multi-Byte
program. - If you want an MDI program instead of an SDI program, perform actions like those given in Using Libraries.
- I don't believe many of the bugfixes made it to the SWC refactoring example. For instance, the mouse could be dragging a docker way beyond the right side of the main window and the window would still be attempting to dock, as indicated by the window outline on the main window. In fact, it would dock when the mouse was released. I've modified this so the mouse must be close to the window edge for docking to occur.
- Another, more sinister bug is seen if you delete all the dockers along one edge of the window using the 'X' buttons. Then move another docker over that edge: there won't be any more dock action there until the program is rebooted. I've fixed this in my examples.
- You should be able to easily find any function body in
DWinLib
. As an experiment in the original SWC codebase, try to determine where CMiniDock::OnLButtonUp
is implemented without using the Class Explorer or a global search. Not so easy! And that is only one example of many where definitions are not where you would expect them to be. - One last bugfix is all the dockers were leaked at program termination. No destructors were ever called. That isn't a critical bug, as Windows will reclaim the memory at that point anyway, but it is not a good coding habit to get into. There were additional resource leaks I squashed, although I don't guarantee I got them all. (As an example, the
CReBarCtrlEx::OnPaint
is crafted in a way that never terminates the BeginPaint
call in CPaintDC
's constructor.)
Speaking of leaking dockers, an interesting construct was used to overcome the issue.
During the rewrite, I adopted the SWC method of having an 'idle' loop in the program. Of course, such endeavors are never perfectly straightforward (although it wasn't very difficult, either). Here's the pertinent part of the code:
#ifdef DWL_MDI_APP
HWND mdiClient = gDwlMainWin->mdiClientHwnd();
HWND hwnd = gDwlMainWin->hwnd();
accelTableC = accelC.table(); while ((var = GetMessage(&msg, NULL, 0, 0)) != 0) { 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;
while (idleC) idleC = wIdle();
}
}
}
#else
while ((var = GetMessage(&msg, NULL, 0, 0)) != 0) {
if (var == -1) return var;
TranslateMessage(&msg);
DispatchMessage(&msg);
if (!::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE)) {
idleC = true;
while (idleC) idleC = wIdle();
}
}
#endif
bool Application::wIdle() {
bool result = false;
iteratorInvalidatedC = false; 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;
}
++it;
}
return result;
}
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));
BOOL bPre=TRUE;
if (bresult && bPre)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
virtual BOOL OnIdle() {
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
:
void Idler::addToIdleList(dwl::BaseWin * win) {
auto it = std::find(windowsToIdleC.begin(), windowsToIdleC.end(), win);
if (it != windowsToIdleC.end()) return; 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:
LRESULT swc::FloatingWindow::wNcDestroy() {
if (!beingDestroyed()) {
needToDestroyC = true;
}
return 0;
}
bool swc::FloatingWindow::wIdle() {
if (needToDestroyC) {
delete this;
}
return false; }
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_ptr
s, 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:
class DC {
private:
enum Type { UseBeginPaint, UseGetDC, UseCreateCompatibleDC, Unspecified };
Type typeC = Unspecified;
HWND hwndC; PAINTSTRUCT * psC; 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) {
}
}
HDC operator()() { return dcC; }
HFONT setFont(HFONT font, DeleteAction deleteAction) {
if (fontC.get()) {
HFONT oldFont = (HFONT) SelectObject(dcC, fontC->oldFontC);
}
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()) {
HPEN oldPen = (HPEN) SelectObject(dcC, penC->oldPenC);
}
penC.reset(new swc::Pen(pen, deleteAction));
penC->oldPenC = (HPEN) SelectObject(dcC, penC->penC);
return penC->oldPenC;
}
HBRUSH setBrush(HBRUSH brush, DeleteAction deleteAction) {
if (brushC.get()) {
HBRUSH oldBrush = (HBRUSH) SelectObject(dcC, brushC->oldBrushC);
}
brushC.reset(new swc::Brush(brush, deleteAction));
brushC->oldBrushC = (HBRUSH) SelectObject(dcC, brushC->brushC);
return brushC->oldBrushC;
}
HBITMAP setBitmap(HBITMAP bitmap, DeleteAction deleteAction) {
if (bitmapC.get()) {
HBITMAP oldBitmap = (HBITMAP) SelectObject(dcC, bitmapC->oldBitmapC);
}
bitmapC.reset(new swc::Bitmap(bitmap, deleteAction));
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 DeleteObject
ed 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.
LRESULT swc::DockManagerWindow::wPaint(DC & dc) {
Brush brush(CreateSolidBrush(dwl::colors::windowFace()), DoDelete);
Rect clientRect = getClientRect();
DC memDC(dc);
Bitmap memDcBitmap(CreateCompatibleBitmap(dc(), clientRect.width(), clientRect.height()),
DoDelete);
memDC.setBitmap(memDcBitmap(), DontDelete);
memDC.fillRect(&clientRect, &brush);
dc.bitBlt(0, 0, clientRect.width(), clientRect.height(), memDC(), clientRect.left,
clientRect.top, SRCCOPY);
return TRUE;
}
BOOL DockManager::OnPaint(HDC hDC) {
CRect rcClient;
CPaintDC dc(GetSafeHwnd()); 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);
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 DeleteObject
s 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 DeleteObject
ed 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:
class DC;
namespace swc {
enum DeleteAction { DoDelete, DontDelete };
class Font {
friend class DC;
private:
HFONT fontC;
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.
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();
}
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();
}
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) {
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 friend
ship was a cleaner solution. This is probably the third time I've ever used friend
s in any production code, which tells you how seldom I find the construct useful.
One last item to point out in this regard is I used operator()
to return the items in the fonts, pens, DC, and such. Going through Francisco's code I found an implicit conversion operator I'd never seen before. For the case of implicitly converting an instantiation into its HPEN
member, it looks like this:
operator HPEN() { return penC; }
Even though it provides a convenience, I shy away from implicit conversions of any type. They have bitten me before. I want to be able to tell when functions are being called by looking at the screen:
HPEN pen = theSwcPen();
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:
="1.0"="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[
</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:
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
:
LRESULT MainAppWin::winProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) {
if (msg == UWM_YOU_ARE_ME) return UWM_YOU_ARE_ME;
else if (msg == WM_COPYDATA) {
COPYDATASTRUCT *cds = (COPYDATASTRUCT*)lParam;
if(cds->dwData == UWM_YOU_ARE_ME) { openMultiFiles(wString((TCHAR*)cds->lpData));
return 0;
}
}
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 winProc
s, because that will mess up the internals in DWinLib
itself. But creating a window with Undo
will work as expected.
Logging
If you need to troubleshoot something, and want to capture a bunch of stuff without the tedium of breakpoints, DWinLib
has a rudimentary logging system. To use it,
- Uncomment the
#define DWL_DO_LOGGING
line in PrecompiledHeaders.h for both the library and the main project. (In the future, this can be modified to only affect the main program if desired, but the logger is instantiated in dwl::Application
as it currently stands, and this allows logging within DWinLib
itself if you need to chase items down in it.) - At the top of the .cpp file(s) you wish to capture things in, add the following:
#if defined (DWL_DO_LOGGING)
#include "DwlLogger.h"
extern dwl::Logger * gLogger;
#endif
- At the point(s) logging is required, place code similar to this:
#ifdef DWL_DO_LOGGING
std::tstringstream str;
wString space = _T(" ");
str << _T("Msg: ") << msg;
gLogger->padStream(str, 10);
str << space << gLogger->crack(msg);
gLogger->padStream(str, 34);
str << _T("hwnd: ") << (win) << _T(", wParam: ") << wParam;
gLogger->padStream(str, 70);
str << _T("lParam: ") << lParam;
gLogger->log(str);
#endif
As can be guessed, the previous will log all of the messages sent to a 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:
gLogger =
new Logger(_T("C:\\Users\\David\\Documents\\Programs\\MyProgs\\curLog.txt"));
You can also change the log location at runtime by using the following code in MainAppWin
, after the dwl::MainWin::instantiate
method has been called:
#if defined DWL_DO_LOGGING
if (!gDialogs->openDialog(wString(_T("Log File:\0*.txt\0\0"), 18),
_T("txt"), OFN_HIDEREADONLY)) return;
gLogger->changeFile(gDialogs->fileName());
#endif
- Then run the program and recreate the circumstances you wish to test. If you do multiple runs and a viewing after each one, I recommend Notepad++ because it will prompt you that the file changed. That saves a lot of reloading tedium. And, in my opinion, Notepad++ is an indispensable tool you should know about anyway.
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 DC
s 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
- GDI objects can now be managed more easily as discussed in the GDI Objects section.
- 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.
- Some resource management items were fixed, such as
swc::FloatingWindow::drawFrame
had an extra delete brush
line for some reason. - Removed an extemporaneous
stringEnumC
from dwl::Strings
. - 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_ptr
s 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:
class Toolbar : public dwl::ToolbarControl {
private:
std::vector<std::unique_ptr<dwl::ToolTip>> tooltipsC;
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
- Changed all procedures named
WindowProc
and winProc
to winProc
for consistency. - Renamed the following, so the original names could be used by user code:
gGlobals
to gDwlGlobals
gApplication
to gDwlApp
- 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. - 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. - 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. - 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? - Changed example projects entry point filenames to reflect project, instead of being the same file as the
DwlPwcStudio
(which they were pure copies of). - Fixed miscellaneous items, like eliminating 'forcing X to bool' performance warnings that somehow never appeared when compiling before.
- 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.
- Added methods to insert and retrieve images in
ImageControl
s by string
instead of by number or resource. These shouldn't be used in an imagelist
that was initially set up without string
s, 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)
. - 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. - 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
. - Got the icons straightened out in the examples.
- 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. - 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.) - 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.) - The
ChildrenEnumerator
class was renamed ChildEnumerator
. - 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. 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
.
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. - 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
.) - 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.) - I broke apart a
DockEnum
enum
that was inherited from SWC, and refactored it into logical enumerations with names other than styleC
. My first attempt involved the following, and I thought I was successful:
enum DrawGripperWhen : uint32_t {
Docked = 1,
Floating = 2,
};
enum DrawBorder : uint32_t {
OnLeft = 1,
OnTop = 2,
OnRight = 4,
OnBottom = 8,
};
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:
class EnumBitFieldBase {
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;
}
};
class DrawGripperWhen : public EnumBitFieldBase {
public:
enum {
Docked = 0x1,
Floating = 0x2,
Force32 = 0x7FFFFFFF
};
DrawGripperWhen(DWORD val) : EnumBitFieldBase(val) { }
};
class DrawBorder : public EnumBitFieldBase {
public:
enum {
OnLeft = 1,
OnTop = 2,
OnRight = 4,
OnBottom = 8,
Force32 = 0x7FFFFFFF
};
DrawBorder(DWORD val) : EnumBitFieldBase(val) { }
};
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.
- 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!
- 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:
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 string
s, and when Windows sends a WM_NOTIFY
message requesting the tooltip text, the BaseWin::wNotify
procedure initiates the process of returning the string
.
The sub items, such as the toolbar, contain their own vectors of Tooltip
(without an 's' at the end), and those tooltip items take care of registering the text with the Tooltips
when they are created, and unregistering it at their destruction. As an example, here is the line that handles the 'New File' button in MEdit
:
tooltipsC.push_back(make_unique<ToolTip>(gWinMain->newPerfCB.id(),
_T("Create a new composition")));
When I have more time, I may look into the original issue a bit deeper.
- 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.'