Živě.cz o počítačích a internetu

Tipy a triky v Delphi, díl 162. - OwnerDraw

Jan Šindelář 10.11.2004

Už jsme si dlouho nehráli s vylepšováním vzhledu našich aplikací a tak to dnes trochu napravíme. Magickým heslem dnešního dílu bude "Owner drawing".

Nejjednodušší způsob, jakým vytvořit uživatelské rozhraní aplikace v Delphi, je poskládat jej z hotových komponent. V tom je právě síla podobných nástrojů jako je Delphi. Bez jediného řádku kódu můžete vytvořit jen pomocí myši kompletní prostředí programu a doplnit pak pouze kódy událostí. Jistě, je to trochu zjednodušený pohled na věc, ale s trochou nadsázky se dá říct, že lze v Delphi vytvořit profesionálně vypadající a funkční program bez jediného řádku kódu do několika minut. Má to pochopitelně i horší stránky, různé narychlo "šité" a polofunkční aplikace. To už ale odbíháme od tématu. Tak či onak, časem standardní paleta komponent přestane stačit a člověk zatouží po něčem lepším, hezčím, funkčnějším. Jedna z možností je instalace některé z tisíců dostupných vizuálních komponent a nebo si vylepšit komponenty sami. Nejen tím, že uděláme komponenty nové (či upravíme ty originální), ale také právě využitím vlastnosti OwnerDraw.

Aktivací vlastnosti OwnerDraw u příslušné komponenty (tedy pokud ji obsahuje) se dobrovolně vzdáváme pohodlí, že se o vykreslování postará systém sám a všechno zůstává na nás. Naštěstí toho není mnoho, co musíme zařídit sami. Výsledek pak může vypadat přesně tak, jak si přejeme a to i bez instalace cizích či tvorby vlastních nových komponent.

Náš ukázkový příklad se bude zabývat tvorbou kontextové nabídky (PopupMenu). Obdobným způsobem by šlo upravit i hlavní menu či libovolnou jinou vizuální komponentu (s vlastností OwnerDraw). Když tedy musíme zařídit kreslení sami, jak poznáme, kdy máme co nakreslit? Jednoduše. Systém se na nás "nevykašle" úplně. Sice sám automaticky nic kreslit nebude, ale řekne nám (pomocí zpráv systému), kdykoliv je potřeba danou komponentu překreslit. V událostech OnMeasureItem a OnDrawItem pak předepíšeme, co a jak se má nakreslit. V případě, že vytváříme menu jako v naší ukázce, musíme tyto dvě procedury definovat pro všechny jeho položky.

Začneme nejprve tím, že PopupMenu vytvoříme běžným způsobem pomocí editoru Delphi. Nadefinujeme všechny položky a jejich strukturu a můžeme přidat i ikonky z ImageListu. Poté nastavíme vlastnost OwnerDraw na hodnotu True a legrace začíná.

Vybereme například první položku menu a napíšeme pro ni kód události OnDrawItem. V něm si nadefinujeme vzhled této položky. Budeme chtít, aby bylo použito tučné písmo Verdana černé barvy, aby byl podklad "klasickou šedou" barvou (resp. tou, která je pro tento objekt v systému nastavena) a aby při označení řádku kurzorem byl zvýrazněn zeleným pruhem. Zároveň se písmo označeného prvku změní na kurzívu a bude mít modrou barvu. Příslušný kousek kódu pak může vypadat nějak takto:

with ACanvas do
    begin
      if Selected then
        begin
          Font.Name := `Verdana`;
          Font.Style := [fsBold, fsItalic];
          Font.Size := 12;
          Font.Color := clBlue;
          Brush.Color := clLime;
          FillRect(ARect);
          TextOut(ARect.Left + 30, ARect.Top + 10, Cap);
        end
      else
        begin
          Font.Name := `Verdana`;
          Font.Style := [fsBold];
          Font.Size := 12;
          Brush.Color := clMenu;
          Fillrect(ARect);
          TextOut(ARect.Left + 30, ARect.Top + 10, Cap);
        end;
end;

Jsme-li spokojeni, můžeme přiřadit tyto události i pro všechny ostatní položky a celé menu pak bude mít nový vzhled. Můžeme si i více pohrát, nastavit třeba u každé položky jiné písmo či barvu, ale to bychom museli u každého řádku menu znovu psát příslušnou proceduru OnDraw.

Co ale když zatoužíme jednotlivé řádky menu zvětšit, abychom mohli použít třeba větší obrázek či písmo? Ani to není problém, stačí využít zmiňovanou událost OnMeasureItem u příslušné položky a nastavit požadované rozměry následujícím způsobem:

procedure TForm1.Prvn1MeasureItem(Sender: TObject; ACanvas: TCanvas; var Width, Height: Integer);
begin
  width := 200;
  height := 35;
end;

Aby byl příklad téměř dokonalý, zbývá ještě pár drobností. Jednak musíme před samotným vykreslením textu u jednotlivých položek odfiltrovat znak "&", který přidává podtržítko pro příslušnou horkou klávesu. Je totiž součástí nadpisu položky a komponenta Menu ho nahradí za podtržítko automaticky. Pokud však vykreslujeme položky my, musíme tento znak nějak zpracovat. Pro zjednodušení našeho příkladu jej prostě pomocí funkce Delete odstraníme, ale mohli bychom jej pochopitelně rovněž převést na podtržítko či zvýraznit příslušný (následující) znak jiným způsobem (třeba odlišnou barvou či tučným písmem). Rovněž nesmíme zapomenout nakreslit ikonku, příslušející k dané položce z komponenty ImageList.

Poslední lahůdka na závěr je velmi oblíbený (i když, přiznejme si to, zbytečný) vertikální pruh po levém okraji menu s nějakým textem. Celé to pak připomíná menu Start z Windows. Ten přidáme do našeho příkladu velmi snadno. Předem jsme si nechali volné místo o šířce 30 pixelů (viz. zdrojový kód) a na toto místo jen nakreslíme obdélník příslušné barvy. Do něj pak jako poslední třešničku na dortu napíše vertikálně otočený text.

Tyto poslední úpravy již najdete ve zdrojovém kódu ukázkového projektu, který si můžete stáhnout zde.