style="background-repeat: no-repeat; background-position: right top; width: 25px; height: 20px;">
style="background-repeat: repeat-x; background-position: left top; height: 20px;">  
style="background-repeat: repeat-y; background-position: left top; width: 13px">  

It took almost a week of work to get James' docking toolbar tutorial integrated into DWinLib.  Let me take that back.  It took almost three weeks.  Then there was more putzing around to refine it.

You may think that three weeks was a very prolonged time to change C code into C++, and I tend to agree.  The three weeks gives you something I haven't seen anywhere else, though: FLICKER-FREE DRAWING OF DOCKING WINDOWS DURING RESIZING!  (Maybe I should make that an annoying flashing text.)

The first week was used to produce an operating version of James' docking windows in C++.  I made some stupid assumptions, and that caused me to start over once.  Talk about frustrated.  But I learned a lot (as if that is any consolation).

To see what was gained by my efforts, try any of the big applications with the groovy docking toolbar interfaces. You will be amazed at how the interior controls flicker when the window is resized, especially during resizing from the left edge of the window when there are windows attached to the right side of the application.  Paint Shop Pro, Cool Edit (now Adobe Audition), and one of my new faves, Real Draw, are some of the ones that spring to my mind.  I love those programs, but the snap-in docking system they used could definately be improved in this aspect.

The secret to eliminating this flicker is returning 0 for the WM_ERASEBKGND message, and doing all of the dock window and parent window painting during the WM_WINDOWPOSCHANGED message.  Another thing to be aware of is that the main window must have 'CS_VREDRAW | CS_HREDRAW' specified in the WNDCLASS structure.  This is not necessary for the DockWindowParent and the DockWindowChild classes, but I would add it to them anyway.  (I haven't tested, but the subchilds of the docking windows may not be updated if you don't.)

Oh - there is one other point to be aware of.  The flicker will reappear if you use standard window controls inside your dockers.  James' version used an edit control, a TreeView, and a toolbar.  The flicker came back when I tried those in my wrapper.  It was not as bad as it was in James' version, but was still quite noticable.  I thought this might be eliminatible by subclassing those controls, capturing the WM_ERASEBKGND message, and forwarding the WM_WINDOWPOSCHANGED message to the pre-existing WM_PAINT handler.  Unfortunately, that didn't work.

The StupidSquares example will probably use a modified version of the following code, as doing that exercise will highlight some things the following is probably missing.  The following will not have all the snazzy frame windows and logic that the aforementioned programs have, but it will be much easier to add them from the state of the following code than it would be to make the entire framework form scratch.  Also, the StupidSquares example will show the flicker that results from using standard Window's controls inside the docking windows.  It will be done that way in order to get it up quickly.  Feel free to play around and try to eliminate that flicker.  I tried and was unable.  (I subclassed the window's control procedure and called the WM_PAINT message handler from the WM_ERASEBKGND handler.  It didn't work.)

For simplicity on my part, I'll simply include the main unit of the project right under this, and before the commentary.  Here is a link directly to the commentary.

Here is a zip file of the entire project (171 kb), with an executable you can play with to see what has been gained.

Main entrance point to the project:
dockWindowsProj.cpp
#include "DWinLib.h" #include "ProgramHeaders.h" #pragma hdrstop #include "WinHidden.h" #include "WinMainO.h" #include "WinDockParent.h" #include "WinDockChild.h" #include <commctrl.h> #include <condefs.h> //--------------------------------------------------------------------------- USEUNIT("WinMainO.cpp"); USEUNIT("MenuMain.cpp"); USEUNIT("DWinLib\WinHidden.cpp"); USEUNIT("DWinLib\WinDockChild.cpp"); USEUNIT("DWinLib\Application.cpp"); USEUNIT("DWinLib\WinBaseO.cpp"); USEUNIT("DWinLib\WinCallBackList.cpp"); USEUNIT("DWinLib\WinAccelerator.cpp"); USEUNIT("DWinLib\WinControl.cpp"); USEUNIT("DWinLib\WinDC.cpp"); USEUNIT("MyUtilities.cpp"); USEUNIT("DWinLib\WinMenu.cpp"); USEUNIT("DWinLib\WinDockParent.cpp"); //--------------------------------------------------------------------------- #pragma argsused WINAPI WinMain(HINSTANCE inst1, HINSTANCE inst2, LPSTR str, int show) { INITCOMMONCONTROLSEX ice; ice.dwSize = sizeof(ice); ice.dwICC = ICC_TREEVIEW_CLASSES | ICC_BAR_CLASSES; InitCommonControlsEx(&ice); /**/ Application app(inst1, inst2, str, show); app.addRegistrationFunc(WinHidden::winRegClass); app.addRegistrationFunc(WinMainO::winRegClass); app.addRegistrationFunc(WinDockParent::winRegClass); app.addRegistrationFunc(WinDockChild::winRegClass); if (!app.init()) return 0; return app.run(); }
Commentary

