Using an imagelist with the header control


Although we can use a bitmap with the header control and specify a bitmap for each item in the header individually, there are two benefits of using an image list instead. The first is that the images can be drawn transparently ( and a few other effects ) and the second is that you can manage all the images in a single bitmap or you can use icons. An image in the header can be useful when there isn't enough space to display the complete column header label or even otherwise it can convey some application state.

Since the header control does not support an image list, we have to provide the support in a CHeaderCtrl derived class. The class uses the owner draw feature of the header control to display the image in the image list.

Step 1: Derive class from CHeaderCtrl

If you don't have a class derived from CHeaderCtrl, derive one now. You can use the Class Wizard to create one for you. I used the name CMyHeaderCtrl for the derived class.

Step 2: Add member variables

Add member variables in CMyHeaderCtrl to hold the image list and and information about which image should be displayed with which column. These variables are declared as protected members and we will provide a function to set them. The m_mapImageIndex object will store the column number and the associated image number.
protected:
	CImageList* m_pImageList;
	CMap<int, int, int, int> m_mapImageIndex;

Step 3: Initialize the variables in the constructor

Initialize the m_pImageList variable to NULL in the constructor. This indicates that an image list has not been set.
CMyHeaderCtrl:: CMyHeaderCtrl() 
{
	m_pImageList = NULL;
}

Step 4: Add acess functions in CMyHeaderCtrl

We need to provide a function to set the image list, another to set the image item and while we are at it, lets provide another function to retrieve the item image.

The SetImageList() function simply updates the member variable and returns the previous image list. The GetItemImage() function returns the image associated with a particular header item. It will return -1 if the item does not have any image associated with it.

The SetItemImage() sets up the image mapping and changes the header item to owner drawn. This will ensure that the DrawItem() function gets called. The function then invalidates the header control so that any change is displayed.

CImageList* CMyHeaderCtrl::SetImageList( CImageList* pImageList )
{
	CImageList *pPrevList = m_pImageList;
	m_pImageList = pImageList;
	return pPrevList;
}

int CMyHeaderCtrl::GetItemImage( int nItem )
{
	int imageIndex;
	if( m_mapImageIndex.Lookup( nItem, imageIndex ) )
		return imageIndex;
	return -1;
}

void CMyHeaderCtrl::SetItemImage( int nItem, int nImage )
{
	// Save the image index
	m_mapImageIndex[nItem] = nImage;

	// Change the item to ownder drawn
	HD_ITEM hditem;

	hditem.mask = HDI_FORMAT;
	GetItem( nItem, &hditem );
	hditem.fmt |= HDF_OWNERDRAW;
	SetItem( nItem, &hditem );

	// Invalidate header control so that it gets redrawn
	Invalidate();
}

Step 5: Override DrawItem()

The DrawItem() function is called for each item in the header control that has the HDF_OWNERDRAW format. This where we add the code to display the image from the image list. If an image is drawn, the column label is moved to the right so that it does not overwrite the image.

These are the step we take in the DrawItem() function to draw the image and the column:

  1. Attach the device context handle passed in through the argument to a CDC object for easier device context handling. The handle is detached from the CDC object before the function returns. If we did not detach the handle then the DC would be released when the CDC object is destroyed.
  2. We save the DC and change the clipping region so that all the updates are contrained within the header item for which the DrawItem() function is called. The device context is restored before the function returns.
  3. We compute the offset used when drawing the image and the label. The offset is used to leave a margin around the label and is equal to twice the width of a space character.
  4. We draw the image if one is specified for the header item. The CImageList::Draw() serves us well. We then adjust the rectangle for the item label so that it does not overlap the image we have just drawn.
  5. We determine the format to be used when drawing the column label. Since the column label can be aligned left, center or right, we have to choose an appropriate format for the DrawText() function. You will also notice the flag DT_END_ELLIPSIS. This tells the DrawText() function that if the text doesn't fit with the rectangle specified, then the text should be shortened and three dots appended to the text so that the result fits within the rectangle.
  6. We next adjust the rectangle within which the label will be drawn and then draw the lable using DrawText().
