Rozšířený prvek ComboBoxEx.
4.2.2002
V tomto článku si řekneme o rozšířeném prvku ComboBoxEx, který patří stejně jako ComboBox mezi standardní ovládací prvky Windows. Ovládací prvek ComboBoxEx má některé další rozšiřující vlastnosti a schopnosti oproti běžnému prvku ComboBox. Tato výhoda však současně vyžaduje od programátora "trochu více práce" při jeho vytvoření a použití. Dejme se tedy do práce. Prvním krokem, který musíme učinit před vlastním vytvořením tohoto prvku je správná inicializace knihovny běžných ovládacích prvků, comctl32. Provedeme ji pomocí funkce InitCommonControlsEx s použitím hodnoty ICC_USEREX_CLASSES, jak vidíte v následujícím kódu, umístěném před vlastním vytvořením okna nebo dialogového okna obsahujícího ComboBoxEx. INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
icc.dwICC = ICC_WIN95_CLASSES | ICC_USEREX_CLASSES;
if ( !InitCommonControlsEx(&icc) )
return -1;
Dalším rozdílem je, že prvek ComboBoxEx nemůžeme definovat přímo ve skriptu prostředků, tedy nemůžeme jej vytvořit vizuálním editorem dialogu. Vytvoříme jej tedy v obsluze zprávy WM_INITDIALOG pomocí funkce CreateWindowEx. Tento prvek dále umožňuje jednoduchým způsobem (bez uživatelského kreslení, které samozřejmě není nijak složité, ale..) zobrazovat u položek obrázky. Tyto obrázky musí být obsaženy v prvku ImageList, což je sada jednotlivých obrázků stejné velikosti. Každé položce pak přiřadíme index obrázku z tohoto ImageListu, který má být zobrazován v levé části položky. ImageList nastavíme prvku ComboBoxEx pomocí zprávy CBEM_SETIMAGELIST. Vše uvidíte v dalším výpisu kódu, v kterém si také ukážeme, jak lze nastavit prvku ComboBoxEx vlastní kurzor a dále jiný kurzor editačnímu prvku, obsaženém v ComboBoxu. V té souvislosti si musíme říci, z čeho se vlastně rozšířený ComboBoxEx skládá. Vlastní okno prvku ComboBoxEx totiž v sobě obsahuje ještě (jako dětské okno) běžný prvek ComboBox, a ten jak jsme mohli poznat v minulých dílech obsahuje prvek Edit, představující editační pole v případě, že ComboBox má nastaven potřebný styl CBS_DROPDOWN). Kromě tohoto Editu je pak při rozbalení ComboBoxu dynamicky tvořen prvek ListBox. Položky do prvku ComboBoxEx přidáváme pomocí zprávy CBEM_SETIMAGELIST, jejímž parametrem je adresa struktury COMBOBOXEXITEM, obsahující vlastnosti položky. Nyní se tedy již podívejme na výpis obsluhy zprávy WM_INITDIALOG:
BOOL NaInitDialog(HWND hWnd)
{
HWND hCombo = CreateWindowEx(0,
WC_COMBOBOXEX,
NULL,
WS_BORDER | WS_VISIBLE | WS_CHILD | CBS_DROPDOWN,
0, 0, 0, 100, // polohu a rozměry nastvíme později
hWnd,
(HMENU)IDC_COMBOEX, // nastavíme ID pro snadnější použití
GetModuleHandle(NULL),
NULL);
g_hImageList = ImageList_LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_IMAGELIST),
16, 3, 0x0000FF00);
if ( !g_hImageList )
return FALSE;
SendMessage(hCombo, CBEM_SETIMAGELIST, 0, (LPARAM)g_hImageList);
SetWindowPos(hCombo, NULL,10,32,250,120,SWP_NOACTIVATE | SWP_NOZORDER);
COMBOBOXEXITEM cbei;
// Naplníme ComboBox 4 položkami.
ZeroMemory(&cbei, sizeof(cbei));
cbei.mask = CBEIF_IMAGE | CBEIF_TEXT | CBEIF_SELECTEDIMAGE;
cbei.pszText = _T("první položka");
cbei.iImage = 0;
cbei.iItem = 0;
cbei.iSelectedImage = 0;
SendMessage(hCombo, CBEM_INSERTITEM, 0, (LPARAM)&cbei);
cbei.pszText = _T("druhá položka");
cbei.iImage = 1;
cbei.iItem = 1;
cbei.iSelectedImage = 1;
SendMessage(hCombo, CBEM_INSERTITEM, 0, (LPARAM)&cbei);
cbei.mask = CBEIF_IMAGE | CBEIF_TEXT | CBEIF_SELECTEDIMAGE;
cbei.pszText = _T("třetí položka");
cbei.iImage = 2;
cbei.iItem = 2;
SendMessage(hCombo, CBEM_INSERTITEM, 0, (LPARAM)&cbei);
cbei.mask = CBEIF_IMAGE | CBEIF_TEXT | CBEIF_SELECTEDIMAGE;
cbei.pszText = _T("čtvrtá položka");
cbei.iImage = 3;
cbei.iItem = 3;
cbei.iSelectedImage = 3;
SendMessage(hCombo, CBEM_INSERTITEM, 0, (LPARAM)&cbei);
// Nastavíme výchozí výběr
SendMessage(hCombo, CB_SETCURSEL, 1, 0);
// Zjistíme "dětský" ComboBox a prvek Edit
HWND hChildCombo = (HWND)SendMessage(hCombo, CBEM_GETCOMBOCONTROL, 0,0);
HWND hEdit = (HWND)SendMessage(hCombo, CBEM_GETEDITCONTROL, 0,0);
// Nastavíme vlastní kurzory
SetClassLongPtr(hChildCombo, GCLP_HCURSOR,
(LONG_PTR)LoadCursor(GetModuleHandle(NULL), MAKEINTRESOURCE(IDC_CURSOR1)));
SetClassLongPtr(hEdit, GCLP_HCURSOR,
(LONG_PTR)LoadCursor(GetModuleHandle(NULL), MAKEINTRESOURCE(IDC_CURSOR2)));
return TRUE;
}
Dále si ještě ukažme, jak v případě rozšířeného ComboBoxu reagovat na změnu výběru položky a následně získat a nějakým způsobem zobrazit některá data položky. Konkrétně budeme zobrazovat text vybrané položky do prvku Static a obrázek vybrané položky ve formě ikony v prvku Static typu STM_ICON - který v editoru dialogu vložíme jako Picture Control. V proceduře dialogu budeme reagovat na oznamovací zprávu CBN_SELCHANGE. Vypíšeme si pro úplnost kompletní kód procedury dialogu z doprovodného příkladu:
// Procedura dialogu
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch ( uMsg )
{
case WM_COMMAND:
switch ( LOWORD(wParam) )
{
case IDOK:
EndDialog(hWnd, IDOK);
break;
case IDCANCEL:
EndDialog(hWnd, IDCANCEL);
break;
case IDC_COMBOEX:
if ( HIWORD(wParam) == CBN_SELCHANGE )
NaSelChange(hWnd);
break;
}
break;
case WM_INITDIALOG:
NaInitDialog(hWnd);
NaSelChange(hWnd);
break;
}
return FALSE;
}
Obsluha oznamovací zprávy CBN_SELCHANGE vypadá pak takto:
void NaSelChange(HWND hWnd)
{
TCHAR szText[100];
COMBOBOXEXITEM cbei;
ZeroMemory(&cbei, sizeof(cbei));
cbei.mask = CBEIF_IMAGE | CBEIF_TEXT;
cbei.pszText = szText;
cbei.cchTextMax = sizeof(szText);
// index vybrané položky
cbei.iItem = SendDlgItemMessage(hWnd, IDC_COMBOEX, CB_GETCURSEL, 0, 0);
SendDlgItemMessage(hWnd, IDC_COMBOEX, CBEM_GETITEM, 0, (LPARAM)&cbei);
SetDlgItemText(hWnd, IDC_COMBO_TEXT, szText);
SendDlgItemMessage(hWnd, IDC_IMAGE, STM_SETIMAGE, IMAGE_ICON,
(LPARAM)ImageList_GetIcon(g_hImageList, cbei.iImage, ILD_NORMAL));
}
Doprovodný projekt je ke stažení zde: win_api_comboboxex.zip