Here is a quick link to the zip file of the entire project (171 kb).  Now, on to the notes.

The zip file includes the entire DWinLib as it existed after finishing this example.  I don't guarantee the code to the controls that I haven't used in awhile, but I believe most of it still plugs directly into DWinLib without modification.

The following code is an old unit that directly achieved some of the functionality in James' original code.  I am including it here so that you can see some tricks of DWinLib, and the ease that DWinLib enables you to accomplish some things.  The three or so paragraphs following the code discuss some of the finer details of this code, and will help you understand some of how DWinLib was designed.  I took that unit out of the above units during my redesign of his codebase, but during review of this page I decided to leave the original commentary in this HTML.  You can easily modify 'WinMainO::newWindow(WinObject * )' function to create one of these windows and see how it works.  (You will have to modify the following code, as the ctor arguments are not enough to initialize WinDockChild appropriately.  The hazards of old code...)

Old MyDocker3 unit. This unit recreates one of James' docking windows in the DWinLib framework.
MyDocker3.h
#ifndef MyDocker3H #define MyDocker3H #include "WinDockChild.h" #include "WinCallBackList.h" class MyDocker3 : public WinDockChild { private: HWND editC; WNDPROC origProcC; WinCallBackItem buttonCallBack; public: MyDocker3(WinDockParent * parent, WinDockPosition dockPos); virtual int wPosChanged(WINDOWPOS * windowPos); static int editProc(HWND hwnd, unsigned int msg, unsigned int wParam, long lParam); static WinCallBack::CallBackFunc callBack; }; #endif
MyDocker3.cpp
#include "DWinLib.h" #include "ProgramHeaders.h" #pragma hdrstop #include "MyDocker3.h" #pragma package(smart_init) MyDocker3::MyDocker3(WinDockParent * parent, WinDockPosition dockPos) : WinDockChild(parent, false, dockPos), buttonCallBack(callBack, this) { DWORD dwToolBarStyles = WS_CHILD | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE | CCS_NOPARENTALIGN | CCS_NORESIZE| CCS_NODIVIDER; int t = buttonCallBack.id(); // 0 TBBUTTON tbb[] = { { STD_FILENEW, t, TBSTATE_ENABLED, TBSTYLE_BUTTON, { 0, 0 }, 0, 0 }, { STD_FILEOPEN, t, TBSTATE_ENABLED, TBSTYLE_BUTTON, { 0, 0 }, 0, 1 }, { STD_FILESAVE, t, TBSTATE_ENABLED, TBSTYLE_BUTTON, { 0, 0 }, 0, 2 }, { 0, t, TBSTATE_ENABLED, TBSTYLE_SEP, { 0, 0 }, 0, 0 }, { STD_CUT, t, 0, TBSTYLE_BUTTON, { 0, 0 }, 0, 3 }, { STD_COPY, t, 0, TBSTYLE_BUTTON, { 0, 0 }, 0, 4 }, { STD_PASTE, t, TBSTATE_ENABLED, TBSTYLE_BUTTON, { 0, 0 }, 0, 5 }, { 0, t, TBSTATE_ENABLED, TBSTYLE_SEP, { 0, 0 }, 0, 0 }, { STD_UNDO, t, 0, TBSTYLE_BUTTON, { 0, 0 }, 0, 6 }, { STD_REDOW, t, 0, TBSTYLE_BUTTON, { 0, 0 }, 0, 7 }, { STD_FIND, t, TBSTATE_ENABLED, TBSTYLE_BUTTON, { 0, 0 }, 0, 8 }, { STD_DELETE, t, TBSTATE_ENABLED, TBSTYLE_BUTTON, { 0, 0 }, 0, 9 }, }; char szTBStrings[] = "New\0Open\0Save\0Cut\0Paste\0Copy\0Undo\0Redo\0Find\0Delete\0\0"; int iNumButtons = 13; editC = CreateToolbarEx(hwndC, dwToolBarStyles, 1, iNumButtons, HINST_COMMCTRL, IDB_STD_SMALL_COLOR, tbb, iNumButtons, 55, 36, 36, 36, sizeof(TBBUTTON)); origProcC = (WNDPROC)SetWindowLong(editC, GWL_WNDPROC, (LONG)editProc); SetWindowLong(editC, GWL_USERDATA, (LONG)this); /**/ SendMessage(editC, TB_ADDSTRING, 0, (LPARAM)szTBStrings); SendMessage(editC, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON),0); SendMessage(editC, TB_AUTOSIZE, 0, 0); //Uncomment the following two lines to make the toolbar vertical //RECT r; //SendMessage(editC, TB_SETROWS, MAKEWPARAM(13, FALSE), (LPARAM)&r); MoveWindow(editC, 8, 4, clientWidth()-12, clientHeight()-8, TRUE); } int MyDocker3::wPosChanged(WINDOWPOS * ) { //The following is the only way I could figure out to force the edit box to repaint //the entire client area. Comment out the second, dock a winow to the right, and //resize from the left to see what I am talking about. MoveWindow(editC, 8, 4, clientWidth()-12, clientHeight()-9, FALSE); MoveWindow(editC, 8, 4, clientWidth()-12, clientHeight()-8, TRUE); InvalidateRect(editC, NULL, TRUE); UpdateWindow(editC); return 0; } void MyDocker3::callBack(WinObject * ) { // ctrl } int MyDocker3::editProc(HWND hwnd, unsigned int msg, unsigned int wParam, long lParam) { MyDocker3 * d = (MyDocker3*)GetWindowLong(hwnd, GWL_USERDATA); switch (msg) { case WM_ERASEBKGND: { //CallWindowProc(d->origProcC, d->editC, WM_ERASEBKGND, wParam, lParam); //CallWindowProc(d->origProcC, d->editC, WM_PAINT, wParam, lParam); return 0; } case WM_WINDOWPOSCHANGED: CallWindowProc(d->origProcC, d->editC, WM_WINDOWPOSCHANGED, wParam, lParam); HDC dc = GetDC(d->editC); CallWindowProc(d->origProcC, d->editC, WM_ERASEBKGND, (WPARAM)dc, 0); CallWindowProc(d->origProcC, d->editC, WM_PAINT, (WPARAM)dc, 0); ReleaseDC(d->editC, dc); return 0; } return CallWindowProc(d->origProcC, hwnd, msg, wParam, lParam); }

