Wstęp do Custom Draw - kontrolka TTreeView      Strona 1 z 1
w oparciu o materiały ze stron BCB CAQ

       Wraz z nadejściem IE3, Windows dają daleko posuniętą możliwość dostosowywania wyglądu kontrolek. Ta nowa technologia została nazwana Custom Draw

      Większość kontrolek umożliwia wybranie stylu owner-drawn. Problem tkwi w tym, że cały proces rysowania spada na programistę. Na przykład, aby stworzyć ListView typu owner-drawn należy mapować komunikaty WM_DRAWITEM i WM_MEASUREITEM. Potem napisać handlery zawierające kod rysujący kolejne pozycje, subpozycje, zaznaczone pozycje, pozycje aktywne; wszystko równocześnie z zachowaniem odpowiednich odstępów w tekście i odpowiedniej szybkości działania. Innymi słowy DUŻY kłopot.

      Custom Draw umożliwia programiście wybierać selektywnie, które aspekty mają zostać narysowane. Jeżeli życzysz sobie zmienić font, to po prostu wysyłasz komunikat i wybierasz nowy font. W dowolnym momencie programista może pozwolić Windowsowi dokonać standardowego rysowania, albo całkowicie je ominąć. Takie selektywne rysowanie ma wiele zalet - aby zmienić kolor pojedynczej pozycji listy nie trzeba wpisywać żadnego kodu rysującego.

      Trzeba pamiętać, że Custom Draw to nic więcej aniżeli sposób komunikacji pomiędzy Tobą a Windows. Windows wysyła komunikat o rozpoczęciu rysowania (CDDS_PREPAINT). Ty odpowiadasz, które fazy rysowania Cię interesują, w tym przypadku CDRF_NOTIFYITEMDRAW dla każdego węzła. Windows odpowiada komunikatem odnoszącym się do każdego rysowanego węzła (CDDS_ITEMPREPAINT). W końcu, zmieniasz niektóre atrybuty i mówisz, że je zmieniłeś (CDRF_NEWFONT); w dowolnym momencie możesz kazać przeprowadzić standardowe rysowanie (CDRF_DODEFAULT) albo nie rysować nic i przejąć całkowitą kontrolę nad tym procesem (CDRF_SKIPDEFAULT).

//w nagłówku 
typedef struct tagNMCUSTOMDRAWINFO 
{ 
    NMHDR  hdr; 
    DWORD  dwDrawStage; 
    HDC    hdc; 
    RECT   rc; 
    DWORD  dwItemSpec; 
    UINT   uItemState; 
    LPARAM lItemlParam; 
} NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW; 
typedef struct tagNMTVCUSTOMDRAW 
{ 
    NMCUSTOMDRAW nmcd; 
    COLORREF clrText; 
    COLORREF clrTextBk; 
    int iLevel;  // IE 4 only 
} NMTVCUSTOMDRAW, *LPNMTVCUSTOMDRAW; 

// stałe Custom Draw
#define NM_CUSTOMDRAW (NM_FIRST - 12) 
#define CDDS_ITEM 0x10000 
#define CDDS_PREPAINT 0x1 
#define CDDS_ITEMPREPAINT CDDS_ITEM | CDDS_PREPAINT 

#define CDRF_DODEFAULT 0x0 
#define CDRF_NEWFONT 0x2 
#define CDRF_NOTIFYITEMDRAW 0x20 

#define CDIS_SELECTED 0x0001 

  TFont *NewFont; 

// mapujemy komunikat WM_NOTIFY

  void __fastcall WMNotify(TMessage &Msg); 

BEGIN_MESSAGE_MAP 
    MESSAGE_HANDLER(WM_NOTIFY, TMessage, WMNotify) 
END_MESSAGE_MAP(TForm)

//------------------------------------------------------
//------------------------------------------------------
//w źródle
__fastcall TForm1::TForm1(TComponent* Owner) 
    : TForm(Owner) 
{ 
    // tworzymy nowy font, który przypiszemy
    // konkretnym węzłom TTreeView
    NewFont = new TFont(); 
    NewFont->Size = 10; 
    NewFont->Name = "Comic Sans MS"; 
} 
  
void __fastcall TForm1::WMNotify(TMessage &Msg) 
{ 
// wskaźnik do struktury NM_TREEVIEW
LPNM_TREEVIEW lpnm = (NM_TREEVIEW *)Msg.LParam; 

 // sprawdza, czy komunikat to Custom Draw
 if (lpnm->hdr.code == NM_CUSTOMDRAW) 
 { 
  // sprawdza, czy komunikat pochodzi z TreeView1
  if (lpnm->hdr.hwndFrom == TreeView1->Handle) 
   { 
    // wskaźnik do struktury NMTVCUSTDRAW
    LPNMTVCUSTOMDRAW lptvcd = (NMTVCUSTOMDRAW *)Msg.LParam; 

    // sprawdza fazę rysowania
    switch (lptvcd->nmcd.dwDrawStage) 
     { 
       // przed rysowaniem...
       case CDDS_PREPAINT: 
        { 
           // mówi Windowsowi, że chcemy
           // powiadomienia o rysowaniu każdej pozycji
            Msg.Result = CDRF_NOTIFYITEMDRAW; 
            break; 
        } 

     // powiadomienie o rysowaniu pozycji
     case CDDS_ITEMPREPAINT: 
        { 
         // zmienia font pierwszej pozycji w TreeView1
         ULONG HItem = (ULONG)TreeView1->Items->Item[0]->ItemId; 
           if (lptvcd->nmcd.dwItemSpec == HItem) 
              { 
               SelectObject(lptvcd->nmcd.hdc, NewFont->Handle); 
              } 

         // określa stan pozycji TreeView1
         UINT State = lptvcd->nmcd.uItemState; 

         // zmienia kolor tła jeżeli zaznaczona pozycja
         // jeżeli nie, to kolor TreeView1
          if (State & CDIS_SELECTED) 
            lptvcd->clrTextBk = ColorToRGB(clHighlight); 
          else 
            lptvcd->clrTextBk = ColorToRGB(TreeView1->Color); 

          // mówi Windowsowi, że zmienił atrybut
          Msg.Result = CDRF_NEWFONT; 
          break; 
    } 

     //w innym przypadku standardowe rysowanie
     default: Msg.Result = CDRF_DODEFAULT; 
   } 
  } 
 } 

    // jeżeli komunikat nie jest Custom Draw
    // to pozwala formie obsłużyć go
    else TForm::Dispatch(&Msg); 
} 
  

// czyścimy...
void __fastcall TForm1::FormClose(TObject *Sender,
                                      TCloseAction &Action) 
{ 
    delete NewFont;  
} 

      Custom Draw może być używane z większością kontrolek. Niektóre są trudniejsze do zaimplementowania, ale na pewno o wiele łatwiej jest to zrobić niż poprzez styl owner-drawn. Inną kwestią jest to, że Custom Draw nie powoduje denerwującego migotania, co można zaobserwować przy wielu kontrolkach owner-drawn. Nie trzeba więc używać podwójnego buforowania.


Tłumaczenie:  Maciek Frankiewicz