Efektywne menu

Sheitar

1 Przygotowanie ikonek
2 Wczytywanie ikonek
3 Tworzenie menu
4 Tworzenie pozycji w menu
5 Mierzenie rozmiaru elementu
6 Zwalnianie użytej pamięci
7 Do pobrania

<justify>W tym krótkim gotowcu opiszę jak wykonać bardzo ładne menu, które zawierać będzie ikony 32 bitowe (czyli z kanłem Alpha) o rozmiarze 32px na 32px, oraz dwa wiersze tekstu, z których jeden będzie zawierał tytuł pozycji w menu, a drugi jego wskazówkę.</justify>

Końcowy efekt tych działań będzie następujący:

menu_glowne.png

menu_dodatkowe.png</span>
<justify>Menu takie będzie wyglądać dobrze, jeśli projektuje się prostą aplikację z kilkoma opcjami, gdyż menu pokroju Worda wykonane w tej technice będzie przerażająco wielkie i będzie tylko odstraszać zamiast cieszyć oko.</justify>

Przygotowanie ikonek

<justify>Najpierw musimy stworzyć zasoby z ikonami, ja do tego przykładu znalazłem w google pierwsze lepsze ikonki, które spełniają wymagania (32x32 px i 32 bit) i są free, ale zakładam, że każdy kto będzie chciał takie menu stworzyć to posiada już własną bazę ikonek. Z ikonek tworzymy zasób, kod pliku .rc wygląda następująco:</justify> ```delphi I0 ICON "ikony/ac0036-32.ico" I1 ICON "ikony/ei0021-32.ico" I2 ICON "ikony/wi0009-32.ico" I3 ICON "ikony/wi0054-32.ico" I4 ICON "ikony/wi0062-32.ico" I5 ICON "ikony/wi0063-32.ico" I6 ICON "ikony/wi0064-32.ico" I7 ICON "ikony/wi0096-32.ico" I8 ICON "ikony/wi0111-32.ico" I9 ICON "ikony/wi0122-32.ico" I10 ICON "ikony/wi0124-32.ico" I11 ICON "ikony/wi0126-32.ico" ``` <justify>Ważne jest tutaj nazewnictwo zasobu z ikoną, w tym przypadku od I0 do I11, gdyź odczytywanie ich będzie można bardzo łatwo zakodować poprzez iterację zmiennej liczbowej. Kompilujemy to za pomocą brcc32.exe i dopisujemy zasób do projektu wstawiając w kod taką dyrektywę:</justify> ```delphi {$R Resource\ikony.RES} ```

Wczytywanie ikonek

<justify>Kolejnym etapem jest wczytanie ikon do pamięci, Do tego celu potrzebujemy tablicy ikon, którą deklarujemy w sekcji prywatnej.</justify> ```delphi private { Private declarations } Icons: array[0..11] of hIcon; ``` <justify>Nastęnie podczas tworzenia formy wczytujemy wszystkie ikony za pomocą pętli do tablicy. Odpowiada za to następujący kod.</justify> ```delphi procedure TFMain.FormCreate(Sender: TObject); var n: Integer; begin // wczytujemy ikonki do tabelki for n := 0 to High(Icons) do Icons[n] := LoadImage(hInstance, pChar('I' + IntToStr(n)), IMAGE_ICON, 0, 0, 0); end; ```

Tworzenie menu

<justify>Po dodaniu komponentu TMainMenu na formę musimy przestawić dwie właściwości, a mianowicie OwnerDraw na wartość True oraz AutoHotKeys na wartość maManual. Pierwsza zmiana zapewnia nam kontrolę nad rysowaniem elementów menu, a druga zapobiega wstawianiu znaczka & (który powinien być przekształcony na podkreślenie oznaczające literkę skrótu, ale ze względu iż my sami obsługujemy malowanie więc ta funkcja nie jest dostępna) w tytuł elementu (ale tracimy w ten sposób automatycznie tworzone skróty).</justify>

Tworzenie pozycji w menu

<justify>Dla przykładu tworzymy dwie nadrzędne pozycje w menu - Główne i Dodatkowe, a w każdej z nich dodajemy jakieś opcje, pamiętając o kilku następujących rzeczach:</justify> * Tag odpowiada numerowi ikony w tablicy, -1 brak ikony * Caption będzie narysowany pogrubioną czcionką * Hint będzie narysowany zwykłą czcionką poniżej Caption

Mierzenie rozmiaru elementu

