Friday, January 11, 2008

eDirectory Authentication using LdapConnection and custom certificate validation

Introduction
This article explains how to authenticate a user over LDAPS using the System.DirectoryServices.Protocols.LdapConnection class, performing custom certificate validation.

Background
Recently, I ran into trouble using System.DirectoryServices.DirectoryEntry to connect to a Novell eDirectory server because the certificate was self-signed. When run in an ASP.NET application, the machine-level certificate store was not examined. So, even though the self-signed certificate was in the trusted store, DirectoryEntry was still refusing to establish a connection.

The LdapConnection class is a better choice for this situation, as it allows the user to validate the certificate manually. Note that the DirectoryEntry approach works fine with a trusted self-signed certificate when run in a Windows Forms application. A good example of using DirectoryEntry to connect to eDirectory can be found here.

Using the code
The example uses a Login control for simplicity. I recommend that in the "real world" you create a custom MembershipProvider. The following example code was used to connect to a Novell eDirectory server over secure LDAP. However, the code should work fine with other directory providers as long as the correct server/port/root DN is used.

Also remember to omit the con.SessionOptions.SecureSocketLayer = true line if you aren't using LDAPS. However, if you aren't using LDAPS you might as well use DirectoryEntry!

Connecting and authenticating
First, we set up our LdapConnection class. We specify the address and port of the server that we will be connecting over SSL, set up the certificate callback (more on that later) and provide the default credentials for authentication.

This example uses eDirectory's "contextless login" feature, so blank credentials are allowed. Depending on your LDAP server, you may need to specify credentials to search the directory. Also note that if no port is specified, then the default value of 389 will be used.

LdapConnection con = new LdapConnection(new LdapDirectoryIdentifier(
"EDIRECTORYSERVER:636"));
con.SessionOptions.SecureSocketLayer = true;
con.SessionOptions.VerifyServerCertificate =
new VerifyServerCertificateCallback(ServerCallback);
con.Credential = new NetworkCredential(String.Empty, String.Empty);
con.AuthType = AuthType.Basic;

Now we bind the initial connection. If con.Bind() executes without throwing an exception, the server and credentials specified are valid.

using (con)
{
con.Bind();

The next step is to search for the user's fully qualified, distinguished name. This is a necessary step because when users provide their usernames, they don't provide the full context of their names in the directory; i.e. jlennon is provided instead of cn=jlennon,ou=Beatles,ou=Artists,o=AppleRecordsLDAPDirectory.

First, we prepare the SearchRequest object. We specify the root DN, the search filter and the search scope. Then we send the request.

SearchRequest request = new SearchRequest(
"o=LDAPRoot",
"(&(objectClass=Person)(uid=" + Login1.UserName + "))",
SearchScope.Subtree);

SearchResponse response = (SearchResponse)con.SendRequest(request);

If we get this far without an exception being thrown, we know that the root DN and search filter specified are valid. If either is invalid, a DirectoryOperationException will be thrown.

Now we can extract the DN from the search result. If you want to provide a "no such username" message, you can check that response.Entries.Count > 0. An ArgumentOutOfRangeException will be thrown on the following line if the Username provided does not exist.

SearchResultEntry entry = response.Entries[0];
string dn = entry.DistinguishedName;

Now that we have the full DN for the user, we can check if the given password is valid. We set a new NetworkConnection on the LdapConnection object and re-bind. con.Bind() will throw LdapException if the password provided is invalid.

con.Credential = new NetworkCredential(dn, Login1.Password);
con.Bind();
}

If we get this far, we have successfully authenticated! We can now use a SearchRequest to search for group membership, etc. However, that's out of scope for this example.

VerifyServerCertificateCallback
This should need no explanation. We simply load the certificate file from disk and compare it to the certificate presented by the server. Production code should handle exceptions associated with reading the certificate: File not found, access denied, etc. If you really trust the server, you could omit all of this and just return true. ;)

public static bool ServerCallback(
LdapConnection connection, X509Certificate certificate)
{
try
{
X509Certificate expectedCert =
X509Certificate.CreateFromCertFile(
"C:\\certificates\\certificate.cer");

if (expectedCert.Equals(certificate))
{
return true;
}
else
{
return false;
}
}
catch (Exception ex)
{
return false;
}
}

Source Code Line Counter

Introduction
While updating my cv, I wanted a quick way of seeing how many lines of code my application contained (160,430 lines!). I haven't looked into Visual Studio Add-ins too much so I wrote a simple stand-alone application that has a few nice features.

Using the Application
Fire up the application and select a folder which contains your code or projects - that's it. The application then searches for all code files and counts every line of code. It displays the files included along the line count. It then generates a total at the bottom.

Checking the "Include subfolders" will instruct the software to search all the sub folders, and you also have the option to look for particular source files, *.cs for C# and *.vb for VB.net.

Nice Feature
Some of my projects are spread across two folders, (client / server) so I needed a way of adding these two totals together. After calculating the total for one folder, you can click on the Grouping > Add menu to add the total and directory to a list. As you tally up the folders a total is show in the menu.

Not a lot of Code
There isn't a lot of code to this but here's the important parts.

Using the DirectoryInfo.GetFiles function allows for easy use of searching just a folder or all folders contained within it with a search pattern, *.cs or *.vb. It's fast and saves time having to write your own recursive function.

DirectoryInfo dir = new DirectoryInfo(folderBrowserDialog1.SelectedPath);
FileInfo[] files = dir.GetFiles(cmbSearchPattern.SelectedItem.ToString(),
(chkIncludeSubfolders.Checked ? SearchOption.AllDirectories :
SearchOption.TopDirectoryOnly));

In case you aren't familiar with the inline if statement I hope this makes it a little clearer.

(true_or_false ? Do_this_if_true : Do_this_if_false)
(chkIncludeSubfolders.Checked ? SearchOption.AllDirectories :
SearchOption.TopDirectoryOnly));

Counting the lines
I figured that to count the lines I should open the text and use a RegEx "\n" to calculate the lines, but I guess that the RegEx is doing the same thing I would which is open the file and read each line. So I just use a loop to count the lines, which is just as quick I reckon.

reader = file.OpenText();
while (reader.ReadLine() != null)
{
fileCounter++;
}
reader.Close();

Collection.RemoveAt
Got stuck trying to remove items within a collection because I only need to remove a certain amount of items from a particular index. Items 0 to 3 contain the Add, Clear All, Total and a Separator. This solution below sorts this out.

// if there was two items or more in the collection then
// one item would always be left.
for (int i = 4; i < toolStripGrouping.DropDownItems.Count; i++)
{
toolStripGrouping.DropDownItems.RemoveAt(i);
}
// this fixes it
int count = toolStripGrouping.DropDownItems.Count;
for (int i = 4; i < count; i++)
{
toolStripGrouping.DropDownItems.RemoveAt(4);
}

Line Counter Code
The updated line counter code was changed to include or exclude certain lines. The easiest way for me to do this was to first remove the "\t" tabs from the line and then investigate the line of code.

Collapse // Replace the tabs
s = s.Replace("\t", "");

// Comment
if (s.StartsWith("//"))
{
dontCount = true;
}

// Blank line
if (s == "")
{
dontCount = true;
}

// This autoGenerateCode flag lets the software know that
// it should be looking for the first #endregion to close
// the starting one.
if (autoGeneratedCode)
{
if (s.Contains("#endregion"))
{
dontCount = true;
autoGeneratedCode = false;
}
}
else if (s == "#region Windows Form Designer generated code")
{
dontCount = true;
autoGeneratedCode = true;
}


History of Searches
I thought that re-browsing projects I have already counted was getting a bit repetitive so adding a history made sense.

Basically it works like this: if a search is performed it checks to see if it exists in the history stored in the ToolStripItems. If it does then it just moves that ToolStripItem to the top using the Insert function with a index set to 0. If it doesn't then it first checks to see if it has reached the maximum amount allowed (currently 5), removing the last one if it has and inserting the new search into index.

Collapseprivate void AddToFolderHistory(string FolderPath)
{
// Check if folder exists in the list
ToolStripItem[] searchItems = btnBrowse.DropDownItems.Find(FolderPath,
true);
if (searchItems.Length == 0)
{
// Doesn't exist
if (btnBrowse.DropDownItems.Count == maximumHistory)
{
btnBrowse.DropDownItems.RemoveAt(maximumHistory-1);
}

ToolStripMenuItem item = new ToolStripMenuItem();
item.Name = item.Text = FolderPath;
item.Click += new EventHandler(FolderHistory_Click);
this.btnBrowse.DropDownItems.Insert(0, item);
}
else
{
// Does exist so just move to the top
this.btnBrowse.DropDownItems.Insert(0, searchItems[0]);
}
}
private void FolderHistory_Click(object sender, EventArgs e)
{
// History item has been selected so move to the top
lblFolder.Text = ((ToolStripMenuItem)sender).Text;
this.btnBrowse.DropDownItems.Insert(0, ((ToolStripMenuItem)sender));
CountFiles(lblFolder.Text);
}


Conclusion
In my opinion, a neat little application that does exactly what it says on the tin. It's helped with my cv but no job offers just yet!

History
Added the ability to not count blank lines, comments, Visual Studio auto-generated code, or designer files. This is accessed through the File Options menu items.


Changed the search routine so that you can browse multiple extensions (*.cs|*.vb) at the same time.
Copy the current list of files and number of lines to the clipboard for email/printing
Redesigned the Browse feature with a Toolbar
Added a Recount button
A quick history list of the last 5 (configurable in code) searches - explained below.
Add the additional code to filter vb files.


If someone could send me the "#region" and "#endregion" in C++.net I can add this to the routine as well.

No ASPX just yet. I have a real software job to do.

License

Docking Toolbars in Plain C

First, we'll discuss the "floating" aspect of tool windows - i.e. how to get tool windows to stay floating on top of all other windows, how to get the window activation working correctly, etc. Later, we'll see how to get these floating tool windows to "dock" to a side of the owner window, and discuss various methods of window management.