The first thing to point out is how the WinCallBack thingy works.  I developed them, and the WinCallBackList, in order to automate the handling of window controls.  If you look in the WinControlWin unit of DWinLib, you will see that each control automatically instantiates a WinCallBackItem.  So far, wrappers have been developed for WinButtons, WinCheckBoxes, WinComboBoxes, WinEditBoxes, WinRadioButtons, and WinToolbars.

I did not use a WinToolBar in the implementation of James' example program.  Instead, I chose to show you how you can implement a WinCallBackItem to achieve the desired effect.  Note the WinCallBackItem and the static WinCallBack::CallBackFunc in the MyDocker3 unit.

The WinCallBackItem is there to ensure a unique ID for the toolbar.  (You will need one for each button you want to handle.)  The trickery to the whole method is accomplished in the WinControl::wCommand procedure.  That ties into the Application, which has a CallBackList in it, and that CallBackList forwards the message to the CallBackList::CallBackFunc specified in the ctor of the CallBackListItem.  It is the simplest method I could see to accomplish what I wanted to accomplish.

One thing to be aware of is that this method makes overriding the wCommand proc somewhat brittle, especially if you want to use some DWinLib objects in with the override.  As a result of thinking about this, I changed the wCommand proc to take a boolean which allows you to skip calling the DefWindowProc after executing.  It's signature used to be:

   int WinControl::wCommand(int notifyCode, int ctrlId, HWND ctrlHwnd);

It is now:

   int WinControl::wCommand(int notifyCode, int ctrlId, HWND ctrlHwnd, bool callDefProc = true);

This allows you to very carefully call WinControl::wCommand from your overridden wCommand procedure.  I have not had reason to do this, but I figured I'd point it out if the necessity arose.

Another thing I want to point out is that during the design of the main code above (not the extra code), I also added a WinAccelerator unit to the scheme of things.  The Application now holds a WinAccelerator inside of it, and accelerated keystrokes can be automatically handled.  That unit also takes advantage of the control ID, and you can see how these intermesh more fully in the MenuMain unit by looking at how the 'p' WinMenuItem temporary variable is used to obtain the ID for the menu items.  As I mention in the WinAccelerator code, the implementation is not exactly efficient, but it works.  The Application::run procedure was slightly affected by this change, but as you will see, that is still efficient.

I will again bring to your attention the fact that using this method for docking windows allows you to have virtually flicker-free resizing as long as you don't use standard window controls inside your dockers.  I believe that is because the programmers of the original controls relied heavily upon the WM_ERASEBKGND message.  I have found no work-around to this, but if you find one, let me know, and I will include it here.

Oh yeah, another thing...  (Sorry for this extended length.)  I will mention that if you decide to use DWinLib in a project, it has been designed so that you can dynamically change the callbacks to most items.  Look at WinCallBackItem::changeCallback.  Note that the ID will probably change when this is called.  Also, the WinButton, WinCheckBox, and other controls have methods for changing the static callback function.  They do not work as nicely as Borland's '__closure' method, but they are simple, and do the job, without resorting to Boost's binding mechanisms.

Also, I'll quickly note the binary size increase due to using this wrapper.  James' original version was 11 kb.  (Reading other parts of his site, I became aware that he is using several 'tricks' to accomplish this feat.)  My compiler compiled his code to 60 kb, an increase of 545%.  Obviously, I do not know the tricks to optimizing Borland for minimal code.  (Or Borland just isn't as good at optimizing code as VC is, and that is well known.)  Borland Builder 4 compiles the above code to 176 kb, or a 293% increase from the 60 kb base size.  This 116 kb increase will be seen in any projects that use the above code base.  My days of worrying about these size increases are over; it isn't worth worrying about.  Anything that makes coding easier is worth knowing and using.  (I say that as a hypocrite, as I don't know MFC.  The above code has no dependencies to anything other than the standard user libraries, unlike MFC, though.  I know that.)

In addition to the increased code size, you will note that the above library has an additional window handle compared to James' version.  The WinDockParent window is embedded into the main window, whereas James simply used the main window as the parent window for the dockers.  I did this to obtain the major benefits of object orientation: code reuse and separation of responsibilities.  You will see that I wasn't totally able to separate the responsibilities, as the main window needs to forward the WM_WINDOWPOSCHANGED, WM_NOTIFY, WM_NCACTIVATE, and WM_ENABLE messages on to the docker window so that it can handle its tasks.  Again, I feel the extra window handle and the requirement of forwarding these messages is a small price to pay for the cleaner code, and the improved ease of use of the library.

And on a technical note, much pain has been taken to make the code as understandable as possible.  A good example is to compare the method used to determine if a docker being dragged is dockable.  The old method used the WM_NOTIFY message to determine this.  That level of indirection has been eliminated in the above code.  (See the 'WinDockChild::isDockable()' method and how it interacts with the WinDockParent to see the modifications.)

As I write this page, I know that some things will change from the above codebase.  I do not promise to update the above code for some of these changes, unless more significant changes come down the pike in the future.  For instance, the current code uses WinDockWin::dockedPos() in place of the above WinDockWin::dockedState() function.  (Maybe this will be incorporated in the code you are reading - regardless, the StupidSquares code will be different than the above code.)

And with that, I'll get on to other stuff.  Thanks for tagging along for the ride.


All content © 2005, David O'Neil