Radek Chalupa   konzultace a školení programování, vývoj software na zakázku

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.

win-api-combobox-ex

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