The source code presented will culminate in a Win32 DLL called DockWnd.dll whose functions can be used by any Win32 program to easily support docking tool windows. There are many different ways to create a docking window. I imagine most of the code on the Internet uses well-designed C++ classes to hide the implementation. However, I still prefer to code in C, so the design of this library will be a non-object oriented approach and therefore easily useable by an application written in any language.

This code/article is based upon some original free code provided by James Brown. His website, contains an earlier, different version of this code (as well as numerous other free Win32 tutorials/examples).

Contents
DOCKINFO structure
How DockingCreateFrame creates a floating tool window
Prevent tool window deactivation
Show all tool windows as active
Sync the activation of all tool windows
Sync the enabled state of all tool windows
Docking a tool window
Floating versus docked size
Moving a window with a drag-rectangle
Drawing a drag-rectangle
Redrawing the docked windows
Enumerating tool windows
Various other features of the library
An application
Creating a tool window
Handling WM_SIZE message in the owner
Handling WM_NCACTIVATE message in the owner
Handling WM_ENABLE message in the owner
Handling messages sent to standard controls
Multiple child windows inside a tool window
Closing a tool window
Saving/Restoring a tool window size/position
Miscellaneous remarks
DOCKINFO structure
Because our docking library needs to maintain some information about each tool window it manages, we need some structure to store the information. We'll define a DOCKINFO structure (in dockwnd.h), and allocate a DOCKINFO for each tool window created. The application will call the DockingAlloc function in our docking library to allocate a DOCKINFO (initialized to default values), and then pass it to the DockingCreateFrame function which creates the actual tool window associated with that DOCKINFO. This struct will hold information such as the handle (HWND) to the tool window, the handle to the tool window's owner window, whether the tool window is currently docked to its owner or floating, and other information for our private use in managing docking tool windows. We store the handle to the tool window in the DOCKINFO's hwnd field. We store the handle to the owner window in the DOCKINFO's container field. And the value of the DOCKINFO's uDockedState field tells whether the tool window is floating or docked. This field is OR'ed with DWS_FLOATING (and therefore a negative value) when a tool window is floating. We'll discuss the other DOCKINFO fields later.

Note: It is the application's responsibility to create and manage the owner window. Our library deals only with creating/managing the tool windows.

We register our own window class (with the class name of "DockWnd32") for our tool windows. The window procedure (dockWndProc) for this class is inside of our library. We use the GWL_USERDATA field of the tool window to store a pointer to that tool window's DOCKINFO struct. In this way, we can easily and quickly fetch the appropriate DOCKINFO given only a handle (HWND) to a particular tool window.

We don't want to limit the application to only one owner window, and its set of docked windows. For example, perhaps an application will have two owner windows, each with its own set of docked windows. Nor, do we want to limit the application to a particular number of tool windows. So, we may be asked to create DOCKINFOs for numerous sets of tool windows and owners. By storing a pointer to a tool window's DOCKINFO in the tool window itself, and storing handles to the tool window and its owner window inside the DOCKINFO, we can easily get all of the information we need for our library to do what it needs to do, with minimal work on the part of the application.

