Programowanie w języku Delphi » Gotowce

Efektywne menu

  • 2005-12-29 15:41
  • 14 komentarzy
  • 1610 odsłon
  • Oceń ten tekst jako pierwszy

<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:

<center>{{Image:menu_glowne.png}}

{{Image:menu_dodatkowe.png}}</center>
<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>
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>
{$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>
  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>
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>
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 Caption oraz w Hint 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>

Malowanie elementu menu


<justify>Zostało nam już tylko obsłużyć rysowanie elementów menu. Posłużymy się do tego drugim zdarzeniem, a mianowicie OnDrawItem.</justify>
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>
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).

{{File: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

kosoft 2006-04-24 17:00

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

RedSand 2006-04-01 21:08

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

zolw17 2006-03-20 19:03

No i gdzie to jest do pobrania??

-=JaCkObS=- 2006-01-23 11:07

Wychodzi bardzo "efektywny" efekt :D

Sheitar 2006-01-08 22:39

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

-=JaCkObS=- 2006-01-07 10:54

Bardzo fajny art :D

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

krzysiup94 2005-12-31 11:50

Działa. Jest dość ciekawe.

SebaZ 2005-12-30 10:43

no efektowne jest ;)

brodny 2005-12-30 10:00

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

Bełdzio 2005-12-29 22:11

wygląda cool :)

Sheitar 2005-12-29 18:19

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

brodny 2005-12-29 17:11

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

Sheitar 2005-12-29 15:31

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

brodny 2005-12-29 15:25

Chyba "efektowne" menu :>