Multimedia w Delphi

Adam Boduch

Zastanawiasz się pewnie, czy to nie za wcześnie dla Ciebie na tak poważne rzeczy. Nie martw się. W Delphi w prosty sposób można operować grafiką, tworzyć animacje itp. Na słowo ?multimedia? składa się programowanie grafiki, muzyki i animacji. Tym właśnie zajmiemy się w tym rozdziale. Z doświadczenia wiem, że jest to ciekawy rozdział dla Ciebie, gdyż nauczysz się jak uzyskiwać w Delphi fajne efekty.

Klasa TCanvas

Delphi udostępnia Ci klasę TCanvas dzięki której w łatwy sposób będziesz mógł operować grafiką. Zawiera ona gotowe procedury do rysowania np. różnych figur geometrycznych, tekstu itp. Przy pomocy tej klasy możesz rysować na formularzu, na pulpicie, w pamięci lub nawet na drukarce. Nie musisz koniecznie korzystać z tej klasy, ale bej jej wykorzystania rysowanie staje się... powiedzmy nieco trudniejsze.

Zaczynamy!

Za chwilę napiszesz pierwszy program z wykorzystaniem tej klasy. Nie będzie on robił nic nadzwyczajnego. Narysuje tylko pewien tekst. To wszystko. Otwórz więc nowy projekt, nazwij go jak chcesz. Umieść na formie przycisk i wygeneruj jego zdarzenie OnClick. Procedura obsługi tego zdarzenia powinna wyglądać tak:
procedure TMainForm.Button1Click(Sender: TObject);
begin
  Canvas.TextOut(10, 10, 'Delphi. Czarna księga');
end;

Chcąc skorzystać z klasy TCanvas nie musisz ponownie tworzyć zmiennej, która wskazywałaby na ten obiekt - jest to już za Ciebie zrobione. Spójrz więc na powyższy przykład. Następuje w nim wykonanie polecenia 'TextOut'. Ta procedura zawierać musi trzy parametry. Pierwsze dwa to położenie X i Y na formularzu. W tym punkcie (tj. X = 10 i Y = 10) zostanie namalowany tekst. Ostatni parametr tej procedury to oczywiście tekst, który ma zostać wyświetlony. Ok, możesz teraz uruchomić program i zaobserwować jego działanie. Po naciśnięciu przycisku pokaże się na formie tekst. Spróbuj teraz zminimalizować program lub zasłonić okno innym. Po ponownym wysunięciu programu na wierzch tekst znikł! Windows po każdym odsłonięciu okna aplikacji odświeża wszystko co zostało na niej namalowane. Są jednak sposoby na uniknięcie tego - o tym w dalszej części rozdziału.
Oto zmodyfikowana wersja poprzedniego programu - spójrz:

  Canvas.Font.Color := clBlue;  // zmiana czcionki na niebieską
  Canvas.TextOut(10, 10, 'Delphi. '); // namalowanie pierwszego członu
  Canvas.Font.Color := clBlack; // zmiana koloru na czarny
  Canvas.Font.Style := [fsBold]; // pogrubienie czcionki
  Canvas.TextOut(Canvas.TextWidth('Delphi. '), 10, 'Czarna księga'); // namalowanie drugiego członu

Tym razem jest to coś trudniejszego. Po wklepaniu tego kodu i uruchomieniu tekst będzie wyświetlany w dwóch kolorach - czarnym i białym. W pierwszym wierszu odwołujemy się do obiektu TFont (o tym czytaj w następnym podpunkcie), a konkretniej do właściwości Color tego obiektu. Powoduje to zmianę koloru czcionki na niebieski. W Delphi każdy kolor poprzedzony jest członem 'cl'. Nazwy kolorów jednak są zaczerpnięte z języka angielskiego więc jeżeli znasz ten język to praktycznie nie będziesz miał problemów z ustawieniem odpowiedniej czcionki. Bo jaki to np. kolor: 'clRed'? Oczywiście czerwony. A 'clGreen'? Oczywiście zielony. To łatwe.

W Delphi 6 oprócz standardowych kolorów, które był w wersji poprzedniej dodano parę nowych: clMoneyGreen, clSkyBlue, clCream, clMedGray. Sprawdź jakie działanie da narysowanie czegoś przy pomocy tych kolorów.

Po narysowaniu pierwszego członu (jedynie tekst 'Delphi') następuje przywrócenie czarnej czcionki i pogrubienie jej (właściwość Style obiektu TFont). Szczegółowych informacji na ten temat dowiesz się w następnym podpunkcie. Na końcu narysowanie obok napisu 'Delphi' drugiego członu, czyli 'Czarna księga'. Zwróć jednak uwagę na wywołanie na początku funkcji TextWidth. Otóż funkcja ta podaje szerokość tekstu wpisanego jako jej parametr. Szerokość ta podawana jest w pikselach. Istnieje także funkcja TextHeight, która jak łatwo się domyśleć zwraca wysokość tekstu.

Obiekt TFont

W poprzednim przykładzie skorzystałem z właściwości tej klasy. Otóż służy ona do ustawień czcionki stosowanej przez w naszym wypadku klasę TCanvas. W taki sam sposób możesz ustawić również czcionkę dla wszystkich obiektów wizualnych typu TButton. Tak się składa, że klasa ta przydzielona jest praktycznie do wszystkich obiektów w Delphi. W wypadku komponentów nie musisz pisać kodu - wystarczy, że zmienisz odpowiednie ustawienia w Inspektorze Obiektów.

Tabela 1

WłaściwośćKrótki opis
ColorOkreśla kolor czcionki
NameOkreśla krój czcionki.
SizeWielkość stosowanej czcionki w punktach (np. 10, 12, 14 )
HeightWielkość czcionki w pikselach.
PitchMoże przybrać trzy wartości określające czcionkę: fpDefault (domyślna wartość), fpFixed ( wszystkie znaki mają równą szerokość), fpVariable (znaki mają różne szerokości)
StyleStyl czcionki: fsBold (pogrubiony), fsItalic (przekreślony), fsUnderline (podreślenie), fsStrikeOut (przekreślenie).
</p>

Tu jednak moja mała uwaga. Jeżeli chcesz, aby tekst został pogrubiony możesz napisać tak:

Font.Style := [fsBold];

W takim jednak wypadku tekst zostanie TYLKO pogrubiony. Czyli jeżeli wcześniej był pisany kursywą to teraz kursywa zostanie odznaczona, a tekst będzie tylko pogrubiony. Nam nie o to chodzi, gdyż chcemy, aby tekst był pogrubiony, ale także żeby zachowane były wcześniejsze style. Trzeba wiec napisać to tak:

Font.Style := Font.Style + [fsBold];

Tutaj zastosowałem operator +. Jeżeli jednak chciałbyś, aby od stylu tekstu odjęty został styl pogrubiania możesz napisać tak:

Font.Style := Font.Style - [fsBold];

Jeżeli chcesz zresetować wszystkie style (wszystko zostanie odznaczone) to piszesz coś takiego:

Font.Style := [];

Przedstawię Wam teraz program, który chyba dobrze zaprezentuje sposoby korzystania z klasy TFont. Będzie on po prostu prezentował tekst napisany różnymi stylami (czcionka, krój itp.) wybranymi przez użytkownika.

Formularz główny programu zawiera kilka komponentów (rysunek 1.). Przede wszystkim komponent (lista rozwijalna) TComboBox do wyboru kroju czcionki; komponent TEdit zawiera rozmiar czcionki. W dolnym lewym rogu zawarte są komponenty TCheckBox po które określają wygląd czcionki (właściwość TFont.Style). Natomiast komponent do wyboru koloru nazywa się TColorGrid i jest umieszczony Samples. Służy on jedynie do graficznego przedstawienia kolorów. To tyle jeśli chodzi o interfejs. Właściwie cały program wykonywać będzie jedna tylko procedura 'SetFont'. Zadeklarować ją musisz w sekcji published:

Published    
  procedure SetFont(Sender: TObject);    
end;
5.1.jpg Rysunek 1

Teraz musisz ustawić zdarzenie OnClick każdego komponentu na SetFont. W ten sposób po naciśnięciu każdego komponentu wykonywać się będzie jedna procedura SetFont. A jej deklaracja przedstawia się następująco:

procedure TMainForm.SetFont(Sender: TObject);
begin
  with Canvas do
  begin
    Font.Color := ColorGrid.ForegroundColor; // użyj kolory wybranego w komponencie
    Font.Size := StrToInt(edtFontSize.Text); // użyj wartości wpisanej w komponencie
    Font.Name := cbFontName.Text; // czcionka taka jaką zaznaczył w komponencie użytkownik
  {
    jeżeli zdarzenie pochodzi z komponentu typu TCheckBox to sprawdź, czy
    został on odznaczony, czy dopiero zaznaczony. Jeżeli został zaznaczony to
    do stylu wyświetlania czcionki dodaj ten z zaznaczonego komponentu.
  }
    if TCheckBox(Sender).Checked then
      Font.Style := Font.Style + [TFontStyle(TCheckBox(Sender).Tag)]
    else Font.Style := Font.Style - [TFontStyle(TCheckBox(Sender).Tag)];
    Canvas.TextOut(50, 50, ExText);
  end;
end;

Już spieszę z wyjaśnieniami. Wszystkie komponenty typu TCheckBox zawierają właściwość Checked. Jeżeli komponent jest zaznaczony (widnieje na nim taki "ptaszek") to ta właściwość ma wartość True. I teraz, aby ustawić odpowiedni styl tekstu musiałbym porównać każdy komponent (czy jest on zaznaczony) i odpowiednio zareagować ustawiając styl. Nie jest to dobre rozwiązanie, gdyż jestem leniwy, a takie coś wymagałoby wiele linii kodu. Ustawienia tekstu (pogrubiony, kursywa itp.) są w rzeczywistości zbiorem. (o zbiorach mówiliśmy w rozdziale 3) TFontStyle. W Delphi jednak przy użyciu rzutowania można zrobić coś takiego:

  Canvas.Font.Style := [TFontStyle(3)];

Jak widzisz tutaj zamiast napisać fsStrikeOut zastosowałem rzutowanie TFontStyle na cyfrę 3. Cyfra ta albowiem odpowiada elementowi fsStrikeOut w zbiorze TFontStyle. Tak, tak, Delphi jest na tyle elastyczne, że takie coś również można zastosować. I ja to wykorzystałem w tym programie. Otóż każdy komponent zawiera właściwość 'Tag'. Właściwość ta nie jest przez Delphi wogule wykorzystywana, a służy jedynie programiście - możesz ją dowolnie wykorzystywać do swoich celów, i ja z tego skorzystałem. Nadałem bowiem komponentowi cbBold właściwość Tag = 0; komponentowi cbItalic właściwość Tag = 1 itd. Następnie wykorzystałem to w procedurze SetFont - dokładnie w tym miejscu:

if TCheckBox(Sender).Checked then
      Font.Style := Font.Style + [TFontStyle(TCheckBox(Sender).Tag)]
    else Font.Style := Font.Style - [TFontStyle(TCheckBox(Sender).Tag)];

Pierwszy wiersz to sprawdzenie czy (jeżeli) zdarzenie pochodzi z komponentu typu TCheckBox to i, czy właściwość Checked tego komponentu jest ustawiona na TRUE. Jeżeli tak to z tego komponentu odczytywana jest właściwość Tag i na tej podstawie do tekstu dodawany jest kolejny styl. Może to proste nie jest, ale na pewno oszczędza czas i program jest krótszy...

Pozostałe instrukcje zawarte w programie nie powinny chyba sprawić kłopotu. Pierwszy wiersz procedury SetFont ustawia kolor wg. koloru odczytanego z komponentu ColorGrid. Dalej wszystko jest odczytywane z komponentów i na tej podstawie możemy narysować tekst. A tajemnicze słowo 'ExText' to zwykła stała:

const
  ExText = 'Delphi';

Byłbym zapomniał o pewnej komendzie z procedury OnCreate. Wygeneruj bowiem zdarzenie OnCreate dla formularza i wpisz taką linię kodu:

procedure TMainForm.FormCreate(Sender: TObject);
begin
{ do komponentu przypisz liste czcionek dostępnych w systemie }
  cbFontName.Items := Screen.Fonts;
end;

'Screen.Fonts' zawiera listę wszystkich dostępnych czcionek w systemie. Ta lista jest w tym momencie dopisywana komponentowi cbFontName.

Główne metody klasy TCanvas

W tym podpunkcie zajmiemy się bardziej szczegółowo rysowaniem figur geometrycznych w Delphi. Nie jest to trudne, gdyż są oczywiście do tego gotowe polecenia. W nich wystarczy tylko podać współrzędne oznaczające figurę. I tak np. jeżeli chcesz narysować kwadrat to skorzystasz z procedury 'Rectangle':
  Canvas.Rectangle(50, 50, 150, 150);

W tym wypadku na ekranie pojawi się kwadrat. Pierwsze dwa parametry to współrzędne lewego górnego rogu prostokąta, a dwa ostatnie to współrzędne prawego, dolnego rogu. W tabeli poniżej przedstawiam Wam nazwy poleceń służące do rysowania figur.

Tabela 2

MetodaKrótki opis
ArcRysuje łuk.
ChordRysuje zamkniętą figurę (wielokąt)
EllipseRysuje elipsę.
MoveToZawiera dwa parametry. Ustawia pozycje początku rysowania. Stosowana w połączeniu z LineTo.
LineToRysuje linię.
PieRysuje wycinek koła
PolygonRysuje figurę na podstawie tablicy punktów przekazanej jako parametr.
PolylineRysuje linię łamaną
RectangleRysuje prostokąt.

Nie będę tutaj zamieszczał przykładów zastosowań tych funkcji bo to nie ma sensu. Jeżeli nie znasz dokładnej liczby parametrów danego polecenia możesz poczytać pomoc do Delphi. Przedstawię jedynie przykład rysowania linii gdyż to może sprawiać problemy. Jeżeli napiszesz coś takiego:

Canvas.LineTo(200, 200);

To Delphi namaluje linię pochodzącą z punktu 0,0 czyli z lewego, górnego rogu ekranu. Parametry tej procedury oznaczają jedynie miejsce zakończenia linii, czyli punkt 200, 200. I dlatego właśnie tą procedurę stosuje się wraz z poleceniem MoveTo. Bowiem ono ustawia początek rysowania - np:

Canvas.MoveTo(100, 100);
  Canvas.LineTo(200, 200);

W tym wypadku linia będzie miała swój początek w punkcie 100,100 - tam ustawiliśmy początek przy pomocy polecenia MoveTo. A koniec linia będzie miała w punkcie 200,200.

Pióra

Ta właściwość klasy TCanvas określa styl rysowanych linii. Dzięki tej właściwości możemy określić grubość krawędzi np. prostokąta, styl rysowanej linii (przerywana, ciągła itp.), kolor itp. Oto właściwości klasy TPen:

Tabela 3

WłaściwośćKrótki opis
ColorOkreśla po prostu kolor rysowanej linii
StyleWskazuje na typ TPenStyle i określa styl rysowanej linii.
WidthSzerokość rysowanej linii.
ModeOkreśla sposób rysowanej linii w stosunku do np. koloru tła.
</p>

Jeżeli więc chcesz narysować kwadrat, ale o nieco grubszej linii to piszesz tak:

Canvas.Pen.Width := 10;
Canvas.Rectangle(50, 50, 200, 200);

Szerokość linii będzie w tym wypadku wynosiła 10. Styl rysowanych linii może być następujący:

Tabela 4

psSolidLinia ciągła
psDashLinia przerywana
psDotKropki
psDashDotNaprzemian kropki i kreski
psDashDotDotNaprzemian kreska i dwie kropki.
psClearBez obrysowania.
</p>

Żeby lepiej zaprezentować działania tych styli wykonaj prosty programik. Umieść na formularzu komponent TRadioGroup. Może on zawierać pozycje do zaznaczenia, tyle, że tylko jedna pozycja może być zaznaczona. Po umieszczeniu w Inspektorze Obiektów masz właściwość Items. Zaznacz je. Wtedy pojawi się po prawej stronie mały kwadracik z trzykropkiem. Po jego naciśnięciu oczom Twoim ukarze się okienko edycyjne. W nim możesz wpisać pozycje, które będą widniały w komponencie. Uwaga! Pozycje te muszą znajdować się jedna po drugiej. Ja w tej liście wpisałem takie pozycje:

psSolid
psDash
psDot
psDashDot
psDashDotDot
psClear
psInsideFrame

Teraz musisz wygenerować zdarzenie OnClick komponentu. Oto kod:

Canvas.Pen.Style := TPenStyle(gbStyle.ItemIndex);
  Canvas.Rectangle(80, 10, 250, 150);

Ta procedura zdarzeniowa ma tylko dwa wiersze. Pierwsza z nich ustawia styl pióra wg. pozycji zaznaczonej w komponencie. Tak jak w poprzednich przykładach tutaj zastosowałem rzutowanie, aby określić styl pióra. A w drugim wierszu wg. ustawień pióra rysowany jest prostokąt.

Pędzle

Pędzel natomiast służy do ustawienia stylu wypełniania dla figury. Podobnie jak w przypadku pióra stosować można różne style. Można także jako obszar wypełniania ustawić bitmapę, ale tym zajmiemy się później. Dla pędzla ustawić możesz kolor jak i styl:

Tabela 5

StylKrótki opis
bsSolidCałkowite wypełnienie
bsCrossSiateczka
bsClearWypełnienie przeźroczyste
bsDiagCrossTakże siatka przecinająca się pod kątem prostym
bsHorizontal Linie poziome
bsVerticalLinie pionowe
bsBDiagonal Ukośne z lewego dolnego narożnika do górnego, prawego
bsFDiagonal Ukośne z lewego, górnego narożnika do dolnego, prawego
</p>

Nic więc nie stoi na przeszkodzie napisania czegoś takiego:

Canvas.Pen.Width := 5;
Canvas.Pen.Color := clBlue;
Canvas.Brush.Style := bsBDiagonal;
Canvas.Rectangle(80, 10, 250, 150);

Dozwolone są więc wszelkie kombinacje styli. Jak widzisz programowanie przy użyciu klasy TCanvas wcale nie jest trudne! A przekonasz się bardziej o tym patrząc na przykłady wyświetlania obrazków.

Bitmapy

Delphi dla celów ładowania do pamięci bitmapy udostępnił klasę TBitmap. Jej operowania jest także bardzo proste. Do załadowania obrazka potrzebne są zaledwie trzy linie - do jego wyświetlenia na formie - kolejna. I to właściwie wszystko. Spójrz na przykład ładowania bitmapy:
var
  Bitmap : TBitmap; // deklaracja nowego typu
begin
  Bitmap := TBitmap.Create; // stworzenie klasy
  Bitmap.LoadFromFile('C:\\Moje dokumenty\\reyalp.bmp'); // załadowanie bitmapy z dysku
  Bitmap.Free; // zwolnienie zasobów

I to właściwie wszystko! Lecz po wykonaniu takiego kodu na ekranie nie ujrzysz żadnych efektów bo bitmapa zostanie załadowana tylko do pamięci! Równie dobrze przed wyświetleniem bitmapy na ekranie możesz ją edytować w pamięci komputera. Takie załadowanie bitmapy do pamięci nazywamy bitmapą pamięciową. W celu wyświetlenia bitmapy na ekranie skorzystaj z klasy TCanvas i jej polecenia - Draw.