There are times when our library needs to be able to enumerate all tool windows for a particular owner window. We'll get to the particulars of that later. In some of our example code below, we'll just refer to a placeholder function called DockingNextToolWindow which you should assume will fetch the next tool window (actually, that tool window's DOCKINFO) for a particular owner window. In the actual source code for the library, this is replaced by more complex code that we'll examine later.

How DockingCreateFrame creates a floating tool window
A floating tool window is just a standard window with the WS_POPUP style. When a popup window is created with an owner window, the popup is positioned so that it always stays on top of that owner window. This is how we can create and display a floating tool window:

// Create a floating (popup) tool window
HWND hwnd = CreateWindowEx(
WS_EX_TOOLWINDOW,
"ToolWindowClass", "ToolWindow",
WS_POPUP | WS_SYSMENU | WS_THICKFRAME | WS_CAPTION | WS_VISIBLE,
200, 200, 400, 64,
hwndOwner, NULL, GetModuleHandle(0), NULL
);

Note: In the above example, it is assumed that hwndOwner is a handle to some other window the application created to be the owner window for our tool window. The application must create this window, and then pass its handle to DockingCreateFrame. In other words, the application must create the owner window before any tool window can be created for it.

The WS_EX_TOOLWINDOW extra style doesn't do anything special, other than to make a window with a smaller titlebar. It doesn't make the window magically float - this is achieved automatically by specifying WS_POPUP style and an owner window. Here's what the above CreateWindowEx may display:



A tool window floating above its owner window ("Main window").

Prevent window deactivation
The image above shows the owner window ("Main window") with an inactive titlebar. This is entirely normal, because only one window at a time can have the input focus, and the operating system normally shows only that one window with an active titlebar. So, when we create our tool window, the operating system shows the tool window as active, and shows the owner window as deactivated.

But, it is normal practice for tool windows and their owner window to appear active at the same time. It looks more natural this way. So we need to devise a strategy to keep our tool window and owner window both appear active, even if only one technically has the input focus.

The solution involves the WM_NCACTIVATE message. The operating system sends this message when a window's non-client area (the titlebar and border) needs to be activated or deactivated. As with all window messages, WM_NCACTIVATE is sent with two parameters - wParam and lParam. When a window receives WM_NCACTIVATE with wParam=TRUE, this indicates that the titlebar and border should be shown as active. When wParam=FALSE, this indicates that the titlebar and border should be shown as inactive.

Note: MSDN states that WM_NCACTIVATE's lParam will always be 0. However, I have observed that lParam indicates the window handle of the window being deactivated. This appears to be true under Win95, 98 and NT, 2000, XP. Our solution relies upon this undocumented feature.

So, when we create our tool window, our owner window receives a WM_NCACTIVATE with wParam=FALSE and our tool window receives a WM_NCACTIVATE with wParam=TRUE.

When this message is passed to DefWindowProc(), the operating system does two things. First, the titlebar is drawn as either active or inactive, depending upon whether wParam is TRUE or FALSE respectively. Secondly, the operating system sets an internal flag for the window which remembers if the window was painted as active or inactive. This enables DefWindowProc() to process subsequent WM_NCPAINT messages to paint the titlebar with the proper activation. It is advisable to always pass WM_NCACTIVATE to DefWindowProc() so that this internal flag is set, even if you also do your own processing of WM_NCACTIVATE.

This WM_NCACTIVATE message provides us with a way to make all our tool windows, and owner window, look active, even if only one window technically has the focus. To do this, whenever our tool windows or owner window receive a WM_NCACTIVATE, we will always substitute TRUE for wParam when we pass the WM_NCACTIVATE to DefWindowProc(). The result is that the operating system always renders the titlebars of our tool windows and owner window as active.

Here is some code we could add to the window procedure of all our tool windows, and owner window, to show them all as simultaneously active:

case WM_NCACTIVATE:
return DefWindowProc(hwnd, msg, TRUE, lParam);



Both the tool window and its owner window are shown active.

Incidentally, MDI child windows also use this same technique to keep their titlebars active. The only difference is that MDI windows have the WS_CHILD style, instead of WS_POPUP.

Show all tool windows as active
The above method seems to do what we want, but there is a problem. Our owner window and tool windows will always appear active, even if our application is not in the foreground. For example, if the end user switches to some other application's window, our tool window and owner window still will look active, which can be a bit disconcerting to the end user.

Also, whenever we display a message box or a normal dialog box, the owner window and tool windows will still appear active, when in this scenario we ideally want to make them look inactive.

This calls for a more careful study of window activation messages. Here is a description of the series of window activation messages sent when one window becomes active, and another inactive:

WM_MOUSEACTIVATE is sent to the window about to become active, to ask it whether or not the activation request should be allowed. What your window procedure returns (i.e., MA_ACTIVATE or MA_NOACTIVATE) affects the subsequent activation messages.
WM_ACTIVATEAPP is sent if a window belonging to a different application is about to become active (or inactive). This message is sent both to the window that is currently active (to tell it that it is about to become inactive) as well as the window that is about to become active. The return value should always be zero, and never affects subsequent messages' behaviour.
As described above, WM_NCACTIVATE is sent when a window's non-client area needs to be shown activated or deactivated.
WM_ACTIVATE is sent last of all to the window becoming active. When this message is passed to DefWindowProc(), the operating system sets the input focus to that window.
With all of these activation messages, only two windows are actually involved - the window being deactivated, and the window being activated. So, even if we have many floating tool windows, not all of them will receive these messages. Only the one window being activated, and the one window being deactivated, receive the messages. But to make things look and feel right, we want the displayed state (i.e., whether a tool window's titlebar is shown active or inactive) of each tool window to be the same as all other tool windows. So, even though not all of our windows will receive the above messages, we still need to have all windows synced to the same state.

This same discussion applies whenever we need to disable or enable our owner window. If the owner window is to be disabled or enabled, we want to sync all the tool windows to that same state. (But, a different set of messages are sent for a window being disabled or enabled.)

So, our docking library has a bit of work to do in order to make things look and feel right:

When our application is activated / deactivated, we need to sync the active / inactive display of all tool windows with each other.
This also applies to activation within our own application. For example, if the user activates a window we create that isn't one of our tool windows, then we want to show all tool windows deactivated. And if the user switches back to a tool window from that window, we want all tool windows shown active.

When the owner window is disabled due to a modal dialog or message box being displayed, then we must disable all tool windows (and any modeless dialogs) to prevent the user from interacting with them while the modal dialog / message box is on screen.
A first try at a solution
Our first stab at a solution will be to concentrate on the WM_ACTIVATE message. This message is received whenever a window is activated or deactivated. The direction we will take will be to decide if the window receiving this message is active or inactive, and synchronise all other windows to the same state by manually sending them a "spoof" WM_NCACTIVATE message. This spoof message will force the other windows to update their titlebars to the same state as the window receiving the WM_ACTIVATE.

Here's a function that we could add to our docking library. Whenever one of our tool windows, or owner window, receives a WM_ACTIVATE message, it will call this function to sync the state of all tool windows:

Collapse/*********************** DockingActivate() **********************
* Sends WM_NCACTIVATE to all the owner's tool windows. A
* tool or owner window calls this in response to receiving
* a WM_ACTIVATE message.
*
* container = Handle to owner window.
* hwnd = Handle to window which received WM_ACTIVATE (can
* be the owner, or one of its tool windows).
* wParam = WPARAM of the WM_ACTIVATE message.
* lParam = LPARAM of the WM_ACTIVATE message.
*/

LRESULT WINAPI DockingActivate(HWND container,
HWND hwnd, WPARAM wParam, LPARAM lParam)
{
DOCKINFO * dwp;
BOOL fKeepActive;

fKeepActive = (wParam != WA_INACTIVE);

// Get the DOCKINFO of the next tool window for this owner window. when 0
// is returned, there are no more tool windows for this owner.
while ((dwp = DockingNextToolWindow(container)))
{
// Sync this tool window to the same state as the window that called
// DockingActivate.
SendMessage(dwp->hwnd, WM_NCACTIVATE, fKeepActive, 0);
}

// Allow the window that called DockingActivate to handle its WM_NCACTIVATE
// as normally it would.
return DefWindowProc(hwnd, WM_ACTIVATE, wParam, lParam);
}

It works, after a fashion. All tool windows activate and deactivate correctly, and all at the same time. This solution is not the best though.

The problem is that every tool window's titlebar flashes whenever the active window changes. This is because of the way the operating system sends the WM_ACTIVATE message. This message is first sent to the window that is being deactivated. If that happens to be a tool window or the owner window, it will call DockingActivate to deactivate all the tool windows. WM_ACTIVATE is then sent to the active window. If that window also happens to be a tool window or owner window, it will call DockingActivate to (correctly) activate all the tool windows. It is the fact that DockingActivate is quickly called twice (once to deactivate the tool windows, and then to activate them) that causes all the windows to flash.

A partial solution is to perform a check before deactivating the tool windows. We know that if a window is being deactivated, lParam identifies the (other) window about to be activated. And if this other window is one of our tool windows, we can skip deactivating the tool windows, because we know the other (tool) window is going to subsequently activate them anyway.

if (fKeepActive == FALSE)
{
while ((dwp = DockingNextToolWindow(container)))
{
if (dwp->hwnd == (HWND)lParam)
return DefWindowProc(hwnd, WM_ACTIVATE, wParam, lParam);
}
}

This prevents every tool window from briefly deactivating, then activating again. There is still a problem, albeit a minor one. The problem is, the single tool window that is being deactivated will still flicker briefly before being activated again. This is because it will already have received its WM_NCACTIVATE message, which caused the window to be redrawn deactivated. The window gets its activated look eventually, but this brief flicker is still visible.

Sync the activation of all tool windows
We need to take a step back and approach the problem from a slightly different direction. Instead of handling WM_ACTIVATE, which is called after a window's titlebar is redrawn, we'll go straight to the heart of the problem, and rewrite DockingActivate to be called whenever a window receives a WM_NCACTIVATE message. This will ensure that no unnecessary activation or deactivation will take place.

The function presented below performs several tasks on behalf of the tool (or owner) window that calls DockingActivate:

Search the list for the other window being activated/deactivated (the window specified by lParam, rather than the window receiving WM_NCACTIVATE). If this other window is a tool window, then we force all tool windows as activated.
Synchronize all current tool windows to our (possibly new) state.
Activate/deactivate the window that calls DockingActivate, depending on our new state.
The code looks like this:

CollapseLRESULT WINAPI DockingActivate(HWND container,
HWND hwnd, WPARAM wParam, LPARAM lParam)
{
DOCKINFO * dwp;
BOOL fKeepActive;
BOOL fSyncOthers;

// If this is a spoof'ed message we sent, then handle it
// normally (but reset LPARAM to 0).
if (lParam == -1)
return DefWindowProc(hwnd, WM_NCACTIVATE, wParam, 0);

fKeepActive = wParam;
fSyncOthers = TRUE;

while ((dwp = DockingNextToolWindow(container)))
{
// UNDOCUMENTED FEATURE:
// If the other window being activated/deactivated (i.e. not the one that
// called here) is one of our tool windows, then go (or stay) active.
if ((HWND)lParam == dwp->hwnd)
{
fKeepActive = TRUE;
fSyncOthers = FALSE;
break;
}
}

if (fSyncOthers == TRUE)
{
// Sync all other tool windows to the same state.
while ((dwp = DockingNextToolWindow(container)))
{
// Send a spoof'ed WM_NCACTIVATE message to this tool window,
// but not if it is the same window that called here. Note that
// we substitute a -1 for LPARAM to indicate that this is a
// spoof'ed message we sent. The operating system would never
// send a WM_NCACTIVATE with LPARAM = -1.
if (dwp->hwnd != hwnd && hwnd != (HWND)lParam)
SendMessage(dwp->hwnd, WM_NCACTIVATE, fKeepActive, -1);
}
}

return DefWindowProc(hwnd, WM_NCACTIVATE, fKeepActive, lParam);
}

The code above uses an undocumented feature of the WM_NCACTIVATE message which I observed while experimenting with these activation messages. The MSDN documentation states that lParam is unused (presumably zero), but this is not the case under Windows 95, 98, ME, and NT, 2000, XP.

Instead, lParam is a handle to the other window being activated/deactivated in our place (i.e., if we are being deactivated, lParam will be the handle to the window being activated). This is not always the case, specifically when the other window being activated/deactivated belongs to another process. In this case, lParam will be zero.

Sync the enabled state of all tool windows
Now, we need to tackle the other problem. When our owner window is disabled (perhaps because a modal dialog or message box has popped up), we need to disable all tool windows too. This feature prevents the user from clicking on and activating not only the main window, but also any tool window, while the modal dialog or message box is displayed.

The solution is similar to how we solved the activation problem, except that this time we write a function that a tool window or the owner window calls whenever it receives a WM_ENABLE message. DockingEnable simply enables/disables all the tool windows to the same state as the owner window.

Collapse/*********************** DockingEnable() **********************
* Sends WM_ENABLE to all the owner's tool windows.
* A window calls this in response to receiving a
* WM_ENABLE message.
*
* container = Handle to owner window.
* hwnd = Handle to window which received WM_ENABLE (can
* be the owner, or one of its tool windows).
* wParam = WPARAM of the WM_ENABLE message.
* lParam = LPARAM of the WM_ENABLE message.
*/

LRESULT WINAPI DockingEnable(HWND container,
HWND hwnd, WPARAM wParam, >LPARAM lParam)
{
DOCKINFO * dwp;

while ((dwp = DockingNextToolWindow(container)))
{
// Sync this tool window to the same state as the window that called
// DockingEnable (but not if it IS the window that called here).
if (dwp->hwnd != hwnd) EnableWindow(dwp->hwnd, wParam);
}

// Allow the window that called DockingEnable to handle its WM_ENABLE
// as normally it would.
return DefWindowProc(hwnd, WM_ENABLE, wParam, lParam);
}

Docking a tool window
The previous discussion took you through the steps necessary to create floating tool windows. Now, we'll discuss the techniques necessary to get these floating windows to "dock" with their owner window. I'm not going to reproduce all the library's source code in this tutorial, because quite a lot is involved. I'm instead going to give an overview of the approach taken, and you can study the profusely commented source code for details.

First of all, we need to define the terms "Docked" and "Undocked". A tool window is undocked when it is floating. And as we already know, in order to make that happen, the tool window must have the WS_POPUP style.

On the other hand, a tool window is docked when it is visually contained completely within its owner window, alongside one of the owner's borders. In order to make this happen, we must create the tool window with the WS_CHILD (not WS_POPUP) style (or change the style from WS_POPUP to WS_CHILD), and make its owner window also its parent window. When a tool window has the WS_CHILD style, the operating system restricts it to the area inside of its parent window, and the tool window is graphically "anchored" to its parent window (i.e., when the end user moves the parent window, the child window automatically moves with it).

But note that when the parent window is resized, the parent window will need to also move/resize the docked tool window so that the tool window remains "attached" to the border. (Of course, our library has functions the owner window can call to make this as easy as possible.)

A good docking library must allow the end user to be able to dock and undock any tool window by grabbing the tool window with the mouse and dragging it over to a dockable or undockable area. There are many different ways to implement docking windows. This is because there is no standard, built-in docking window support in Windows. Application developers have had to implement their own docking windows, or rely upon third party libraries to do the work for them (such as MFC).

There are two common types of docking window implementations. The most common (and intuitive, in my opinion) is the type where you grab the tool window (by a "gripper bar" or its title bar) with the mouse, and drag it around the screen. When you drag the tool window, instead of the window itself moving, a drag-rectangle (feedback rectangle) is XORed on the screen, showing the outline of where the window will move to when you release the mouse - like the way windows work when full-window dragging is turned off. With this method, when a window is dragged to / from a window, the feedback rectangle visibly changes to indicate that the window can be dropped. This is the docking implementation that our docking library uses.



A tool window being dragged. You can see the drag rectangle.

The second type of docking implementation can be found in some newer style applications (such as Microsoft Outlook). Instead of a feedback rectangle, windows can be directly "teared" or "snapped" on or off the owner window - i.e., they snap into place as soon as you manipulate them. Personally, I don't like this type of user-interface, and our docking library does not use it.

Our tool windows will have the following characteristics:

A docked tool window will have a "gripper bar" along its left side to allow the user to grab it and undock it.
A tool window will use a feedback (drag) rectangle as it's moved around the screen - even if the "full window drag" system setting is in effect. This is shown in the picture above.
While a drag-rectangle is dragged around the screen, at some point it will intersect one of the borders of its owner window. When this happens, the drag-rectangle will need to visibly change in order to reflect the fact that the tool window is now within a docking "region". Normal convention is for a wide (say three pixel) shaded rectangle to represent a floating position, and for a single-pixel rectangle to represent a docked position.
When the mouse is released after dragging a tool window, a test must be made to see if the window should be made to dock or float. (i.e., was the drag-rectangle ultimately moved to one of these docking "regions", or is it outside of any such region and therefore the tool window is floating?)
At the end user's discretion, a tool window can be forced to float, even when the drag-rectangle is released over a dockable area. This is usually achieved by the end user holding the key down.
When floating, a tool window can be resized just like any normal window. No special processing is required to do this - the standard Windows sizing behaviour can be used in this case.
When docked, a tool window can be resized either vertically or horizontally (but not both) to decrease or increase its size. A tool window docked to the top or bottom border of its owner can be resized horizontally. A tool window docked to the left or right border can be resized vertically.
When the user double-clicks a floating tool window's titlebar, or a docked window's gripper bar, the tool window is toggled from floating to docked, or vice versa.
Our docking library keeps track of whether a tool window is docked or floating. And if it is docked, we need to know to which of the owner's borders the tool window is docked. The uDockedState field of the DOCKINFO is used to store this state. As mentioned, if this field is OR'ed with DWS_FLOATING, then the tool window is floating. If not OR'ed with DWS_FLOATING, then the tool window is docked, and the remaining bits of the field are either DWS_DOCKED_LEFT, DWS_DOCKED_RIGHT, DWS_DOCKED_TOP, or DWS_DOCKED_BOTTOM depending upon to which border the tool window is docked.

We need to be able to toggle a tool window between being a child window (docked) and being a popup window (floating). This is basically accomplished with the code shown below.

// Assume "dwp" is a pointer to the tool window's DOCKINFO.

DWORD dwStyle = GetWindowLong(dwp->hwnd, GWL_STYLE);

// Is the window currently floating?
if (dwp->uDockedState & DWS_FLOATING)
{
// Toggle from WS_POPUP to WS_CHILD. We do this by altering
// the window's style flags to remove WS_POPUP, and add
// WS_CHILD. Then, we set the owner window as the parent.
SetWindowLong(dwp->hwnd, GWL_STYLE, (dwStyle & ~WS_POPUP) | WS_CHILD);
SetParent(dwp->hwnd, dwp->container);
}
else
{
// Toggle from WS_CHILD to WS_POPUP. We do this by altering
// the window's style flags to remove WS_CHILD, and add
// WS_POPUP. Then, we make sure it has no parent.
SetWindowLong(dwp->hwnd, GWL_STYLE, (dwStyle & ~WS_CHILD) | WS_POPUP);
SetParent(dwp->hwnd, NULL);
}

Look at the second SetParent API call in the code above. The only way to make a child (docked) window into a popup (floating) window is to set its parent window to zero (NULL). Because the tool window no longer has a parent, it is not visually confined to some other window. It can float freely around the desktop. But because it still has an owner window, the operating system keeps it floating above that owner window. In other words, when a window is docked, its owner window is also its parent window. When a window is floating, its owner window is no longer its parent as well.

Floating versus docked size
As mentioned, a tool window can be in one of two states: docked, or floating (undocked). We will remember the size of a tool window both in its floating state, and its docked state, and store this information in the DOCKINFO. In this way, the end user can give the tool window different sizes for its two states. Because we also allow the end user to quickly toggle between the two states by double-clicking on the gripper/titlebar, we need to remember where the tool window was last positioned in both states.

When a tool window is floating, it can be resized just like a normal window. This means that we will need to store both the width and height in the DOCKINFO. And of course, in order to remember its position, we need to store its X and Y position (in screen coordinates). These values are stored in the DOCKINFO's cxFloating, cyFloating, xpos, and ypos fields respectively.

Note: cxFloating and cyFloating are actually set to the size of the floating tool window's client (inner) area instead of the physical size of the tool window itself (including its titlebar and borders). This is because we always want the client area to remain the same size, even when the system settings change (i.e. the titlebar height is modified using the Control Panel).

When a tool window is docked, it can be resized in only one direction -- vertically or horizontally. This means that we need to remember only its width or height, but not both. If the tool window is docked to the top or bottom border of the owner, then we remember its height. If the tool window is docked to the left or right border of the owner, then we remember its width. Whichever value we remember, we store it in DOCKINFO's nDockedSize field. As far as its position is concerned, that is already remembered in the DOCKINFO's uDockedState field.

Moving a window with a drag-rectangle
The first obstacle we encounter is getting Windows to show a feedback rectangle when the end user moves a floating window around. Starting with Windows 95, a new user-interface feature was introduced. This feature is normally referred to as "Show window contents while dragging". When enabled, windows are no longer moved and sized using the standard feedback rectangle.

Unfortunately, there is no way to turn this feature off for specific windows. The SystemParametersInfo API call (with the SPI_GETDRAGFULLWINDOWS setting) can turn this feature on and off, but this is a system-wide setting, and is not really suitable. Of course, we could devise a method where we temporarily turn off the drag-window system setting just during the window movement (actually, this would be very straight-forward). The point is, it's a bit of a hack, and I prefer proper solutions to problems like this.

The only solution is to override the standard Windows behaviour and manually provide a feedback rectangle. This means processing a few mouse messages. Now, I don't want to show any code - again, the source code clearly demonstrates how to get this working (in the window procedure for a tool window, dockWndProc). What I will do is give a basic outline of the processing that is required.

The most important task is to stop the user from dragging the window around with the mouse. I know this sounds counter-productive, but we need to completely take over the standard window movement logic. This is actually quite simple - our docking window procedure just needs to handle WM_NCLBUTTONDOWN, and return 0 if the mouse is clicked in the caption area. By preventing the default window procedure from handling this message, window dragging is completely disabled.

In order to simulate the window being moved, we need to handle a few mouse messages. Only three need processing:

WM_NCLBUTTONDOWN - This message is received when the end user clicks on a tool window. In addition to returning 0 to prevent the operating system from doing normal window dragging, we draw the drag-rectangle at its initial position, and set the mouse capture using the SetCapture API call. We also install a keyboard hook so we can check if the end user presses the CTRL key (to force the tool window floating) or the ESC key (to abort the operation).
WM_MOUSEMOVE - This message is received whenever the mouse is moved. Our response is to redraw the drag-rectangle in the new position (erase it in the old position and draw it in the new position). In addition, we need to decide what type of rectangle to draw, depending on if the end user has moved the rectangle into a dockable region, or not.
WM_LBUTTONUP - This message is received when the mouse is released. We remove the drag-rectangle from the screen, release the mouse capture, and then take the appropriate action to physically reposition the tool window. This may mean docking / undocking, or simply moving the window if it was already floating.
As you can see, there's a little bit of work involved, but nothing particularly complicated. The big advantage of using this method is that the same mouse code can be used when the window is docked or floating. This keeps the code short and simple.

Drawing a drag-rectangle
A drag-rectangle is basically just a simple rectangle. This rectangle ideally needs to be drawn using XOR blitting logic, so that we can easily draw / erase the rectangle as it is moving around.



A tool window being dragged. You can see the drag rectangle.

The code below draws a shaded rectangle with the specified coordinates. The equivalent function in the source code does a little more than the code below (it draws both types of drag-rectangles), but I've stripped it down to keep it simple.

Collapsevoid DrawXorFrame(int x, int y, int width, int height)
{
// Raw bits for bitmap - enough for an 8x8 monochrome image
static WORD _dotPatternBmp1[] =
{
0x00aa, 0x0055, 0x00aa, 0x0055, 0x00aa, 0x0055, 0x00aa, 0x0055
};

HBITMAP hbm;
HBRUSH hbr;
HANDLE hbrushOld;
WORD *bitmap;

int border = 3;

HDC hdc = GetDC(0);

// Create a patterned bitmap to draw the borders
hbm = CreateBitmap(8, 8, 1, 1, _dotPatternBmp1);
hbr = CreatePatternBrush(hbm);

hbrushOld = SelectObject(hdc, hbr);

// Draw the rectangle in four stages - top, right, bottom, left
PatBlt(hdc, x+border, y, width-border, border, PATINVERT);
PatBlt(hdc, x+width-border, y+border, border, height-border, PATINVERT);
PatBlt(hdc, x, y+height-border, width-border, border, PATINVERT);
PatBlt(hdc, x, y, border, height-border, PATINVERT);

// Clean up
SelectObject(hdc, hbrushOld);
DeleteObject(hbr);
DeleteObject(hbm);

ReleaseDC(0, hdc);
}

As you can see, we have the bitmap data for our rectangle as global data in our docking library. And we simply call some graphics functions to blit in onto the screen (in a rectangular shape) at the screen position where the end user has currently moved the mouse.

Redrawing the docked windows
When a tool window's state changes from docked to floating, or vice versa, this means that the layout of the owner window needs to be redrawn. For example, if a tool window was floating, and then is docked to the owner window, then other tool windows already docked may need to be resized/repositioned to accommodate the new docked tool window.

And if a tool window was docked to the owner window, but is torn off and left floating, that means the other, remaining docked windows may likewise need to be resized/repositioned to fill the "hole" left by the formerly docked window.

Whenever a tool window's state toggles between states, our docking library has a function named updateLayout that is called to send a spoofed WM_SIZE message to the owner window to inform it that it needs to redraw itself. The owner window then is expected to redraw its contents and call a docking library function named DockingArrangeWindows. DockingArrangeWindows does all the work of repositioning and redrawing the docked tool windows.

Enumerating tool windows
In the above code excerpts, we had a placeholder function named DockingNextToolWindow that enumerated the tool windows for a given owner window. We don't actually have such a function in the docking library. Let's examine how our docking library actually enumerates tool windows.

Unfortunately, the Windows operating system does not have a function to enumerate all the windows owned by a particular window. If it did, we could just pass our owner window to that function. What the operating system does have is a function called EnumChildWindows. This enumerates all of the child windows of a given parent window. Since a docked tool window has its owner window as its parent also, EnumChildWindows will enumerate all the docked tool windows for a given owner. But EnumChildWindows will not enumerate any of the floating tool windows, because the owner window is not also the parent of the floating tool windows.

There is another operating system function called EnumWindows. This enumerates all of the top-level (i.e., popup) windows on the desktop. Since our floating tool windows are WS_POPUP style, this works to enumerate them. (But, it will enumerate all windows on the desktop in addition to our tool windows, so we have a little extra work to do to isolate only the desired tool windows). EnumWindows does not enumerate any of the children (WS_CHILD windows) of those top-level windows. So, EnumWindows will not enumerate any docked tool windows.

Therefore, enumerating all the tool windows will be a two-step process. First, we'll call EnumChildWindows to enumerate the docked windows for a given parent window (which also happens to be the owner window). Then, we will call EnumWindows to enumerate the floating tool windows for a given owner window, and do some extra processing to make sure that the windows we isolate are for the desired owner window.

Let's examine a function that counts how many total tool windows a given owner window has, both floating and docked.

Collapsetypedef struct {
UINT count;
HWND container;
} DOCKCOUNTPARAMS;

/***************** DockingCountFrames() *****************
* Counts the number of tool windows for the specified
* owner window.
*
* container = Handle to owner window.
*/

UINT WINAPI DockingCountFrames(HWND container)
{
DOCKCOUNTPARAMS dockCount;

// Initialize count to 0, and store the desired owner window
dockCount.count = 0;
dockCount.container = container;

// Enumerate/count the floating tool windows
EnumWindows(countProc, (LPARAM)&dockCount);

// Enumerate/count the docked tool windows
EnumChildWindows(container, countProc, (LPARAM)&dockCount);

// Return the total count
return dockCount.count;
}

/******************* countProc() ********************
* This is called by EnumChildWindows or EnumWindows
* for each window.
*
* hwnd = Handle of a window.
* lParam = The LPARAM arg we passed to EnumChildWindows
* or EnumWindows. That would be our DOCKCOUNTPARAMS.
*/

static BOOL CALLBACK countProc(HWND hwnd, LPARAM lParam)
{
DOCKINFO * dwp;

// Is this one of the tool windows for the desired owner window?
if (GetClassWord(hwnd, GCW_ATOM) == DockingFrameAtom &&
(dwp = (DOCKINFO *)GetWindowLong(hwnd, GWL_USERDATA)) &&
dwp->container == ((DOCKCOUNTPARAMS *)lParam)->container)
{
// Yes it is. Increment count.
((DOCKCOUNTPARAMS *)lParam)->count += 1;
}

// Tell operating system to continue.
return TRUE;
}

Notice that we use countProc() as the callback for both EnumWindows and EnumChildWindows. And we pass our own initialized DOCKCOUNTPARAMS structure to our callback. First, we call EnumWindows to enumerate the floating windows. Then we call EnumChildWindows to enumerate the docked windows for our desired owner. So, let's examine countProc(). The entire key to making this work is to fetch and check the class ATOM for the window. If it matches the ATOM we got when we registered our own docking window class (returned by RegisterClassEx), then we know this is one of our tool windows. And if it is one of our tool windows, we know that its GWL_USERDATA field should contain its DOCKINFO. And note that the owner window handle has been stored in the DOCKINFO's container field. So we need only compare this handle with the owner handle passed to DockingCountFrames in order to determine if it is a tool window for the desired owner window.

Various other features of the library
The discussion above details all of the most important aspects of our docking library's features. But, there are some more, incidental features which are optional. You can enable any of these features for a given tool window just by setting the appropriate value into its DOCKINFO's dwStyle field. For example, you can force a tool window to always stay docked or floating. You can force it to keep its original size. You can restrict to which sides of the owner window the tool window may be docked.

When DockingAlloc creates a DOCKINFO, none of these extra features are enabled.


--------------------------------------------------------------------------------

An application
Up to this point, we've discussed only the code in the docking library. Since the whole intent of the library is to be used by an application, now we'll turn our attention to a sample application. There is a sample C application called DockTest included with the library. This example creates one owner window. The owner has a View -> Tool Window menu item you can select to create a tool window. You can then move the tool window around, docking and undocking it, to get a feel for how the implementation works. Each time you select this menu item, another tool window is created, so you can see how multiple tool windows can be floated and docked.

The owner window we create is an MDI window, and its window procedure is frameWndProc. You can open a document window with the File -> New menu item, and see how the docked tool windows interact with a document window. (But, as we'll see later, the application needs to do a little work to manage this interaction.)

Creating a tool window
Let's examine how the application creates a tool window. This happens when the View -> Tool Window menu item is selected, so the place where we create the tool window is in frameWndProc's handling of WM_COMMAND for menu ID IDM_VIEW_TOOLWINDOW. Below is a slightly simplified version of what needs to be done to create a tool window:

Collapsevoid createToolWindow(HWND owner)
{
DOCKINFO *dw;
HWND frame;

// Allocate a DOCKINFO structure.
if ((dw = DockingAlloc(DWS_DOCKED_BOTTOM)))
{
// Create a Docking Frame window (ie, the tool window).
if ((frame = DockingCreateFrame(dw, owner, "My title")))
{
// Create the child window that will be hosted inside of the Docking
// Frame window (ie, the contents of the tool window's client area)
// and save it in the DOCKINFO's focusWindow field. We'll create an
// EDIT control to be the contents, but you can utilize any standard
// control, or a child window of your own class.
if((dw->focusWindow = CreateWindow("EDIT", 0,
ES_MULTILINE|WS_VSCROLL|WS_CHILD|WS_VISIBLE,
0,0,0,0,
frame,
(HMENU)IDC_MYEDIT, GetModuleHandle(0), 0)))
{
// Show the Docking Frame.
DockingShowFrame(dw);

// Success!
return;
}

// Destroy the tool window if we can't create its contents.
// NOTE: The docking library will free the above DOCKINFO.
DestroyWindow(frame);
}
MessageBox(0, "Can't create tool window", "ERROR", MB_OK);
}
else
MessageBox(0, "No memory for a DOCKINFO", "ERROR", MB_OK);
}

First, we call DockingAlloc to get a DOCKINFO structure. We pass the desired initial state, which will be one of DWS_FLOATING, DWS_DOCKED_LEFT, DWS_DOCKED_RIGHT, DWS_DOCKED_TOP, or DWS_DOCKED_BOTTOM, depending upon whether we want the tool window initially created floating, or docked to one of the four borders. The docking library creates a DOCKINFO and initializes it to default values, returning a pointer.

At this point, we could modify the DOCKINFO if we want something other than the default features. In the above code, we simply go with the defaults.

Next, we call DockingCreateFrame to create the actual tool window. We pass the DOCKINFO we just got, the handle to our owner window, and the desired title for the tool window (which is shown only when the tool window is floating). DockingCreateFrame will create the tool window and return its handle. The tool window is not created visible, so nothing has yet shown up onscreen.

Now, a tool window with nothing inside of it would not be of much use. So we need to create something inside of the tool window that is of use to the end user. We can say that the tool window needs some "contents". Specifically, we need to create some WS_CHILD window which has the tool window as its parent. This can be any standard control, such as an Edit box, list box, tree-view control, etc. Or it could be a window of our own class. In the above code, we simply create a multi-line Edit control. Note that we have set the tool window to be this control's parent, and also specified the WS_CHILD style. This will cause the Edit control to be visually embedded inside of the tool window, and automatically move with the tool window. The size and position of the Edit control is not important now, because it will be sized and positioned later, before the tool window is finally made visible. We stuff the handle to this control into the DOCKINFO's focusWindow field. The docking library will automatically size this control to fill the client area of the tool window, and also give the control the focus whenever the user activates that tool window.

Finally, we call DockingShowFrame. This first sends a WM_SIZE message to our owner window (which is where we will do the final sizing/positioning of the tool window and its contents-window), and then makes the tool window visible.

That's all there is to creating a tool window. At this point, the docking library will manage the docking and undocking of this window.

Handling WM_SIZE message in the owner
The docking library transparently handles most aspects of the tool windows. But there are a couple times when it needs help from the application. One such time is whenever the owner window is resized. Given a new size for the owner window, it stands to reason that any docked tool window may also need to be resized and repositioned so that it stays docked to the desired side of the owner window. For this reason, the owner window will have to do the following when it receives a WM_SIZE:

Collapse case WM_SIZE:
{
HDWP hdwp;
RECT rect;

// Do the default handling of this message.
DefFrameProc(hwnd, MainWindow, msg, wParam, lParam);

// Set the area where tool windows are allowed.
// (Take into account any status bar, toolbar etc).
rect.left = rect.top = 0;
rect.right = LOWORD(lParam);
rect.bottom = HIWORD(lParam);

// Allocate enough space for all tool windows which are docked.
hdwp = BeginDeferWindowPos(DockingCountFrames(hwnd,
1) + 1); // + 1 for the MDI client

// Position the docked tool windows for this owner
// window. rect will be modified to contain the "inner" client
// rectangle, where we can position an MDI client.
DockingArrangeWindows(hwnd, hdwp, &rect);

// Here we resize our MDI client window so that it fits into the area
// described by "rect". Do not let it extend outside of this
// area or it (and the client windows inside of it) will be obscured
// by docked tool windows (or vice versa).
DeferWindowPos(hdwp, MainWindow, 0, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOACTIVATE|SWP_NOZORDER);

EndDeferWindowPos(hdwp);

return 0;
}

First, we pass the WM_SIZE to DefFrameProc to let the operating system do the default sizing of the owner window. We need to do this first so that the owner window's size is finalized before we go ahead and resize/reposition the docked tool windows.

The docking library has a function called DockingArrangeWindows that redraws all of the docked windows for a given owner. So to completely redraw its docked windows, all the owner needs to do is call this one function. But, there are a couple prerequisites. First, the owner must fill in a RECT with the dimensions of its client area, and pass this to DockingArrangeWindows. Secondly, the owner window must call the Windows API BeginDeferWindowPos to reserve enough space for all the docked windows. (The docking library has a function called DockingCountFrames which can be called to retrieve the total number of docked tool windows in an owner.) We use BeginDeferWindowPos so that, if there are many tool windows, we defer the final painting until after all of them are sized and positioned. This is more efficient and doesn't cause any unsightly visual artifacts for the end user to witness.

One very important aspect to note is that, after DockingArrangeWindows resizes and repositions all of the docked tool windows, it updates the RECT so that it encompasses only the owner client area not occupied by the tool windows. In other words, the RECT is the remaining, blank client area. We take this remaining area, and we resize/reposition our MDI child so that it fills only this remaining area. In this way, our document windows are not obscured by docked tool windows, and vice versa.

Handling WM_NCACTIVATE message in the owner
Another instance where our docking library needs help from the application is whenever the owner window receives a WM_NCACTIVATE message. Remember earlier we discussed how to keep all tool windows' titlebar activation in sync with the owner window. Now, we need the owner window to let the docking library know whenever it receives a WM_NCACTIVATE message. The owner window will need to do the following:

case WM_NCACTIVATE:
{
DOCKPARAMS dockParams;

dockParams.container = dockParams.hwnd = hwnd;
dockParams.wParam = wParam;
dockParams.lParam = lParam;
return(DockingActivate(&dockParams));
}

We simply fill in a DOCKPARAMS struct (defined in DockWnd.h) with the values we receive from the WM_ACTIVATE message, and also fill in the owner window handle (in DOCKPARAMS container field) and the handle of the window receiving the WM_ACTIVATE (which here, is the owner window). Then we call DockingActivate which takes care of syncing all tool windows' titlebar activation.

Handling WM_ENABLE message in the owner
Another instance where our docking library needs help from the application is whenever the owner window receives a WM_ENABLE message. Remember earlier we discussed how to keep all tool windows' enabled state in sync with the owner window. Now, we need the owner window to let the docking library know whenever it receives a WM_ENABLE message. The owner window will need to do the following:

case WM_ENABLE:
{
DOCKPARAMS dockParams;

dockParams.container = dockParams.hwnd = hwnd;
dockParams.wParam = wParam;
dockParams.lParam = lParam;
return(DockingEnable(&dockParams));
}

This is almost the same as the WM_NCACTIVATE handling, but it concerns the WM_ENABLE message, and we call a function named DockingEnable. DockingEnable takes care of syncing all tool windows' enabled state.

Handling messages sent to standard controls
You'll note that we used a standard Edit control as the content of our tool window. As you should know, an Edit window sends messages to its parent for certain actions. For example, when the end user alters the contents of the Edit control, a WM_COMMAND message is sent with a notification code of EN_CHANGE.

But remember that the parent window is the tool window, and our tool window procedure (dockWndProc) is in the docking library. So how does an application get a hold of that message?

There is a DockMsg field in the DOCKINFO. Into this field, we will stuff a pointer to a function in our application. We do this after we DockAlloc the DOCKINFO as so:

// Allocate a DOCKINFO structure.
if ((dw = DockingAlloc(DWS_DOCKED_BOTTOM)))
{
// Set our own function for the docking library to call.
dw->DockMsg = myMessages;

...

Whenever dockWndProc receives a message that it doesn't handle, such as a WM_COMMAND or WM_NOTIFY, it will call our application function, passing the DOCKINFO of the tool window, as well as the message, WPARAM, and LPARAM parameters.

Here is an example of the function we could add to handle a EN_CHANGE from our IDC_MYEDIT edit control:

LRESULT WINAPI myMessages(DOCKINFO * dwp, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
if (LOWORD(wParam) == IDC_MYEDIT)
{
if (HIWORD(wParam) == EN_CHANGE)
{
// Here we would handle the EN_CHANGE, and then return
// 0 to tell the docking library we handled it.
return 0;
}
}
}
}

// Return -1 if we want the docking library to do default handling.
return -1;
}

Note: Each DOCKINFO can have its own DockMsg function, so you do not need to worry about control ID conflicts between tool windows.

Multiple child windows inside a tool window
Above, we used a single Edit control to fill the client area of the tool window. But what if we would like several controls inside the tool window, for example, an Edit control as well as a push button labeled "Clear" which clears the text from the Edit control?

This is entirely possible, but there are a couple of prerequisites. First, when we create the Edit and button controls, we must make both of them children of the tool window. Secondly, we must provide a function that will resize and reposition the controls, and stuff a pointer to this in the DOCKINFO's DockResize field after we DockAlloc the DOCKINFO.

// Allocate a DOCKINFO structure.
if ((dw = DockingAlloc(DWS_DOCKED_BOTTOM)))
{
// Set our own functions for the docking library to call.
dw->DockMsg = myMessages;
dw->DockResize = myResize;

...

When the docking library calls our function, it passes the DOCKINFO for the tool window, as well as a RECT that encompasses the area that we need to fill. Here is an example of a function we could add to resize and reposition the edit and button controls so that the button stays near the bottom border of the tool window, and the edit control fills the rest of the area:

void WINAPI< myResize(DOCKINFO * dwp, RECT * area)
{
HWND child;

// Position the button above the bottom border
child = GetDlgItem(dw->hwnd, IDC_MYBUTTON);
SetWindowPos(child, 0, rect->left + 10,
rect->bottom - 20, 50, 18, SWP_NOZORDER|SWP_NOACTIVATE);

// Let the edit fill the remaining area
child = GetDlgItem(dw->hwnd, IDC_MYEDIT);
SetWindowPos(child, 0, rect->left, rect->top,
rect->right - rect->left, (rect->bottom - rect->top) - 22,

SWP_NOZORDER|SWP_NOACTIVATE);
}

Closing a tool window
When an owner window is destroyed, all of its tool windows are also automatically destroyed (except if you use the DWS_FREEFLOAT style. In that case, your owner window should handle WM_DESTROY and call DockingDestroyFreeFloat). The docking library will normally free the DOCKINFO for each tool window destroyed.

If you wish to manually close a tool window, you simply call the Windows API DestroyWindow, passing the handle to the desired tool window. Again, the docking library will normally free the DOCKINFO.

If you wish to override the docking library's default behavior of freeing the DOCKINFO, then you must write your own function, and stuff a pointer into the DOCKINFO's DockDestroy field. The docking library will call this function (passing it the DOCKINFO) whenever the tool window has been destroyed. It is your responsibility to eventually free the DOCKINFO by passing it to DockingFree. One use for this is to keep a DOCKINFO allocated (for a given tool window) throughout the lifetime of your application. You will reuse this same DOCKINFO with DockingCreateFrame each time the end user reopens that tool window. Because the DOCKINFO stores the last size and position of the tool window, this means that the tool window will reappear where it was located right before it was previously destroyed. You will then free the DOCKINFO only when your application is ready to terminate.

Saving/Restoring a tool window size/position
Each time that you run your application, you will normally want to restore the same sizes and positions for the tool windows that the end user set upon the last time your application was run. The docking library has two functions to help save and restore tool window positions. The data is saved to the Windows registry under some key of your choosing. The size/position of each tool window is saved separately as values under that key.

Before you free a tool window's DOCKINFO, you should first create/open some registry key of your choosing in order to save that tool window's settings. Then, you will pass the DOCKINFO, and a handle to this open key, to DockingSavePlacement. The docking library will save that tool window's settings. Here is an example of how we could save the settings of a tool window under the registry key "HKEY_CURRENT_USER\Software\MyKey\MyToolWindow":

HKEY hKey;
DWORD temp;

// Open/Create the "Software\MyKey\MyToolWindow" key under CURRENT_USER.
if (!RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\MyKey\\MyToolWindow",
0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKey, &temp))
{
// Let the docking library save this tool window's settings.
DockingSavePlacement(dw, hKey);

// Close registry key.
RegCloseKey(hKey);
}

Whenever your program runs, it should restore those settings by calling DockingLoadPlacement right after DockAlloc'ing the DOCKINFO for that tool window. Here is an example of restoring the previously saved settings:

// Allocate a DOCKINFO structure.
if ((dw = DockingAlloc(DWS_DOCKED_BOTTOM)))
{
HKEY hKey;

// Open the "Software\\MyKey\\MyToolWindow" key under CURRENT_USER.
if (!RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\MyKey\\MyToolWindow",
o, KEY_ALL_ACCESS, &hKey))
{
// Let the docking library restore this tool window's settings.
DockingLoadPlacement(dw, hKey);

// Close registry key.
RegCloseKey(hKey);
}

...

Note: If there are no previously saved settings for the tool window, then none of the DOCKINFO fields are altered

CWaitingTreeCtrl

What is it & Why?
My real intention, when I started this project, was to develop a tree control to display network resources. Enumerating network resources could be a lengthy operation, so I decided to actually do enumeration only when the user tries to expand an item. I realized soon that this kind of behavior could be useful to many other purposes, so I put all the code needed to have this behavior in a base class for my tree control, that was ready to use with other classes. I also added support for visual feedback, while an item is being populated.

When the user clicks to expand an item, a new item is added which contains a wait message. If you implement some animation inside the tree control the message is visible, otherwise it remains hidden. The blue rectangle is the area in which you can draw: you can do everything you want, even completely overwrite the wait message, as long as you stay within that area. Timed animations are also supported.

In the meantime, you can populate the item being expanded with new child items and the tree control will redraw itself to reflect the new content only when you have finished: the user will see only your animation. You may also choose not to display anything while populating, this is very easy.

I also added the ability to repaint the control while populating. The main thread is busy until the population process completes, this is "by design", but I noticed that it still receives WM_ERASEBKGND messages, at least on Win2K. So now I take a snapshot of the control before the process takes place and use that bitmap to redraw the control's background until the expanded item gets populated completely. (Please, report if this works on other platforms)

If you want to see some examples of what you can do with this class take a look at these articles:

CNetworkTreeCtrl
CShellTreeCtrl
CProgressFX and CHourglassFX
Using the Class
This class provides a common interface for two types of derived classes: those providing animation effects and those providing the items for the tree control. Obviously you can implement a single class that provides both content and animation, but you may lose in code re-use.

If you keep the two implementations separated, you can:

focus your attention on the content provider class, which will handle the underlying tree control
choose a ready-to-use animation provider class to add visual feedback to your content provider class or create your own
leave the control without any animation (users won't see the wait message)
enable the wait message when you use your content provider class
Providing Tree Content
Just derive your class from CTreeCtrl and then replace each occurence of CTreeCtrl with CWaitingTreeCtrl in both your '.h' and '.cpp' files.

class CMyTreeCtrl : public CWaitingTreeCtrl
{
...
};

Tip: if you want to use Class Wizard to generate message handlers later, you may temporarily change the base class in the BEGIN_MESSAGE_MAP macro to CTreeCtrl, but remember to restore it to the correct one (CWaitingTreeCtrl) and to change every call to the base implementation in the generated message handlers.

BEGIN_MESSAGE_MAP(CMyTreeCtrl, CWaitingTreeCtrl)
//{{AFX_MSG_MAP(CMyTreeCtrl)
...
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

To fill in the tree control with items you are required to override PopulateItem and to call PopulateRoot somewhere to insert items at the first level. You may also override WantsRefresh to automatically refresh some items whenever they are expanded by user.

CWaitingTreeCtrl::PopulateItem
virtual void PopulateItem(HTREEITEM hParent)

This function gets called in the main thread after an item is expanded and the wait message is displayed. The animation takes place during the execution of this function and it is stopped when the function returns.

Tipically, you use the hParent parameter to get information about the parent item to populate and then you start adding new items. No redraw will happen until this function has finished, this is by design.

Also, there's currently no mean to abort this function, because it blocks the main thread, but you may create the underlying tree control in another user interface thread and provide your own means.

When you implement this function you have to remember the following rules:

you are supposed to add only one level of child items and you should only work with the hParent item and its children
you can always suppose that the hParent item has no children when the function gets called
you need to handle the special case in which hParent is TVI_ROOT
you should set the cChildren field of the TVITEM structure to 1 if you want that child item to be expandable
you should notify your progress to the base class (see below)
Returning TRUE means that the base class will check if you added any items and if it doesn't find any you will not be able to expand the hParent item again (the plus button will be removed).

Returning FALSE means that the hParent item will be always expandable, even if it has no children now. Note that it doesn't mean that the hParent item will be refreshed every time it's expanded, but that it will be expandable until it gets some children. If you want to refresh the hParent item each time it's expanded you have to override WantsRefresh.

Note: if you associate some data with each item you will probably handle the TVN_DELETEITEM notification. If you do so, you should ignore items that have the lParam field equals to zero. They could be the items used to display the wait message and they have no associated data. However this should not be a problem, since a zero value is tipically a NULL pointer.

CWaitingTreeCtrl::WantsRefresh
virtual BOOL WantsRefresh(HTREEITEM hParent)

This function gets called just before PopulateItem, only if the hParent item already has children, to ask a derived class if it wants the item's children to be refreshed.

You have to override this function if you want an item to refresh its children whenever the user expand it. Remember that to refresh items that have no children, you have to tell the base class not to check for inserted items by returning FALSE in your PopulateItem override.

Return TRUE if you want to automatically refresh the item (in the sense described above), or FALSE if you don't want automatic refresh. The base class implementation simply returns FALSE.

CWaitingTreeCtrl::PopulateRoot
void PopulateRoot()

You have to call this function somewhere in your derived class, if you want to see some items. A good place could be in your PreSubclassWindow or OnCreate override, but you may decide to have a function which initializes some data associated with the root item and then simulates a root expansion, populating the first level of items (PopulateItem is called with TVI_ROOT as the parent item).

You may also populate the first or some deeper levels of the tree without requiring the user to expand any items. A parent item that already has children won't be passed to PopulateItem, unless WantsRefresh says it must be refreshed. So static items are perfectly legal and can be used in conjunction with dynamic items.

CWaitingTreeCtrl::SetPopulationCount
void SetPopulationCount(int iMaxSubItems, int iFirstSubItem = 0)

You should call this function in your PopulateItem override, before adding any item, to set the total count of items you plan to insert (iMaxSubItems) and the optional initial value (iFirstSubItem).

You should set the total count to zero if you don't know how many items you're going to insert.

CWaitingTreeCtrl::IncreasePopulation
void IncreasePopulation(int iSubItemsToAdd = 1)

You should call this function in your PopulateItem override, when you insert a new item or a group of items. The current count is increased of the iSubItemsToAdd parameter, which can be a negative value.

CWaitingTreeCtrl::IncreasePopulation
void UpdatePopulation(int iSubItems)

You should call this function in your PopulateItem override, when you insert a new item or a group of items. The current count is updated to the value of iSubItems.

Providing Animation
Create a generic class and derive it from CWaitingTreeCtrl. Later you may replace the base class with a template, so that you can use the class as a plug-in to add custom animations and visual effects to a generic CWaitingTreeCtrl-derived class. (I'm not familiar with templates, so there could be a better way to do it).

template
class CMyAnimationFX : public BASE_TYPE
{
...
};

There are two types of animations: those refreshing at specified time intervals and those getting updated only when new items are inserted by a content provider class.

To display an animation frame, you have to override DoAnimation. If you want to initialize some variables related to the message item, such as the position of the animation frames, you need to override PreAnimation and PostAnimation (only if you need to free some memory at the end).

In all these functions you should call the base class implementation, especially when not directly deriving from CWaitingTreeCtrl. This way, and using templates, you can add more than a single animation to your content provider class.

CWaitingTreeCtrl::PreAnimation
virtual void PreAnimation(HTREEITEM hItemMsg)

This function gets called in the main thread just before the animation starts. Its only argument is the handle of the tree item displaying the wait message. You may use it to calculate the position in which to show the animation, but you shouldn't store it for later use. You can't assume the item still exists during the animation.

The wait message item is not visible by default, if you want to use its attributes to display something near to it, such as an animated item's image, or if you change its visual aspect, you have to call ShowWaitMessage in the constructor (or somewhere before entering this function).

You usually implement this function to initialize additional variables you may need during the animation. Remember that the hItemMsg item is a temporary item: you can use it only from inside this function, and only if the wait message is visible.

If you use the hItemMsg to draw something, you need to make it visible. If the message item is visible, you may also change the visual aspect of the tree control, such as scrolling the client area to properly displaying the animation. If the wait message is hidden the tree control can't redraw itself. You may have this situation if you don't draw the animation inside the tree control, but instead you use another window or control.

CWaitingTreeCtrl::DoAnimation
virtual void DoAnimation(BOOL bTimerEvent, int iMaxSteps, int iStep)

This function gets called in a higher priority worker thread each time you need to update your animation. If the bTimerEvent argument is TRUE this function has been called when the timer interval elapsed, otherwise the item count has been updated. If you want to handle timer events you have to specify the timer period calling SetAnimationDelay before the animation starts, either in the constructor or in PreAnimation.

The other two arguments reflect the current progress of the enumeration process: iMaxSteps is the total number of steps of the process (zero means unknown), while iStep is the current step. You may choose to ignore these during a timer event. You tipically use this information to display a progress bar.

You need to implement this function to draw a new frame of your animation. If you draw within the tree control, you should only draw inside the rectangle obtained by calling GetItemRect(hItemMsg, lpRect, FALSE). Remember that you can't call this function here: you may use CTreeCtrl::GetItemRect or CWaitingTreeCtrl::GetItemImageRect only inside PreAnimation.

CWaitingTreeCtrl::PostAnimation
virtual void PostAnimation()

This function gets called in the main thread just after the animation ends.

You have to implement this function only if you need to clean up some additional variables.

CWaitingTreeCtrl::SetAnimationDelay
void SetAnimationDelay(UINT nMilliseconds)

You may call this function in the constructor, or in your PreAnimation override, to set the delay between timer events. A delay of zero means you don't need timer events and it is the initial value. If you want timer events you have to explicitly call this function.

Note that there is only one timer, so the last call to this function before the animation starts takes precedence (if you want to use multiple animation classes).

CWaitingTreeCtrl::GetItemImageRect
BOOL GetItemImageRect(HTREEITEM hItem, LPRECT pRect)

You may call this function if you need to draw over an item's image, usually beside the wait message.

The return value is TRUE if the function is successful, FALSE otherwise.

Public Functions
There are also a few public functions that your content provider class will inherit.

CWaitingTreeCtrl::SetWaitMessage
void SetWaitMessage(LPCTSTR pszText, HICON hIcon = NULL)

You may call this function to change the wait message's text and image. If the hIcon argument is NULL the item gets a blank icon.

CWaitingTreeCtrl::ShowWaitMessage
void ShowWaitMessage()

You need to call this function in your animation provider class, either in the constructor or in any place before PreAnimation gets called, if you want to draw within the tree control. You are supposed to draw only in the rectangular area occupied by the wait message, so it must be visible. If you draw your animation in another window or if you use another control to provide visual feedback, you are not required to make the wait message visible.

You may also call this function if you don't have any animations, but you want the static wait message. Never call this function in your content provider class, the choice to display the message is left to the final user of your tree control.

Remember that the animation provider class should always assume that the wait message is not visible, and so it must show it if it needs to draw inside the control.

CWaitingTreeCtrl::RefreshSubItems
void RefreshSubItems(HTREEITEM hParent)

You may call this function to manually refresh the hParent item's children. You can only refresh items that can be expanded, whether or not they actually have children. In fact you assign a button only to items that can be expanded by the user, and only those items can be refreshed.

Updates
17 Oct 2000
Initial public release.
27 Sep 2001
Fixed a bug with refreshing empty items and possibly some other things
Added background repainting while busy (at least on Win2k)
License changed to Artistic License
Conclusion
This class probably needs some adjustments, but it's enough for me now. I think you may better understand this class if you see some implementations, so take a look at the other articles (see top). I will appreciate any comment, suggestion or contribution. Any help to improve this (and the others) article is welcome, I know it's not easy for you to read and it has been difficult for me to write.

License
This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors may to use can be found here

Quick Print

This printing class is based on the article by Rob A. Fraydl, "Creating a Printing Class". In fact, it's just an extension of the original class. What exactly has been extended? Well, first of all, I renamed the class to QPrint (stands for quick print). Big achievement, eh? OK, OK, this feature is not an extension at all, but anyway it's a start.

The second extension was to add the ability to manage big strings that extend beyond the right border of the physical paper size. Now, the big strings are not clipped at the end of the right border, but instead they continue to the next line.

Last but not the least, the new printing class supports tables as well as page orientation.

Background
As already said in the beginning, this piece of code did not start from scratch. You can always refer to the original printing class named CPrivatePrint. Please feel free to use and improve this code. Other than the additions I have made, I also corrected some bugs. I am not sure in some points if they were bugs, especially the part with the logical units, using the macros CALCF(x), CALCY(y), and CALCX(x). I deleted CALCX(x) completely, and the CALCY(x) partially, from the source code. So, if you meet "strange" results, please let me know. The others were safe point bugs that got corrected successfully.

I tested this class using the JawsPDF Creator (to save paper, time, patience, and finally money!) and different ink jet printers. It worked flawlessly.

Using the code
I chose this printing class as a base to build QuickPrint, because it was compact and easy to use. So, the only thing that you have to do is to declare the CQPrint prt and start using it. The following example shows the basic steps that you have to do:

void TestDlg::OnPrintButton ()
{
CQPrint prt;
HPRIVATEFONT hFont;

// Step 1 : call the CPrintDialog
if (prt.Dialog() == -1)
return;

//Step 2 : Start the Print
prt.StartPrint();

//Step 3 : Create a printing font
hFont = prt.AddFontToEnvironment("Arial Greek",8,8);

//Step 4 : Start Page
prt.StartPage();

//Step 5 : The actual printing goes here
prt.Print(hFont,"Set Font Arial Size = 8",FORMAT_NORMAL);

//Step 6 : now end the page
prt.EndPage();

//Step 7 : close the print document
// and release it in the spooler
prt.EndPrint();
}

You can see a live example (like the picture in this tutorial) in the following code:

Collapse CQPrint prt;
HPRIVATEFONT hFont,hFont1,hFont2;
CSize dim;
CRect margins;



// initializing the class and printer selection
if (prt.Dialog() == -1)
return;

// create the print document in the spooler
prt.SetPageOrientation (DMORIENT_PORTRAIT);
prt.StartPrint();


// Adding the arial font to the class
hFont = prt.AddFontToEnvironment("Arial Greek",8,8);
hFont1 = prt.AddFontToEnvironment("Arial Greek",9,9);
hFont2 = prt.AddFontToEnvironment("Arial Greek",10,10);

prt.SetMargins(40,160,200,200);
prt.SetDistance(5);

prt.GetDrawDimension(dim);
prt.GetMargins(margins);
prt.SetHPos(dim.cy-margins.top);
// activate a header and footer line
prt.SetActiveFont (hFont);
prt.ActivateHF(HeaderFooter);



// set margins and line spacing

prt.StartPage();
// start printing the lines


prt.Print(hFont,"Set Font Arial Size = 8",FORMAT_NORMAL);
prt.Print(hFont,"My name is George Papaioannou",FORMAT_NORMAL);
prt.Line (PS_SOLID);


prt.Print(hFont1,"Set Font Arial Size = 9",FORMAT_NORMAL);
prt.Print(hFont1,"My name is George Papaioannou",FORMAT_NORMAL);
prt.Line (PS_SOLID);


prt.Print(hFont2,"Set Font Arial Size = 10",FORMAT_NORMAL);
prt.Print(hFont2,"My name is George Papaioannou",FORMAT_NORMAL);
prt.Line (PS_SOLID);



// ===================== TABLE 1 ==========================

prt.lf(hFont2);
prt.lf(hFont2);
prt.Print(hFont2,"TABLE 1",FORMAT_CENTER);
prt.lf(hFont2);
prt.lf(hFont2);


int size[5];
size[0]=800;
size[1]=400;
size[2]=400;
size[3]=400;


prt.SetTableColumns (3);
prt.SetTableColumnsSize(size);
prt.SetTableHorLine (TRUE);
prt.SetTableVerLine (TRUE);
prt.SetTableBorderSize (200);
prt.SetTableJustify(FORMAT_CENTER);
prt.SetTableBorderLine(5);
//set the headers
prt.SetFace (hFont,FACE_NORMALBOLD);
prt.AddTableRecord (hFont,"NAME|GRADE|CITY",FORMAT_CENTER);
//set the records
prt.SetFace (hFont,FACE_NORMAL);
prt.AddTableRecord (hFont,"George Papaioannou|Grade"
" A|Lives at Athens (Greece)",FORMAT_CENTER);
prt.AddTableRecord (hFont,"Kostas Papadimitriou|"
"Grade B|Lives at Larissa (Greece)",FORMAT_CENTER);

prt.lf(hFont2);
prt.lf(hFont2);

prt.Print(hFont2,"AppWizard has created this PrintJob"
" application for you. This application "
"not only demonstrates the basics"
" of using the Microsoft Foundation classes "
"but is also a starting point for"
" writing your application.",FORMAT_NORMAL);

// ===================== TABLE 2 ==========================
prt.lf(hFont2);
prt.lf(hFont2);
prt.Print(hFont2,"TABLE 2",FORMAT_CENTER);
prt.lf(hFont2);
prt.lf(hFont2);


size[0]=800;
size[1]=400;
size[2]=600;
size[3]=600;

prt.SetTableReset();
prt.SetTableColumns (4);
prt.SetTableColumnsSize(size);

//set the headers
prt.SetFace (hFont,FACE_NORMALBOLD);
prt.AddTableRecord (hFont,"NAME|GRADE|CITY|ADDRESS",
FORMAT_CENTER);
//set the records
prt.SetFace (hFont,FACE_NORMAL);
prt.AddTableRecord (hFont,"George Papaioannou|Grade A|"
"Athens (Greece)|23 Octobriou",FORMAT_CENTER);
prt.AddTableRecord (hFont,"Kostas Papadimitriou|Grade B|"
"Larissa (Greece)|Irron Polytechneiou",FORMAT_CENTER);


// =================== TABLE 3 ==============================
prt.lf(hFont2);
prt.lf(hFont2);
prt.Print(hFont2,"TABLE 3",FORMAT_CENTER);
prt.lf(hFont2);
prt.lf(hFont2);

size[0]=800;
size[1]=400;
size[2]=600;
size[3]=600;

prt.SetTableReset();
prt.SetTableColumns (4);
prt.SetTableColumnsSize(size);
prt.SetTableHorLine (TRUE);
prt.SetTableBorderSize (200);
prt.SetTableJustify(FORMAT_RIGHT);


//set the headers
prt.SetFace (hFont,FACE_NORMALBOLD);
prt.AddTableRecord (hFont,"NAME|GRADE|CITY|ADDRESS",FORMAT_CENTER);
//set the records
prt.SetFace (hFont,FACE_NORMAL);
prt.AddTableRecord (hFont,"George Papaioannou|Grade A|"
"Athens (Greece)|23 Octobriou",FORMAT_CENTER);
prt.AddTableRecord (hFont,"Kostas Papadimitriou|Grade B|"
"Larissa (Greece)|Irron Polytechneiou",FORMAT_CENTER);

// now end the page
prt.EndPage();

// close the print document and release it in the spooler
prt.EndPrint();

You can call the following table methods:

CQPrint::SetTableColumns(int Cols)
SetTableColumns sets the table columns.

prt.SetTableColumns(3);

CQPrint::SetTableBorderSize(int Border)
SetTableBorderSize sets the table border space. Default is 50.

prt.SetTableBorderSize(50);

CQPrint::SetTableColumnsSize(int Cols,int size[])
SetTableColumnsSize sets the table column size. The maximum number of columns permitted is 20.

int size[4];
size[0]=800;
size[1]=400;
size[2]=600;
size[3]=600;

prt.SetTableColumnsSize(4,size);

CQPrint::SetTableHorLine(BOOL HLine)
SetTableHorLine sets the horizontal lines. Default is OFF (no lines).

prt.SetTableHorLine(TRUE);

CQPrint::SetTableVerLine(BOOL HLine)
SetTableVerLine sets the vertical lines. Default is OFF (no lines).

prt.SetTableVerLine(TRUE);

CQPrint::SetTableBorderLine(int Border)
SetTableBorderLine sets the width of the border line. Default is 0 (thin line).

prt.SetTableBorderLine(10);

CQPrint::SetTableJustify(int Justification)
SetTableJustify justifies the table. Default is FORMAT_NORMAL. You can also use FORMAT_CENTER and FORMAT_RIGHT.

prt.SetTableJustify(FORMAT_CENTER);

CQPrint::SetTableReset()
SetTableReset resets all the table parameters to the default state. Use this if you don't want to change values when using consecutive tables with different visual parameters.

prt.SetTableReset();

CQPrint::AddTableRecord(HPRIVATEFONT font, CString strText, UINT Format)
AddTableRecord inserts values to the current table. Use the | symbol to change the column in strText.

prt.AddTableRecord (hFont,"Column 1|Column 2|"
"Column 3|Column 4",FORMAT_CENTER);

If you want to change the page orientation, use SetPageOrientation before StartPrint. Use the values DMORIENT_PORTRAIT or DMORIENT_LANDSCAPE.

prt.SetPageOrientation (DMORIENT_PORTRAIT);
prt.StartPrint();

You can always use the "original" methods of the class like SetMargins(), Line(), lf() etc. More details about these functions can be found in the demo. You have to experiment a little before you use it. Don't forget that this class also supports bitmap printing with the InsertBitmap() method. I was wondering if there is someone who can extend this class to support other image formats using GDI+. Maybe the next version of QPrint will support print preview as well as image printing (JPG, GIF, PCX etc.), but it's a little bit complicated, since my wife Natasa is already negative to this perspective. So, don't count on this.

Don't forget that you can always use the callback function ActivateHF() that is used to print headers and footers. I have also included this feature in my demo. Anyway, there are more methods in QPrint that you can find in this short tutorial, so start playing with the demo to get an idea of how this stuff works, and then try to discover the more advanced capabilities that this class supports.

Points of Interest
Please, if you see problems with this class, especially with the (LOGPIXELSY, LOGPIXELSX) width and height of the letters, feel free to contact with me.

History
There is no history for the Quick Print (QPrint) class. This is the first and the only version. The base and the original code for this class was from Rob A. Fraydl and is called CPrivatePrint.

License