Introduction
WinMain
simply calls WinMain_Internal
. WinMain_Internal
does the following:
- Initializes common controls.
INITCOMMONCONTROLSEX InitCC;
InitCC.dwICC =
InitCC.dwICC =
ICC_LISTVIEW_CLASSES |
ICC_PROGRESS_CLASS |
ICC_TREEVIEW_CLASSES |
ICC_BAR_CLASSES |
ICC_UPDOWN_CLASS |
ICC_USEREX_CLASSES |
ICC_TAB_CLASSES |
ICC_COOL_CLASSES |
ICC_DATE_CLASSES |
0;
InitCC.dwSize = sizeof(INITCOMMONCONTROLSEX);
if(!InitCommonControlsEx(&InitCC))
return -2;
-1 is the standard return value for DialogBox
failure, so we use -2. Of course, defining error constants with more readable values is recommended.
Notes:
- Although Ole32.lib is linked, only
VARIANT
comparations and BSTR
manipulator are used. So, OleInitialize
call is not required.
- Not all of the
ICC_*
constants are required for this sample. You may choose the appropriate set that fits your needs.
- The sample assumes the list view in report mode.
- Calls
DialogBox
to show our sample dialog.
The dialog has 4 handlers.
MainDlg_OnClose
: simply calls EndDialog
with return code 0. It does not guarantee that the list view content is stable (still modifying its items, style etc.). A cleanup routine that returns a BOOL
(TRUE
= the window cannot be closed at this time), or forcing the end of current operation(s) should be implemented in order to avoid data corruption or Windows crashes.
MainDlg_OnInitDialog
initializes the child list view and posts a WM_SIZE
message to itself to force the call of the resize handler.
MainDlg_InitListView
, the list view initialization routine:
- Calls
LV_SetInitial
. It does the following:
- Sets a list view property indicating that the list view has no items (useful for
WM_PAINT
). SetProp(hWnd, _T("HasItems"), (HANDLE)(FALSE));
- Sets the initial rect for the part of the text ("More...") displayed in the list view when the list has no items:
lpRectMore = (LPRECT)malloc(sizeof(RECT));
SetRectEmpty(lpRectMore);
SetProp(hWnd, _T("RECT_More"), (HANDLE)(lpRectMore));
- Gets the list view tooltips, add our own tool and set the tooltip window handle as a property of the list view window. (This has a bug - after adding our tool, the label tip functionality is not working - I have to correct this):
hwndTT = ListView_GetToolTips(hWnd);
ToolInfo.cbSize = sizeof(TOOLINFO);
ToolInfo.uFlags = TTF_TRANSPARENT | TTF_CENTERTIP;
ToolInfo.hwnd = hWnd;
ToolInfo.uId = 0;
ToolInfo.hinst = NULL;
ToolInfo.lpszText = LPSTR_TEXTCALLBACK;
GetClientRect(hWnd, &ToolInfo.rect);
ToolTip_AddTool(hwndTT, &ToolInfo);
ToolTip_TrackActivate(hwndTT, &ToolInfo);
ToolTip_SetTipMaxWidth(hwndTT, GetRectWidth(ToolInfo.rect) / 2);
phwndTT = (HWND *)malloc(sizeof(HWND));
*phwndTT = hwndTT;
SetProp(hWnd, _T("Tooltip_HWND"), (HANDLE)(phwndTT));
- Sets the list view styles to full row select, subitem images, gridlines and labeltip.
ListView_SetExtendedListViewStyle
(
hwndLV,
LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES |
LVS_EX_GRIDLINES | LVS_EX_LABELTIP
);
- Inserts the columns looking in the
g_pcSampleListColumns
global variable.
lvCol.mask = LVCF_TEXT | LVCF_WIDTH;
lvCol.pszText = lpszBuffer;
for(iColCount = 0;
g_pcSampleListColumns[iColCount].lpszName[0] != 0;
iColCount++);
for(iColIdx = 0; iColIdx < iColCount; iColIdx++)
{
_tcscpy(lpszBuffer, g_pcSampleListColumns[iColIdx].lpszName);
lvCol.cx = g_pcSampleListColumns[iColIdx].uCXCol;
lvCol.cchTextMax = _tcslen(lpszBuffer);
ListView_InsertColumn(hwndLV, iColIdx, &lvCol);
}
- Subclasses the list view.
DefLVProc = SubclassWindow(hwndLV, LVProc);
- Gets the header control, subclasses it, and makes all its items owner-drawn:
hwndHdr = ListView_GetHeader(hwndLV);
DefLVHeaderProc = SubclassWindow(hwndHdr, LVHeaderProc);
for(iColIdx = 0; iColIdx < iColCount; iColIdx++)
{
HDITEM hdItem;
memset(&hdItem, 0, sizeof(HDITEM));
hdItem.mask = HDI_FORMAT;
if(Header_GetItem(hwndHdr, iColIdx, &hdItem))
{
hdItem.mask = HDI_FORMAT;
hdItem.fmt |= HDF_OWNERDRAW;
Header_SetItem(hwndHdr, iColIdx, &hdItem);
}
MainDlg_OnSize
is the resize handler. It repositions the list view centered in the dialog in corner (1, 1) and with size decreased with 2 or width and height, if possible.
MainDlg_OnNotify
responds to notifications from the list view: the header click (LVN_COLUMNCLICK
) and key down (LVN_KEYDOWN
).
MainDlg_OnKeyDown
intercepts two keys: F5 refreshes the list view (i.e. populates it with some sample items), and DEL erases its content. Nothing ensures that the list view is stable at the moment of operations (i.e. you can press DEL during a list view update). You have to protect the list view on your own. Perhaps the best (and simplest) mechanism is to reflect the notifications (as the ON_NOTIFY_REFLECT
does) to the list view, so the control will know how to operate on its data, styles, content etc. and how to avoid side effects or concurrent access.
MainDlg_OnListColumnClick
will modify the sort orientation according to the global variable that holds the column information (will set the sort order, priority, or, if the sort became inactive, will affect the priorities of the following column(s) in the sort order). BOOL
MainDlg_OnListColumnClick(HWND hWnd, LPARAM lParam)
{
LPNMLISTVIEW lpNMLV;
int iColumnClicked;
lpNMLV = (LPNMLISTVIEW)lParam;
iColumnClicked = lpNMLV->iSubItem;
MainDlg_SetSortOnColumn(hWnd, iColumnClicked);
MainDlg_SortList(hWnd);
return FALSE;
}
Let's take a closer look:
VOID
MainDlg_SetSortOnColumn(HWND hWnd, int iColIdx)
{
int iNextPrior;
int iSortOrder;
HWND hwndLV;
HWND hwndHdr;
hwndLV = GetDlgItem(hWnd, IDLV_REPORT);
hwndHdr = ListView_GetHeader(hwndLV);
MainDlg_GetColumnSortPriority(hWnd, iColIdx, &iNextPrior);
if(iNextPrior == LVSORTPRIORITY_NONE)
{
MainDlg_FindNextSortPriority(hWnd, &iNextPrior);
MainDlg_SetColumnSortOrder(hWnd, iColIdx, LVORDER_ASCENDING);
MainDlg_SetColumnSortPriority(hWnd, iColIdx, iNextPrior);
}
else
{
MainDlg_GetColumnSortOrder(hWnd, iColIdx, &iSortOrder);
if(iSortOrder == LVORDER_ASCENDING)
{
MainDlg_SetColumnSortOrder(hWnd, iColIdx, LVORDER_DESCENDING);
}
else
{
MainDlg_SetColumnSortOrder(hWnd, iColIdx, LVORDER_NONE);
MainDlg_DecreasePriority(hWnd, iColIdx);
}
}
InvalidateRect(hwndHdr, NULL, TRUE);
}
The list sorting routine has nothing special. It looks on the data type it has to compare, converts the cells' data from string to the appropriate type (looking on SAMPLELISTVIEWCOLUMN
nSortType
member (it can be LVORDER_STRING
(VT_BSTR
), LVORDER_NUMERIC
(VT_I4
) or LVORDER_DATETIME
(VT_DATE
), in this sample) and performs the VARIANT
comparison calling VarCmp
if the conversion succeeded. If an error occurred (conversion, comparison, etc.), the items are considered equal.
List view and header
The list view implements the following:
- The mouse messages for tooltip (
Tooltip_RelayEvent
); all of them are routed to the LV_OnMouseMessage
.
LV_OnDestroy
handler removes all the properties added to the list view handle.
- The
LV_OnPaint
handler is called only when the list view has no items. If it has, then the default window procedure is called.
- Finally,
LV_OnDrawItem
draws the header items (all were set as owner drawn in LV_SetInitial
).
The rest is GDI 'acrobatics'.