Radek ChalupaČlánkyUkázky kóduTipy a trikyAktualityŠkolení a konzultaceVývoj softwareFreewareKontakt

Uživatelsky kreslená tlačítka.  28.1.2002

V minulém článku jsme si slíbili, že úpravu vzhledu tlačítek si necháme do dalšího pokračování. Bylo tomu tak z důvodů, že u tlačítek nelze použít zprávu skupiny WM_CTLCOLORxxx tak, jak jsme si použili u prvků v minulém díle.

win-api-28

U tlačítek máme v zásadě 2 možnosti. Tou jednodušší je možnost nastavit tlačítku ikonu nebo bitmapu. Z toho však plynou leckdy závažná omezení. Nemůžeme například dynamicky měnit text tlačítka (funkcí SetWindowText), neboť text okna se na tlačítku nezobrazí.Celý "obraz" tlačítka totiž je tvořen tlačítkem nakresleným systémem, na kterém je námi nastavená bitmapa. Nic více tam již nedostaneme. Museli bychom tedy mít předem připraveny bitmapy se všemi možnými texty a tyto celé bitmapy pak dynamicky měnit, což by určitě bylo pracné a také náročné na zdroje programu. Druhou možností je použít tzv. uživatelsky kreslená tlačítka. Pak máme možnost plné kontroly nad vzhledem tlačítka a můžeme na něj kreslit vše, co umíme nakreslit do kontextu zařízení (HDC). Na obrázku vidíte vlevo ukázku tlačítka s nastavenou bitmapou. K nastavení bitmapy (popř. ikony) použujeme zprávu BM_SETIMAGE, tak jak vidíte v ukázce kódu doprovodného projektu:
// Při spuštění nastavíme tlačítku bitmapu (handler WM_INITDIALOG)
void On_InitDialog(HWND hWnd)
{
  SendDlgItemMessage(hWnd, IDC_HONZA, BM_SETIMAGE, (WPARAM)IMAGE_BITMAP,
    (LPARAM)LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_HONZA)));
}
Tato funkce je volána v handleru zprávy WM_INITDIALOG, tedy "těsně" před prvním zobrazením dialogu na obrazovce. Ještě musíme v editoru zdrojů příslušnému tlačítku nastavit vlastnost "Bitmap" na True (styl okna BS_BITMAP). Nyní se podívejme na další 2 tlačítka, která jsou již plně uživatelsky kreslena. V editoru zdrojů musíme takovým tlačítkům nastavit vlastnost "Owner Draw" na True (odpovídá to stylu okna BS_OWNERDRAW). Když nyní dialog spustíme, tyto tlačítka vůbec na dialogu nebudou vidět, musíme je tedy ve správný čas kreslit sami. O tom, kdy nastal ten správný čas, nás informuje systém. Rodičovskému oknu, tedy našemu dialogu je poslána zpráva WM_DRAWITEM, obsahující v parametru lParam adresu struktury DRAWITEMSTRUCT:
typedef struct tagDRAWITEMSTRUCT {
    UINT  CtlType;
    UINT  CtlID;
    UINT  itemID;
    UINT  itemAction;
    UINT  itemState;
    HWND  hwndItem;
    HDC  hDC;
    RECT  rcItem;
    ULONG_PTR  itemData;
} DRAWITEMSTRUCT;
V této struktuře máme všechny potřebné informace k vykreslení buttonu (totéž platí i o dalších prvcích podporující uživatelské kteslení - listbox, combobox apod.). Struktura obsahuje také informace o stavu tlačítka - zda je například právě zamáčknuté, zda má klávesnicový fokus apod. Podívejme se nyní na konkrétní realizaci. V projektu máme 2 tlačítka kreslená uživatelsky. Chceme mít na obou stejné pozadí tvořené bitmapou. Tuto bitmapu si tedy přidáme do zdrojů (IDB_POZADI). Zobrazované ikony jsou systémové ikony (IDI_INFORAMTION a IDI_ERROR) a každé tlačítko má svůj vlastní text. Vše ostatní již realizujeme v handleru zprávy WM_DRAWITEM, ve kterém zavoláme následující vlastní kreslící funkci, které jako parametr předáme lParam procedury dialogu.
// Uživatelské kreslení tlačítek
void On_DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
  HBRUSH hbPozadi = CreatePatternBrush(LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_POZADI)));
  if ( lpDIS->itemState & ODS_SELECTED )
    DrawFrameControl(lpDIS->hDC, &lpDIS->rcItem, DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_PUSHED);
  else
    DrawFrameControl(lpDIS->hDC, &lpDIS->rcItem, DFC_BUTTON, DFCS_BUTTONPUSH);
  InflateRect(&lpDIS->rcItem, -4,-4);
  FillRect(lpDIS->hDC, &lpDIS->rcItem, hbPozadi);
  SetBkMode(lpDIS->hDC, TRANSPARENT);
  SetTextColor(lpDIS->hDC, 0x00A0FFFF);
  switch ( lpDIS->CtlID )
  {
    case IDC_INFO:
      DrawIcon(lpDIS->hDC,
        (lpDIS->rcItem.right - lpDIS->rcItem.left + 8
          - GetSystemMetrics(SM_CXICON))/2,
        lpDIS->rcItem.top + 5,
        LoadIcon(NULL, IDI_INFORMATION));
        lpDIS->rcItem.top = lpDIS->rcItem.bottom - 20;
      DrawText(lpDIS->hDC, TEXT("O programu"), -1, &lpDIS->rcItem,
        DT_SINGLELINE | DT_CENTER | DT_VCENTER);
      break;
    case IDC_CHYBA:
      DrawIcon(lpDIS->hDC,
        (lpDIS->rcItem.right - lpDIS->rcItem.left + 8
          - GetSystemMetrics(SM_CXICON))/2,
        lpDIS->rcItem.top + 5,
        LoadIcon(NULL, IDI_ERROR));
        lpDIS->rcItem.top = lpDIS->rcItem.bottom - 20;
      DrawText(lpDIS->hDC, TEXT("Bug report"), -1, &lpDIS->rcItem,
        DT_SINGLELINE | DT_CENTER | DT_VCENTER);
      break;
  }
  DeleteObject(hbPozadi);
}