var
  Bitmap : TBitmap; // deklaracja nowego typu
begin
  Bitmap := TBitmap.Create; // stworzenie klasy
  Bitmap.LoadFromFile('C:\\Moje dokumenty\\reyalp.bmp'); // załadowanie bitmapy z dysku
  { po załadowaniu bitmapy do pamięci wyświetlamy ją na ekranie }
  Canvas.Draw(10, 10, Bitmap);
  Bitmap.Free; // zwolnienie zasobów

Polecenie Draw z klasy TCanvas powoduje wyświetlenie obrazu na formie. Pierwsze dwa parametry procedury to pozycje X i Y w których bitmapa ma być wyświetlona. Ostatni parametr to nazwa zmiennej, która wskazuje na bitmapę. Bitmapę taką również można ustawić jako tło dla pędzla. Spójrz na poniższy przykład:

var
  Bitmap : TBitmap; // deklaracja nowego typu
begin
  Bitmap := TBitmap.Create; // stworzenie klasy
  Bitmap.LoadFromFile('C:\\Moje dokumenty\\quadrill.bmp'); // załadowanie bitmapy z dysku
  { po załadowaniu bitmapy do pamięci ustawiamy bitmapę jako tło dla pędzla }
  Canvas.Brush.Bitmap := Bitmap;
  Canvas.Pen.Style := psClear;
  Canvas.Ellipse(10, 10, 500, 500);
  Bitmap.Free; // zwolnienie zasobów

Ale tutaj na programistę czyha pułapka! Jeżeli bitmapa będzie w takim wypadku większa niż 8x8 pikseli to program jako tło do rysowania weźmie tylko fragment bitmapy o wielkości 8x8 pikseli.

Dotychczas korzystając z klasy TCanvas wykonywaliśmy operacje tylko na formularzu. Takie operacje można wykonywać również dla bitmapy pamięciowej. Bitmapa albowiem ma także swoją własną klasę TCanvas tak więc można odwoływać się bezpośrednio do niej i np. rysować po bitmapie pamięciowej.

Na przykład można najpierw załadować bitmapę do pamięci, dokonać edycji i następnie wyświetlić. W podanym niżej przykładzie na bitmapie pamięciowej wyświetlany jest najpierw tekst, a dopiero później sama bitmapa jest wyświetlona na ekranie.

var
  Bitmap : TBitmap; // deklaracja nowego typu
begin
  Bitmap := TBitmap.Create; // stworzenie klasy
  Bitmap.LoadFromFile('C:\\Moje dokumenty\\reyalp.bmp'); // załadowanie bitmapy z dysku
 { najpierw dokonujemy edycji bitmapy w pamięci, a później wyświetlamy ją na formie }
  Bitmap.Canvas.TextOut(10, 10, '4programmers.net');
  Canvas.Draw(10, 10, bitmap);
  Bitmap.Free; // zwolnienie zasobów

Nic nie szkodzi, aby później tak zmodyfikowaną bitmapę zapisać na dysku. Uważaj jednak! Wszystkie działania jakie planujesz na bitmapie muszą być wykonane przed wywołaniem procedury 'Free' klasy. W przeciwnym wypadku na ekranie pojawi Ci się błąd Access Denied.
Zapisywanie bitmapy jest równie proste jak jej ładowanie. Do tego służy procedura SaveToFile. Jako parametr tej procedury wystarczy podać nazwę pliku, a konkretniej ścieżkę, gdzie plik ma zostać zapisany.

  Bitmap.SaveToFile('C:\\Moje dokumenty\\reyalp_kopia.bmp');

Pliki JPEG

Bitmapy są OK, ale w obecnych czasach wychodzą one z użycia. Powód jest prosty - zbyt duży rozmiar pliku graficznego. Pliki JPEG natomiast potrafią taki plik całkiem nieźle skompresować nie tracąc przy tym zbytniej jakości. Do tej książki dołączony jest program, który kiedyś napisałem. Prezentuje kompresje plików BMP do JPEG. Myślę, że jest to dobry przykład zaprezentowania klasy TJPEGImage mimo, ze sam kod źródłowy wybiega trochę w przyszłość - tzn. są tam zawarte elementy, których jeszcze w tej książce nie przerabialiśmy.

Korzystanie z plików JPG jest równie proste jak w przypadku BMP. Naprawdę. Delphi udostępnia do tego celu wygodną w użyciu klasę TJPEGImage. Wystarczy więc dodać do elementów uses słowo 'jpeg' aby spokojnie korzystać z dobrodziejstw plików jpg.

var
  JPG : TJPEGImage;
begin
  JPG := TJPEGImage.Create;
  JPG.LoadFromFile('C:\\Moje dokumenty\\reyalp.jpg');
  Canvas.Draw(10, 10, JPG);
  JPG.Free;

Jak więc w tym wypadku tak jak w przypadku klasy TBitmap kluczową rolę odgrywa procedura LoadFromFile, która ładuje plik z dysku. Procedura użycia tutaj jest właściwie identyczna jak w przypadku plików bmp.

Tak jak w przypadku bitmap tutaj zapisywanie plików następuje także po wykonaniu polecenia SaveToFile. Nim to jednak nastąpi możesz ustalić stopień kompresji, rodzaj itp. Oto kawałek kodu:

var
  JPG : TJPEGImage;
begin
  JPG := TJPEGImage.Create;
  JPG.LoadFromFile('C:\\Moje dokumenty\\reyalp.jpg');
  Canvas.Draw(10, 10, JPG);

  JPG.CompressionQuality := 20;
  JPG.Compress;
  JPG.SaveToFile('C:\\Moje dokumenty\\reyalp_kopia.jpg');
  JPG.Free;

end;

Kluczową rolę podczas kompresji pełni tutaj właściwość 'CompressionQuality' klasy TJPEGImage. Określa ona bowiem stopień kompresji. Zakres możesz ustalić od 0 to 100, gdzie 100 to jakość najlepsza, ale także rozmiar pliku będzie największy. W naszym wypadku, gdy ustawiłem na 20 jakość nie będzie najlepsza, ale rozmiar się zmniejszy. A sama kompresja następuje po wykonaniu polecenia Compress. Na samym końcu następuje zapisanie pliku za pomocą wspomnianego wcześniej polecenia SaveToFile.

Rysowanie tekstu

Klasa TCanvas do tego przygotowała procedurę TextOut, którą już w tym rozdziale używałeś. Pierwszymi dwoma parametrami jest położenie początkowe tekstu (X i Y). Ostatni parametr to tekst, który ma zostać wyświetlony. Przy czym wcześniej można ustawić kolory czcionki, pędzla - np:
Canvas.Brush.Style := bsClear; // tło jako przeźroczyste
Canvas.Font.Name := 'Arial Black';
Canvas.TextOut(100, 100, 'Delphi');

Istnieje jeszcze jedna funkcja TextRect. Ma ona inne zastosowanie. Otóż wypisuje tekst, ale jest on ograniczony prostokątem. Także jeżeli tekst będzie dłuższy i będzie przekraczał szerokość i wysokość prostokąta zostanie skrócony. Pierwszy jego parametr to zmienna typu TRect. Jest to specjalny typ danych, który musi zawierać cztery elementy określające pozycje: lewa, góra, prawa, dół. Czyli można zadeklarować ten typ tak:

var
  R : TREct;
begin
  R := Rect(10, 10, 100, 50); // określ prostokąt w ramach którego wyświetlany będzie tekst

W nawiasie musisz wypisać cztery parametry. Tak więc w tym wypadku prostokąt będzie miał szerokość 100 px; wysokość 50, a lewy, górny jego róg będzie się znajdował w punkcie 10, 10.

procedure TForm1.Button1Click(Sender: TObject);
var
  R : TREct;
begin
  R := Rect(10, 10, 100, 50); // określ prostokąt w ramach którego wyświetlany będzie tekst
  Canvas.TextRect(R, 10, 10, 'To jest długi tekst, który może się nie zmieścić');
  // tło prostokąta na przeźroczyste
  Canvas.Brush.Style := bsClear;
  Canvas.Rectangle(R);
end;

Zwróć uwagę na przed ostatni wiersz, czyli instrukcje Rectangle. Podany w niej parametr jest typu TRect, a nie jak wcześniej pisałem cztery parametry typu Integer. Po prostu procedura ta jest opatrzona dyrektywą overload (o której mówiłem w rozdziale drugim). I to właściwie wszystkie funkcje do operowania na tekście, które są udostępnione w klasie TCanvas.

Natomiast WinAPI (Windows Application Programming Interface) zawiera bardzo użyteczną i rozbudowaną funkcję DrawText, lecz jej korzystanie jest nieco trudniejsze od klasy TCanvas.

Uchwyty

Każde okno w systemie Windows, obojętnie jaka kontrolka posiada tzw. uchwyt. Jest to zmienna identyfikująca dane okno w systemie. Podczas korzystania z polecenia DrawText będziesz musiał podać ten właśnie uchwyt okna. W naszym wypadku będzie to uchwyt formularza, prawda? Bo tam właśnie chcemy narysować swój tekst. Budowa funkcji DrawText przedstawia się następująco:
int DrawText(

    HDC hDC,	
    LPCTSTR lpString,	
    int nCount,	
    LPRECT lpRect,	 
    UINT uFormat 	
   );

Oczywiście jest to nagłówek C++ gdyż jest to funkcja API. Pierwszy parametr tej funkcji to właśnie uchwyt. W tym miejscu podać będziemy musieli uchwyt nie do formy, ale uchwyt do klasy TCanvas. Drugi parametr to tekst, który ma być wyświetlony na ekranie (wartość -1 = wszystkie znaki). Trzeci określa liczbę znaków, które będą wyświetlone. Przed ostatni to wskazanie na zmienną TRect, w której to obrębie ma być wpisany tekst. Ostatni parametr to tzw. flagi, czyli dodatkowe parametry, które mają określać wpisany tekst. Spójrz na poniższy przykład:

procedure TForm1.Button1Click(Sender: TObject);
var
  R: TRect;
begin
  R := Rect(10, 10, 100, 40);
  Canvas.Rectangle(R);
  DrawText(Canvas.Handle, 'Delphi is good', -1, R, DT_CENTER or DT_VCENTER or DT_SINGLELINE);
end;

Jeżeli uruchomisz taki program to na formularzu narysowany zostanie prostokąt, a w jego wnętrzu tekst, który jest wyśrodkowały w poziomie jak i w pionie. Zajmijmy się jednak naszą funkcją... Pierwszy parametr to jak już mówiłem uchwyt. Przyzwyczaj się, że każdy uchwyt jest prezentowany za pomocą właściwości Handle. Tak więc w przypadku, gdy napiszesz:

Button.Handle;

To odwołasz się do uchwytu komponentu Button itd., itp. Właściwie kluczowe w tej procedurze są flagi. W tym wypadku zastosowałem trzy. 'DT_CENTER' oznacza iż tekst będzie wyśrodkowany w poziomie. 'DT_VCENTER' oznacza natomiast, że tekst będzie wyśrodkowany w pionie względem zmiennej 'R' (typ TRect). Ostatnia flaga oznacza iż tekst wpisany ma być w jednej linii. Jeżeli nie byłoby tej flagi to tekst zostałby zawinięty w przypadku, gdy jest dłuższy niż szerokość prostokąta, w którym jest wpisany.

Funkcja DrawText ma wiele ciekawych flag - jeżeli chcesz się o niej dowiedzieć czegoś więcej to w edytorze kodu kliknij podwójnie na nazwę 'DrawText'. Wtedy ten wyraz zostanie podświetlony. Po naciśnięciu klawisza F1 wyświetli Ci się pomoc na temat tej funkcji. Ciekawą flagą jest 'DT_END_ELLIPSIS'. Jeżeli bowiem tekst nie będzie się mieścił w obrębie "terenu" zadeklarowanego w zmiennej typu TRect to zostanie skrócony i na sam jego koniec dodany zostanie trzykropek.

Odtwarzanie dźwięków

Odtwarzanie za pomocą polecenia PlaySound

Najpierw zajmijmy się plikami WAV (od ang. waveform ( fala )). Jest to popularny format, ale ma tendencje do przyjmowania dużych rozmiarów. Nie jest na pewno tak popularny jak pliki mp3. Jeżeli chcesz odtwarzać dźwięki WAV musisz skorzystać z funkcji API i modułu MMSystem. Dodaj więc te nazwę do listy uses.

Oto przykład dotwarzania dźwięku WAV:

uses MMSystem;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog.Execute then
    PlaySound(PChar(OpenDialog.FileName), 0, SND_FILENAME);
end;

Jak widzisz skorzystałem tutaj ze znanego Ci już komponentu - OpenDialog. Zajmiemy się jednak budowa funkcji PlaySound. Pierwszy parametr to nazwa pliku (ścieżka) pliku muzycznego. Drugi parametr jest na razie niepotrzebny - w przypadku, gdy odtwarzamy dzwięk z zasobów będziemy tutaj musieli coś wpisać, ale jeszcze nie teraz więc wpisz w to miejsce 0. Trzeci natomiast parametr to flaga. Możliwe są do zastosowania następujące:

Tabela 6

StylKrótki opis
SND_ALIASpierwszy parametr określa pozycje w rejestrze. Windows posiada tzw. dźwięki systemowe, których wartości znajdują się w rejestrze i także możesz skorzystać z tego.
SND_FILENAMEPierwszy parametr ma być ścieżką pliku.
SND_NOWAITPolecenie nakazuje zignorowanie odtwarzania w przypadku, gdy jest odtwarzane coś innego.
SND_NOSTOPTa flaga powoduje uruchomienie odtworzenia, w przypadku, gdy nie jest uruchomiony żaden inny utwór.
SND_RESOURCEW przypadku tej flagi utwór będzie odtwarzany z zasobów.
SND_LOOPTylko wraz z SND_ASYNC. Powoduje odtwarzanie ciągłę utworu.
SND_ASYNCPo wybraniu tej flagi odtwarzanie będzie następować w tle.
SND_NODEFAULTW przypadku braku pliku generowany jest dzwięk 'piknięcia'. Jeżeli zastosujesz tę flagę tego dźwięku nie będzie
SND_PURGE Flaga oznacza zatrzymanie odtwarzania

Oczywiście istnieje możliwość kombinacji tych flag za pomocą operatora 'or'. Wyjaśnienie należą się chyba tylko pierwszemu z wymienionych tutaj parametrów. Otóż możesz odtwarzać za pomocą polecenia PlaySound dźwięki systemowe - np. 'MailBeep'. Wypróbuj:

PlaySound('MailBeep', 0, SND_ALIAS or SND_NODEFAULT);

Zwróć uwagę, że w tym wypadku dwie flagi zostały zsumowane. Pierwsza z nich oznacza oczywiście, że dźwięk odtwarzany jest dźwiękiem systemowym. Drugi parametr oznacza iż w przypadku nie ustawienia tego dźwięku przez użytkownika nie będzie generowane standardowe piknięcie.

Komponent TMediaPlayer

Bawienie się funkcjami API nie ma zbytnio sensu. Bo co jeśli chcemy przewinąć, spauzować dźwięk? Przyznam się, że zabawa API przy odtwarzaniu dzwięków nie jest prosta. Bo oto przykładowy kod, który odpowiada tylko (!) za zatrzymanie odtwarzania:
procedure Close; stdcall;
var
  Gen : TMCI_Generic_Parms;
begin
{
  Zamknij - przestan odtwarzac dzwiek.
}
  FFlags := mci_Notify;
  Gen.dwCallback := FHandle;
  FError := mciSendCommand(FDeviceID, mci_Close, FFlags, Longint(@Gen));
end;

procedure Stop; stdcall;
var
  GenParm: TMCI_Generic_Parms;
begin
{
  Przestan odtwarzac dzwiek.
}
  FFlags := mci_Notify;
  GenParm.dwCallback := FHandle;
  FError := mciSendCommand(FDeviceID, mci_Stop, FFlags, Longint(@GenParm));
  Close;  // &lt;-- wywolaj procedure Close zamykajaca na "Amen" odtwarzanie dzwieku
end;

Przyznasz, że nie jest za ciekawie? Dlatego my w Delphi mamy udostępniony komponent TMediaPlayer, który w rzeczywistością jest kontrolką ActiveX - MediaPlayer 1.0

ActiveX - jest to standard rozpowszechniony przez Microsoft. Są to najkrócej mówiąc komponenty mogące być wykorzystywane przez każdy wizualny język programowania (Delphi, MS Visual C++, Builder C++).

Tak więc komponent TMediaPlayer jest w rzeczywistości programem MediaPlayer 1.0! Korzystanie z niego jest równie łatwe jak w przypadku całego VCL'a. Komponent ten znajduje się na zakładce System. W tym momencie (gdy umieściłeś komponent na formie) powinna pojawić się paleta sterowania, z przyciskami umożliwiającymi sterowanie komponentem. Jako, że te przyciski nie wyglądają zbyt ładnie ja zawsze ukrywam ten komponent zmieniając właściwość Visible na False. Odtwarzanie dzwięku przy pomocy tego komponentu może przedstawiać się następująco:

MediaPlayer.FileName := 'plik.mid'; // wybierz plik
MediaPlayer.Open; // otwarcie pliku
MediaPlayer.Play; // odtwarzanie

Korzystanie z tego komponentu jest raczej intuicyjne. Po napisaniu nazwy komponentu, postawieniu kropki i naciśnięciu Ctrl+spacja mamy listę z możliwymi do wyboru właściwościami komponentu. Bo cóż może robić procedura Stop lub Pause? To chyba oczywiste.
Komponent ten ma także możliwość odtwarzania filmów AVI:

  if OpenDialog.Execute then
  begin
    with MediaPlayer1 do
    begin
      FileName := OpenDialog.FileName;
      Open;
      Play;
    end;
  end;

Jak widzisz otwieranie filmu następuje tak samo jak w przypadku plików WAV. Tyle, że film zostanie otworzony w nowym okienku. Można to oczywiście zmienić przypisując właściwość 'Display' dla innego komponentowi.

MediaPlayer.Display := Panel1;

W tym wypadku przypisujemy tej właściwości nazwę komponentu na którym chcemy, aby film był wyświetlany. W tym wypadku film będzie wyświetlany na komponencie Panel. Lecz co w wypadku, gdy film będzie mniejszy lub większy niż rozmiar komponentu? Z tym także można sobie poradzić. Jeżeli chesz, aby film był dostosowany do rozmiaru komponentu piszesz tak:

procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog.Execute then
  begin
    with MediaPlayer1 do
    begin
      FileName := OpenDialog.FileName;
      Open;
      Display := Panel1;
      DisplayRect := Panel1.ClientRect;
      Play;
    end;
  end;
end;

I jak myślisz - co za to odpowiada? Tak, masz racje - właściwość DisplayRect. Do tej właściwości (typu TRect) przypisujemy rozmiar komponentu Panel (właściwość ClientRect tego komponentu).

Zasoby

Normalnie chcąc w programie wyświetlić bitmapę czy ikonę musisz plik ten dołączyć do programu. To znaczy nie do pliku wykonywalnego EXE, ale będzie to po prostu dodatkowy plik. Jest to pewna niedogodność bo lepiej mieć "wszystko w jednym". I to umożliwiają właśnie zasoby

