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

Uživatelsky kreslený ListBox - II.

30.1.2002

V dnešním pokračování si vylepšíme "souborový" ListBox z minulého článku. Budeme implementovat následující:

  • volbu zobrazení malé/velké ikony
  • umožníme přidávat položky (soubory) přetažením (Drag and Drop)
  • otevírat soubory poklepáním (dvojklikem) na položku
  • zefektivníme (na námět čtenáře) vykreslování.
  • zobrazování pouze názvů souborů bez cesty

win-api-uk-listbox-2

Volba zobrazení malé/velké ikony

Nastavení malé nebo velké ikony si budeme udržovat pro další použití v globální proměnné a dále si vytvoříme funkci pro změnu této volby, která zajistí okamžité překreslení ListBoxu.

BOOL g_VelkeIkony = TRUE;
void NastavVelkeIkony(HWND hListBox, BOOL Velke)
{
  g_VelkeIkony = Velke;
  int vyska;
  if ( g_VelkeIkony )
    vyska = GetSystemMetrics(SM_CYICON)+2;
  else
    vyska = GetSystemMetrics(SM_CYSMICON)+2;
  SendMessage(hListBox, LB_SETITEMHEIGHT, 0, (LPARAM)vyska);
  RedrawWindow(hListBox, NULL, NULL,
    RDW_ERASE | RDW_INVALIDATE | RDW_ERASENOW );
}

Vytvoříme si novou funkci na přidání již vybraného souboru, kterou budeme volat ze dvou míst. Dále si změníme způsob získávání handle asociované ikony při vykreslování. Tento handle si uložíme do dat položky při jejím přidání a při vykreslování pak při jeho získání ušetříme nějaký ten takt procesoru.

void PridatPolozku(HWND hListBox, LPCTSTR lpSoubor)
{
  LRESULT Polozka =
    SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)lpSoubor);
  WORD index = 0;
  HICON hIcon = ExtractAssociatedIcon(g_hInstance, (LPTSTR)lpSoubor, &index);
  SendMessage(hListBox, LB_SETITEMDATA, Polozka, (LPARAM)hIcon);
}

Vykreslování položek

Upravíme si také obsluhu zprávy WM_DRAWITEM. Nyní budeme zobrazovat pouze názvy souborů bez cesty, musíme vzít v úvahu aktuální nastavení malé-velké ikony a handle ikony nyní získáváme z dat položky, které dostaneme jako prvek itemData struktury DRAWITEMSTRUCT. Celá obsluha bude nyní vypadat takto:

void DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
  TCHAR szSoubor[MAX_PATH];
  HBRUSH hbPozadi;
  SendMessage(lpDIS->hwndItem, LB_GETTEXT, lpDIS->itemID, (LPARAM)szSoubor);
    WORD index = 0;
  int xIkona, yIkona; // rozměry malé nebo velké ikony
  if ( g_VelkeIkony )
  {
    xIkona = GetSystemMetrics(SM_CXICON);
    yIkona = GetSystemMetrics(SM_CYICON);
  }
  else
  {
    xIkona = GetSystemMetrics(SM_CXSMICON);
    yIkona = GetSystemMetrics(SM_CYSMICON);
  }
  HICON hIcon = (HICON)lpDIS->itemData;
  if ( lpDIS->itemState & ODS_SELECTED )
  {
    hbPozadi = GetSysColorBrush(COLOR_HIGHLIGHT);
    SetTextColor(lpDIS->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
  }
  else
  {
    hbPozadi = GetSysColorBrush(COLOR_WINDOW);
    SetTextColor(lpDIS->hDC, GetSysColor(COLOR_WINDOWTEXT));
  }
  
  SetBkMode(lpDIS->hDC, TRANSPARENT);
  FillRect(lpDIS->hDC, &lpDIS->rcItem, hbPozadi);
  
  if ( g_VelkeIkony )
    DrawIcon(lpDIS->hDC,
      lpDIS->rcItem.left+1,
      lpDIS->rcItem.top+1, hIcon);
  else
    DrawIconEx(lpDIS->hDC,
      lpDIS->rcItem.left,
      lpDIS->rcItem.top, hIcon,
      xIkona, yIkona,
      0, NULL, DI_NORMAL);
  RECT rect;
  CopyMemory(&rect, &lpDIS->rcItem, sizeof(RECT));
  rect.left += xIkona + 2;
  LPTSTR  lpSoubor = strrchr(szSoubor, '\\');
  if ( !lpSoubor ) // pro jistotu :))
    lpSoubor = szSoubor;
  else
    lpSoubor++;
  DrawText(lpDIS->hDC, lpSoubor, -1, &rect,
    DT_SINGLELINE | DT_VCENTER);
}