/////////////////////////////////////////////////////////////////////////////
// procedura dialogu

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch ( uMsg )
  {
    case WM_COMMAND:
      switch ( LOWORD(wParam) )
      {
        case IDOK:
        case IDCANCEL:
          DeleteObject(g_hbPozadi);
          EndDialog(hwndDlg, LOWORD(wParam));
          break;
        case IDC_HONZA:
          ShellExecute(NULL, TEXT("open"),
            TEXT("http://www.rplusj.cz/index.asp?stranka=fotogalerie.htm"),
            NULL, NULL, SW_SHOWMAXIMIZED);
          break;
        case IDC_CHYBA:
          ShellExecute(NULL, TEXT("open"),
            TEXT("http://www.rplusj.cz/obrazek.asp?src=data/foto/sranda/kdyz_usa_prohraje.jpg"),
            NULL, NULL, SW_SHOWMAXIMIZED);
          break;
        case IDC_INFO:
          ShellExecute(NULL, TEXT("open"),
            TEXT("http://www.rcsoft.cz"),
            NULL, NULL, SW_SHOWMAXIMIZED);
          break;
      }
      break;
    case WM_DRAWITEM:
      On_DrawItem((LPDRAWITEMSTRUCT)lParam);
      break;
    case WM_INITDIALOG:
      On_InitDialog(hwndDlg);
      break;
  }  
  return FALSE;
}
Doprovodný projekt je ke stažení zde: win_api_28.zip

Copyright © 2019 - Radek Chalupa