<justify>Obsługując tryb OwnerDraw musimy zadbać o obsłużenie dwóch zdarzeń dla każdego elementu menu. Pierwsze z tych zdarzeń to OnMeasureItem, która pyta nas o rozmiar elementu, aby ustalić wysokość i szerokość całego menu. My stworzymy jedną taką procedurę, a każdy element będzie się do niej odwoływał poprzez parametr Sender.</justify> ```delphi procedure TFMain.MMGlowneOpcja1MeasureItem(Sender: TObject; ACanvas: TCanvas; var Width, Height: Integer); var SzerCapt, SzerHint: Integer; begin SzerCapt := ACanvas.TextWidth(TMenuItem(Sender).Caption); SzerHint := ACanvas.TextWidth(TMenuItem(Sender).Hint); if SzerCapt < SzerHint then Width := SzerHint + 46 else Width := SzerCapt + 46;

// Zamiast powyższego ifa można dać to na funkcji Max, lecz
// trzeba do uses dopisac modul Math
// Width := Max(SzerCapt, SzerHint) + 46;

Height := 36;
end;

<justify>Jedyne co musimy tutaj zrobić to zmierzyć szerokość tekstu zawartego w <i>Caption</i> oraz w <i>Hint</i> i wybrać większą wartość. Wysokość jest ustawiona na sztywno na 36px, gdyż zapewnia to miejsce dla ikonki (32px) plus po 2px pustego miejsca wkoło ikony, wtedy ramka przy zaznaczeniu elementu nie będzie nachodzić na ikonę. Wartość 46 została ustalona eksperymentalnie i zapewnia miejsce na ikonę (32px ikona + 2px wolnego miejsca), odstęp między ikoną, a tekstem oraz kawałek pustego miejsca po prawej stronie, gdzie pojawi się trójkąt od menu rozwijanego (w przeciwnym wypadu ten trójkącik najdzie na tekst, a tego nie chcemy)</justify>

<h1>Malowanie elementu menu</h1>
<justify>Zostało nam już tylko obsłużyć rysowanie elementów menu. Posłużymy się do tego drugim zdarzeniem, a mianowicie <i>OnDrawItem</i>.</justify>
```delphi
procedure TFMain.MMGlowneOpcja1DrawItem(Sender: TObject; ACanvas: TCanvas;
  ARect: TRect; Selected: Boolean);
begin
 // tło
 if Selected then ACanvas.Rectangle(ARect) else
  begin
   ACanvas.Pen.Color := ACanvas.Brush.Color;
   ACanvas.Rectangle(ARect);
  end;

 // ikona
 if TMenuItem(Sender).Tag >= 0 then
  DrawIcon(ACanvas.Handle, ARect.Left + 2, ARect.Top + 3, Icons[TMenuItem(Sender).Tag]);

 // kolor napisu
 ACanvas.Brush.Style := bsClear; 
 if not TMenuItem(Sender).Enabled then ACanvas.Font.Color := clGrayText;

 // napisy
 // Caption ma być pogrubiony
 ACanvas.Font.Style := ACanvas.Font.Style + [fsBold];
 // malowanie Captiona
 ACanvas.TextOut(ARect.Left + 38, ARect.Top + 2, TMenuItem(Sender).Caption);
 // zdjęcie pogrubienia
 ACanvas.Font.Style := ACanvas.Font.Style - [fsBold];
 // jeśli jest jakiś hint - namalowanie go
 if TMenuItem(Sender).Hint <> '' then
  ACanvas.TextOut(ARect.Left + 38, ARect.Top + 18, TMenuItem(Sender).Hint);
end;

<justify>Pierwszym krokiem będzie zamalowanie tła, zależnie od tego czy dany element menu jest zaznaczony (wtedy rysujemy prostokąt z ramką) czy też nie (wtedy rysujemy prostokąt bez ramki). Kolory ustawione są automatycznie wg schematu kolorów systemu. Następnie rysujemy ikonę, której numer w tablicy odpowiada właściwości Tag dla danego elementu menu. Teraz zostają nam napisy. Po pierwsze ustawiamy kolor napisu na kolor systemowy clGrayText jeśli pozycja menu jest nieaktywna, w przeciwnym wypadku nie ruszamy koloru, gdyż będzie ustawiony na odpowienią wartość z schematu kolorów systemowych. Kolejnym etapem jest narysowanie napisu Caption z pogrubieniem oraz Hint bez pogrubienia, pod warunkiem, że pole Hint zawiera jakiś ciąg.</justify>

Zwalnianie użytej pamięci

<justify>Wczytaliśmy ikony więc przydałoby się zwolnić zasoby komputera zużyte przez tą operację. Podczas niszczenia formy można wywołać prostą pętlę niszczącą ikony.</justify> ```delphi procedure TFMain.FormDestroy(Sender: TObject); var n: Integer; begin // zwalniamy pamięć zajmowaną przez ikony for n := 0 to High(Icons) do DestroyIcon(Icons[n]); end; ```

Do pobrania

Możesz pobrać projekt w Delphi do tego przykładu (pisane w Delphi 7 Personal).

EfektywneMenu.zip

Taka wersja menu nie działa poprawnie z elementamu menu, które mają być używane jako CheckBox lub RadioButton, oraz nie obsługuje oznaczania litery skrótu za pomocą podkreślenia. Być może niedługo zaktualizuję ten artykuł oraz plik źródłowy dodając taką funkcjonalność.

14 komentarzy

a może z torrenta albo emule można to ściągnąć. hehe

Nie widze downloadu...:(
Jak mam to pobrać...?

No i gdzie to jest do pobrania??

Wychodzi bardzo "efektywny" efekt :D

-=JaCkObS=- i czego się cieszysz? :P
Jakbyś chciał wsadzić pięść w dziurkę od nosa to jaki efekt przewidujesz?

Bardzo fajny art :D

//Jak załadowałem ikony 128x128 to były bardzo fajnie rozmazane :D

Działa. Jest dość ciekawe.

no efektowne jest ;)

No też mówię, że jest efektowne ;P Dobra, już się nie udzielam, żeby nie było :)

wygląda cool :)

Dobra, już przestańcie się czepiać :P

Ale w założeniu chodziło chyba o efektowne :) A czy efektywne to nie wiem - nie przeglądałem ;)

W zasadzie to powinno być i efektywne i efektowne :)

Chyba "efektowne" menu :>