Realizace Drag and Drop

Jak nyní na realizaci Drag and Drop způsobu přijímání souborů. Aby okno vůbec mohlo být cílem "puštění", musíme toto povolit pomocí funkce DragAcceptFiles, jejíž 2. parametr povolí nebo zakáže příjem položek pomocí Drag and Drop. Toto v našem případě nastavíme nejlépe v obsluze WM_INITDIALOG.

DragAcceptFiles(GetDlgItem(hWnd, IDC_LISTBOX), TRUE);

Od tohoto okamžiku bude okno dostávat při puštění položky zprávu WM_DROPFILES, jejímž parametrem wParam je handle interní struktury HDROP, ze které získáme potřebná data. Musíme ale nejprve na ListBox aplikovat již dříve probíraný subclassing, tady přesměrování jeho procedury okna na vlastní funkci, což provedeme opět v obsluze WM_INITDIALOG takto:

wndprocLB = (WNDPROC)SetWindowLongPtr(GetDlgItem(hWnd, IDC_LISTBOX),
  GWLP_WNDPROC, (LONG_PTR)WindowProcListBox);

Vlastní procedura okna ListBoxu bude obsluhovat pouze zmíněnou zprávu WM_DROPFILES, při čemž si zavoláme vlastní funkci pro její zpracování. Obě funkce vypadají následovně:

void DropFiles(HWND hWnd, HDROP hDrop)
{
  TCHAR szSoubor[MAX_PATH];
  int Pocet = (int)DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
  if ( Pocet <= 0 )
    return;
  for ( int i = 0; i < Pocet; i++ )
  {
    DragQueryFile(hDrop, i, szSoubor, MAX_PATH);
    PridatPolozku(hWnd, szSoubor);
  }
  DragFinish(hDrop);
}

WNDPROC wndprocLB;

LRESULT CALLBACK WindowProcListBox(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch ( uMsg )
  {
    case WM_DROPFILES:
      DropFiles(hWnd,  (HDROP)wParam);
      break;
  }
  return CallWindowProc(wndprocLB,  hWnd, uMsg, wParam, lParam);
}

Otevírání souborů v položkách

Nyní zbývá ještě realizovat schopnost na poklepání (dvojklik) položky příslušný soubor otevřít stejným způsobem jako Průzkumník Windows, tedy v přidruženém programu, pokud takový existuje, nebo přímo spustit pokud jde o spustitelný soubor. Budeme tedy zachytávat v proceduře dialogu oznamovací zprávu LBN_DBLCLK a z ní volat vlastní funkci které předáme handle ListBoxu a která vypadá takto..

void LBNDblClk(HWND hListBox)
{
  TCHAR szSoubor[MAX_PATH];
  LRESULT Vyber = SendMessage(hListBox, LB_GETCURSEL, 0, 0);
  SendMessage(hListBox, LB_GETTEXT, Vyber,
    (LRESULT)szSoubor);
  ShellExecute(NULL, _T("open"), szSoubor,
    NULL, NULL, SW_SHOWNORMAL);
}

Pro úplnost si v části kódu procedury dialogu ukážeme její volání. Z procedury dialogu je ve výpisu pouze obsluha oznamovacích zpráv ListBoxu a CheckBoxu přepínajícího zobrazení na malé/velké ikony.

INT_PTR CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch ( uMsg )
  {
    case WM_COMMAND:
      switch ( LOWORD(wParam) )
      {
        case IDC_LISTBOX:
          if ( HIWORD(wParam) == LBN_DBLCLK )
            LBNDblClk((HWND)lParam);
          break;
        case IDC_VELKE_IKONY:
          if ( HIWORD(wParam) == BN_CLICKED )
          {
            NastavVelkeIkony(GetDlgItem(hWnd, IDC_LISTBOX),
              IsDlgButtonChecked(hWnd, IDC_VELKE_IKONY) == BST_CHECKED);
          }
          break;
      }
      break;
  }
  return FALSE;
}

Doprovodný projekt je ke stažení zde: win_api_uk_listbox_2.zip