Drive-list control
4 minute read •
This tutorial will show you how to implement a drop-down list of all the logical drives installed on a Windows system. There are basically three major steps involved in achieving this goal, listed in the order that we will tackle them.
- Enumerating the disk drives on the current system.
- Obtaining the correct icon images used to draw the drives.
- Handling the
WM_DRAWITEM
message to draw the drop-down list.
Enumerating disk drives
This one is really easy. All we do is call the GetLogicalDriveStrings
API call to retrieve the complete list of all logical drives currently installed.
The returned buffer contains a number of zero-terminated strings, one after the other. The buffer is terminated by an additional null character, to mark the end of the drive names. The code below demonstates how to retrieive the list of drive names, and extract each name in turn from the string buffer.
char szDrives[1024];
char szText[32];
int drivelen;
drivelen = GetLogicalDriveStrings(1023, szDrives);
while(drivelen != 0)
{
int drivetype, len;
len = lstrlen(driveptr) + 1; //plus the NULL
drivelen -= len;
//convert drive name to lower case
driveptr = strlwr(driveptr);
lstrcpyn(szText, driveptr, 3); //only copy the 'X:' portion
//retrieve the index of the icon in the system image list
//add the drive name to the combo box
driveptr += len;
}
Getting the icon image
This is the easiest job of all. We don’t need to draw our own icons, because we can use the icons that the Windows shell uses to display drives. These system icons are located in a resource called the System Image List. So, we need to obtain the HIMAGELIST
handle to this image list, and the index into this list of the icon image we want. Thankfully there is a function which obtains an index this for us, called ShGetFileInfo
.
SHFILEINFO shfi;
HIMAGELIST hSysImgList;
//retrieve the index of the icon in the system image list
hSysImgList = (HIMAGELIST)SHGetFileInfo("C:", 0, &shfi, sizeof(SHFILEINFO),
SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_ICON);
The snippet above retrieves a handle to the system image list, and also the index of the icon associated with the “C:” drive. The icon index is stored in the shfi.iIcon
variable when the ShGetFileInfo
function returns.
Handling the WM_DRAWITEM message
It is also pretty straight-forward to draw the icons along side each drive name. We do this using the standard Owner-Draw feature that a Combo box supports. Owner-Draw is enabled for a combo-box by creating the control with the CBS_OWNERDRAWFIXED
style.
BOOL DriveListMeasure(HWND hwnd, UINT uCtrlId, MEASUREITEMSTRUCT *mis)
{
HFONT hFont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0);
mis->itemHeight = DriveList_GetLineHeight(hwnd, hFont, nBitmapHeight);
//(16)
nBitmapYOff = (mis->itemHeight - (nBitmapHeight-1)) / 2;
return TRUE;
}
BOOL DriveListDraw(HWND hwnd, UINT uCtrlId, DRAWITEMSTRUCT *dis)
{
HWND hwndCombo = GetDlgItem(hwnd, uCtrlId);
char szText[MAX_PATH+1];
char *ptr;
int idx;
switch(dis->itemAction)
{
case ODA_FOCUS:
if(!(dis->itemState & ODS_NOFOCUSRECT))
DrawFocusRect(dis->hDC, &dis->rcItem);
break;
case ODA_SELECT:
case ODA_DRAWENTIRE:
SendMessage(hwndCombo, CB_GETLBTEXT, dis->itemID, (LONG)szText);
if(dis->itemState & ODS_SELECTED)
{
SetTextColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
SetBkColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHT));
}
else
{
SetTextColor(dis->hDC, GetSysColor(COLOR_WINDOWTEXT));
SetBkColor(dis->hDC, GetSysColor(COLOR_WINDOW));
}
// we set the SB_SETITEMDATA DWORD to be the system image list index
// for this drive icon. Retrieve the value so we know which picture
// to display for this drive
idx = dis->itemData;
ptr = strchr(szText, ':');
if(ptr) ptr[1] = '\0';
//Draw the drive letter
ExtTextOut(dis->hDC, dis->rcItem.left+22, dis->rcItem.top+1,
ETO_OPAQUE, &dis->rcItem, szText, lstrlen(szText), 0);
//Draw the drive volume name
if(ptr)
ExtTextOut(dis->hDC, dis->rcItem.left+22+16,
dis->rcItem.top+1, 0, 0, ptr+1, lstrlen(ptr+1), 0);
if(dis->itemState & ODS_FOCUS)
{
if(!(dis->itemState & ODS_NOFOCUSRECT))
DrawFocusRect(dis->hDC, &dis->rcItem);
}
//Draw the drive icon over the top of the selection
ImageList_Draw(hSysImgList, idx, dis->hDC,
dis->rcItem.left + 2,
dis->rcItem.top + nBitmapYOff,
ILD_TRANSPARENT);
break;
}
return TRUE;
}
Conclusion
That’s all there is to do for now. I’ve omitted a few details for clarity’s sake, but the source code download shows how to do this in full.
This implementation of a drive-list uses the standard Windows combo box to display the drives. The ComboBoxEx
control which is available in the Common Controls Library could have been used instead. This new style combo box has automatic support for item images through an image list. There were two reasons I chose not to use this new control. The first is that the ComboBoxEx is not available under some versions of Windows (those which have an early version of the common controls library). The second reason is that I wanted to achieve the same look and feel as a “normal” drive list control, so the standard Combo box was chosen.