Item Selection in List Control under MFC

The MFC list control using the report view can be a very powerful implementation of a control, with many sub items separated by clearly visible grids that still do not ruin the overall picture. Making an application inherit this Excel lookalike style is easier than it sounds. The down side of this, however, is that it lacks some major features, such as clear sub item selection.

Now this is not necessarily a bad thing. If it were already implemented it could sometimes be a down side for the user, as an additional unused code segment. On the other hand, if you want to have this option, you may implement it using some MFC paired with OOP knowledge. Of course this is a troublesome process, because as easy it seems at first glance, the painful aspect involves taking care of all of the special cases.

Due to this, many users turn to an already-implemented class. Just browse the Codeproject page, and after a few clicks you will find some implementations. A few are freeware that you may use it inside your application for no fee at all. The only problem with this approach is that users who did this in their spare time implement these, and code maintenance and debugging was not done throughout the implementation. 

This small problem can be bypassed by acquiring a third party library. Nevertheless, keep in mind that you will have to pay some cash for this. However, if you can afford the time to develop your own, you will have the advantage of seeing both what happens in front of and behind the scenes, so if something strange happens you can find the upsetting code section more easily.

This article is designed to help you with this task, or to introduce a method for how you can achieve something like this. Additionally I will also show you how to exploit the custom drawing inside the MFC. This article is the third part of the series I allocated to the introduction of the MFC list control.

If you failed to read the first two, I invite you to read them here on the ASP Free website under the names of Controls inside MFC and The Virtual List. Of course, you may find them faster by clicking on my name and selecting from there the corresponding parts. These articles contain some crucial information that you will need to understand this article as well, so if you intend to keep reading I strongly advise to start with those.

{mospagebreak title=The Custom Draw}

Probably the most important section of this article is the custom draw: what you can do with it and how to use it. So let us get on with it. Originally, it was introduced with version 4.70 and soon revealed itself as a great tool to share the drawing task between the user and Windows.

The name itself describes a small amount of what it does, but if you are more into it, the MDSN help files treat every single aspect of it in long and boring detail. Reduced to a few words, it is a different, smarter kind of "owner drawn" control. 

You already saw during the article in which I covered the virtual list what advantages we can get from owner-managed data. In the same manner was implemented the owner-drawn controls. Windows does not draw these; everything is left to you. However, this can turn out to be a high price to pay for just a little extra touch on the list controls’ overall look. This was probably what the person who came up with the idea of custom draw was thinking. 

With custom draw, Windows periodically sends you some messages in which it ask you if you want to handle the drawing yourself or let it be done by the program. Nevertheless, you can choose to ignore it. In the essence, you can stylishly just modify the tools with what Windows is drawing on the screen and finally let the program do the job, or take over just a portion of the task.

These steps are divided into four parts. Each one of them can be referred to as a "drawing stage." For starters, you can divide it into two sections: erase and print. These two can be reduced to the state prior and after the process itself. This totals the four stages. The message you return inside the custom draw function is crucial, as this will notify the environment when you want to receive further messages and for which objects you want these messages.

Briefly sketched, there are five kind of actions you can take. First, you may let the OS do all the work by returning the CDRF_DODEFAULT. Alternatively, you can select a font change (CDRF_NEWFONT) and in this case, you want to report that the rectangle for the control needs to be recalculated.

If you are a good worker, you may tell it that you have done all the work already (CDRF_SKIPDEFAULT) and skip the usual tasks. The option to receive a message for each item also exists (CDRF_NOTIFYITEMDRAW). Finally, if you want to do some sort of special touch for each sub-item in particular you can opt for the CDRF_NOTIFYSUBITEMDRAW return message.

For each of these commands you will get back another message when the drawing stage occurs. These are the CDDS_SUBITEM, CDDS_ITEMPREPAINT, CDDS_PREPAINT, CDDS_ITEMPOSTPAINT messages. Therefore, in the same function you need to check when you get these messages and react to them.

{mospagebreak title=Enable}

All of this looks much easier in theory to resolve than the whole control, and this is one of those facts that are just as simple as they sound. All you need to resolve is to catch the NM_CUSTOMDRAW message, implement the request, and answer the sub messages.

The supported controls are all on the following list: headers, list view, rebar toolbar, ToolTip, trackbar, and tree view and button control. To process the message, first add the handle to catch it like this into the message map, and add a prototype inside the class followed by an implementation of it:


ON_NOTIFY( NM_CUSTOMDRAW, IDC_OF_THE_LIST, OnCustomDraw);

afx_msg void gbReportListCtrl::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult)


If you have extended the control previously via a derivation from one of the classes, you can use the notification system for the NM_CUSTOMDRAW message:


ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &gbReportListCtrl::OnNMCustomdraw)


Of course the prototype and its implementation remains. Now you are locked and loaded. You can extract all of the information you need about the item that is to be drawn/erased from the first argument with the following cast:


NMLVCUSTOMDRAW* nmcd=(NMLVCUSTOMDRAW*)pNMHDR;


The NMLVCUSTOMDRAW struct contains the following data:

  • Window handle of the control
  • ID of the control
  • The draw stage you are in
  • Device handle to use for drawing
  • Rectangle of the item/sub-item/control being drawn
  • Coordinates of the item on the screen
  • Flag indicating the state of the item (for instance selected/grayed)
  • A LPARAM where is the data of the item

From here, you should absolutely remember the device context handle, as it’s with this that you will want to make that subtle change on your control. Of course, this is not mandatory, as you may choose to just change the font/foreground/background color of the text that is painted. In this case, Windows will do the paint job; all you need to do is tell it, and it will do the rest. 

{mospagebreak title=In action}

Enough with the theory! The time has arrived for some implementation. As I said before, we will extend our application created in the previous part with some smarter selection. The goal is to make the selection as intuitive as possible, and possible for multiple items as well.

We will keep the coordinates (row, column) inside an enlisted vector, and cause items to be selected when the user presses the left or the right button on the mouse. For selecting multiple items I implemented this, embedded with the control and shift key. With the shift key, all the items between the last coordinate of the selected item and the new coordinate are added to the selected list. The control key simply adds the newly-pointed-to item to the list.

Additionally, with the tab and arrow keys, you can navigate easily inside the list when you have a single item selected. Furthermore, there are two kinds of selected color types. One is used when the view has the focus, while the other is used when you lose the focus. Look over the key moments for this article and the source code I created:


std::vector<ListItem> m_selected;

COLORREF m_selFocusColor;

COLORREF m_selNoFocusColor;


void gbReportListCtrl::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult)

{

NMLVCUSTOMDRAW* nmcd=(NMLVCUSTOMDRAW*)pNMHDR;


*pResult=CDRF_DODEFAULT; // set to default action


int row;

int col;


switch(nmcd->nmcd.dwDrawStage) // act accordingly to the //message we got

{

case CDDS_PREPAINT: // request a message upon item draw

*pResult=CDRF_NOTIFYITEMDRAW;

// else CDRF_DODEFAULT which tell windows to paint // itself


return;


case CDDS_ITEMPREPAINT: //request a message at each / //subitem draw

*pResult=CDRF_NOTIFYSUBITEMDRAW;

return;


case CDDS_SUBITEM|CDDS_ITEMPREPAINT: // upon pre-paint //and subItem draw

{

*pResult=0;

row=nmcd->nmcd.dwItemSpec;

col=nmcd->iSubItem;


CString str=GetItemText(row,col); // The text to be //drawn


CRect rect;

CDC*pDC=CDC::FromHandle(nmcd->nmcd.hdc); // request a //DC with what to work


if(col>0) //Get the coordinates where to draw

GetSubItemRect(row,col,LVIR_BOUNDS,rect);

else

GetItemRect(row,&rect,LVIR_LABEL);


 

UINT uCode = 0;

switch (col)

{

case 0: uCode = DT_LEFT; break;

case 1: uCode = DT_CENTER; break;

}


 

if(IsSelected(ListItem(row,col))) // If we got the //right selection

{

COLORREF kolor; //Lost focus Color


if(GetFocus()==this) //Active focus Color

kolor = this->m_selFocusColor;

else

kolor = this->m_selNoFocusColor;


CBrush brush(kolor); //paint the Brush

pDC->FillRect(&rect,&brush);

}

rect.left +=5; // draw a little in

pDC->DrawText(str,&rect,uCode); //Draw the text


*pResult=CDRF_SKIPDEFAULT; // We handled the painting //process so tell to Windows that skip //the paint process itself


break;

}

}

}


Of course, the application that makes all this happen is a little more complex with the key response implementation; however, to present those extra points exceeds the purpose of this article. If you are interested in it, take a look at the source code; I tried to comment it as deeply as needed, and most probably you will comprehend it. Feel free to try it. The solution was created with Visual Studio 2008. Nevertheless, it should also work with prior versions as long as you recompile it.



As always, you can make another set of improvements, such as enabling the selection of multiple items with the mouse (alias grid selection), and you may also implement the ability to replace the items with a in-place control at edit time (like a combo box, for instance, where you can choose from a limited set of options).

I will leave the implementation to you. It will be a great opportunity to practice your coding style. For now, I thank you for sticking with me throughout this series on the controls in MFC, and invite you to leave a comment here on the blog. If you feel like it you may also join our friendly and ever-growing society over the Dev Hardware or Dev Articles and ask our experts about whatever thoughts you may have. Until next time all, I want to say is to Live With Passion!

[gp-comments width="770" linklove="off" ]