void CMyHeaderCtrl::DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct )
{
	CDC dc;

	dc.Attach( lpDrawItemStruct->hDC );

	// Get the column rect
	CRect rcLabel( lpDrawItemStruct->rcItem );

	// Save DC
	int nSavedDC = dc.SaveDC();

	// Set clipping region to limit drawing within column
	CRgn rgn;
	rgn.CreateRectRgnIndirect( &rcLabel );
	dc.SelectObject( &rgn );
	rgn.DeleteObject();

	// Labels are offset by a certain amount  
	// This offset is related to the width of a space character
	int offset = dc.GetTextExtent(_T(" "), 1 ).cx*2;


	// Draw image from image list
	int imageIndex;
	if (m_pImageList && 
		m_mapImageIndex.Lookup( lpDrawItemStruct->itemID, imageIndex ) )
	{
		if( imageIndex != -1 )
		{
			m_pImageList->Draw(&dc, imageIndex, 
						CPoint( rcLabel.left + offset,offset/3 ),
						ILD_TRANSPARENT );

			// Now adjust the label rectangle
			IMAGEINFO imageinfo;
			if( m_pImageList->GetImageInfo( imageIndex, &imageinfo ) )
			{
				rcLabel.left += offset/2 + 
					imageinfo.rcImage.right - imageinfo.rcImage.left;
			}
		}
	}

	// Get the column text and format
	TCHAR buf[256];
	HD_ITEM hditem;
	
	hditem.mask = HDI_TEXT | HDI_FORMAT;
	hditem.pszText = buf;
	hditem.cchTextMax = 255;

	GetItem( lpDrawItemStruct->itemID, &hditem );

	// Determine format for drawing column label
	UINT uFormat = DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP 
						| DT_VCENTER | DT_END_ELLIPSIS ;

	if( hditem.fmt & HDF_CENTER)
		uFormat |= DT_CENTER;
	else if( hditem.fmt & HDF_RIGHT)
		uFormat |= DT_RIGHT;
	else
		uFormat |= DT_LEFT;

	// Adjust the rect if the mouse button is pressed on it
	if( lpDrawItemStruct->itemState == ODS_SELECTED )
	{
		rcLabel.left++;
		rcLabel.top += 2;
		rcLabel.right++;
	}

	rcLabel.left += offset;
	rcLabel.right -= offset;

	// Draw column label
	if( rcLabel.left < rcLabel.right )
		dc.DrawText(buf,-1,rcLabel, uFormat);

	// Restore dc
	dc.RestoreDC( nSavedDC );

	// Detach the dc before returning
	dc.Detach();
}

Step 6: Add member variable for header control in list view class

Now that we are done with the CMyHeaderCtrl class, we have to add a member to the CListCtrl or the CListView derived class so that we can access the extended functionality. Add a protected member.
protected:
	CMyHeaderCtrl	m_headerctrl;

Step 7: Subclass the header control

We have to sub-class the header control so that the DrawItem() function in CMyHeaderCtrl can get called. If you are using a CListView derived class, you can place the sub-classing code in OnInitialUpdate(). If you are using a CListCtrl derived class, then put the code in PreSubclassWindow(). In either case, make sure you call the base class version of the function before subclassing the header control. If the listview control was not created in the report view mode, then you have to change the style of control before trying the subclass the control. You can use ModifyStyle() for this. The reason why we need to change the style to the report view mode is that the header control is created only when the control first taken to the report view mode.
void CMyListCtrl::PreSubclassWindow() 
{
	CListCtrl::PreSubclassWindow();

	// Add initialization code
	m_headerctrl.SubclassWindow( ::GetDlgItem(m_hWnd,0) );
}

Step 8: Use SetImageList() and SetItemImage()

Before we use SetItemImage() to choose an image for a column, we should first initialize an image list and call SetImageList() to inform the header control about which image list to use. If you want to remove all images from the header, you can call SetImageList() with a NULL argument. To remove an image from a single column, use SetItemImage() and specify -1 for the image index.



Comments

  • Like

    Posted by BJ-zhaoyf on 05/16/2008 12:54pm

    I like it very much

    Reply
  • ListControl with bitmap

    Posted by Legacy on 07/05/2002 12:00am

    Originally posted by: Savita

    can anybody tell me how to load bitmap in ListControl?
    

    • are you code source??

      Posted by huamoran on 09/07/2006 08:49am

      could you send mail to me? huamoran@126.com

      Reply
    Reply
  • 2 comments

    Posted by Legacy on 02/01/2002 12:00am

    Originally posted by: Andrei Korobkov

    1. When the header is clicked the image rectangle needs to be adjusted exactly in the same way you adjust the label rectangle.


    Reply
  • Help me! It doesn't work well

    Posted by Legacy on 12/02/2001 12:00am

    Originally posted by: Yong-Chul, Kwon

    I want to use this code.
    but it doesn't work well.
    There is some problems this code.
    =>CMap/*<int, int, int, int>*/ m_mapImageIndex;

    and I don't know the way to load bitmap image.

    I should say thank you, if you sand me your source code.

    thank you for your reading and have a nice day!

    Reply
  • I can't get it to work... CMap<int, int, int, int>

    Posted by Legacy on 11/05/2001 12:00am

    Originally posted by: Erik Larson

    It seems that when the map definition is present in the header file, the header will not compile, giving me these errors:
    
    

    error C2143: syntax error : missing ';' before '<'
    error C2501: 'CMap' : missing storage-class or type specifiers
    error C2059: syntax error : '<'
    error C2238: unexpected token(s) preceding ';'

    It appears that it doesn't know what a CMap is.. I have tried including:

    #include <afxtempl.h>

    at the beginning of the header file, but this causes problems with other macros that I am using. I am trying to include this code inside of a MFC extension DLL, which exports the class, and if I use the above include line, the silly compiler thinks I am then trying to IMPORT the class instead of exporting.. HELP......

    • Re:

      Posted by seek2begin on 01/27/2008 11:31pm

      Add this header file #include "afxtempl.h" in your main header file like StdAfx.h. There will be no problem. It's working in my case.
      
      Raj

      Reply
    Reply
  • One little comment

    Posted by Legacy on 08/25/2001 12:00am

    Originally posted by: Saar

    I would just like to comment that some bitmaps that are attached to an item in a header control can also be drawn transparently, it depends on their type.

    Reply
  • How Do U Hold the listview item?

    Posted by Legacy on 07/30/2001 12:00am

    Originally posted by: Miwa

    Sorry but i dont know the way to hold the highlighted item while i can click on other buttons and it does not affected the highlight to go off. Is there any code that can do so? thanx

    Reply
  • Having a problem with presubclassing (step 7)

    Posted by Legacy on 07/18/2001 12:00am

    Originally posted by: Andy Boylan

    The getdlgitem is coming back with a handle of 0 (NULL). This is causing problems with the new headerctrl's SetItemImage since the window handle is NULL. Is there a timing issue I've overlooking?

    Reply
  • can you give me your sourcecode about this example to me?

    Posted by Legacy on 01/09/2001 12:00am

    Originally posted by: David Sha

    I do it as you said.But it still not work.can you help me?
    mail to vcpp@mail.com,Thanks

    Reply
  • How can i make it work?!

    Posted by Legacy on 11/25/2000 12:00am

    Originally posted by: NieZheng

    I wrote my code to test the CHeadCtrl, but it dosn't work!
    
    can you mail me your source code?
    very thanks!

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Instead of only managing projects organizations do need to manage value! "Doing the right things" and "doing things right" are the essential ingredients for successful software and systems delivery. Unfortunately, with distributed delivery spanning multiple disciplines, geographies and time zones, many organizations struggle with teams working in silos, broken lines of communication, lack of collaboration, inadequate traceability, and poor project visibility. This often results in organizations "doing the …

  • Live Event Date: May 7, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT This eSeminar will explore three popular games engines and how they empower developers to create exciting, graphically rich, and high-performance games for Android® on Intel® Architecture. Join us for a deep dive as experts describe the features, tools, and common challenges using Marmalade, App Game Kit, and Havok game engines, as well as a discussion of the pros and cons of each engine and how they fit into your development …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds