Top

C++/Win32: List Views

March 2, 2008

At this point, we’ve done a lot. Over the past sixteen lessons, we’ve made our app do what it’s supposed to do, and just recently we made it print and save its output. For some strange reason, we’re still not completely bored and over this thing, so we’re trying to figure out how to make it better.

Hmm… what to do, what to do. Wait, I know! Let’s give it a graphical output! What do I mean by that? Well, let’s take a look at SolarWind’s Traceroute program:

See how nice that is? Images, text, and graphs all in one control, and all of it ordered into a neat little table. It would really be great if we could build something like that! Well, we don’t have to. It’s called a List View, and it’s already built for us. Sort of.

The first thing we need to do is decide how we’re going to use it. The obvious thing that springs to mind is to have one field of each entry be a small icon — either a red LED or a green LED — indicating whether a port is open or closed. Next, we’d like to list the port that was just scanned. We would also like to list the IP address scanned. And third, we’d like to compare the port number to a list of well-known ports and if we find one, list the service known to run on that port.

One thing to keep in mind here is that if we are using a list view to display our output, we are not going to easily be able to dump text to the printer or to a file. We would either have to keep a massive buffer to hold text results (which we would then write to at the same time as we update the list view), or we’d have to analyze the list view when the user requests a save or print, generating the necessary text. Fortunately, we’ve already built the app thus far using an edit control, which puts us in good shape for two reasons: first, we don’t have to keep a buffer, since we can just keep using the edit control. Second, it allows us to give the user the option of switching from text to graphical output and back again at the push of a button. I’m all about features — the more the better. And when you don’t have to do any additional work to put them in your app, even better.

Note: you must call either InitCommonControls or InitCommonControlsEx before using the list view control.

So, here’s how you create a list view. Guess what? It’s a window!

m_hListView	= CreateWindow(WC_LISTVIEW, "", WS_VISIBLE | WS_CHILD | LVS_REPORT | LVS_EDITLABELS,
                               10, 100, 470, 340, m_hWnd, (HMENU)IDL_LISTVIEW, m_hInstance, NULL);

Pretty familiar by now, I hope. The LVS_REPORT specifies the type of list we’re building (a report). There are four views you can use: LVS_ICON, LVS_SMALLICON, LVS_LIST, and LVS_REPORT. You can switch from one view to another, giving your users the choice of which view they prefer, by changing the window styles using GetWindowLong and SetWindowLong as with any window.

When we specify the LVS_EDITLABELS style flag, we mean that item text can be edited in place (by processing the LVM_ENDLABELEDIT message in our message handler). Here’s the full list of LVS styles:

LVS_ALIGNLEFT 
Items are left-aligned in icon and small icon view. 

LVS_ALIGNMASK 

Determines the control’s current alignment. 

LVS_ALIGNTOP 
Items are aligned with the top of the list-view control in icon and small icon view. 

LVS_AUTOARRANGE 
Icons are automatically kept arranged in icon and small icon view. 

LVS_EDITLABELS 
Item text can be edited in place. The parent window must process the
LVN_ENDLABELEDIT notification message. 

LVS_ICON 
This style specifies icon view. 

LVS_LIST 
This style specifies list view. 

LVS_NOCOLUMNHEADER 

Column headers are not displayed in report view. By default, columns
have headers in report view. 

LVS_NOLABELWRAP 
Item text is displayed on a single line in icon view. By default,
item text may wrap in icon view. 

LVS_NOSCROLL 
Scrolling is disabled. All items must be within the client area. This
style is not compatible with the LVS_LIST or LVS_REPORT styles. See
Knowledge Base Article Q137520 for further discussion. 

LVS_NOSORTHEADER 
Column headers do not work like buttons. This style can be used if
clicking a column header in report view does not carry out an action,
such as sorting. 

LVS_OWNERDATA 
Version 4.70. This style specifies a virtual list-view control. For
more information about this list control style, see List-View Controls Overview. 

LVS_OWNERDRAWFIXED 
The owner window can paint items in report view. The list-view control
sends a WM_DRAWITEM message to paint each item; it does not send separate
messages for each subitem. The iItemData member of the DRAWITEMSTRUCT
structure contains the item data for the specified list-view item. 

LVS_REPORT 

This style specifies report view. When using the LVS_REPORT style with
a list-view control, the first column is always left-aligned. You cannot
use LVCFMT_RIGHT to change this alignment. See LVCOLUMN for further
information on column alignment. 

LVS_SHAREIMAGELISTS 
The image list will not be deleted when the control is destroyed.
This style enables the use of the same image lists with multiple
list-view controls. 

LVS_SHOWSELALWAYS 
The selection, if any, is always shown, even if the control does
not have the focus. 

LVS_SINGLESEL 
Only one item at a time can be selected. By default, multiple
items may be selected. 

LVS_SMALLICON 
This style specifies small icon view. 

LVS_SORTASCENDING 
Item indexes are sorted based on item text in ascending order. 

LVS_SORTDESCENDING 

Item indexes are sorted based on item text in descending order. 

LVS_TYPEMASK 
Determines the control’s current window style. 

LVS_TYPESTYLEMASK
Determines the window styles that control item alignment and
header appearance and behavior.

Ok, so we’ve created our list view, and put it where the text output edit control used to be. Now we need to create an image list, which is just a list of images available to the list view. We need to make a separate list for each view the list supports — i.e. a list of small icons for LVS_SMALLICON, large icons for LVS_ICON, etc. We do this by sending a LVM_SETIMAGELIST message specifying what kind of images the list will contain. Our application will not support switching views, and will use the report view, so we will only need to create one image list. However, if your apps do support multiple lists, you must remember to add images to the list in the same order, because images in all lists are referenced by a single value. Here’s how we’ll add our two small icons (the red and green LED images) to our list view:

HIMAGELIST hSmall;
HICON hIcon;

// Create the ImageList
hSmall = ImageList_Create(GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),
                              ILC_MASK, 1, 1);

// Add two icons to ImageList
hIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(IDI_ICON4));
ImageList_AddIcon(hSmall, hIcon);
DestroyIcon(hIcon);
hIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(IDI_ICON5));
ImageList_AddIcon(hSmall, hIcon);
DestroyIcon(hIcon);

// Assign ImageList to List View
ListView_SetImageList(m_hListView, hSmall, LVSIL_SMALL);

In report view, we can add columns to the list, and assign text to the columns. From the plan we made for how to use this list view, we’ll need three columns: Status, Port and Service.

LVCOLUMN lvc;
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;

lvc.iSubItem	= 0;
lvc.pszText		= "Port";
lvc.cx			= 50;
lvc.fmt			= LVCFMT_LEFT;

ListView_InsertColumn(m_hListView, 0, &lvc);

lvc.iSubItem	= 1;
lvc.pszText		= "Status";
lvc.cx			= 100;
lvc.fmt			= LVCFMT_CENTER;

ListView_InsertColumn(m_hListView, 1, &lvc);

lvc.iSubItem	= 2;
lvc.pszText		= "Service";
lvc.cx			= 320;
lvc.fmt			= LVCFMT_CENTER;

ListView_InsertColumn(m_hListView, 2, &lvc);

Notice that before calling ListView_InsertColumn we have to set some flags in the mask field of our LVCOLUMN structure. These flags signal which of the other fields are valid. In this example, the format, width, text and subitem fields are valid (and so we consquently fill them in).

Now the fun part: filling in the list.

Here’s how to enter in an item:

LVITEM lvI;

lvI.mask		= LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
lvI.state		= 0;
lvI.stateMask	        = 0;
lvI.iItem		= 0;
lvI.iImage		= 0;
lvI.iSubItem	        = 0;
lvI.lParam		= (LPARAM)25;
lvI.pszText		= "25";

ListView_InsertItem(m_hListView, &lvI);

The flags in mask specify we’ll be giving an image, some text, an lParam (more on that in a bit) and a state value.

Subitem text is set by sending a LVM_SETITEM message to the control. Here’s how to set the subitem text:

lvI.mask		= LVIF_TEXT | LVIF_STATE;
lvI.state		= 0;
lvI.stateMask	        = 0;
lvI.iItem		= 0;
lvI.iImage		= 0;
lvI.iSubItem	        = 1;
lvI.pszText		= "OPEN";

SendMessage(m_hListView, LVM_SETITEM, 0, (LPARAM)(&lvI));

lvI.mask		= LVIF_TEXT | LVIF_STATE;
lvI.state		= 0;
lvI.stateMask	        = 0;
lvI.iItem		= 0;
lvI.iImage		= 0;
lvI.iSubItem	        = 2;
lvI.pszText		= "SMTP (Mail)";

SendMessage(m_hListView, LVM_SETITEM, 0, (LPARAM)(&lvI));

By setting the subitem text this way, we are permanently setting it. We can only change it with another call to SendMessage. But, we can also tell the list view that we want it to send us a message whenever it is preparing to draw its text. It will send LVN_GETDISPINFO notifications to our message handler, which we must handle by supplying the text.

So if we set our item like this:


LVITEM lvI;

lvI.mask		= LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
lvI.state		= 0;
lvI.stateMask	        = 0;
lvI.iItem		= 0;
lvI.iImage		= 0;
lvI.iSubItem	        = 0;
lvI.lParam		= (LPARAM)25;
lvI.pszText		= LPSTR_TEXTCALLBACK;

ListView_InsertItem(m_hListView, &lvI);

We have sent LPSTR_TEXTCALLBACK instead of text for the item, indicating that the list view should request the text by sending the LVN_GETDISPINFO notification to our message handler. We could handle it like this:

case WM_NOTIFY:
    switch (((LPNMLVDISPINFO)lParam)->item.iSubItem)
    {
        case 0:
            plvdi->item.pszText = "some text";
            break;
        case 1:
            plvdi->item.pszText = "some text";
            break;
        // etc...
    }
    return 0;

So you see that changing values can be updated whenever this notification is received. Our app doesn’t need this at the moment, however, so we will just set the text and move on. All we really need is an index to keep track of which item in the list we are at when we get a new result back. Simply, that means adding an int to our class and incrementing it after each entry is added to the list. We’ll want to, for now, add the items and subitems in out goScan function. Have a look at the source for the full picture.

We run into a decision here. How should we handle the second scan? Should we add items to the end of the list? Or should we erase all items from the list and move on? Since we’re keeping the text edit active (even though it’s hidden), it becomes a sort of log/history output. So in my opinion, we should clear out the list when we start a new scan. That will also make it nice if we later add a continuous scan option, because we’ll be expecting fresh results in the list already — it will just be a matter of not clearing the list, but updating the list.

So… how do we clear the list? If you look at the code, I’ve added a member variable to the PortScannerWindow class called listItemCount, and I’ve initialized it to 0 in the constructor. The goScan function increments this value every time it adds an item to the list. If I want to clear the list, i can call ListView_DeleteItem listItemCout-1 times in a loop, and wipe the entries out. If I leave it alone, the list will just keep building in a logical way. So if I change my mind later, its no big deal — just remove the loop that clears the list. One thing to remember is that we’ll have to count backwards when deleting items from the list, because if we delete item 0, item 1 becomes item 0.

// clear out the list if necessary
if (listItemCount != 0) {
	for (int k=listItemCount-1; k >=0; k--)
		ListView_DeleteItem(m_hListView, k);
	listItemCount = 0;
}

Now, walking you through all the other little tweaks I’ve made to the code for this build would be silly, because it’s nothing you haven’t seen already. I added a new button, and a menu item item Appearance->Text View which switches back and forth between text view and list view. All that amounts to is switching one window to visible while switching the other to invisible. And that’s about it.

Later on in the development of the Port Scanner, I’ll want to enable the user to configure a list of well-known ports. We can use a listview with text editable in-place and a save button allowing the user to update the list.

Next: Sounds.


Source Code:

win32tut_part17.zip [3.02MB zipped]

Additional Information:

· MSDN - ListView Control Reference

Further Reading:

· Nitty Gritty Windows Programming with C++ by Henning Hansen.

· Thinking in C++ by Bruce Eckel.

Comments

35 Responses to “C++/Win32: List Views”

  1. Henris on May 22nd, 2008 7:44 pm
  2. Caroline on May 23rd, 2008 5:31 pm
  3. Kristofer on May 25th, 2008 1:59 pm
  4. Sebastyn on May 26th, 2008 6:29 am
  5. Richard on May 26th, 2008 9:20 pm
  6. Miranda on May 27th, 2008 8:21 pm
  7. Fernando on May 29th, 2008 8:34 am
  8. Diegos on May 29th, 2008 9:12 pm
  9. Brungilda on May 30th, 2008 7:11 pm
  10. Piterman on June 1st, 2008 8:30 am
  11. Lolita on June 4th, 2008 4:09 pm
  12. Valentin on June 5th, 2008 9:28 am
  13. Jesika on June 5th, 2008 8:53 pm
  14. Forest on June 6th, 2008 8:05 pm
  15. Nikalai on June 8th, 2008 8:16 pm
  16. Kristian on June 10th, 2008 7:19 am
  17. Freddi on June 16th, 2008 1:22 am
  18. Miranda on June 16th, 2008 6:55 pm
  19. Frenki on June 18th, 2008 7:28 pm
  20. Marina on June 19th, 2008 8:27 pm
  21. Garcia on June 21st, 2008 7:55 pm
  22. Recardo on June 23rd, 2008 12:52 pm
  23. Martin on June 24th, 2008 10:04 am
  24. Valentin on June 25th, 2008 7:21 am
  25. Saimon on June 29th, 2008 9:33 am
  26. Frencis on June 30th, 2008 1:25 am
  27. Diegos on June 30th, 2008 3:43 pm
  28. Rekardo on July 8th, 2008 6:50 am
  29. cleanboyblac on July 9th, 2008 8:18 am

    yes pets ugly watch letter red glass german man dog english

  30. Timati on July 11th, 2008 5:13 am
  31. Herbert on July 15th, 2008 6:48 pm
  32. iojxbly shgjp on July 18th, 2008 6:32 am

    vflkxem nfrokeu phlowxie bzqkmxc zjvghc ucjyieht hxolpkb

  33. sgakzom mzwgourt on July 18th, 2008 6:35 am

    xyvl jgosnwu ywhl lhazs afyeknild kdhtrew eridjq

  34. Trevis on July 19th, 2008 6:58 pm
  35. Marisobel on July 21st, 2008 6:44 pm

Got something to say?





Bottom