Click here to Skip to main content
13,002,343 members (73,481 online)
Click here to Skip to main content
Add your own
alternative version


129 bookmarked
Posted 14 Dec 2000

Using the Header Control

, 12 May 2001
Rate this:
Please Sign up or sign in to vote.
An article describing how to use the header control


This tutorial is designed to introduce you to the CHeaderCtrl class when used in the context of the CListCtrl. Although it is possible to use the CHeaderCtrl outside of the CListCtrl, the two are intimiately related in most applications. The topics covered include:

It also covers the more advanced issue of subclassing your own CHeaderCtrl in place of the standard CHeaderCtrl. This section addresses topics including:

This article assumes that you are relatively familiar with VC6++ and are able to navigate using both the Workspace window as well as ClassWizard. It also assumes that you are comfortable creating a dialog-based application, creating controls, and using ClassWizard to attach member variables to those controls. If you are not familiar with these topics, please consult Dr. Asad Altimeemy's article, <A href="(Empty Reference!)" target=>A Beginners Guide to Dialog Base Applications - Part 1. The CHeaderCtrl demonstration project utilizes a dialog-based MFC application with a CListCtrl in Report mode. For an introductory tutorial addressing how to use a CListCtrl in a dialog-based application, see <A href="" target=>Using the List Control.

About CHeaderCtrl

Although the CHeaderCtrl is a relatively infrequently used Common Control class (compared to say, CButton or CEdit), it is most often used in tandem with the CListCtrl. When the CListCtrl is shown in Report mode (LVS_REPORT), the top portion of the ListView is taken over by a CHeaderCtrl to segregate the entries into items and sub-items. When the CListCtrl is displaying its contents in the other modes, the CHeaderCtrl is hidden. This is most easily seen in Explorer when the view is toggled between Folder Details and any of the other modes.

The fact that the CHeaderCtrl's visibility is managed by the CListCtrl will become important later when we address message handling. Whereas other Common Controls placed on CDialog or CForm objects have those Windows as their parents, the CHeaderCtrl's parent is actually the CListCtrl. This level of indirection causes problems for ClassWizard's default Message Map entries.

Retrieving a CHeaderCtrl Pointer

Given a CListCtrl in Report mode, there are two different ways to retrieve a pointer to the CHeaderCtrl. One is documented, while the other may be encountered in legacy code.

Supported Method

The easiest way to retrieve a CHeaderCtrl is to use the CListCtrl::GetHeaderCtrl() member function. The demonstration project uses this member function in the CHeaderCtrlDemoDlg::OnListHdr() function as follows:

// Get a CHeaderCtrl pointer
CHeaderCtrl *pHeader = m_cListCtrl.GetHeaderCtrl();

Undocumented Method

If memory serves me, the CListCtrl::GetHeaderCtrl() is a recent addition to the MFC library. If you are tasked with documenting or revising existing code, you may encounter an alternative way to retrieve a CHeaderCtrl pointer. This method relies on the Control ID that the CListCtrl assigns to the CHeaderCtrl. The easiest way to find this ID is to examine the CListCtrl with Spy++, which is truly invaluable when you are trying to determine how the Common Controls work and where they send their messages.

Examining the CListCtrl with Spy++ reveals that the Control ID assigned to the CHeaderCtrl is always 0. This implies an alternative method to retrieve the CHeaderCtrl is to use code such as that used in CHeaderCtrlDemoDlg::InitHeaderCtrl:

// Get a CHeaderCtrl pointer
CWnd *pWnd = m_cListCtrl.GetDlgItem(0);
CHeaderCtrl *pHeader = static_cast<CHeaderCtrl*>(pWnd); 

In either case, once the CListCtrl has been created we can access the CHeaderCtrl and begin to manipulate its contents and/or behavior.

Inserting Items

For most situations, inserting items into the CHeaderCtrl will be done via the CListCtrl::InsertColumn(...) function. This function ultimately uses the LVCOLUMN structure to insert items into the CHeaderCtrl child window. This overloaded function allows you to pass the 0-based column index and either a pointer to a LVCOLUMN structure itself, or simply the parameters for the new column. In the latter case the CListCtrl handles creating the LVCOLUMN structure and sending the appropriate message. The CHeaderCtrl demo project uses the following snippet in CHeaderCtrlDemoDlg::InitListCtrl() to create the columns:

// Create the columns
CRect rect;
int nInterval = rect.Width()/5;
m_cListCtrl.InsertColumn(0, _T("Item"), LVCFMT_LEFT, nInterval*2);
m_cListCtrl.InsertColumn(1, _T("Type"), LVCFMT_LEFT, nInterval);
m_cListCtrl.InsertColumn(2, _T("Price"), LVCFMT_LEFT, rect.Width()-3*nInterval-16); 

The reason for the peculiar width assigned to column 3 will be discussed below.

Formatting Options

Items in the CHeaderCtrl can be aligned in three different ways: left, center, and right. If the CHeaderCtrl format is specified by calling the CListCtrl::InsertColumn(...) function, then the constants are:


Alternatively, item formatting can be controlled by functions exposed by the CHeaderCtrl class itself. Functions such as CHeaderCtrl::InsertItem(...) and CHeaderCtrl::SetItem(...) the constants are defined as:


In reality, these constants are #define (d) to be equivalent values (see COMMCTRL.H), but they have distinct names. The CHeaderCtrl demo project uses the alignment options in CHeaderCtrlDemoDlg::InitHeaderCtrl(...) which is discussed below.

Using Images

Like many other Common Controls, the CHeaderCtrl supports images. However, unlike other Common Controls, when items are inserted into the control using the more common CListCtrl interfaces, there is no obvious way to attach the image list. Attaching an image list to the CHeaderCtrl is a three step process:

  1. Create the CImageList
  2. Attach the CImageList
  3. Set the item images

Each of these steps is followed in the CHeaderCtrlDemoDlg::InitHeaderCtrl initialization function.

1. Create the CImageList

CImageList creation is fairly standard. To create a CImageList populated with images stored in a bitmap resource, use code such as:

VERIFY(m_cImageList.Create(IDB_HEADER_CTRL, 16, 4, RGB(255, 0, 255)));

Please note that the m_cImageList member variable is a member of CHeaderCtrlDemoDlg, not a locally declared variable. The reason for this is that when the CImageList is attached to the CHeaderCtrl, the CHeaderCtrl does not make a copy of the object.. Instead, the caller is responsible for maintaining the validity of that address, since it is only a pointer that is passed when the CImageList is attached to the Common Control. Therefore, if a locally defined CImageList variable were used, the address would no longer be valid when the function goes out of scope. Making the m_cImageList a member of CHeaderCtrlDemoDlg avoids this problem.

2. Attach the CImageList

To attach the newly created CImageList to the CHeaderCtrl, we need only a pointer to the CHeaderCtrl to access the CHeaderCtrl::SetImageList(...) member function. Assigning the CImageList can therefore be accomplished with the following:

// Get a CHeaderCtrl pointer
CWnd *pWnd = m_cListCtrl.GetDlgItem(0); // Could also use m_cListCtrl.GetHeaderCtrl();
CHeaderCtrl *pHeader = static_cast<CHeaderCtrl*>(pWnd); 

//  Set the CImageList

With the ImageList attached, the CHeaderCtrl images can now be assigned.

3. Set the item images

Setting CHeaderCtrl images is done via the CHeaderCtrl::SetItem(...) member function. To my knowledge, there is no way to assign images to the CHeaderCtrl items using methods exposed by the CListCtrl. Therefore, if you wish to set the CHeaderCtrl images, you need to access the items that were inserted with CListCtrl methods and modify them using CHeaderCtrl methods. This can be done using the HDITEM structure and the CHeaderCtrl::SetItem(...) function. The HDITEM structure is similar to other Common Control item structures (LVITEM, TVITEM) and is used to both Set and Get information about an item. Like these other structures, the most important element in the structure is the HDITEM.mask variable. This UINT specifies either:

  • The fields that contain valid data; used for CHeaderCtrl::SetItem(...)
  • The fields whose data should be retrieved; used for CHeaderItem::GetItem(...)

For instance, the CHeaderCtrlDemo project uses the HDITEM structure to set the header image and alignment in the CHeaderCtrlDemoDlg::InitHeaderCtrl() member function:

// Iterate through the items and set the image
for (int i=0; i < pHeader->GetItemCount(); i++)
	pHeader->GetItem(i, &hdi);
	hdi.fmt |= g_uHDRStyles[i%3] | HDF_IMAGE;
	hdi.mask |= HDI_IMAGE | HDI_FORMAT;
	hdi.iImage =  i;
	pHeader->SetItem( i, &hdi);

In this loop, you first retrieve the contents of each item in the CHeaderCtrl. Then you also set the HDI_IMAGE and HDI_FORMAT flags in the retrieved structure, populate the necessary fields and re-assign the CHeaderCtrl item. Note that g_uHDRStyles is an array of UINTs that stores the three (3) different CHeaderCtrl text alignment constants (see above).

Finally, because we are using both an image and a HDF_RIGHT alignment parameter on the thirdmost column, we need to explicitly account for this difference when assigning column widths. This accounts for the rather unusual sizing calculation made above, to offset the width of the image which is "added" to the existing size of the column.

Handling Notification Messages

Handling CHeaderCtrl messages in a CListCtrl strains ClassWizard's magic powers. The problem is that ClassWizard considers the CHeaderCtrl embedded in a CListCtrl to be of the same "message depth" as any other Common Control (CButton or CEdit Control, for instance). In practice however, the CHeaderCtrl is actually a child of the CListCtrl in which it resides. While the CListCtrl's immediate parent is the CDialog in which it is instantiated, the CHeaderCtrl's immediate parent is the CListCtrl. This is also suggested by the following two points:

  • Unlike other Common Controls on a CDialog, the CHeaderCtrl in the CListCtrl is not given an explicit resource ID;
  • The existence of the CListCtrl::GetHeaderCtrl(...) method

This problem arises when you use ClassWizard to create CHeaderCtrl handlers in a CDialog derived class. To create a CHeaderCtrl message handler, you first right click the CListCtrl object in the Resource Editor and select ClassWizard from the popup menu. ClassWizard then enumerates all the messages for this CListCtrl, including those sent by the CHeaderCtrl window. These messages are handled through the ON_NOTIFY macro construction. The list looks something like this:

Attempting to implement a message handler for HDN_<X> messages using Class Wizard will then generate an incorrect Message Map entry. For instance, creating a message handler for the HDN_ENDDRAG message will create an entry such as: ON_NOTIFY(HDN_ENDDRAG, IDC_LIST_CTRL, OnEnddragListCtrl). Unfortunately, the IDC_LIST_CTRL is not responsible for notifying the parent of a HDN_ENDDRAG message. It is the embedded CHeaderCtrl which sends the message, so the ON_NOTIFY macro should use the ID of the CHeaderCtrl. Using Spy++, we can determine that the ID of the CHeaderCtrl is 0, so the ON_NOTIFY entry needs to be manually edited to the following: ON_NOTIFY(HDN_ENDDRAG, 0, OnEnddragListCtrl). This roundabout way connects the WM_NOTIFY messages of the CHeaderCtrl to the "second-level" parent of the CHeaderCtrl, which is often where message processing is handled.

Updating Items

Like the CListCtrl, items in the CHeaderCtrl can be updated at runtime. In the sample product, this is illustrated in CHeaderCtrlDemoDlg::OnSetHdr() which sets the column header text. After validating the input, the column header text is updated using the HDITEM structure. This structure is similar to the LVITEM and TVITEM structures that are used to Set and Get information from the CListCtrl and CTreeCtrls, respectively. The caller first specifies which HDITEM fields are valid (using the HDITEM.mask field) and then executes the Set method. In the sample project, this is done in the following snippet:

// Update this item
hdi.mask = HDI_TEXT;
hdi.pszText = (LPTSTR)(LPCTSTR)m_strColText;
pHeader->SetItem(m_nCol, &hdi);

Deriving a Custom CHeaderCtrl

In some cases it is useful to derive a custom CHeaderCtrl class that can be re-used in other projects; perhaps a CHeaderCtrl that can manage icons in the Header area denoting the current sort order. To derive a custom CHeaderCtrl class, invoke ClassWizard and create a custom class as in the following:

In the sample project, the classname for the custom CHeaderCtrl is CAdvHeaderCtrl. This class will be responsible for attaching images to the items in the column as well as dynamically setting the column image as the user changes its width.


The next problem is replacing the default CHeaderCtrl embedded in the CListCtrl with your custom CMyHeaderCtrl. This can be done by exposing an initialization function from your custom class (see CAdvHeaderCtrl::Init(...)) which can be called when the dialog or view is created; typically CDialog::OnInitDialog(), CWnd::PreCreateWindow() or CWnd::Create(). In the sample project, this is accomplished in the following snippet of CAdvHeaderCtrl::Init(CHeaderCtrl *pHeader):

ASSERT(pHeader && pHeader->GetSafeHwnd());
if (!SubclassWindow(pHeader->GetSafeHwnd()))
	OutputDebugString(_T("Unable to subclass existing header!\n"));
	return FALSE;

This replaces the existing WindowProc of the CHeaderCtrl with the WindowProc of the CAdvHeaderCtrl such that the custom class can respond to both conventional Window messages and HDN_<X> notifications.

Handling Reflected Notification Messages

The CAdvHeaderCtrl is a simple class whose only responsibility is to dynamically change an item image as the user changes the column width. It also overrides the default CHeaderCtrl style to enable Hot-Tracking: the CHeaderCtrl column text color changes when the user moves the mouse over its contents.

It accomplishes this through the handling of two reflected messages:


However, this also proves to be the most difficult aspect of the CHeaderCtrlDemo project. Although I have not spent sufficient time to identify the exact problem, there appears to be an issue with ANSI versus UNICODE HDN_<X> notification messages. That is, the expected ANSI messages are not sent. MSFT KB article <A href="(Empty Reference!)" target=>Q148533 discusses a similar problem regarding Common Controls: ANSI notification messages are not received when the controls are created in response to a WM_CREATE message handler in a CDialog or CFormView derived class. I suspect that something similar is afflicting the CAdvHeaderCtrl demo project. Therefore, I was forced to implement Message Map macros for both the ANSI and UNICODE versions of the reflected WM_NOTIFY messages. Without these entries, the reflected necessary message handlers were never called.

The Message Map therefore consists of four manually entered entries:

BEGIN_MESSAGE_MAP(CAdvHeaderCtrl, CHeaderCtrl) 
// NOTE - the ClassWizard will add and remove mapping macros here. 

These entries seemed to do the trick, although I cannot guarantee that they will continue to do so in the future. Nevertheless, the OnBeginTrack and OnEndTrack are relatively simple functions that demonstrate the types of things that can be done in a self-contained CHeaderCtrl-derived class. Each function follows the standard prototype for WM_NOTIFY handlers: OnReflectedHandler(NMHDR * pNMHDR, LRESULT* pResult). OnBeginTrack queries the selected item for its image index, saves the index, and sets a new image index.

// Get the current image in the item and save the index
hdi.mask = HDI_IMAGE;
GetItem(pHdr->iItem, &hdi);
m_nSavedImage = hdi.iImage;
// Set the new image
SetImage(pHdr->iItem, 3);

CAdvHeaderCtrl::SetImage(nCol, nImage) simply set the image of nCol. OnEndTrack just restores the temporary assignment to the original value:

if (-1 == m_nSavedImage)

SetImage(pHdr->iItem, m_nSavedImage);
m_nSavedImage = -1;

In this way, the behavior of the custom CHeaderCtrl can be managed by the class itself, rather than placing responsibility on the parent.


The CHeaderCtrl is a relatively underutilized (and thus underappreciated ?) MFC Common Control class. Particularly when working in database applications, a CHeaderCtrl is crucial to providing an effective user interface. It can also provide cues in response to user input. I hope that this tutorial has been a useful introduction to the CHeaderCtrl


13 May 2001 - Download now available


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 might use can be found here


About the Author

Matt Weagle
Web Developer
United States United States
No Biography provided

You may also be interested in...


Comments and Discussions