Zasób - plik mogący zawierać bitmapy, ikony, kursory oraz inne pliki. Taki zasobów może następnie być włączony do pliku wykonywalnego EXE.

W zasobach możesz umieścić praktycznie wszystko, a następnie możesz to włączyć do pliku wykonywalnego EXE. Oczywiście rozmiar takiego programu będzie większy o rozmiar zasobów, ale według mnie jest to lepszy sposób niż dołączanie do programu osobnych plików.

Edytor zasobów

Do Delphi dołączony jest prosty i mały program, który umożliwia Ci korzystanie z zasobów (patrz: rysunek 2.). Przy jego pomocy możesz w łatwy sposób nie tylko włączyć do zasobów kursy, ikony, czy bitmapy, ale także je stworzyć. Lecz jak już mówiłem jest to dość prosty program i ma swoje ograniczenia. Nie można bowiem umieszczać bitmap, które mają więcej niż 255 kolorów nie mówiąc już o plikach JPG. Na początek jednak zajmiemy się tworzeniem prostych zasobów i ten program będzie w sam raz. Program ten jest programem zewnętrznym nie włączonym do Delphi. Możesz go więc uruchomić wybierając Menu Start -> Borland Delphi 6 -> Image Editor lub mając otwarte Delphi z menu Tools -> Image Editor. 5.2.jpg Rysunek 2

Oto jak przedstawia się wygląd tego programu. Po lewej stronie mamy do wyboru różne narzędzia rysowania znajdujące się praktycznie w każdym programie, czyli rysowanie tekstu, różnych figur oraz wypełnianie. Osobiście jednak nie polecałbym tego programu do tworzenia bitmap gdyż Image Editor zwyczajnie jest za prosty... Dobrze jednak się może nadawać do tworzenia kursorów.

Wybierając z menu File pozycje New mamy do wyboru stworzenie jednego z kilku dostępnych dokumentów: pliku zasobu (.res), pliku zasobu dla komponentu (.dcr - tym jednak nie będziemy się zajmować), ikony (.ico), kursor (.cur) oraz bitmapy (*.bmp).
Korzystanie z tego programu nie jest trudne, ale ja na wszelki wypadek poprowadzę Cię przez stworzenie Twojego pierwszego pliku z zasobami. Wybierz więc z menu File -> New -> Resource File. Po wybraniu tej opcji na ekranie pojawi się nowe okno jak na rysunku 3.

5.3.jpg Rysunek 3

Program w tym oknie będzie tworzył tzw. drzewa oznaczające konkretne pliki włączone do zasobu.

Kursory

Musisz wiedzieć, że możesz dopasować kursor do każdego komponentu, który jest umieszczony na formie. Możesz więc ustawić inny kursor nad komponentem Button, a inny nad pozostałą częścią formy. Możesz to sprawdzić. Mając zaznaczony komponent Button odnajdź w Inspektorze Obiektów właściwość Cursor. Z listy rozwijalnej możesz wybrać jeden z dostępnych tam kursorów wbudowanych. W takim wypadku nie musisz się on nic martwić, mam tu na myśli dołączanie kursorów do projektu - Delphi zrobi to za Ciebie. Lecz wybór w tym wypadku jest niewielki bo możemy korzystać tylko z kursorów wbudowanych. Ja pokaże Ci teraz jak korzystać z kursorów zewnętrznych - najpierw spróbujemy załadować taki kursor z pliku, a później dodamy go do zasobu.

Delphi oferuje oczywiście procedurę służącą do ładowania kursorów - LoadCursorFromFile. Po jej wywołaniu kursor zostanie załadowany z pliku. Nim to zrobisz musisz wiedzieć, że kursory wbudowane mają swoje indeksy i Ty ładując kursor z pliku także musisz przydzielić mu indeks. Kursy wbudowane mają indeksy od -22 do 0 więc ładując swój kursor musisz mu przydzielić indeks większy od 0.

  Screen.Cursors[1] := LoadCursorFromFile('c:\\ani.cur');
  Screen.Cursor := 1;

Pierwszy wiersz powoduje załadowanie kursora do tablicy kursorów. W tym wypadku przydzieliłem mu indeks nr. 1. Druga instrukcja natomiast powoduje przydzielenie kursora nr 1 co w rezultacie daje korzystanie z kursora o indeksie 1 w naszej aplikacji.

No, ale przejdźmy do zasobów. W obrębie białego pola kliknij prawym przyciskiem myszy i wybierz opcje New -> Cursor. Zauważysz, że w oknie powstały nowe pola. Kliknij na napis 'Cursor1' prawym klawiszem myszy i wybierz opcje 'Rename' (zmiana nazwy). Wpisz np. '1st_cur'. Ponieważ my nie chcemy tworzyć od nowa kursora będziemy musieli go załadować z pliku. Wybierz w tym celu opcje File -> Open i w oknie wybierz jakiś kursor. Po wybraniu w edytorze zasobów pojawi się nowe okno z kursorem, który wybrałeś. Będziesz teraz musiał go skopiować do schowka. Naciśnij Ctrl+A, aby zaznaczyć kursor, a następnie Ctrl+C, aby go skopiować. Możesz już zamknąć okno z podglądem kursora - nie będzie nam więcej potrzebne. Kliknij teraz na pozycje '1st_cur' - otworzy się okno do edycji - tutaj będziesz musiał wkleić kursor ze schowka (Ctrl+V). Zamknij okno edycji - do zasobów dodałeś właśnie kursor. Zapiszmy gdzieś ten plik - z menu File wybierz Save. Wybierz lokalizacje z jakimś projektem Delphi, a jako nazwę zasobu wpisz np: 'resource'. W miejscu które wybrałeś stworzony zostanie plik 'resource.res', który jest naszym zasobem z kursorem w środku. Teraz otwórz Delphi - napiszemy kod, który wyciągnie z tego pliku kursor i wyświetli w programie.

Przede wszystkim musisz sam plik zasobów włączyć do projektu za pomocą dyrektywy. Wpisz więc w programie (najlepiej w sekcji Interface) taką linię:

{$R RESOURCE.RES}

Powoduje ona włączenie do projektu tego pliku. Teraz już swobodnie możesz korzystać z zasobów umieszczonych w tym pliku. Wygeneruj teraz zdarzenie OnCreate formy (kod będzie wykonywany po uruchomieniu programu).

procedure TMainForm.FormCreate(Sender: TObject);
begin
{ załaduj kursor do tablicy zasobów }
  Screen.Cursors[1] := LoadCursor(hInstance, '1st_cur');
{ wyświetl kursor }
  Screen.Cursor := 1;
end;

Jak widzisz ładowanie kursora z zasobów nie różni się zbytnio od ładowania kursora z pliku. Jedyna różnica to polecenie - nie LoadCursorFromFile, ale po prostu LoadCursor. Pierwszym parametrem jest uchwyt zasobu. Uchwyt ten jest potrzebny gdyż zasoby możemy ładować nie z aplikacji, ale np. z biblioteki DLL. My jednak ładujemy kursor z zasobów, które są włączone do programu EXE i w to miejsce należy wpisać hInstance.

Ikony

Z ikonami sprawa jest podobna, a nawet prostsza. Delphi domyślnie tworzy plik z rozszerzeniem *.res, który zawiera ikonę programu EXE. Możesz to sprawdzić. Przejdź do obojętnie jakiego katalogu z projektem, a zauważysz plik o tej samej nazwie jak nazwa projektu, tyle, że ze zmienionym rozszerzeniem. Jeżeli dajesz cały projekt (wraz ze źródłami) koledze to nie musisz koniecznie dołączać tego pliku - Delphi przeżyje jeżeli go nie będzie. Wyświetli tylko błąd o jego braku i w to miejsce wstawi nowy.

Najpierw omówię ładowanie ikony z pliku. Tutaj skorzystamy z klasy TIcon VCL'a. Wcześniej bowiem przy ładowaniu kursorów wykorzystałem funkcję API - LoadCursor. Przy okazji zaprezentuje Ci nowy komponent - TImage (z palety Additional). Jest to komponent służący do wyświetlania wszelkiego rodzaju grafiki. Jest łatwy w użyciu i przeze mnie często stosowany. Posiada on również klasę TCanvas więc można na nim bezproblemowo rysować.

procedure TMainForm.btnLoadIconClick(Sender: TObject);
var
  Icon : TIcon;
begin
  Icon := TIcon.Create; // wywołanie konstruktora
  Icon.LoadFromFile('ikona.ico');  // załadowanie z pliku
  imgIcon.Picture.Icon := Icon; // wyświetlenie na komponencie TImage
  Icon.Free; // zwolnienie
end;

Klasa TIcon posiada własną procedurę LoadFromFile, która ładuje z pliku ikonę. Jak więc widzisz dzięki VCL sprawa jest znacznie ułatwiona. Zwróć uwagę, że podczas ładowania niekonieczne było podawanie pełnej ścieżki do ikony. Bez podania ścieżki Delphi będzie szukał domyślnie ikony w katalogu z programem. Kolejny wiersz odpowiada za wyświetlenie ikony na komponencie Image. Równie dobrze można by bylo ikonę namalować za pomocą funkcji Draw z klasy TCanvas, ale takie rozwiązanie poprzez komponent Image wydaje mi się lepsze.
Uwaga! Pamiętasz jak omawialiśmy klasę TCanvas? Po namalowaniu czegoś na formie, a następnie po przykryciu tej formy innym oknem namalowany tekst znikał. Stosowanie komponentu Image jest rozwiązaniem bowiem już raz namalowany na tym komponencie tekst nie zniknie:

  imgIcon.Canvas.TextOut(10, 10, 'Adam Boduch');  { imgIcon to oczywiście komponent TImage }

Możesz teraz minimalizować i zasłaniać okno, a tekst i tak się zachowa... Równie skutecznym rozwiązaniem jest malowanie po komponencie PaintBox (z palety System).

Masz jeszcze otwarty edytor zasobów, a w nim plik resource.res? Jeżeli tak to dodamy do niego jeszcze ikonę. Postępuj tak samo jak w przypadku kursora, lecz z menu wybierz oczywiście 'Icon', a nie 'Cursor'. Po tym będziesz musiał wybrać rozmiar ikony oraz ilość kolorów jaką może zawierać. Wybierz rozmiar 32x32 oraz 16 kolorów. Możesz teraz, albo namalować ikonę ręcznie, albo zrobić tak jak w przypadku kursora. Ja namalowałem ręcznie i nazwałem ikonę '1st_ico'. Teraz pozostaje już zapisanie zasobu i napisanie kodu, który by odczytał ikonę z zasobów. Niestety VCL i klasa TIcon nie posiada procedury ładującej ikonę z zasobów - trzeba skorzystać z API, ale to nie będzie trudne. Skorzystamy bowiem z funkcji LoadIcon:

procedure TMainForm.btnLoadIconClick(Sender: TObject);
var
  Icon : TIcon;
begin
  Icon := TIcon.Create;
  Icon.Handle := LoadIcon(hInstance, '1st_ico');   // załaduj ikonę z zasobów
  imgIcon.Picture.Icon := Icon;
  Icon.Free;
end;

W tym wypadku przypisałem rezultat wykonania funkcji LoadIcon do uchwytu zmiennej Icon. Jako, że funkcja LoadIcon zwraca rezultat w postaci typu HICON, który jest typem API nie możemy przypisać go do klasy TIcon więc przypisałem go do uchwytu klasy TIcon. Taki kod powinien pięknie się skompilować i wyświetlić ikonę w komponencie TImage.

Bitmapy

O bitmapach już mówiłem wcześniej w tym rozdziale. Teraz jednak włączymy bitmapę do zasobów. Wcześniej, aby wyświetlić bitmapę na formularzu trzeba się było napisać (co prawda nie tak dużo, ale zawsze...). Korzystając z komponentu Image i chcąc załadować bitmapę z dysku wystarczy jedna (!) linia kodu: Image.Picture.LoadFromFile('bitmapa.bmp'); A ładowanie bitmapy z zasobu jest równie proste, gdyż i tym razem komponent Image przychodzi nam z pomocą - służy do tego procedura LoadFromResourceName. Ok, ale najpierw stwórz w zasobach jakaś bitmapę. Mam nadzieje, że teraz już wiesz jak to się robi i zbędne są jakiekolwiek objaśnienia. Jedynie ładowanie bitmapy powinno wyglądać tak:
  imgBitmap.Picture.Bitmap.LoadFromResourceName(hInstance, '1st_bitmap');

Tutaj chyba należą Ci się jakieś wyjaśnienia. bo te polecenia są dla Ciebie pewnie niezbyt zrozumiałe. Raz odwołuje się do Picture - teraz do Bitmap, wcześniej do Icon.

5.4.jpg Rysunek 4

Tak więc komponent Image zawiera w "sobie" odwołania do różnych klas. Rysunek 4. przedstawia hierarchię wartości klas. Nadrzędną jest klasa TImage (komponent). Następnie TPicture, która zawiera odwołania do klas TBitmap (wyświetlanie bitmap), TIcon (ikony), TMetafile (pliki metafile - *.wmf, *.emf). Mam nadzieje, że dzięki temu rysunkowi wiele rzeczy się wyjaśniło.

Ręczne tworzenie zasobów

Jak już pewnie zauważyłeś edytor zasobów nie ma takich funkcji, które często są nam potrzebne - nie potrafi bowiem umieszczać plików JPG lub nawet bitmap o większej ilości kolorów. Jest jednak na to sposób. Razem z Delphi masz dołączony program brcc32.exe, który powinien znajdować się w katalogu bin. Jest to program DOS, który potrafi skompilować pliki do postaci RES. Tak wogule to do tworzenia zasobów bardzo pomocniczy jest Visual C++ Microsoftu. Jednakże jest on płatny więc zajmiemy się ręcznym pisaniem skryptów. Tak, tak, pliki takie będą miały rozszerzenie *.rc.

Ładowanie plików JPG

Polecam Ci najpierw stworzyć jakiś katalog z projektem i do niego skopiować plik brcc32.exe. Następnie przy pomocy Notatnika utworzyć w tym katalogu plik - np. 'zasoby.rc'. Wpisz w tym pliku tak: PIC JPEGFILE "sfp.jpg"

Pierwszy człon tej linii to nazwa tego zasobu - za pomocą tej nazwy będziemy się odwoływali do pliku JPG. Drugi człon to typ wykorzystywanych zasobów - to także nam będzie potrzebne podczas pisania kodu programu. Ostatni człon wpisany w apostrofach to nazwa pliku do włączenia. Plik jest zwykłym obrazkiem JPG. Dobrze, mamy już nasz skrypt zasobów - zapisz go. Teraz pozostaje go tylko skompilować. Otwórz konsole DOS'a i przejdź do katalogu z projektem. Przypominam, że w DOSie jeżeli chciałeś przejść katalog wyżej to należało wpisać cd.. natomiast żeby przejść do konkretnego katalogu - cd NazwaKatalogu.

Jeżeli już jesteś w katalogu z programem ( i z plikiem *.rc ) powinieneś wpisać:

brcc32.exe zasoby.rc

Spowoduje to skompilowanie skryptu i utworzenie w programie pliku zasoby.res. Moje gratulacje! Właśnie utworzyłeś plik zasobu w sposób ręczny. To nie było takie trudne - problem raczej leży w sposobie odczytania tego. Klasa TJPEGImage, z której wcześniej korzystaliśmy nie posiada gotowej procedury obsługi tego zdarzenia. Trzeba będzie stworzyć nową klasę:

{ klasa dziedziczaca z TJPEGImage, ktora posiada jedna dodatkowa
  funkcje ladowania z zasobow } 
  TJPEGRes = class(TJPEGImage)
  public
    procedure LoadFromResource(const ResID: PChar); virtual;
  end;

Ta klasa dziedziczy z klasy TJPEGImage i posiada jedną procedurę, którą w przyszłości swobodnie będzie można wykorzystać do ładowania plików z zasobów. A oto jak powinna wyglądać ta procedura:

procedure TJPEGRes.LoadFromResource(const ResID: PChar);
var
  Res : TResourceStream; // utwórz zmienna
begin
{ zalduj obrazek z zasobow }
  Res := TResourceStream.Create(hInstance, ResID, 'JPEGFILE');
  try
    LoadFromStream(Res); // ładuj obrazek do strumienia ze zmiennej Res
  finally
    Res.Free;  // zwolnij pamięć
  end;
end;

W tym wypadku skorzystałem z nie omawianej wcześniej klasy - TResourceStream (strumienie zasobów). Na razie nie będę się w ten temat zagłębiał - szczegółowo omówię strumienie później podczas omawiania sposób na wykorzystanie plików. Powiem jednak, że jest to dobry sposób, aby dobrać się do wszelkiego rodzaju zasobów bez korzystania z funkcji API. Podczas wywoływania konstruktora tej klasy pierwszym parametrem jest znane nam już słowo kluczowe hInstance. Kolejny parametr to nazwa zasobu do "wyciągnięcia". W to miejsce podstawiłem wartość parametru ResID. I w końcu ostatni parametr konstruktora - typ zasobów taki jak wpisaliśmy w pliku 'zasoby.rc' - JPEGFILE. Tym sposobem mamy już praktycznie odczytany plik. Teraz pozostaje tylko jego konwersja. Odpowiada za to procedura LoadFromStream, która odczytuje dane ze strumienia. Procedura ta jest zawarta w klasie TJPEGImage. W przykładzie tym skorzystałem z nie omawianego wcześniej elementu programowania w Delphi - wyjątków! To te słowa kluczowe try i except. Nie przejmuj się tym na razie - nie zwracaj na to uwagi - zajmiemy się tym w dalszej części książki.
Dobra, klasę mamy gotową - teraz pozostaje napisanie procedury, która by z tej klasy korzystała:

procedure TMainForm.btnLoadResClick(Sender: TObject);
var
  JPG : TJPEGRes;
begin
  JPG := TJPEGRes.Create; // tworzenie nowej klasy
  try
    JPG.LoadFromResource('PIC'); // załadowanie odpowiedniego zasobu 
    Canvas.Draw(10, 10, JPG); // wyświetlenie obrazka
  finally
    JPG.Free;
  end;
end;

Jak widzisz następuje tutaj utworzenie nowej klasy poprzez wywołanie konstruktora, a następnie wywołania naszej procedury - LoadFromResource. W parametrze tej procedury należy podać nazwę zasobu taką jaką wpisaliśmy w pliku *.rc. No w końcu wyświetlamy obrazek metodą 'Draw'.

Ładowanie plików EXE

W przypadku pliku EXE sprawa jest prosta, gdyż wyciągnięcie pliku jest bardzo podobne jak w przypadku plików JPG. Następuje praktycznie tak samo. Tak więc stwórz jakiś plik - np. exe_file.rc. W nim wpisz takie linie:
ASCII EXEFILE "ascii.exe"

Pierwszy czełon to uchwyt pliku. Kolejny to typ zasobu. Ostatni wreszcie to plik do włączenia do zasobu. Kompilacja przebiega w tym wypadku tak samo jak w przypadku plików JPG. Jeżeli wszystko poszło dobrze powinieneś mieć w katalogu plik 'exe_file.res', w którym to znajduje się program ASCII.exe. Teraz w swoim programie w Delphi dodaj ten plik do projektu:

  {$R EXE_FILE.RES}

W naszym projekcie użyjemy komponentu 'SaveDialog.' Przypominam, że jest to komponent, który wyświetla standardowe Windowsowskie okno z wyborem do zapisania pliku. Przypominam, że funkcja Execute tego komponentu powoduje pojawienie się okna. Tak więc cały kod procedury, która wyciągnie plik EXE z zasobu powinna wyglądać tak:

procedure TForm1.btnExtractClick(Sender: TObject);
var
  Res : TResourceStream;
begin
  if SaveDialog.Execute then // jeżeli okno zostanie wyświetlone
  begin
  { wywołaj konstruktor klasy }
    Res := TResourceStream.Create(Hinstance, 'ASCII', 'EXEFILE');
    try
      Res.SaveToFile(SaveDialog.FileName); // zapisz zasób do pliku
    finally
      Res.Free;
    end;
  end;
end;

Prawie wszystko tutaj następuje tak samo jak w przypadku plików JPG. Z tą różnicą, że wywołujemy tutaj procedure 'SaveToFile' klasy TResourceStream. W parametrze tej procedury należy podać ścieżkę w której plik ma być zapisany. Ścieżkę tę uzyskujemy z komponentu SaveDialog. Możesz prztestować działanie programu. Najpierw z menu Project wybierz Build All, co spowoduje zbudowanie pliku EXE i włączenie do niego zasobów.

Odtwarzania dźwięku z zasobu

Podczas omawiania funkcji PlaySound wspominałem o fladze, której zastosowanie powoduje iż dźwięk będzie odtwarzany z zasobów. Problem następuje w stworzeniu zasobu, w którym będzie znajdował się plik WAV. I znowu użyjemy do tego celu programu brcc32.exe, którym to skompilujemy zasób. Stwórz więc plik wave.rc, w którym umieść następującą linię:
ID_WAVE WAVE "hover.wav"

Jak w pozostałych przypadkach pierwszy człon to nazwa identyfikująca plik. Kolejny człon to typ zasobu, a ostatni to plik do włączenia. W tym wypadku włączamy plik WAV. Po skompilowaniu do postaci pliku RES powinieneś mieć plik wave.res. Odtwarzanie dźwięku następuje tak:
PlaySound('ID_WAVE', hInstance, SND_ASYNC or SND_RESOURCE);
Nie zapomnij oczwiście o dodaniu do listy uses modułu MMSystem. Jak widzisz jako nazwę pliku podałem tutaj nazwe identyfikującą plik w zasobie 'wave.res'. Drugi parametr funkcji PlaySound to słowo kluczowe hInstance (w pozostałych przypadkach w to miejsce wstawialiśmy 0). Na końcu pozostaje ustawienie flagu SND_RESOURCE, która mówi iż dźwięk będzie ładowany z zasobów.

Animacje tekstowe w Delphi

Wbrew pozorom nie jest to takie trudne. Cały problem polega na płynnym przesuwaniu danego obiektu. Otóż jeżeli chcesz w pętli np. przesuwać tekst przy użyciu klasy TCanvas to za każdym wykonaniem pętli będziesz musiał odświeżać obraz. W rezultacie spowoduje to iż przesuwany tekst nie będzie przesuwany płynnie, a obraz będzie migał. Jest na to rozwiązanie. Wystarczy zastosować bitmapę pamięciową i narysować tekst na bitmapie pamięciowej a samą bitmapę wyświetlać w pętli co spowoduje za każdą iteracją wyświetlenie się bitmapy z przesunięciem. Jeżeli zrobisz właśnie tak to efekt będzie o niebo lepszy - animacja będzie całkiem płynna. W całkiem prosty sposób da się narysować teks 3D. Wystarczy w tym celu dany tekst narysować dwa razy, tyle, że innym kolorem czcionki i z minimalnym przesunięciem. Oto taki przykład:
Canvas.Font.Name := 'Courier New'; // czcionka
  Canvas.Font.Size := 20; // rozmiar czcionki
  Canvas.Font.Style := Font.Style + [fsBold]; // pogrubienie
  Canvas.Brush.Style := bsClear; // tło przeźroczyste
  Canvas.Font.Color := clWhite; // kolor czcionki
  Canvas.TextOut(20, 20, 'WWW.4PROGRAMMERS.NET');
  Canvas.Brush.Style := bsClear; // tło przezroczyste
  Canvas.Font.Color := clBlack; // kolor czcionki
  Canvas.TextOut(19, 19, 'WWW.4PROGRAMMERS.NET');

Na samym początku należy ustawić odpowiednią czcionkę. Następnie ustawić należy tło dla tekstu jako przeźroczysty ( bsClear ). Czcionkę ustawiamy na białą i teraz następuje narysowanie białego tekstu w punkcie 20, 20. Później tekst zostanie narysowany czarną czcionką tyle, że z przesunięciem 1 piksela. Taki kod da napis wyglądający tak jak na rysunku 5.

5.5.jpg Rysunek 5.

Całkiem nieźle, prawda? Co prawda nie jest to OpenGL, ale ujdzie. Spróbujmy teraz czegoś trudniejszego - stworzenie animacji "maszyny do pisania". Po naciśnięciu przycisku wywoływana zostaje procedura, która rysuje literkę po literce dając efekt maszyny do pisania.

Najpierw przypatrz się samej procedurze, a później omówię zasadę jej działania:

const DText = 'Delphi...';

procedure TMainForm.KrokPoKroku(X, Y: Integer);
var
  TextLength, I: Integer;
begin
  TextLength := Length(DText); // pobierz długość tekstu

  with Canvas do
  begin
    for I := 1 to TextLength do // pętelka...
    begin
      Application.ProcessMessages;
      Sleep(100);  // czkeja 100 milisekund
      Brush.Style := bsClear; // styl na przezroczysty
      Font.Name := 'Courier New'; // czcionka
      Font.Color := clWhite; // kolor czcionki - biały
      Font.Size := 16;  // rozmiar
{ Wyswietlaj tekst jedna litera po drugiej z przesunięciem }
      TextOut((X + i * 16), Y, DText[i]);
      Brush.Style := bsClear;
      Font.Color :=  clBlack;
      TextOut((X + i * 16) -2, Y -2, DText[i]); // wyświetl ten sam tekst w innym położeniu - efekt cienia
   end;
  end;
end;

W pierwszym wierszu bloku begin następuje pobranie ilość liter stałej 'DText'. Służy do tego procedura 'Length'. Zastosowałem tutaj parę poleceń, których jeszcze nie znasz, a które na pewno znajdą zastosowanie w Twoich przyszłych programach. Po pobraniu ilości liter następuje pętla for. W pętli tej pierwszy wiersz to procedura 'ProcessMessages' z klasy Application. Normalnie podczas stosowania pętli for komputer zawiesza działanie programu na czas wykonywania tej pętli. Zastosowanie procedury ProcessMessages powoduje iż podczas wykonywania pętli będzie również możliwe wykonywania innych czynności. W kolejnym wierszu następuje wywołanie procedury Sleep, która na określony czas wstrzymuje działanie programu. W naszym wypadku odstęp pomiędzy kolejnymi iteracjami pętli wynosić będzie 100 milisekund. Następnie ustawiana zostaje czcionka 'Courier New'. Ustawienie tej czcionki jest konieczne ze względu na to iż każdy znak pisany tą czcionką ma taką samą szerokość w pikselach. Po tym na formie rysowany zostaje tekst, a konkertnie kolejna litera z tekstu. Każda litera od kolejnej jest oddalona o i*16, gdzie i to kolejna litera. Czyli pierwsza litera znajdować się będzie w punkcie - przykładowo - X: 16, a już kolejny znak w punkcie X: 32. Tekst jest rysowany w 3D gdyż ta sama litera jest rysowana podwójnie z minimalnym przesunięciem:

5.6.jpg Rysunek 6

Oto efekt działania tego programu (rysunek 6.). Po naciśnięciu przycisku kolejna litera wyrazu 'Delphi...' jest rysowana w odstępie 100 milisekund. Taka animacja jest całkiem płynna, prawda? Sam przetestuj działanie takiej procedury. Wywołanie jej może wyglądać następująco:

procedure TMainForm.btnRunClick(Sender: TObject);
begin  
  Repaint; // odśwież Canvas  
  KrokPoKroku(100, 100); // wywołaj procedurę
end;

Kolejny program, który Ci zaprezentuje będzie wyświetlał tekst na pasku aplikacji. To także będzie animacja tekstowa. Często ją możesz zaobserwować w różnych programach. Da efekt podobny jak w przypadku wcześniejszego przykładu. Jak maszyna do pisania będzie na pasku aplikacji wyświetlać różne teksty. Aplikacja, którą niebawem napiszemy będzie dawała taki oto efekt (rysunek 7.):

5.7.jpg Rysunek 7.

Zwróć w tym wypadku na pasek aplikacji. Po naciśnięciu przycisku na pasku zostaje pisany tekst. Tekst takie będzie powtarzany aż do zakończenia działania programu. Na początek w sekcji Implementation musisz zadeklarować taką zmienną, która będzie oznaczała, czy animacja jest w trakcie działania, czy też nie:

var Running : Boolean = TRUE;  // zmienna określa, czy animacja ma być uruchomiona

Następnie w sekcji private zadeklaruj taką procedurę:

    procedure Go(const PText : TStrings);

Ta procedura jako parametr ma typ danych - TStrings. Ten typ danych służy do przechowywania tekstu. Jest to specyficzna klasa (!), gdyż zawiera gotowe procedury do wczytania tekstu po której wywołaniu cały plik zostanie wczytany do pamięci. Teraz mamy dostęp do każdej linii tekstu za pomocą tej właśnie zmiennej. W naszym jednak przypadku to Ty zadeklarujesz kilka linii, z której każda po kolei będzie wyświetlana w postaci animacji na pasku aplikacji. W taki sposób będzie wyglądało uruchomienie procedury wyświetlającej animację:

procedure TMainForm.btnGoClick(Sender: TObject);
var
  sText : TStrings;
begin
  btnStop.Visible := True;  // pokazanie przycisku umożliwiającego zatrzymanie
  sText := TStringList.Create;
  try
    with sText do
    begin
    { dodanie kolejnych linii, ktore po kolei beda wyswietlane }
      Add('Delphi 6.0 ...');
      Add('...jest doskonałym narzędziem typu RAD do szybkiego tworzenia aplikacji...');
      Add('...możesz się o tym przekonać sam. Ten program zajmuje 92 linie.');
      Add('Adam Boduch');
    end;
    Go(sText); // wywołaj procedurę z parametrem
  finally
    sText.Free;
  end;
end;

Procedura 'Go' jako parametru wymagać będzie zmiennej typu TStrings więc zmienną takiego typu trzeba przekazać tej procedurze. Najpierw następuje zadeklarowanie zmiennej typu TStrings, a później wywołania jej konstruktora. Następnie następuje wywołanie procedury 'Add' tej klasy, w której jako parametr podajemy tekst, który do zmiennej będzie dodany. Procedura 'Add' zapisuje swój parametr w osobnej linii, tak więc inaczej mówiąc procedura ta dodaje do zmiennej typu TStrings nową linię. Na samym końcu następuje przekazanie do procedury Go parametru - zmiennej sText. Pozostaje jeszcze napisanie samej procedury - animacji. Sama procedura wygląda tak:

procedure TMainForm.Go(const PText: TStrings);
var
  PTextLong : Integer; // długość tekstu
  I : Integer;
  LinesCount : Integer; // ilość linii
begin
  while (Running) do  // dopóki zmienna będzie wartości true
  begin
    Application.ProcessMessages;
    if not Running then Break;   // sprawdzaj, czy przypadkiem nic się nie zmieniło?
    for LinesCount := 0 to PText.Count -1 do  // zaczynaj od pierwszej linii
    begin
      if not Running then Break;  // znów sprawdź...
      PTextLong := Length(PText[LinesCount]);  // pobierz długość linii
      Sleep(500); // odczekaj pol. sekundy
      Caption := '';  // wymaż Caption
      for I := 0 to PTextLong do   // wykonuj pętle literka po literce
      begin
        Application.ProcessMessages;
        Sleep(100);  // z przerwa 100 milisekund
        if not Running then Break;  // znów sprawdź!
        Caption := Caption + PText.Strings[LinesCount][i]; // wyświetl po kolei wszystkie litery
      end;
    end;
  end;
end;

Zadeklarowane zmienne oznaczają kolejno: długość (w znakach) kolejnej linii do wyświetlenia, zmienną pomocniczą do pętli oraz ilość linii do wyświetlenia. Na samym początku pętla while będzie wykonywana dopóki zmienna Running nie osiągnie wartości TRUE. Zresztą w samej pętli następuje sprawdzenie, czy przypadkiem podczas wykonywania pętli nie zaszła zmiana i wartość Running nie przybrała wartości FALSE. Jeżeli tak się stanie to natychmiast przerwij pętle. Później pętla for będzie wykonywana tyle razy ile jest linii w zmiennej PText (parametr procedury). Ilość linii podaje funkcja 'Count'. Zauważ, że w samej procedurze istnieją aż trzy pętle w sobie zagnieżdżone. Zwróć uwagę także, że w każdej z nich następuje sprawdzenie, czy przypadkiem zmienna Running nie ma wartości FALSE. Jest to konieczne, gdyż w przypadku, gdy użytkownik zechciałby przerwać działanie programu cała animacja byłaby wykonywana do końca.

PTextLong := Length(PText[LinesCount]);  // pobierz długość linii

Tutaj następuje pobranie długości linii (w znakach). Za pomocą nawiasu klamrowego można się więc odwołać do konkretnej linii. W tym momencie zmienna 'PTextLong' zawiera długość linii i na tej podstawie wykonywana będzie kolejna pętelka:

      for I := 0 to PTextLong do   // wykonuj pętle literka po literce
      begin
        Application.ProcessMessages;
        Sleep(100);  // z przerwa 100 milisekund
        if not Running then Break;  // znów sprawdź!
        Caption := Caption + PText.Strings[LinesCount][i]; // wyświetl po kolei wszystkie litery
      end;

Dodaje ona do właściwości Caption formy kolejną literkę. Sczególną uwagę zwróć na tę linijkę:

Caption := Caption + PText.Strings[LinesCount][i]; // wyświetl po kolei wszystkie litery

Zauważysz tutaj specyficzny zapis. Najpierw odwołujemy się do konkretnej linii pliku (LinesCount), a później również za pomocą nawiasu klamrowego do konkretnego znaku tej linii. Pokazuje Ci to żebyś wiedział, że również takie kombinacje są możliwe.
Tak przedstawia się cały listing programu:

unit MainFrm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, 
  StdCtrls;

type
  TMainForm = class(TForm)
    btnGo: TButton;
    btnStop: TButton;
    procedure btnGoClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure btnStopClick(Sender: TObject);
  private
    procedure Go(const PText : TStrings);
  public

  end;

var
  MainForm: TMainForm;

implementation

{$R *.DFM}

var Running : Boolean = TRUE;  // zmienna określa, czy animacja ma być uruchomiona

procedure TMainForm.Go(const PText: TStrings);
var
  PTextLong : Integer; // długość tekstu
  I : Integer;
  LinesCount : Integer; // ilość linii
begin
  while (Running) do  // dopóki zmienna będzie wartości true
  begin
    Application.ProcessMessages;
    if not Running then Break;   // sprawdzaj, czy przypadkiem nic się nie zmieniło?
    for LinesCount := 0 to PText.Count -1 do  // zaczynaj od pierwszej linii
    begin
      if not Running then Break;  // znów sprawdź...
      PTextLong := Length(PText[LinesCount]);  // pobierz długość linii
      Sleep(500); // odczekaj pol sekundy
      Caption := '';  // wymaz Caption
      for I := 0 to PTextLong do   // wykonuj pętle literka po literce
      begin
        Application.ProcessMessages;
        Sleep(100);  // z przerwa 100 milisekund
        if not Running then Break;  // znów sprawdź!
        Caption := Caption + PText.Strings[LinesCount][i]; // wyświetl po kolei wszystkie litery
      end;
    end;
  end;
end;

procedure TMainForm.btnGoClick(Sender: TObject);
var
  sText : TStrings;
begin
  btnStop.Visible := True;  // pokazanie przycisku umożliwiającego zatrzymanie
  sText := TStringList.Create;
  try
    with sText do
    begin
    { dodanie kolejnych linii, ktore po kolei beda wyswietlane }
      Add('Delphi 6.0 ...');
      Add('...jest doskonałym narzędziem typu RAD do szybkiego tworzenia aplikacji...');
      Add('...możesz się o tym przekonać sam. Ten program zajmuje 92 linie.');
      Add('Adam Boduch');
    end;
    Go(sText); // wywołaj procedurę z parametrem
  finally
    sText.Free;
  end;
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Running := False; // przy próbie zamknięcia zmień wartość
end;

procedure TMainForm.btnStopClick(Sender: TObject);
begin
  Running := False; // zmień wartość jeżeli chcesz zatrzymać
end;

end.

W tym programie wygenerowałem także procedurę OnClose formy. Występuje ona w momencie, gdyż użytkownik będzie chciał zamknąć program. W obsłudze tego zdarzenia zmieniłem wartość zmiennej Running na False.

Cóż, pokazałem Ci pare przykładów tworzenia animacji. To tylko od Ciebie zależy jaką masz wyobraźnie i jak nabytą wiedzę wykorzystasz do tworzenia bardziej skomplikowanych efektów.

Podsumowanie

Podczas pisania tego rozdziału zastanawiałem się czy to nie za szybko? To w końcu dopiero piąty rozdział, a ja już zabieram się za tworzenie grafiki. To co dopiero będzie później?! Chciałem po prostu, aby programowanie w Delphi stało się dla Ciebie bardziej przyjemne i zdecydowałem się na ten krok. Z drugiej strony programowanie grafiki przy użyciu Delphi nie jest znowu takie trudne. Co innego OpenGL czy DirectX - w takich wypadkach programowanie takie opiera się na fizyce i matematyce. Delphi udostępnia nawet moduł OpenGL.pas, w którym są nagłówki procedur z biblioteki OpenGL.dll, ale przyznam szczerze, że 90% programów, w których wykorzystuje się OpenGL jest pisane w C++. Jeżeli chodzi o DirectX to w sieci są dostępne komponenty do wykorzystania bibliotek DirectX'a. Najpopularniejszym jest chyba pakiet DelphiX.

7 komentarzy

No art boski :P

spox xD

moja ocena 6.0 =]

brodny: mnie też nie!!

Adam: Pokażny artykuliczek <:)

Deti: Mi też ten styl pasuje

Potężny artykuł - nie ma porównania z innymi... Jedynie mógłbym się przyczepić spójności - jakoś to takie wszystkie pomieszane, i dla początkującego programity może sprawić problem (np opis klasy TCanvas - jak działa, bądź też [ tyle co pamietam ] opis styli czcionki - w końcu jest to "zbiór wlasciwości" o czym chyba tu nie było mowy). Mimo wszystko oceniam na 5+ :)

Btw: podoba mi sie ten styl:

"Zastanawiasz się pewnie, czy to nie za wcześnie dla Ciebie na tak poważne rzeczy. Nie martw się. W Delphi w prosty sposób można operować grafiką" :)

To wprost jest maly kurs grafiki a nie artykul :D

Ale mnie to jakoś nie przeszkadza :)