Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5382070
  • 博文数量: 671
  • 博客积分: 10010
  • 博客等级: 上将
  • 技术积分: 7310
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-14 09:56
文章分类

全部博文(671)

文章存档

2011年(1)

2010年(2)

2009年(24)

2008年(271)

2007年(319)

2006年(54)

我的朋友

分类:

2007-08-07 10:12:57

Sample Image - ListCtrlDemo.gif

Introduction

This tutorial is designed to introduce you to the CListCtrl MFC class. This tutorial covers the following aspects of the CListCtrl:

  • Adding a CListCtrl to your dialog
  • Associating the CListCtrl with a member variable
  • CListCtrl styles
  • Inserting columns
  • Using images
  • Inserting items
  • Using item data
  • Selecting items
  • Determining which items have been selected
  • Getting item information
  • Handling CListCtrl messages

This tutorial also addresses the following slightly more advanced topics

  • Using check marks
  • Using background images (Update 11/10/2002)
  • Using Info Tips
  • Using Hot Cursors
  • About extended styles

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 and including controls in that application. If you are not familiar with these topics, please consult Dr. Asad Altimeemy's article, .

Adding a CListCtrl to your dialog

Once you have a dialog-based application, navigate to the Resources tab in the Workspace window. Then select your dialog in the Dialog child node so that it is displayed on the right hand side of the screen. To insert the list control, select it from the Controls floating toolbar and create a bounding rectangle equal to the size of the list control you would like:

ClassWizard will automatically assign a numeric ID constant to the new control, such as IDC_LIST1. To change this, right-click on the list control and select Properties from the context menu. Then change the ID to something more intuitive, IDC_LIST_CTRL.

At this point you have placed a list control window on your dialog, but have no (obvious) way of controlling its behavior. The next step is to associate a member CListCtrl variable with this object as described in the next section.

Associating a CListCtrl member variable

To associate a MFC CListCtrl with this object, you need to create a CListCtrl member variable and use MFC's dialog data exchange macros (DDX_Control(CDataExchange*, int, CWnd&)) to tie the window object with your local variable. Although you could do this by hand, why bother when you can leverage on ClassWizard's magic?

To attach a new member variable, say m_cListCtrl to your list control with ID IDC_LIST_CTRL, invoke ClassWizard by right-clicking on the list control. Navigate to the Member Variables tab and select the Control ID with ID value ID_LIST_CTRL. With this item highlighted, click the Add Variable button and enter a variable name for the new control as follows:

Once you press OK on this dialog as well as the parent MFC ClassWizard dialog, the m_cListCtrl member variable will be added to your CDialog-derived class. From this point, you can easily gain access to MFC's CListCtrl member functions to manipulate the CListCtrl contents.

CListCtrl Styles

The list control supports four different kinds of viewing modes. These mutually exclusive modes are style bits that can either be set in the dialog resource itself (see Inserting Columns, below), or programmatically changed at runtime. The ListCtrlDemo project changes the viewing mode in response to a change in the Tab Control at the top of the dialog (CListCtrlDemoDlg::OnSelchangeListCtrlMode(...)).

Supported modes include the following:

  • LVS_ICON: Shows large icons for each item in the control
  • LVS_SMALLICON: Shows small icons for each item in the control
  • LVS_LIST: Shows a list of items in the control with a small icon for each item
  • LVS_REPORT: Shows a "Details" of the items where extended item information can be viewed

Depending on what type of information you need to present, you may need to create a spreadsheet-like interface that structures data fields in a manner analogous to the Windows Explorer "details" view. This view is enabled by setting the LVS_REPORT style. Once this style is set, the first step is to create the columns so that extended information can be inserted.

Inserting Columns

As a preface, column information is only visible when the CListCtrl has the LVS_REPORT style bit set. However, Visual Studio creates CListCtrl's using the LVS_ICON style by default. To set the LVS_REPORT style in the dialog resource, follow these steps:

  • Right click on the list control in the resources view
  • Select Properties from the context menu
  • Select the Styles tab from the list control Properties pop-up dialog
  • Set the View combo box value to the Report style.

Having said this, you can see in the ListCtrlDemo project that attempting to create columns when the CListCtrl does not have the LVS_REPORT style bit set will not result in failure. The columns will be created, but they will not be visible. As the user changes the state of the CListCtrl, they are shown when appropriate.

To create columns, use CListCtrl::InsertColumn(...), which is often done as part of CMyDialog::OnInitDialog. The sample application creates columns in CListCtrlDemoDlg::InitListCtrlCols which is called from CListCtrlDemoDlg::OnInitDialog:

CRect rect;
m_cListCtrl.GetClientRect(&rect);
int nColInterval = rect.Width()/5;

m_cListCtrl.InsertColumn(0, _T("Item Name"), LVCFMT_LEFT, nColInterval*3);
m_cListCtrl.InsertColumn(1, _T("Value"), LVCFMT_LEFT, nColInterval);
m_cListCtrl.InsertColumn(2, _T("Time"), LVCFMT_LEFT, rect.Width()-4*nColInterval);

Using Images

Images can be associated with each item in a CListCtrl. The first step in assigning images to items is to create and attach two (2) CImageList objects to m_cListCtrl. The reason for two different CImageLists is that the image shown depends on the current style of m_cListCtrl. CListCtrl supports two different image sizes, LVSIL_NORMAL and LVSIL_SMALL, each one of which is stored in a distinct CImageList.

  • LVSIL_NORMAL: Shown in LVS_ICON view only
  • LVSIL_SMALL: Shown in LVS_SMALLICON, LVS_LIST, and LVS_REPORT modes.

If you restrict your users to only one type of view mode then you may not need to create two different CImageLists. However, the demonstration project supports all four (4) CListCtrl styles both types must be created. The CImageLists (m_cImageListNormal, m_cImageListSmall) are created in CListCtrlDemoDlg::InitImageList with the following code:

// Create 256 color image lists
HIMAGELIST hList = ImageList_Create(32,32, ILC_COLOR8 |ILC_MASK , 8, 1);
m_cImageListNormal.Attach(hList);

hList = ImageList_Create(16, 16, ILC_COLOR8 | ILC_MASK, 8, 1);
m_cImageListSmall.Attach(hList);

// Load the large icons
CBitmap cBmp;
cBmp.LoadBitmap(IDB_IMAGES_NORMAL);
m_cImageListNormal.Add(&cBmp, RGB(255,0, 255));
cBmp.DeleteObject();

// Load the small icons
cBmp.LoadBitmap(IDB_IMAGES_SMALL);
m_cImageListSmall.Add(&cBmp, RGB(255,0, 255));

However, note that the image lists have yet to be attached to the CListCtrl. They must be "tied" to the m_cListCtrl using CListCtrl::SetImageList(...). This is done at the end of CListCtrl::InitImageList:

// Attach them
m_cListCtrl.SetImageList(&m_cImageListNormal, LVSIL_NORMAL);
m_cListCtrl.SetImageList(&m_cImageListSmall, LVSIL_SMALL);

Note: Both m_cImageListNormal and m_cImageListSmall are member variables of CListCtrlDemoDlg, not locally declared. Although you attach an image list to the CListCtrl, the control does not assume ownership of the image list; it merely takes a pointer to the image list in memory. If you use a locally declared image list, the memory location and thus the address will be invalid once the function goes out of scope.

With the columns created and the image lists created and associated, data can finally be inserted into the control.

Inserting Items

There are several overloaded methods by which data may be inserted into a list control. Depending on what information is to be displayed, you may choose to use the entire LVITEM structure or only a LPCTSTR and index parameter. ListCtrlDemo includes two different methods; see CListCtrlDemoDlg::InsertItems(...) and CListCtrlStylesDlg::AddStyleItem(...).

Particularly when working in LVS_REPORT mode, it is important to note that for each item in the CListCtrl, only one call to InsertItem needs to be made. Although it is tempting to "insert" data into the second column, ths list control doesn't understand that operation. Items are defined as the entire row, with text in the additional columns being treated as sub-items. Data in columns beyond the first column (column zero) is set using one of the overloaded CListCtrl::SetItem calls or more narrowly defined SetItemX calls (SetItemData(...), for instance). This is a bit more intuitive if you consider that the CListCtrl supports three modes that do not show sub-items.

LVITEM is often easier to code, since the fields can often be re-used when setting sub-items. This is best shown in CListCtrlDemoDlg::InsertItems, where the LVITEM.iImage field is not changed when defining sub-items.

Collapse
// Use the LV_ITEM structure to insert the items
LVITEM lvi;
CString strItem;
for (int i = 0; i < m_nItems; i++)
{
// Insert the first item
	lvi.mask =  LVIF_IMAGE | LVIF_TEXT;
	strItem.Format(_T("Item %i"), i);
	lvi.iItem = i;
	lvi.iSubItem = 0;
	lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
	lvi.iImage = i%8;		// There are 8 images in the image list
	m_cListCtrl.InsertItem(&lvi);
// Set subitem 1
	strItem.Format(_T("%d"), 10*i);
	lvi.iSubItem =1;
	lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
	m_cListCtrl.SetItem(&lvi);
// Set subitem 2
	strItem.Format(_T("%s"),
	               COleDateTime::GetCurrentTime().Format(_T("Created: %I:%M:%S %p, %m/%d/%Y")));
	lvi.iSubItem =2;
	lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
	m_cListCtrl.SetItem(&lvi);
}

At the heart of this loop is the LVITEM structure. The most important member of the this structure is the mask field which tells CListCtrl::InsertItem(...) which fields are valid. Other LVITEM.mask flags include:

  • LVIF_DI_SETITEM
  • LVIF_INDENT
  • LVIF_NORECOMPUTE
  • LVIF_PARAM
  • LVIF_STATE

To see an alternative way of inserting items into the list control, see CListCtrlStylesDlg::AddStyleItem(...) in ListCtrlDemo.

Using Item Data

It is often useful to keep a unique ID associated with items in the CListCtrl. For instance, perhaps the CListCtrl displays records in a database and selecting a record should allow the user to view additional information about that record. In this case, it would be useful to keep a record number in the CListCtrl item itself to promote faster lookup. For reasons similar to this, each item in the list allows for a DWORD data member that is user-defined.

The ListCtrlDemo project uses this functionality in both CListCtrlStylesDlg and CListCtrlStylesDlgEx. In both dialogs, each element stores the CListCtrl style bit whose state it tracks. This is done with the following bit of code in CListCtrlStylesDlg::AddStyleItem:

int nIndex(1);
if (m_cListStyles.GetItemCount() > 1)
	nIndex = m_cListStyles.GetItemCount()-1;

int nPos = max(m_cListStyles.GetItemCount()-1,0);
nPos = m_cListStyles.InsertItem(nIndex, lpszItem);
m_cListStyles.SetItemData(nPos, dwData);

One could also associate data with CListCtrl items using LVITEM and specifying the LVIF_PARAM in the LVITEM.mask field together with populating the LVITEM.lParam field prior to a call to CListCtrl::InsertItem or CListCtrl::SetItem.

Selecting Items

To programatically select items, the state of the CListCtrl item must be changed. This can be done relatively easily, as long as you know the index of the item which should be selected. CListCtrlDemoDlg::OnItemSelect demonstrates how to programatically select an item in the list as follows:

// .. Snip
	m_cListCtrl.SetItemState(m_nSelectItem, LVIS_SELECTED, LVIS_SELECTED | LVIS_FOCUSED);
	m_cListCtrl.EnsureVisible(m_nSelectItem, FALSE);
// snip

To select an arbitrary item, call CListCtrl::SetItemState(...) with the appropriate parameters. Once the item has been selected, it is advisable to call CListCtrl::EnsureVisible(...) to ensure that the user can see the item whose state has just changed, for it is possible that the item was outside of the visible CListCtrl client area.

Determining Selected Items

The converse of selecting items is to determine which items have been selected. Prior to MFC 4.2 this was significantly more difficult than selecting an item, but two new functions have made this much simpler. Now, you only need to setup a loop and step through the selected items as you would a CList MFC object:

// Get the selected items in the control
POSITION p = m_cListCtrl.GetFirstSelectedItemPosition();
while (p)
{
	int nSelected = m_cListCtrl.GetNextSelectedItem(p);
	// Do something with item nSelected
}

Getting Item Information

Once you have inserted items, if you need to respond to the user's actions you will likely need to retrieve information about those items. This is done in a manner analogous to setting items. Depending on the amount of information you need, you can either use the LVITEM structure in a call to CListCtrl::GetItem(LVITEM*), or use one of the many GetItemX (such as CListCtrl::GetItemText(...)).

If you elect to use the LVITEM structure to retrieve item text, you must first set the LVITEM.mask field to include the LVIF_TEXT parameter. Similar to the setting of items, setting this flag informs the CListCtrl what fields you are requesting. Then you need to supply the buffer into which the text should be copied as well as the size of that buffer. For instance:

TCHAR szBuffer[1024];
DWORD cchBuf(1024);
LVITEM lvi;
lvi.iItem = nItemIndex;
lvi.iSubItem = 0;
lvi.mask = LVIF_TEXT;
lvi.pszText = szBuffer;
lvi.cchTextMax = cchBuf;
m_cListCtrl.GetItem(&lvi);

Note that you need to specify the LVITEM.iSubItem field even if you are not in LVS_REPORT mode. In this case, you should always set the sub item to zero (0). However, if the List Control is in LVS_REPORT mode then you can set the LVITEM.iSubItem field to the zero-indexed sub item of interest.

Handling CListCtrl Messages

There are more than three dozen messages that are sent by the CListCtrl. Of this set, one message which is likely to prove useful is the LVN_ITEMCHANGED message. For example, quite often you may want to take some action in response to a state change within the CListCtrl; such as dynamically enabling or disabling controls based on CListCtrl selections. This can be accomplished by handling the LVN_ITEMCHANGED message, which is sent in response to an item in the CListCtrl being changed. To insert a handler for this message, right click the list control on the dialog in the Resources view and select ClassWizard. Then choose the LVN_ITEMCHANGED message from the Message Maps tab and press Add Function. Accept the default ClassWizard function name. The CListCtrlStylesExDlg class includes a handler for this message in order to control the state of the Use Hot Cursor boolean flag.

ClassWizard will automatically cast the pNMHDR paramter to a NM_LISTVIEW structure which contains information about the item that just changed. With this, you can determine which item is under scrutiny and what state the new item occupies. In the case of CListCtrlStylesExDlg::OnItemchangedListStylesEx, determine the "checked" state of the item to decide if the Use Hot Cursor option should be enabled.

BOOL bChecked = m_cListStylesEx.GetCheck(pNMListView->iItem);

Using check marks

Another use for a list control is when you need to gather use input regarding options. While it is possible to use a multi-selection list control to accomplish this, another option is to use check marks. And since check marks are natively supported via the extended list control style LVS_EX_CHECKBOXES, the resistance of inertia is lowered.

To enable check marks, simply use LVS_EX_CHECKBOXES in a call to ListView_SetExtendedListViewStyle(...). For instance, in CListCtrlStylesDlg::OnInitDialog check marks are enabled with the following:

ListView_SetExtendedListViewStyle(m_cListStyles.m_hWnd,
                                  LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);

To retrieve the check state, use one of the following:

CListCtrl::GetCheck(iIndex)

or

ListView_GetCheckState(m_cMyListCtrl.GetSafeHwnd(), iIndex))

Using background images

In the event that you want to show an image in the background, CListCtrl::SetBkImage seems to be just the ticket. This overloaded function can either accept a LVBKIMAGE structure pointer, a HBITMAP, or a LPTSTR URL. The first step to showing a background image is to initialize the OLE libraries. This is best done with a call to AfxOleInit() in CListCtrlDemoApp::InitInstance:

if (!AfxOleInit())
{
	AfxMessageBox("Unable to initialize OLE.\nTerminating application!");
	return FALSE;
}
After initializing OLE, I naturally started trying to load a bitmap from the resource fork and show it in the background of the list control. Several failed attempts later, I begrudgingly waded into the MFC source. Both CListCtrl::SetBkImage(HBITMAP,...) and CListCtrl::SetBkImage(LPTSTR,...) eventually resolve to CListCtrl::SetBkImage(LPLVBKIMAGE). They are thin wrappers that simplify setting up the LVBKIMAGE structure. When I looked at MSDN for the LVBKIMAGE struct, the following remark about one of the fields seemed to solve the problem:
hbm Not currently used.

Unfortunately, this is the field that is that is populated by CListCtrl::SetBkImage(HBITMAP,...). From this I'm inferring that showing a HBITMAP is not supported. Therefore, the only way I found to show a background image was to provide a filepath (or URL) as in CListCtrlDemoDlg::EnableBkImage(BOOL bEnable).

Update (11/10/2002): in a private correspondence suggested that the res: protocol would solve the problem of an external image file without requiring a live URL. The protocol format is described more fully in this archived MIND article (see also this ), and the solution in our context is to specify a URL as in:
TCHAR szBuffer[_MAX_PATH]; 
VERIFY(::GetModuleFileName(AfxGetInstanceHandle(), szBuffer, _MAX_PATH));
CString sPath;
sPath.Format(_T("res://%s/#2/#142"),szBuffer);
For specifing a bitmap in our executable, #2 indicates the RT_BITMAP type and #142 is the ID of our bitmap (IDB_LISTCTRL_BK). Providing this path as the value of the LVBKIMAGE.pszImage field is sufficient to overcome the external image file limitation. See CListCtrlDemoDlg::EnableBkImage for more information. Thanks Nick :)

Using Info Tips

Info Tips are useful when you would like to provide information about each element in the list control back to the user. Enabling Info Tips is a multi-stage process which, due to ClassWizard's waning magic, must be performed by hand.

Step 1 - Add the LVS_EX_INFOTIP extended style member

Both CListCtrlStylesDlg and CListCtrlStylesDlgEx enable check boxes in their respective OnInitDialog calls with this:

ListView_SetExtendedListViewStyle(m_cListStyles.m_hWnd,
                                  LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);

Step 2 - Declare the OnInfoTip handler function

The InfoTip handler function can be a function of any name that you declare. For explanatory purposes, we chose the name OnInfoTip. It should be declared outside of the //{AFX_MSG ... //}}AFX_MSG block in MyDialog.h. That block is managed by ClassWizard and it is possible that ClassWizard will not recognize the declaration and delete it when you add additional functions. It should take two parameters (NMHDR*, LRESULT*) and return void. For instance:

// Generated message map functions
//{{AFX_MSG(CListCtrlStylesDlg)
virtual BOOL OnInitDialog();
virtual void OnOK();
//}}AFX_MSG
afx_msg void OnInfoTip( NMHDR * pNMHDR, LRESULT * pResult );

Step 3 - Add the WM_NOTIFY message map entry

Info Tips are processed via the LVN_GETINFOTIP which is handled using a WM_NOTIFY wrapper. This message map entry should be located in the BEGIN_MESSAGE_MAP, END_MESSAGE_MAP block in MyDialog.cpp. It ties together the message, list control ID, and handler function, as in:

ON_NOTIFY( LVN_GETINFOTIP, IDC_LIST_CTRL_ID, OnInfoTip )

Step 4 - Define the OnInfoTip handler function

Finally, implement OnInfoTip in MyDialog.cpp. When this function is called, the list control is requesting information from you, the developer. The NMHDR* pointer must first be cast to a NMLVGETINFOTIP* object and then the NMLVGETINFOTIP.pszText filled with the text that should be displayed. An empty implementation would be as follows:

void  CMyDialog::OnInfoTip( NMHDR * pNMHDR, LRESULT * pResult )
{
	NMLVGETINFOTIP*pInfoTip = reinterpret_cast(pNMHDR);
	ASSERT(pInfoTip);

	_tcscpy(pInfoTip->pszText, _T("I am text"));
}

For an implementation, see either CListCtrlStylesDlg::OnInfoTip(...) or CListCtrlStylesDlgEx::OnInfoTip(...).

Using Hot Cursors

The final option demonstrated in ListCtrlDemo is the use of Hot Cursors. A Hot Cursor is a special cursor that is shown when the user moves the mouse over an item. It is only available if both LVS_EX_TRACKSELECT and either LVS_EX_ONECLICKACTIVATE or LVS_EX_TWOCLICKACTIVATE extended styles are set. To use Hot Cursors, you must first load a cursor and then assign it to the CListCtrl using CListCtrl::SetHotCursor(HCURSOR). On my platform, SetHotCursor(...) does not appear in the popup code window, but it is supported. Please see CListCtrlDemoDlg::OnStylesEx() for a sample implementation.

About Extended Styles

List Control extended styles provide significant functionality at a relatively low cost. However, their availability is dependent on the version of the Shell that is installed. For more information about shell versions, please refer to . If your application is targeted to a broad user base, then you may need to code for these differences and/or investigate the possibility of redistributing Internet Explorer as part of your installation. For more on the Internet Explorer Administration Kit, visit .

Of the extended styles supported in ListCtrlDemo, the version breakdown is as follows:

  • Version 4.70 (Internet Explorer 3.0 and higher)
    • LVS_EX_CHECKBOXES
    • LVS_EX_FLATSB
    • LVS_EX_FULLROWSELECT
    • LVS_EX_GRIDLINES
    • LVS_EX_HEADERDRAGDROP
    • LVS_EX_ONECLICKACTIVATE
    • LVS_EX_SUBITEMIMAGES
    • LVS_EX_TRACKSELECT
    • LVS_EX_TWOCLICKACTIVATE
  • Version 4.71 (Internet Explorer 4.0 and higher, integrated Shell)
    • LVS_EX_INFOTIP
    • LVS_EX_UNDERLINECOLD
    • LVS_EX_UNDERLINEHOT

In addition to this list, there are additional extended styles which may prove useful. MSDN is a good source of information on this topic.

About ListCtrlDemo

The included ListCtrlDemo project illustrates all of the topics addressed in this article. It also allows you to set both styles and extended styles for a List Control and investigate their behavior dynamically. Problems or suggestions should be directed to .

 

阅读(5919) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~