QuestionCHeaderCtrl Pin
Member 971707322-Aug-13 10:38
memberMember 971707322-Aug-13 10:38 
QuestionMove Image to right of text... Pin
EclipC28-Jun-12 21:50
memberEclipC28-Jun-12 21:50 
GeneralYes it is a useful article for CHeaderCtrl Pin
mohamed idayathulla22-Apr-07 21:06
membermohamed idayathulla22-Apr-07 21:06 
GeneralChange header's height Pin
G.A.21-Mar-05 21:12
memberG.A.21-Mar-05 21:12 
GeneralCViewList Pin
cheshercat8-Jul-04 2:17
membercheshercat8-Jul-04 2:17 
GeneralHeader Background color Pin
g_bhaskaraiah26-May-04 2:33
memberg_bhaskaraiah26-May-04 2:33 
GeneralRe: Header Background color Pin
ChrisRibe29-Aug-06 12:04
memberChrisRibe29-Aug-06 12:04 
GeneralTrick for the ON_NOTIFY problem Pin
_NR_19-Nov-03 12:13
member_NR_19-Nov-03 12:13 
GeneralRe: Trick for the ON_NOTIFY problem Pin
ChitMak19-Dec-05 12:33
memberChitMak19-Dec-05 12:33 
Questionhow to disable the resizing of column headers (size of the column...shud be fixed)in listcontrol...... Pin
Adi Narayana26-Mar-03 2:50
memberAdi Narayana26-Mar-03 2:50 
AnswerRe: how to disable the resizing of column headers (size of the column...shud be fixed)in listcontrol...... Pin
Thomas Freudenberg10-Mar-04 0:17
memberThomas Freudenberg10-Mar-04 0:17 
AnswerRe: how to disable the resizing of column headers (size of the column...shud be fixed)in listcontrol...... Pin
JuBeC7-Jun-06 1:38
memberJuBeC7-Jun-06 1:38 
GeneralRe: how to disable the resizing of column headers (size of the column...shud be fixed)in listcontrol...... Pin
ChrisRibe31-Aug-06 6:39
memberChrisRibe31-Aug-06 6:39 
Generalusing two list controls Pin
Striges25-Jan-03 11:16
memberStriges25-Jan-03 11:16 
GeneralRe: using two list controls Pin
Uwe Probst28-Sep-04 2:35
memberUwe Probst28-Sep-04 2:35 
GeneralThx for so good Discription Pin
Anonymous4-Sep-02 21:59
sussAnonymous4-Sep-02 21:59 
Generalabout header !!!(please come in.) Pin
cooker21-Aug-02 22:53
susscooker21-Aug-02 22:53 
QuestionMouse Cursor Change ? Pin
Koep15-Jul-02 6:06
memberKoep15-Jul-02 6:06 
GeneralColor the whole header Pin
RobJones4-Dec-01 8:18
memberRobJones4-Dec-01 8:18 
GeneralDrawing on the HeaderCtrl Pin
David Fleming19-Nov-01 22:43
memberDavid Fleming19-Nov-01 22:43 
I have written a sortable ListCtrl subclassed from
CListCtrl. I want to draw an arrow (up or down)
in the header of the sorted column. To avoid needing
a bitmap resource, I would like to simply draw a
little triangle on the header instead of attaching
an ImageList, etc.

How do I get the appropriate DC and do the drawing?

Here's what I have written (below), but as I step
through the code (with Vis Studio and the program
side-by-side), it doesn't seem to do anything at all.

void CCallbackListCtrl::ShowHeaderArrow(int nCol /*= 0*/, BOOL bArrowOn /*= true*/, BOOL bAscending /*= true*/)
	//This routine will draw a little arrow (up or down)
	// at the right end of the column which is sorted.
	//Get pointer to the CHeaderCtrl used by the ListCtrl
	CHeaderCtrl* pHeader = GetHeaderCtrl();
	//Get device context for drawing
	CDC* pDC = pHeader->GetDC();
	//Save the DC
	int iSavedDC = pDC->SaveDC();
	//Get the RECT of the header column
	CRect rect;
	pHeader->GetItemRect(nCol, &rect);
	pHeader->ClientToScreen(&rect); //convert to screen coordinates
	//Get the column Text and Format
	TCHAR szText[256]; //set up string holder
	HD_ITEM hdItem;
	hdItem.mask = HDI_TEXT | HDI_FORMAT; //set what info to retrieve
	hdItem.pszText = szText; //establish buffer to hold text info
	hdItem.cchTextMax = 255; //set maximum # of characters to return
	pHeader->GetItem(nCol, &hdItem);
	//Set format info for drawing the column label
	if(hdItem.fmt & HDF_CENTER)
		uFormat |= DT_CENTER; //centered
	else if(hdItem.fmt & HDF_RIGHT)
		uFormat |= DT_RIGHT; //right aligned
		uFormat |= DT_LEFT; //left aligned
	//Set up some variables to establish position and size of arrow drawn
	int iOffset = (rect.bottom - / 3;
	int iOffsetOffset = 0;
	if(iOffset % 2 != 0)
		iOffsetOffset = iOffset / 2 + 1; //round up for an odd numbered iOffset
		iOffsetOffset = iOffset / 2;
	////Begin drawing
	//Draw the background
	CBrush brush(GetSysColor(COLOR_3DFACE));
	pDC->FillRect(rect, &brush);
	//Draw the column text
	CString cstrText = "Test"; //szText; //create a CString and set it to the column's text
	rect.OffsetRect(10, 0); //just testing
	(void)pDC->DrawText(cstrText, rect, uFormat);
	CSize sizeLeftOffset = pDC->GetTextExtent(cstrText);
	int iLeftOffset = rect.Width() -; //this is the end of text string (x-axis)
	//Draw the sort arrow
		//Set up the pens to use (one light and one dark)
		CPen penLight(PS_SOLID, 1, GetSysColor(COLOR_3DHIGHLIGHT));
		CPen penDark(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW));
		CPen* pOldPen = pDC->SelectObject(&penLight); //set the pen to use and save old pen settings
		//Set up points of triangles
		CPoint Pt1, Pt2, Pt3; //there are 3 points to each triangle (arrow)

			//Draw an up arrow
			//There are 3 points to the triangle.
			// For the up-arrow, we will consider Pt1 the top.
			//  Pt2 is the bottom right, Pt3 is bottom left.
			Pt1.x = rect.right - (iOffset * 3); //rect.right - iLeftOffset + (iOffset * 3);
			Pt1.y = + iOffset;
			Pt2.x = Pt1.x + iOffsetOffset;
			Pt2.y = Pt3.y = Pt1.y + (iOffsetOffset * 2); //Pt2 and Pt3 are along the bottom so have same y value
			Pt3.x = Pt1.x - (iOffsetOffset + 1);
			(void)pDC->SelectObject(&penDark); //change to darker color
			Pt1.Offset(-1, 0); //move the start point over one
			//Pt3.Offset(0, -1); //move the end point up one
			//Draw a down arrow
			//There are 3 points to the triangle.
			// For the down-arrow, we will consider Pt1 the bottom.
			//  Pt2 is the top left, Pt3 is the top right.
			Pt1.x = rect.right - iLeftOffset + (iOffset * 3); //rcIcon.right - (2 * iOffset);
			Pt1.y = + iOffset + (iOffsetOffset * 2); //this is same as bottom of the up-arrow
			Pt2.x = Pt1.x - iOffsetOffset;
			Pt2.y = Pt3.y = Pt1.y - (iOffsetOffset * 2); //Pt2 and Pt3 are along the top so have same y value
			Pt3.x = Pt1.x + iOffsetOffset;
			Pt1.Offset(+1, 0); //move start point over one
		//Restore the pen
	//Restore previous device context settings


Any suggestions would be welcome.
GeneralHide a column Pin
spelger27-Aug-01 5:24
memberspelger27-Aug-01 5:24 
QuestionGreat stuff, but no downloadable demo or source? Pin
David Fleming8-May-01 23:43
memberDavid Fleming8-May-01 23:43 
AnswerRe: Great stuff, but no downloadable demo or source? Pin
Matt Weagle9-May-01 4:56
memberMatt Weagle9-May-01 4:56 
GeneralRe: Great stuff, but no downloadable demo or source? Pin
David Fleming9-May-01 10:50
memberDavid Fleming9-May-01 10:50 
GeneralGood article Pin
Amit Dey29-Dec-00 10:47
memberAmit Dey29-Dec-00 10:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170626.1 | Last Updated 13 May 2001
Article Copyright 2000 by Matt Weagle
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid