Rozdział 9. Multimedia
Adam Boduch
Na samym początku wypadałoby wyjaśnić, co kryje się pod pojęciem multimedia. Najprościej rzecz ujmując, jest to nazwa określająca techniki komputerowe służące do przekazywania informacji, w tym różne środki przekazu. Mam na myśli dźwięk, obraz, film, animację itp.
1 Z czego będziemy korzystać?
1.1 OpenGL
1.2 DirectX
2 Tworzenie bitmap
2.3 Korzystanie z komponentu TImage
3 Klasa TBitmap
3.4 Odczytywanie obrazka ze schowka
3.4.1 Odczyt danych
3.4.2 Zapisywanie danych
4 Pliki JPEG
4.5 Klasa TJPEGImage
4.6 Wyświetlanie obrazków w komponencie TImage
4.7 Przykład działania ? kompresja plików
5 Pliki GIF
6 Zasoby
6.8 Tworzenie zasobów
6.8.3 Dołączanie bitmapy
6.8.4 Pozostałe zasoby
6.9 Wykorzystanie zasobów
6.9.5 Ładowanie bitmapy
6.9.6 Ładowanie ikony
6.9.7 Ładowanie kursora
6.10 Ręczne tworzenie zasobów
6.10.8 Dodawanie plików JPEG
6.10.9 Ładowanie pliku JPG
6.11 Doklejanie plików EXE
7 Klasa TCanvas
8 Pióra i pędzle
8.12 Klasa TPen
8.12.10 Właściwość Mode
8.12.11 Właściwość Style
8.13 Klasa TBrush
8.13.12 Właściwość Style
9 Czcionki
9.14 Właściwości klasy TFont
10 Metody klasy TCanvas
10.15 Draw
10.16 FillRect
10.17 StretchDraw
10.18 TextOut
10.19 TextRect
10.20 TextWidth, TextHeight
10.21 TextExtent
10.22 MoveTo
10.23 LineTo
10.24 Inne funkcje służące do rysowania kształtów
10.25 Przykładowy program
11 Proste animacje tekstowe
11.26 Tekst trójwymiarowy (3D)
11.27 Efekt maszyny do pisania
11.28 Animacja na belce tytułowej
11.29 Inne płynne animacje
12 Odtwarzanie dźwięków
12.30 Funkcja PlaySound
12.31 Użycie komponentu TMediaPlayer
13 Odtwarzanie filmów
13.31.13 Odtwarzanie filmu
13.31.14 Pozycja odtwarzanego filmu
13.31.15 Ustawianie głośności
14 Kontrolka Flash
14.32 Instalacja kontrolki
14.33 Wykorzystanie komponentu
15 Podsumowanie
Rozdział 9. będzie właśnie poświęcony tworzeniu grafiki, odtwarzaniu dźwięków, wyświetlaniu filmów oraz tworzeniu animacji. Nie oczekuj, że od razu będziesz projektował świetne animacje ? do stworzenia takiej grafiki, jaką możemy zobaczyć w grach, potrzebna jest znajomość programowania OpenGL lub DirectX, a to wykracza poza ramy tej książki.
W tym rozdziale:
*dowiesz się, na czym polega malowanie w Delphi;
*napiszemy program umożliwiający odtwarzanie dźwięków;
*pokażę, w jaki sposób za pomocą komponentu TMediaPlayer można wyświetlić film;
8nauczysz się tworzyć proste animacje.
Z czego będziemy korzystać?
W Delphi nie mamy zbyt dużego wyboru narzędzi, jakimi moglibyśmy się posługiwać. Malowanie po formularzu ogólnie polega na zastosowaniu różnych funkcji WinAPI, lecz zostało ono nieco uproszczone dzięki klasie TCanvas
(płótno). Moim skromnym zdaniem Delphi nie nadaje się do tworzenia zaawansowanej grafiki, mimo że posiada wspomaganie narzędzi typu OpenGL czy DirectX. Jednak jest to tylko moje zdanie; wiele osób uważa, że jest wręcz przeciwnie ? w Delphi można tworzyć wspaniałe programy z grafiką.
OpenGL
OpenGL, czyli Open Graphics Library, to biblioteka służąca do tworzenia aplikacji graficznych. W rzeczywistości jest to mały plik DLL, który ułatwia tworzenie różnych aplikacji wykorzystujących skomplikowane efekty graficzne (3D, 2D, obroty, światła itp.). Według niektórych obsługa tej biblioteki jest ?czarną magią?, a dla innych z kolei jest bardzo prosta. Jedno jest pewne: aby programować przy użyciu OpenGL, potrzebna jest dobra znajomość matematyki oraz fizyki. Listing 9.1 przedstawia bardzo prosty program napisany przy użyciu biblioteki OpenGL, wyświetlający prosty trójkąt.
Listing 9.1. Prosty program napisany przy użyciu OpenGL
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, OpenGL;
type
TMainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormResize(Sender: TObject);
private
RC : HGLRC; // rendering
DC : HDC; // moduł
procedure glDraw;
procedure Idle(Sender: TObject; var Done: Boolean);
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.glDraw();
begin
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // czyszczenie
glLoadIdentity(); // także czyszczenie
// procedura tworząca wierzchołki w przestrzeni (tutaj trójkąty)
glTranslate(0, -1, -2); // nasza pozycja lub raczej przesunięcie o wektor wierzchołków
// przypominam, że w OpenGL występuje system x,y,z
glRotatef(10, 20, -10, 0); // obrócenie widoku o dany kąt
// procedura rysująca trójkąt
glBegin(GL_TRIANGLES);
glVertex3f(0, 0, 0);
glVertex3f(0.5, 0.5, 0);
glVertex3f(1, 0, 0);
glEnd();
// procedura rysująca zwroty x,y,z, gdzie:
// x - czerwony,
// y - zielony,
// z - niebieski.
glColor3f(1, 0, 0);
glBegin(GL_LINES);
glVertex3f(0, 0, 0);
glVertex3f(2, 0, 0);
glColor3f(0, 1, 0);
glVertex3f(0, 0, 0);
glVertex3f(0, 2, 0);
glColor3f(0, 0, 1);
glVertex3f(0, 0, 0);
glVertex3f(0, 0, 2);
glEnd();
glColor3f(1, 1, 1);
end;
procedure glInit();
begin
glClearColor(0.0, 0.0, 0.0, 0.0); // kolor wypełnienia: r - czerwony, g - zielony, b - niebieski, a - przezroczystość
glShadeModel(GL_SMOOTH);
glClearDepth(1.0);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
pFd : TPIXELFORMATDESCRIPTOR;
pF : Integer;
begin
DC := GetDC(Handle); // uchwyt modułu OpenGL
pFd.nSize := SizeOf(pFd);
pFd.nVersion := 1;
pFd.dwFlags := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER or 0;
pFd.iPixelType := PFD_TYPE_RGBA;
pFd.cColorBits := 32;
pF := ChoosePixelFormat(DC, @pFd);
SetPixelFormat(DC, pF, @pFd);
RC := wglCreateContext(DC); // nasza scenka
wglMakeCurrent(DC, RC);
Resize;
Application.OnIdle := Idle; //ustalenie wartości ciągłego renderowania
glInit;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
{ Usuniecie uchwytów z pamięci }
wglMakeCurrent(0,0);
wglDeleteContext(rc);
end;
procedure TMainForm.Idle(Sender: TObject; var Done: Boolean);
begin
glDraw(); // renderowanie sceny
SwapBuffers(DC); // przeniesienie z bufora na ekran
end;
procedure TMainForm.FormResize(Sender: TObject);
begin
glViewport(0, 0, Width, Height); // okienko (rozmiary)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(110.0, Width / Height, 0.01, 100.0);
{
pierwsza wartość (110.0) to kąt widzenia, domyślnie 90,
druga to kalkulacja perspektywy - zawsze szerokość/wysokość,
trzecia - z jakiej najbliższej odległości nie rysować sceny,
czwarta - z jakiej najdalszej odległości nie rysować sceny;
}
glMatrixMode(GL_MODELVIEW);
end;
end.
Jak mówiłem, z OpenGL wiąże się sporo działań matematycznych, lecz przed ich użyciem należy zainicjować bibliotekę. Delphi zawiera moduł OpenGL.pas, który importuje wszystkie potrzebne procedury z pliku OpenGL.dll i ułatwia korzystanie z dobrodziejstw oferowanych nam przez ową bibliotekę.
Więcej na temat biblioteki OpenGL możesz dowiedzieć się z książek Wydawnictwa Helion: OpenGL. Księga eksperta oraz Programowanie gier w OpenGL. Informacje na temat tej biblioteki możesz znaleźć także pod adresami:
http://www.opengl.org ? strona poświęcona OpenGL;
http://www.eecs.tulane.edu/www/Terry/OpenGL/Introduction.html ? kurs OpenGL.
DirectX
Odpowiedzią Microsoftu na powstanie biblioteki OpenGL była biblioteka DirectX. DirectX obejmuje większy zakres multimediów, jak np. obsługę dźwięku lub urządzeń typu dżojstik. Wadą DirectX jest to, że obsługuje go jedynie system Windows ? w odróżnieniu od OpenGL, który dostępny jest praktycznie dla każdej platformy.
Jeśli chcemy napisać program w oparciu o DirectX, sprawa jest nieco prostsza, a to dzięki komponentom z serii DelphiX. Należy je pobrać z Internetu i zainstalować (instalacją komponentów zajmiemy się w III części książki) ? wówczas na palecie komponentów znajdzie się kilka kontrolek, dzięki którym tworzenie aplikacji w oparciu o DirectX stanie się o wiele łatwiejsze.
Tworzenie bitmap
Na początek zajmiemy się rzeczą najprostszą ? ładowaniem, wyświetlaniem i zapisywaniem plików typu *.bmp, czyli inaczej mówiąc ? bitmap. VCL jak zawsze o wiele bardziej ułatwia sprawę ? dzięki tej bibliotece wyświetlenie obrazka staje się bardzo proste. Możemy wykorzystać chociażby komponent TImage
.
Korzystanie z komponentu TImage
Komponent TImage
jest bardzo przydatny, jeśli chodzi o wyświetlanie obrazków, a jednocześnie bardzo prosty w użyciu. Aby załadować obrazek do komponentu jeszcze w trakcie projektowania programu, wystarczy skorzystać z właściwości Picture
(rysunek 9.1).
Rysunek 9.1. Zaznaczona właściwość Picture
Kliknięcie przycisku wielokropka spowoduje wyświetlenie edytora obrazków (Picture Editor) ? patrz rysunek 9.2.
Rysunek 9.2. Okno Picture Editor
Naciśnięcie przycisku Load
spowoduje wyświetlenie okna służącego do wyboru pliku graficznego. Następnie taki plik graficzny zostanie umiejscowiony w komponencie TImage
i skompilowany wraz z programem (włączony do wynikowego pliku EXE).
Usunięcie owego pliku nastąpi po naciśnięciu przycisku Clear
.
Ładowanie obrazków w trakcie działania programu
#Umieść na formularzu komponent TImage
#Właściwości Align
komponentu TImage
nadaj wartość alClient
.
#Zmień właściwość Stretch
komponentu TImage
na True
.
#Wygeneruj zdarzenie OnCreate formularza i umieść w nim taki kod:
procedure TMainForm.FormCreate(Sender: TObject);
begin
Image.Picture.Bitmap.LoadFromFile('helion.bmp');
end;
Na samym początku po umieszczeniu komponentu na formularzu i rozciągnięciu go na całą szerokość nadałeś właściwości Stretch wartość True
. Spowoduje to, że każdy obrazek załadowany do komponentu zostanie rozciągnięty lub zwężony tak, aby dostosować się do rozmiarów komponentu (rysunek 9.3).
Rysunek 9.3. Program w trakcie działania
Powróćmy do kodu, który powoduje załadowanie bitmapy. Teraz właśnie masz okazję docenić zalety biblioteki VCL ? wystarczy jeden wiersz kodu! Najpierw za pomocą operatora kropki (.) odwołujemy się do obiektu TPicture
, a następnie do obiektu TBitmap
. Klasa TBitmap
zawiera natomiast procedurę LoadFromFile, która umożliwia załadowanie obrazka z pliku.
Podczas ładowania obrazka należy zwrócić uwagę, czy jest on rzeczywiście zapisany w formacie BMP. W przeciwnym wypadku wykonany zostanie wyjątek EInvalidType
.
Klasa TBitmap
Do operowania na samych plikach *.bmp służy klasa TBitmap
. Dzięki niej możliwe jest dokonywanie operacji pamięciowych, tzn. operacji na bitmapie odbywających się w pamięci komputera i nie dających rezultatów dla użytkownika.
Przed wykorzystaniem metod klasy TBitmap
należy ją utworzyć:
var
Bitmap : TBitmap;
begin
Bitmap := TBitmap.Create;
try
{ operacje na bitmapie }
finally
Bitmap.Free; // zwolnienie klasy
end;
end;
Tworzenie oraz niszczenie obiektu odbywa się identycznie jak w przypadku innych klas VCL. W tabelach 9.1 oraz 9.2 przedstawiam najważniejsze właściwości oraz metody klasy TBitmap
.
Tabela 9.1. Najważniejsze właściwości klasy TBitmap
Właściwość | Opis |
Canvas | Wskazanie na klasę TCanvas ? zajmiemy się tym nieco później |
Empty | Właściwość przybiera wartość True, jeśli bitmapa nie jest załadowana |
Height | Wysokość bitmapy (w pikselach) |
IgnorePalette | Możesz przydzielić tej właściwości wartość True, jeśli szybkość ładowania jest priorytetowa ? wówczas wyświetlana bitmapa będzie posiadała jedynie 255 kolorów |
PixelFormat | Określa format pikseli: pf1bit (1 bit na piksel ? bitmapa czarno-biała), pf4bit, pf8bit, pf15bit, pf16bit, pf24bit, pf32bit, pfCustom (nieokreślone). |
TransparentColor | Zwraca kolor pierwszego piksela w bitmapie (jeżeli TransparentMode jest ustawione na tmAuto) |
TransparentMode | Określa rodzaj przezroczystości. Jeżeli właściwość ma ustawioną wartość tmAuto, to kolor przezroczystości jest określany na podstawie lewego dolnego piksela. Jeżeli właściwość ma ustawioną wartość tmFixed, oznacza to, że kolor przezroczystości ma być odczytany z obiektu |
Transparent | Właściwość określa sposób malowania bitmapy. Po ustawieniu wartości True bitmapa będzie przezroczysta |
Width | Określa szerokość bitmapy w pikselach |
Modified | Właściwość określa, czy bitmapa została zmodyfikowana |
Tabela 9.2. Najważniejsze metody klasy TBitmap
Metoda | Opis |
LoadFromClipboardFormat | Procedura powoduje załadowanie obrazka, który znajduje się obecnie w schowku |
LoadFromResourceID | Ładuje bitmapę z zasobów (o tym w dalszej części rozdziału) na podstawie ID |
LoadFromResourceName | Ładuję bitmapę z zasobów na podstawie nazwy zasobu |
LoadFromStream | Ładuje bitmapę, która jest zapisana w strumieniu (TStream) |
LoadFromFile | Ładuje bitmapę z pliku |
SaveToFile | Zapisuje bitmapę do pliku |
SaveToStream | Zapisuje bitmapę do strumienia (TStream) |
SaveToClipboardFormat | Kopiuję bitmapę do schowka |
FreeImage | Zwalnia obrazek i jednocześnie także pamięć |
Odczytywanie obrazka ze schowka
Może zboczymy w tym momencie trochę z tematu grafiki, albowiem zamierzam opisać sposoby wykorzystania schowka w operowaniu grafiką, a dokładnie zapisywanie do schowka oraz odczytywanie.
Schowek jest specjalnym mechanizmem systemu Windows, umożliwiającym zapisywanie i przechowywanie dowolnych informacji (tekst, grafika) na potrzeby jednego lub kilku programów.
Zapis oraz odczyt danych ze schowka umożliwiają dwie funkcje klasy TBitmap
: LoadFromClipboardFormat
oraz SaveToClipboardFormat
.
procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle; var APalette: HPALETTE); override;
procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle; APalette: HPALETTE); override;
Ogólnie do wykorzystania schowka służy moduł Clipbrd
, więc na samym początku będziesz musiał dodać go do listy uses. Operowanie klasą TClipBoard
(znajduje się ona w module Clipbrd
) jest całkiem proste. Zapisywanie i odczytywanie danych może odbywać się za pomocą procedur GetAsHandle
oraz SetAsHandle
.
Ogólnie w schowku może być kilka rodzajów danych (patrz tabela 9.3), lecz my chcemy odczytać jedynie dane w postaci bitmapy (CF_BITMAP
).
Tabela 9.3. Rodzaje danych mogących znaleźć się w schowku
Flaga | Rodzaj danych |
---|---|
CF_TEXT | Tekst |
CF_BITMAP | Grafika w postaci bitmapy |
CF_METAFILEPICT | Plik metafile |
CF_PICTURE | Zdjęcie (obiekt typu TPicture) |
CF_COMPONENT | Dowolny inny obiekt |
Odczyt danych
Image.Picture.Bitmap.LoadFromClipboardFormat(CF_BITMAP, ClipBoard.GetAsHandle(CF_BITMAP), 0);
W pierwszym parametrze podajemy rodzaj danych do odczytania; drugi parametr to już wywołanie funkcji GetAsHandle
z klasy TClipBoard
. Funkcja ta ma za zadanie odczytać dane i zwraca do nich uchwyt (THandle
).
Wcześniej wypadałoby sprawdzić, czy w schowku rzeczywiście znajdują się dane w postaci bitmapy ? inaczej Delphi wywoła wyjątek:
if ClipBoard.HasFormat(CF_BITMAP) then { tutaj kod }
Funkcja HasFormat
zwraca True
, jeżeli w schowku znajdują się dane określone w parametrze (w tym wypadku CF_BITMAP
).
Zapisywanie danych
Zapisywanie danych jest nieco trudniejsze, gdyż w funkcji SaveToClipboardFormat
parametry muszą być zmiennymi.
Image.Picture.Bitmap.SaveToClipboardFormat(wFormat, AHandle, APalette);
ClipBoard.SetAsHandle(wFormat, AHandle);
Funkcja SaveToClipboardFormat
powoduje przypisanie do zmiennej wFormat nowej wartości ? tak samo jest w wypadku zmiennej AHandle. Dopiero teraz można wywołać polecenie SetAsHandle, zapisujące dane do schowka.
Program w trakcie działania przedstawiony został na rysunku 9.4., a jego kod źródłowy znajduje się w listingu 9.2.
Rysunek 9.4. Program w trakcie działania
Listing 9.2. Kod źródłowy programu
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
TMainForm = class(TForm)
gbImage: TGroupBox;
Image: TImage;
rgSelect: TRadioGroup;
btnDoIt: TButton;
btnClear: TButton;
procedure btnDoItClick(Sender: TObject);
procedure btnClearClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
uses Clipbrd;
procedure TMainForm.btnDoItClick(Sender: TObject);
var
AHandle : THandle;
wFormat : WORD;
APalette : HPALETTE;
begin
case rgSelect.ItemIndex of
0: Image.Picture.Bitmap.LoadFromClipboardFormat(CF_BITMAP, ClipBoard.GetAsHandle(CF_BITMAP), 0);
1: begin
Image.Picture.Bitmap.SaveToClipboardFormat(wFormat, AHandle, APalette);
ClipBoard.SetAsHandle(wFormat, AHandle);
end;
2: Image.Picture.Bitmap.LoadFromFile('helion.bmp');
end;
end;
procedure TMainForm.btnClearClick(Sender: TObject);
begin
{ zwolnij zasoby }
Image.Picture.Bitmap.FreeImage;
Image.Picture.Assign(nil);
end;
end.
Pliki JPEG
Pliki JPEG (albo inaczej ? JPG) są bardzo popularnym formatem zapisu obrazków. Wszystko dzięki znakomitej kompresji, pozwalającej na duże zmniejszenie wielkości pliku bez dużej utraty jakości obrazu.
Klasa TJPEGImage
Do operowania na plikach JPG służy klasa TJPEGImage
, znajdująca się w module JPEG
. Ładowanie i zapisywanie plików wygląda praktycznie tak samo jak w przypadku bitmap. Jedyna różnica polega na tym, że klasa TJPEGImage
przy zapisywaniu stosuje już kompresję. Obrazek helion.bmp, którym posługiwałem się jako przykładem, został zmniejszony z 44 KB do 8 KB.
Najważniejsze właściwości klasy TJPEGImage
przedstawione zostały w tabeli 9.4, a metody ? w tabeli 9.5
Tabela 9.4. Najważniejsze właściwości klasy TJPEGImage
Właściwość | Opis |
---|---|
CompressionQuality | Określa jakość kompresji ? od 1 do 100 |
Grayscale | Określa, czy obrazek ma być czarno-biały czy kolorowy |
Performance | Sposób kompresji: jpBestQuality (lepsza jakość, większy plik), jpBestSpeed (optymalizacja pod względem szybkości ? mniejsza jakość) |
Scale | Sposób wyświetlania obrazka: jsFullSize (pełny obrazek), jsHalf (połowa), jsQuarter (ćwiartka), jsEighth (1/8) |
Klasa TJPEGImage posiada także te same metody i właściwości co TBitmap
? np. Width
, Height
i Empty
.
Tabela 9.5. Najważniejsze metody klasy TJPEGImage
Metoda | Opis |
---|---|
Compress | Kompresuje na podstawie ustawień właściwości takich jak CompressionQuality czy Performance |
Assign | Powoduje ?skopiowanie? danych z innej klasy |
DIBNeeded | Dekompresja pliku JPEG do postaci bitmapy |
Tak samo, jak w przypadku właściwości klasa TJPEGImage
posiada metody klasy TBitmap
(aczkolwiek nie wszystkie), takie jak SaveToFile
, LoadFromFile
, SaveToStream
itp.
Wyświetlanie obrazków w komponencie TImage
Warto wspomnieć o jeszcze jednej kwestii dotyczącej wyświetlenia obrazków w komponencie TImage
. Jak dotąd używaliśmy tego komponentu jedynie do wyświetlania bitmap. Oczywiście nadaje się on również do wyświetlania takich plików, jak np. JPEG, lecz wcześniej konieczne jest dodanie do listy modułów (uses
) pliku JPEG
.
Image.Picture.LoadFromFile('helion.jpg');
Pamiętaj, aby nie odwoływać się w przypadku bitmap do klasy TBitmap
, ale bezpośrednio do TPicture
Przykład działania ? kompresja plików
Na płycie CD-ROM dołączonej do książki znajduje się katalog ..listingi/9/JPEG/, a w nim projekt JpegApp.dpr. Ów program umożliwia kompresowanie bitmap do postaci plików *.jpg. Sama kompresja opiera się wyłącznie na zastosowaniu jednej procedury:
uses jpeg;
procedure TMainForm.btnConvertClick(Sender: TObject);
var
Bitmap : TBitmap;
JPG : TJPEGImage;
begin
Bitmap := TBitmap.Create;
try
{ w przypadku, gdy użytkownik wybierze w oknie plik BMP }
if OpenDialog.Execute then
begin
{ załaduj do klasy }
Bitmap.LoadFromFile(OpenDialog.FileName);
{ miniaturkę wyświetl również w komponencie TImage }
Image.Picture.Bitmap.Assign(Bitmap);
{ utwórz klasę }
JPG := TJPEGImage.Create;
try
{ przypisz obrazek z klasy TBitmap }
JPG.Assign(Bitmap);
if SaveDialog.Execute then
{ zapisanie już skompresowanego obrazka }
JPG.SaveToFile(SaveDialog.FileName);
finally
JPG.Free;
end;
end;
finally
Bitmap.Free;
end;
end;
Oprócz przycisku TButton
na formularzu znajdują się także komponenty TImage
oraz TOpenDialog
i TSaveDialog
. Te dwa ostatnie służą do wyświetlania standardowych ?windowsowych? okien Otwórz plik oraz Zapisz plik.
Jak działa program? Najpierw, po naciśnięciu przycisku, użytkownik musi wskazać plik *.bmp, który ma zostać skompresowany. W tym momencie zostaje utworzona klasa TBitmap
, a podgląd obrazka zostaje wyświetlony w TImage. Następnie użytkownik musi podać nową nazwę pliku, w którym zapisane zostaną skompresowane dane. W tym momencie wystarczy pobrać dane z klasy TBitmap
(za pomocą metody Assign
), a następnie wywołać metodę SaveToFile
.
Pliki GIF
Niestety Delphi nie posiada obecnie żadnego modułu (komponentu) wspomagającego wyświetlanie plików (animacji) GIF. Jedynym rozwiązaniem jest pobranie z Internetu komponentu o nazwie TGIFImage
i zainstalowanie go w Delphi. Instalacją komponentów zajmiemy się w części III tej książki, a sam komponent możesz znaleźć choćby na stronie http://4programmers.net.
Zasoby
Słowo zasoby (ang. resources) może mieć wiele znaczeń, lecz w tym wypadku oznacza dane dodawane i kompilowane wraz z plikiem EXE.
Chcąc wyświetlić w trakcie działania programu jakiś obrazek, musielibyśmy dołączać te wszystkie pliki wraz z programem. Może to się jednak wydać nieco niewygodne ? nie lepiej mieć wszystko w jednej paczuszce (pliku)?
Zasoby w rzeczywistości są plikiem o rozszerzeniu *.res lub *.rc, który może być włączany do programu za pomocą dyrektywy:
{$R ZASOBY.RES}
Powyższa instrukcja może wydawać się komentarzem, ale w rzeczywistości jest poleceniem włączenia pliku ZASOBY.RES
do skompilowanej wersji programu (plik *.exe).
Tworzenie zasobów
Najprostszym środkiem umożliwiającym stworzenie zasobów jest skorzystanie z edytora (program Image Editor). Ów edytor jest standardowo dołączany do Delphi, lecz w rzeczywistości stanowi osobną aplikację; można go uruchomić, wybierając z menu Tools polecenie Image Editor (rysunek 9.5).
Rysunek 9.5. Program Image Editor
Program Image Editor jest w rzeczywistości prostym edytorem graficznym, lecz również nadaje się do tworzenia zasobów.
Aby utworzyć nowy zasób, z menu File należy wybrać New/Resource File. W tym momencie zostanie otwarte nowe okno (rysunek 9.6), które może zawierać różne gałęzie zasobów (bitmapy, ikony, kursory).
Rysunek 9.6. Okienko do tworzenia nowych zasobów
Dołączanie bitmapy
Nie zamierzamy tworzyć nowej bitmapy, lecz wstawić do zasobów już istniejącą. W tym celu z menu File wybierz Open i znajdź na dysku jakiś obrazek BMP.
Image Editor jest dość prostym programem i nie umożliwia wyświetlania obrazków o większej liczbie kolorów niż 255. Ominięciem tego problemu zajmiemy się nieco później.
Najpierw dotychczasowy obrazek musisz skopiować do schowka, naciskając kolejno Ctrl+A (zaznaczenie) i Ctrl+C (skopiowanie). Po tym zabiegu możesz już zamknąć okno z bitmapą. Przejdźmy do naszych zasobów ? kliknij w obszarze tego okna prawym przyciskiem myszy i z menu New wybierz Bitmap. Będziesz musiał wpisać rozmiary nowej bitmapy oraz podać liczbę kolorów (rysunek 9.7).
Rysunek 9.7. Tworzenie nowej bitmapy
Po tym zabiegu na liście pojawiła się nowa gałąź Bitmap, a w niej nasza bitmapa. Po jej otwarciu zobaczymy puste okno ? tutaj należy wkleić skopiowaną uprzednio bitmapę.
Pozostałe zasoby
Tworzenie kolejnych zasobów (kursory, ikony) jest bardzo podobne. W wyniku tych działań nasze okno z zasobami wzbogaci się o nowe gałęzie (rysunek 9.8). Teraz nie pozostaje nam już nic innego, jak zapisać plik (File/Save As) pod nazwą resource.res.
Rysunek 9.8. Bitmapa, kursor oraz ikona włączona do pliku zasobów
Wykorzystanie zasobów
W poprzednim punkcie utworzyłeś plik resource.res, do którego włączony był kursor, bitmapa i ikona. Plik ten możesz znaleźć na płycie CD-ROM w katalogu ..listingi/9/Rest. Pierwszym krokiem jest dołączenie tego pliku do projektu:
{$R RESOURCE.RES}
Ładowanie bitmapy
Podczas omawiania klasy TBitmap
wspominałem o funkcji LoadFromResourceName
, dzięki której w dość prosty sposób można załadować obrazek bezpośrednio z dołączonych zasobów:
procedure TMainForm.btnLoadBitmapClick(Sender: TObject);
begin
imgBitmap.Picture.Bitmap.LoadFromResourceName(hInstance, '1st_bitmap');
end;
W pierwszym parametrze tej procedury należy podać uchwyt do instancji programu. Tak się składa, że zasoby mogą być także ładowane z biblioteki DLL (o tym będzie mowa w kolejnym rozdziale) ? w takim wypadku należałoby podać w tym miejscu uchwyt do biblioteki. Możesz jednak przyjąć, że w większości sytuacji wystarczy słowo kluczowe hInstance. Natomiast drugi parametr procedury LoadFromResourceName
to nazwa bitmapy umieszczonej w zasobach.
Ładowanie ikony
Ikony w Delphi są reprezentowane przez klasę TIcon
. Obsługa tej klasy jest niezwykle podobna do TBitmap
oraz TJPEGImage
? większość właściwości i metod powtarza się, stąd postanowiłem nie omawiać jej dokładniej.
Ładowanie i wyświetlanie ikony może wyglądać tak:
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;
Niestety klasa TIcon
pozbawiona jest funkcji LoadFromResourceName
(dostępna jest tylko LoadFromFile
), więc aby ?wyciągnąć? ikonę z zasobów, należy zastosować metody zastępcze ? w tym wypadku funkcję LoadIcon. Użycie tej funkcji jest podobne do funkcji VCL, lecz zwracany rezultat ma postać typu HICON
(typ WinAPI).
Ładowanie kursora
W Delphi obowiązuje tzw. tablica kursorów. Oznacza to, że każdy kursor ma swój numer i użycie go wiąże się z przypisaniem do odpowiedniej właściwości odpowiedniego numeru ? np. tak:
Screen.Cursor := 1;
W takim wypadku cała aplikacja będzie korzystała z kursora o numerze 1, lecz wcześniej trzeba taki kursor załadować:
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;
Tutaj także korzystamy z funkcji API ? LoadCursor
? i przydzielamy kursor do tablicy Cursors
pod numerem 1. W listingu 9.3. znajduje się przykład ładowania kursora, ikony oraz bitmapy.
Listing 9.3. Kod źródłowy programu
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
{$R RESOURCE.RES}
type
TMainForm = class(TForm)
btnLoadIcon: TButton;
imgIcon: TImage;
imgBitmap: TImage;
btnLoadBitmap: TButton;
procedure FormCreate(Sender: TObject);
procedure btnLoadIconClick(Sender: TObject);
procedure btnLoadBitmapClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
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;
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;
procedure TMainForm.btnLoadBitmapClick(Sender: TObject);
begin
imgBitmap.Picture.Bitmap.LoadFromResourceName(hInstance, '1st_bitmap');
end;
end.
Ręczne tworzenie zasobów
Program Image Editor nie daje nam zbyt dużych możliwości tworzenia bardziej zaawansowanych zasobów, więc należy posłużyć się nieco bardziej skomplikowanymi metodami ? pisaniem kodu zasobów. Tworząc swoje zasoby ręcznie, mamy możliwość większego manipulowania danymi, które mają się tam znaleźć. Możemy umieścić tam dosłownie wszystko ? począwszy od plików JPG, a skończywszy na innych programach.
Całość opiera się na pisaniu skryptów o rozszerzeniu *.rc. Takie skrypty to w rzeczywistości zwykłe pliki tekstowe zawierające odpowiednie polecenia. Następnie ? za pomocą dołączonego do Delphi programu (brcc32.exe) ? są one kompilowane do postaci pliku *.res. Program jest uruchamiany w oknie MS-DOS, a jego rozmiary są bardzo małe. Od razu zalecam skopiowanie go do katalogu, w którym ma się odbyć tworzenie zasobów, gdyż w przeciwnym wypadku wykorzystywanie poleceń systemu MS-DOS będzie dość niewygodne.
Dodawanie plików JPEG
Pierwszym krokiem jest stworzenie w katalogu z programem pliku files.rc. Plik taki możesz otworzyć w każdym edytorze tekstowym i umieścić w nim następujący wiersz:
PIC JPEGFILE "sfp.jpg"
Pierwszy człon tej linii do identyfikator ? słowo, jakie będzie identyfikować właśnie ten obrazek. Kolejny człon to typ pliku. Podawana tu wartość nie jest specjalnie ważna; istotna jest jedynie podczas pisania programu ładującego zasoby. Wreszcie ostatni człon ? słowo umieszczone w cudzysłowach ? to nazwa pliku przeznaczonego do skompilowania do postaci zasobów.
Teraz, mając już plik przeznaczony do skompilowania, musisz uruchomić w oknie MS-DOS program brcc32.exe z parametrem określającym nazwę zasobów ? np.:
brcc32.exe files.rc
W wyniku tego zostanie stworzony plik files.res, który zawiera już obrazek JPG.
Ładowanie pliku JPG
Ponieważ klasa TJPEGImage
nie zawiera procedury umożliwiającej ładowanie obrazków z zasobu, trzeba napisać własną klasę, opartą częściowo na strumieniach.
Pierwszym krokiem jest deklaracja nowej klasy opartej na TJPEGImage
:
{ klasa dziedzicząca po TJPEGImage, która posiada jedną dodatkową
funkcję ładowania z zasobów }
TJPEGRes = class(TJPEGImage)
public
procedure LoadFromResource(const ResID: PChar); virtual;
end;
Dzięki takiemu zabiegowi nasza nowa klasa ? TJPEGRes
? zawiera wszystkie metody z klasy TJPEGImage
, a dodatkowo jeszcze procedurę LoadFromResource
:
procedure TJPEGRes.LoadFromResource(const ResID: PChar);
var
Res : TResourceStream; // utwórz zmienną
begin
{ ładuj obrazek z zasobów }
Res := TResourceStream.Create(hInstance, ResID, 'JPEGFILE');
try
LoadFromStream(Res); // ładuj obrazek do strumienia ze zmiennej Res
finally
Res.Free; // zwolnij pamięć
end;
end;
Mimo że klasa TJPEGImage
jest pozbawiona funkcji LoadFromResourceName
, to znajduje się w niej polecenie LoadFromStream, które teraz okazało się dla nas bardzo przydatne.
Najpierw należało utworzyć klasę TResourceStream
, która posiada bardzo przydatny konstruktor, umożliwiający załadowanie dowolnego pliku wprost z zasobów. Zwróć uwagę na ostatni parametr konstruktora ? jest to typ zasobów, który musi być zgodny z typem, który zadeklarowaliśmy w pliku files.rc. Pełny kod źródłowy tego programu znajduje się w listingu 9.4.
Listing 9.4. Ładowanie pliku JPG z zasobów
unit MainFrm;
interface
{ tutaj przechowywane są zasoby z JPEG }
{$R FILES.RES}
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, jpeg;
type
TMainForm = class(TForm)
btnLoadRes: TButton;
Image: TImage;
procedure btnLoadResClick(Sender: TObject);
end;
{ klasa dziedzicząca po TJPEGImage, która posiada jedną dodatkową
funkcję ładowania z zasobów }
TJPEGRes = class(TJPEGImage)
public
procedure LoadFromResource(const ResID: PChar); virtual;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
{ TJPEGRes }
procedure TJPEGRes.LoadFromResource(const ResID: PChar);
var
Res : TResourceStream; // utwórz zmienną
begin
{ ładuj obrazek z zasobów }
Res := TResourceStream.Create(hInstance, ResID, 'JPEGFILE');
try
LoadFromStream(Res); // ładuj obrazek do strumienia ze zmiennej Res
finally
Res.Free; // zwolnij pamięć
end;
end;
procedure TMainForm.btnLoadResClick(Sender: TObject);
var
JPG : TJPEGRes;
begin
JPG := TJPEGRes.Create; // tworzenie nowej klasy
try
JPG.LoadFromResource('PIC'); // załadowanie odpowiedniego zasobu
Image.Picture.Assign(JPG);
finally
JPG.Free;
end;
end;
end.
Doklejanie plików EXE
Powyższej przedstawiłem sposób na ładowanie obrazków z zasobu wykorzystując klasę TResourceStream
. Chcąc ?wyciągnąć? z zasobów inne pliki ? np. aplikacje ? także należy skorzystać z tego sposobu.
Do pliku exe_file.rc dodałem taką linię:
ASCII EXEFILE "ascii.exe"
W tym wypadku identyfikatorem zasobu jest ASCII, a typ pliku to EXEFILE.
?Wyciągnięcie? zasobu i zapisanie go gdzieś na dysku może 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;
Klasa TCanvas
W Delphi dostępna jest niezwykle wygodna i prosta w użyciu klasa TCanvas
, która uwalnia nas od nieco żmudnego wykorzystywania funkcji API, służących do wykonywania prostych czynności graficznych (wyświetlenie obrazku, namalowanie tekstu itp.). Klasy TCanvas
nie trzeba inicjować ani zwalniać ? wszystko dzięki temu, że zadanie to wykonuje formularz, który posiada właściwość Canvas
.
Prosty przykład:
#Wygeneruj zdarzenie OnPaint formularza.
#W procedurze zdarzeniowej wpisz taki kod:
Canvas.TextOut(10, 10, 'Witaj!');
Instrukcja TextOut
z klasy TCanvas
powoduje wyświetlenie napisu określonego w trzecim parametrze tej procedury. Dwa pierwsze parametry określają pozycje X i Y wyświetlanego napisu (rysunek 9.9).
Rysunek 9.9. Zastosowanie procedury TextOut
Zadajmy sobie pytanie: dlaczego kod musi być umieszczony w zdarzeniu OnPaint
? System Windows działa na zasadzie wielowątkowej. W danym momencie może być uruchomionych wiele programów; mogą one wykonywać swoje, ustalone czynności. Okna te jednak są minimalizowane, a ich pozycje są zmieniane ? system nie jest w stanie ?zapamiętać? obrazu każdego okna, gdyż pochłonęłoby to wiele, wiele pamięci. Stąd w momencie, gdy jakieś okno jest wyświetlane i znajduje się na pierwszym planie, system wysyła do niego komunikat WM_PAINT, stanowiący informację dla programu, że znajduje się on na pierwszym planie. Reakcja an ten komunikat zależy od aplikacji. Stąd obecność zdarzenia OnPaint, dzięki któremu jesteśmy w stanie narysować cokolwiek na formularzu.
Pióra i pędzle
Przed rysowaniem mamy możliwość ustawienia pewnych parametrów. Mam na myśli style określające grubość linii, styl wypełniania figur, wszelkie kolory itp. Ponadto klasa TCanvas udostępnia dwie właściwości ? Pen
(klasa TPen
) oraz Brush
(TBrush
).
Klasa TPen
Właściwość Pen
jest używana do określenia rodzaju linii, koloru linii i innych kształtów w czasie korzystania z klasy TCanvas. Wszystkie właściwości zgromadziłem w tabeli 9.6.
Tabela 9.6. Właściwości klasy TPen
Właściwości | Opis właściwości |
---|---|
Color | Określa kolor rysowanych linii i kształtów (krawędzi) |
Mode | Definiuje tryb rysowanych linii i krawędzi (patrz tabela 9.7) |
Style | Styl rysowanych linii (patrz tabela 9.8) |
Width | Szerokość rysowanej linii (wartość podawana w pikselach) |
Właściwość Mode
Dzięki właściwości Mode
masz dostęp do większej ilości kombinacji definiujących tryb rysowanych linii. Właściwość Mode wskazuje na typ TPenMode
:
type TPenMode = (pmBlack, pmWhite, pmNop, pmNot, pmCopy, pmNotCopy, pmMergePenNot, pmMaskPenNot, pmMergeNotPen, pmMaskNotPen, pmMerge, pmNotMerge, pmMask, pmNotMask, pmXor, pmNotXor);
Sam widzisz, że istnieje stosunkowo sporo rodzajów rysowania ? patrz tabela 9.7.
Tabela 9.7. Wartości typu TPenMode
Wartość | Opis |
pmBlack | Zawsze czarny |
pmWhite | Zawsze biały |
pmNop | Niezmienny |
pmNot | Odwrotność koloru tła klasy TCanvas |
pmCopy | Kolor pióra |
pmNotCopy | Inwersja koloru pióra |
pmMergePenNot | Kombinacja koloru pióra i odwrotności koloru tła |
pmMergeNotPen | Kombinacja koloru tła i inwersja koloru pióra |
pmMerge | Kombinacja koloru tła i pióra. |
pmNotMerge | Odwrotność flagi pmMerge |
Więcej kombinacji możesz znaleźć w systemie pomocy Delphi.
Właściwość Style
Inna ciekawa właściwość ? Style
? określa sposób rysowania linii (ciągły, przerywany itp.). W rzeczywistości właściwość ta wskazuje na typ TPenStyle
:
type TPenStyle = (psSolid, psDash, psDot, psDashDot, psDashDotDot, psClear, psInsideFrame);
W tabeli 9.8 przedstawiłem tłumaczenia powyższych wartości.
Tabela 9.8. Wartości typu TPenStyle
Wartość | Opis |
psSolid | Linia ciągła |
psDash | Linia przerywana |
psDot | Linia kropkowana |
psDashDot | Na przemian: kropki i kreski |
psDashDotDot | Na przemian: kreska i dwie kropki |
psClear | Brak obrysowania |
Mały przykład:
Canvas.Pen.Color := clGreen;
Canvas.Pen.Style := psDashDotDot;
Canvas.Rectangle(80, 10, 250, 150); // narysowanie kwadratu
W powyższym przykładzie zmieniliśmy kolor linii na zielony, a styl rysowania na psDashDotDot
. Następnie z zastosowaniem tych ustawień narysowaliśmy kwadrat (polecenie Rectangle
).
Na płycie CD-ROM w katalogu ..listingi/9/TPenStyle Demo/Demo.dpr znajduje się przykład ilustrujący praktyczne zastosowanie wszystkich stylów.
Jak dotąd nic nie mówiłem na temat kolorów używanych w Delphi. Wszystkie kolory to w rzeczywistości typ TColor. Wszystkie wartości typu TColor posiadają przedrostek cl, tak więc jeśli chcesz skorzystać np. z koloru czarnego i znasz odpowiednik słowa ?czarny? w języku angielskim, możesz się łatwo domyśleć, że wartość odpowiadająca temu kolorowi to clBlack
.
Klasa TBrush
Dzięki klasie TBrush
mamy możliwość ustawienia opcji wypełniania figur (kolor, bitmapa mająca wypełnić figurę, styl). Właściwości owej klasy przedstawione zostały w tabeli 9.9.
Tabela 9.9. Właściwości klasy TBrush
Wartość | Opis |
Bitmap | Wskazuje na klasę TBitmap; określa bitmapę, jaka ma wypełniać figury |
Color | Określa kolor wypełnienia |
Style | Właściwość definiuje styl wypełnienia figur (tabela 9.10) |
Właściwość Style
Podobnie jak w przypadku klasy TPen
, tutaj także właściwość Style określa styl, tyle że dotyczy on wypełnienia figur, a nie obrysowywania.
type TBrushStyle = (bsSolid, bsClear, bsHorizontal, bsVertical, bsFDiagonal, bsBDiagonal, bsCross, bsDiagCross);
Możliwe do zastosowania style opisałem w tabeli 9.10.
Tabela 9.10. Możliwe wartości klasy TBrushStyle
Wartość | Opis |
bsSolid | Pełne wypełnienie |
bsCross | Tło będzie siateczką |
bsClear | Tło przezroczyste |
bsDiagCross | Siatka przecinająca się pod kątem prostym |
bsHorizontal | Linie poziome |
bsVertical | Linie pionowe |
bsBDiagonal | Ukośnie z lewego dolnego narożnika do górnego |
bsFDiagonal | Ukośnie z lewego górnego narożnika do dolnego |
Teraz ? znając właściwości klasy TBrush
? możemy wypróbowywać nie tylko różne ustawienia pióra, ale również pędzla:
Canvas.Pen.Color := clGreen;
Canvas.Pen.Style := psDashDotDot;
Canvas.Brush.Color := clYellow; // kolor tła ? żółty
Canvas.Brush.Style := bsVertical;
Canvas.Rectangle(80, 10, 250, 150); // narysowanie kwadratu
Czcionki
Czcionki w Delphi reprezentowane są przez klasę TFont
, dającą możliwość nie tylko zmiany kroju, ale także zmiany stylu wyświetlanego tekstu. Klasa TCanvas również pozwala na dostęp do TFont
:
Canvas.Font.Name := 'Courier New';
Canvas.TextOut(100, 10, 'Hello World! ');
W powyższym wypadku zmieniliśmy czcionkę na Courier New, a dopiero później narysowaliśmy tekst. Na rysunku 9.10 przedstawiony został przykładowy program, umożliwiający przetestowanie zmiany wyglądu czcionki.
Rysunek 9.10. Testowanie czcionek
Kod źródłowy tego programu jest oczywiście dostępny na płycie CD-ROM.
Właściwości klasy TFont
W tabeli 9.11 znajdują się właściwości klasy TFont
wraz z opisami.
Tabela 9.11. Właściwości klasy TFont
Właściwość | Wartość |
Charset | Kodowanie znaków |
Color | Kolor używanej czcionki |
Height | Wysokość czcionki (w pikselach) |
Name | Nazwa czcionki (musi być podawana w apostrofach) |
Pitch | Moż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) |
Size | Rozmiar czcionki (w punktach) |
Style | Styl czcionki: fsBold (pogrubiony), fsItalic (pochylony), fsUnderline (pokreślenie), fsStrikeOut (przekreślenie) |
Tak się składa, że prawie w każdym komponencie wizualnym znajduje się właściwość Font typu TFont
? wiedza na jej temat może Ci się zatem nieraz przydać.
Tu jednak muszę poczynić małe zastrzeżenie. 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 usunięta, a tekst będzie tylko pogrubiony. Nam nie o to chodzi, gdyż chcemy, aby tekst był pogrubiony, ale przy zachowaniu wcześniejszych styli. Trzeba więc 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 pogrubienia, możesz napisać tak:
Font.Style := Font.Style ? [fsBold];
Jeżeli chcesz zresetować wszystkie style (wszystko zostanie odznaczone), wpisz coś takiego:
Font.Style := [];
Metody klasy TCanvas
Do narysowania różnych kształtów, figur geometrycznych, linii oraz tekstu można skorzystać z metod klasy TCanvas
, które są bardzo proste w użyciu. W większości przypadków należy podać jedynie parametry określające położenie danej figury i jej rozmiar.
W tym podpunkcie przedstawię najważniejsze metody klasy TCanvas
. Część opiszę trochę dokładniej, a inne zaprezentuję w postaci jednego punktu, przedstawiając przykładowy program reprezentujący rysowanie różnych figur geometrycznych.
Draw
procedure Draw(X, Y: Integer; Graphic: TGraphic);
Dzięki funkcji możesz wyświetlić na formularzu jakąś grafikę (np. bitmapę) w miejscu oznaczonym przez parametry X oraz Y.
var
Bitmap : TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile(?helion.bmp?); // ładowanie pliku
Canvas.Draw(10, 10, Bitmap); // wyświetlenie obrazka w punkcie 10, 10
finally
Bitmap.Free;
end;
end;
Dzięki metodzie Draw można się obyć bez komponentów typu TImage
. Pamiętaj jednak, aby powyższy kod umieścić w zdarzeniu OnPaint
albo w jakiejś procedurze zdarzeniowej (np. OnClick
komponentu TButton
).
FillRect
procedure FillRect(const Rect: TRect);
Procedura FillRect
może posłużyć do wypełniania jakiegoś obszaru (określonego parametrem Rect
) z zastosowaniem dotychczasowych ustawień pióra i pędzla.
Canvas.Brush.Color := clWhite;
Canvas.FillRect(Rect(10, 10, 100, 200));
Powyższy kod spowoduje wypełnienie określonego obszaru białym kwadratem. Zwróć uwagę, że w parametrze procedury FillRect znajduje się nieznany dotąd typ ? TRect
. W rzeczywistości jest to rekord, którego deklaracja znajduje się w pliku Windows.pas:
TRect = record
case Integer of
0: (Left, Top, Right, Bottom: Integer);
1: (TopLeft, BottomRight: TPoint);
end;
Nie wykonuje on nic nadzwyczajnego ? chcąc zastąpić ten typ, należałoby zadeklarować w procedurze cztery zmienne typu Integer, które określałyby położenie w pionie i w poziomie oraz szerokość i wysokość. Z zastosowaniem rekordu przekazujemy do procedury tylko jeden parametr.
Zwróć uwagę, że w tym przypadku wcale nie jest konieczna deklaracja nowej zmiennej ? wystarczy przekazać parametry w ten sposób:
Canvas.FillRect(Rect(10, 10, 100, 200));
StretchDraw
procedure StretchDraw(const Rect: TRect; Graphic: TGraphic);
Pamiętasz, jak na początku tego rozdziału zmieniałeś właściwość Stretch komponentu TImage
? Jakie to wówczas dawało efekty? Wtedy osiągaliśmy dopasowanie całego obrazka do wielkości komponentu. Procedura StretchDraw
jest jakby rozszerzeniem polecenia Draw
. Oprócz zwykłego malowania grafiki umożliwia dopasowanie jej rozmiarów ? nie tylko położenia X i Y, ale także szerokości i wysokości.
var
Bitmap : TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('helion.bmp'); // ładowanie pliku
Canvas.StretchDraw(Rect(10, 10, 100, 100), Bitmap);
finally
Bitmap.Free;
end;
end;
W tym wypadku narysowany obraz będzie miał rozmiary 100×100.
TextOut
procedure TextOut(X, Y: Integer; const Text: string);
Z procedurą TextOut
zetknąłeś się już wcześniej. Realizuje ona proste rysowanie po formularzu. Pierwsze dwa parametry są pozycjami reprezentującymi położenie narysowanego tekstu, a ostatni ? typu String
? to tekst, który zostanie wyświetlony.
Oto przykład wyświetlenia bitmapy oraz ? dodatkowo ? rysowania na niej tekstu:
procedure TForm1.Button1Click(Sender: TObject);
var
Bitmap : TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('C:\helion.bmp');
Canvas.Draw(50, 50, Bitmap);
Canvas.Brush.Style := bsClear; // tło rysowanego tekstu ? przezroczyste
Canvas.TextOut(60, 60, 'http://helion.pl');
finally
Bitmap.Free;
end;
end;
Przed narysowaniem tekstu styl pędzla zostaje ustawiony na bsClear
, co gwarantuje, że tekst będzie miał przezroczyste tło (rysunek 9.11).
Rysunek 9.11. Efekt wyświetlania bitmapy i rysowania tekstu
TextRect
procedure TextRect(Rect: TRect; X, Y: Integer; const Text: string);
Oprócz rysowania zwykłego prostokąta procedura TextRect
umożliwia zdefiniowanie prostokąta, wewnątrz którego zostanie narysowany tekst (parametr Rect). Kolejne dwa parametry X i Y określają poziome i pionowe położenie tego tekstu:
Canvas.Brush.Color := clWhite;
Canvas.TextRect(Rect(10, 10, 100, 50), 20, 20, 'Helion');
Aby bardziej uwidocznić istnienie tego prostokąta, na początku zmieniłem kolor pędzla na biały. Efekt zastosowania takiego kodu widać na rysunku 9.12.
Rysunek 9.12. Efekt zastosowania funkcji TextRect
TextWidth, TextHeight
function TextWidth(const Text: string): Integer;
function TextHeight(const Text: string): Integer;
Przypominam, że do obliczania długości łańcucha (w znakach) służy funkcja Length
. W przypadku, gdy chcemy obliczyć wysokość i szerokość tekstu w pikselach, musimy skorzystać z funkcji TextWidth
, TextHeight
. W parametrach tych funkcji należy wpisać jedynie tekst, który ma zostać zmierzony.
Szerokość tekstu zależy również od czcionki, jaką zastosowano. Np. w czcionce Courier New zawarte są znaki o tej samej szerokości, w odróżnieniu od np. czcionki Arial.
TextExtent
function TextExtent(const Text: string): TSize;
Funkcja TextExtent
podaje zarówno wysokość tekstu, jak i jego szerokość. Zwracana wartość ma postać rekordu TSize:
type
TSize = packed record
cx: Longint;
cy: Longint;
end;
MoveTo
procedure MoveTo(X, Y: Integer);
Procedura MoveTo
służy do przeniesienia punktu startowego przed rysowaniem np. linii. Przykładowo rysowana linia ma mieć swój początek w prawym, górnym rogu formularza. Wówczas aby ustawić punkt startowy, należy skorzystać z funkcji MoveTo
:
Canvas.MoveTo(500, 1); // ustawienie punktu startowego
Teraz jeśli chcemy narysować linię, wystarczy wywołać funkcję LineTo
.
LineTo
procedure LineTo(X, Y: Integer);
W owym poleceniu należy podać parametry X i Y rysowanej linii.
procedure TForm1.FormPaint(Sender: TObject);
begin
Canvas.Pen.Width := 5;
Canvas.MoveTo(500, 20);
Canvas.LineTo(10, 200);
end;
Aby lepiej pokazać rysowaną linię, jej szerokość ustawiłem na 5 pikseli (rysunek 9.13).
Rysunek 9.13. Rysowanie linii
Inne funkcje służące do rysowania kształtów
Oprócz funkcji przedstawionych wcześniej istnieje szereg poleceń, dzięki którym możesz narysować wiele różnych figur geometrycznych (tabela 9.12).
Tabela 9.12. Nazwy funkcji służących do rysowania figur geometrycznych
Funkcja | Opis |
Arc | Rysowanie łuku |
Chord | Zamknięta figura (wielokąt) |
Ellipse | Rysowanie elipsy |
Pie | Wycinek koła |
Polygon | Figura rysowana podstawie tablicy punktów |
Polyline | Linia łamana |
Rectangle | Prostokąt |
Przykładowy program
Na dołączonej do książki płycie CD-ROM znajduje się projekt (..listingi/9/PaintTest/PaintTest.dpr), który prezentuje sposób rysowania różnych figur geometrycznych (rysunek 9.14). Użytkownik ma możliwość wyboru rodzaju pędzla oraz pióra ? stylów i kolorów.
Rysunek 9.14. Prezentacja rysowania figur geometrycznych
W listingu 9.5 znajduje się kod źródłowy modułu MainFrm.pas, będącego częścią projektu PaintTest.dpr.
Listing 9.5. Kod źródłowy modułu MainFrm.pas
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Spin;
{ Stałe oznaczające odpowiednio style wypełnienia (TBrushStyle) oraz
styl pędzla. }
const
BrushStyle : array[0..6] of TBrushStyle =
(bsSolid, bsCross, bsDiagCross, bsHorizontal, bsVertical, bsFDiagonal,
bsBDiagonal);
PenStyle : array[0..5] of TPenStyle =
(psDash, psDashDot, psDashDotDot, psDot, psInsideFrame, psSolid);
type
TMainForm = class(TForm)
Panel1: TPanel;
Shape: TRadioGroup;
Setup: TGroupBox;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
LineSize: TSpinEdit;
Style: TComboBox;
PColorLine: TPanel;
PBrushColor: TPanel;
LineColor: TColorDialog;
BrushColor: TColorDialog;
Label5: TLabel;
PStyle: TComboBox;
procedure ShapeClick(Sender: TObject);
procedure PColorLineClick(Sender: TObject);
procedure PBrushColorClick(Sender: TObject);
private
{ Procedura ustawiająca właściwości pędzla oraz wypełnienia }
procedure Ustaw(Sender: TObject);
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.ShapeClick(Sender: TObject);
{ Stała ta zawiera współrzędne punktów, wymagane do wyświetlenia
wycinka koła }
const
Wielokat: array[0..3] of TPoint =
((X: 0; Y:0), (X: 150; Y:50), (X:230; Y:130), (X:40; Y:120));
var
R : TRect;
begin
R := ClientRect; //obszar formy
{ do obszaru odejmij szerokość Panelu }
R := Rect(R.Left, R.Top, R.Right ? Panel1.Width, R.Bottom);
{ W zależności od wybranej opcji na komponencie "TRadioButton" wykonywane
są odpowiednie funkcje. }
case Shape.ItemIndex of
0: begin
Repaint; // odswież obraz
Ustaw(Sender); // wykonaj procedurę
Canvas.Rectangle(20, 20, 220, 220); // prostokąt
end;
1: begin
Repaint;
Ustaw(Sender);
Canvas.Ellipse(R); // elipsa
end;
2: begin
Repaint;
Ustaw(Sender); // wycinek koła
Canvas.Pie(0, 0, ClientWidth ? Panel1.Width, ClientHeight, 90, 0, 300, 10);
end;
3: begin
Repaint;
Ustaw(Sender);
with ClientRect do // łamana [ działa tylko wtedy, gdy Pen.Style = psSolid ]
Canvas.Arc(Left, Top, Right ? Panel1.Width, Bottom, Right, Top, Left, Top);
end;
4: begin
Repaint;
Ustaw(Sender);
Canvas.RoundRect(20, 20, 220, 220, 30, 30); // prostokąt z zaokrąglonymi narożnikami
end;
5: begin
Repaint;
Ustaw(Sender);
Canvas.Polygon(Wielokat); // wielokąt
end;
6: begin
Repaint;
Ustaw(Sender);
with ClientRect do // odcinek
Canvas.Chord(Left, Top, Right ? Panel1.Width, Bottom, Right, Top, Left, Top);
end;
end;
end;
procedure TMainForm.Ustaw(Sender: TObject);
begin
// Ustaw styl wypełnienia w zależności od wybranego stylu w kontrolce
Canvas.Brush.Style := BrushStyle[Style.ItemIndex];
// kolor wypełniania w zależności od koloru Panelu
Canvas.Brush.Color := PBrushColor.Color;
// Ustaw styl pędzla w zależności od opcji wybranej w kontrolce
Canvas.Pen.Style := PenStyle[PStyle.itemIndex];
// kolor pędzla w zależności od koloru panelu
Canvas.Pen.Color := PColorLine.Color;
// Ustaw szerokość pędzla w zależności od wybranej opcji w kontrolce
Canvas.Pen.Width := LineSize.Value;
end;
procedure TMainForm.PColorLineClick(Sender: TObject);
begin
// wyświetla komponent i ustawia kolor Panelu w zależności
// od wybranego koloru w komponencie
if LineColor.Execute then
PColorLine.Color := LineColor.Color;
end;
procedure TMainForm.PBrushColorClick(Sender: TObject);
begin // j/w
if BrushColor.Execute then
PBrushColor.Color := BrushColor.Color;
end;
{ END }
end.
Właściwie wszystko odbywa się tutaj z pomocą instrukcji warunkowej case. Gdy użytkownik wybierze odpowiedni kształt, program pobiera ustawienia kolorów, style pędzla i pióra, a następnie wyświetla żądany kształt.
Proste animacje tekstowe
Pisząc animacje tekstowe, mam na myśli wizualizację tekstu ? tj. jego przemieszczenie i rysowanie 3D. Do napisania programu, który wykonywałby jakieś bardziej skomplikowane animacje (obroty figur 3D), najlepiej jest wykorzystać biblioteki OpenGL albo DirectX.
Tekst trójwymiarowy (3D)
W gruncie rzeczy narysowanie tekstu trójwymiarowego jest bardzo proste. Polega jedynie na podwójnym namalowaniu tego samego napisu, z minimalnym przesunięciem i innym kolorem. Oto 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 przezroczyste
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 należy określić tło tekstu jako przezroczyste (bsClear
). Ustawiamy białą czcionkę 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. Efektem takiego kodu będzie napis wyglądający tak, jak na rysunku 9.15.
Rysunek 9.15. Wyświetlenie napisu 3D
Efekt maszyny do pisania
Kolejnym efektem graficznym, jaki pragnę zaprezentować, jest efekt, który nazwałem maszyną do pisania. W tej animacji litery wyświetlane są jedna po drugiej (rysunek 9.16).
Rysunek 9.16. Wyświetlanie jednej litery po drugiej
Cały efekt opiera się na wyświetlaniu kolejnych liter w czasowych odstępach ? np. 100 milisekund. Na samym początku konieczne staje się pobranie długości napisu, który chcemy przedstawić:
TextLength := Length(DText); // pobierz długość tekstu
Skorzystałem tutaj z funkcji Length, gdyż naszym celem jest pobranie ilości znaków. Ważne jest również to, aby użyta czcionka posiadała wszystkie znaki o tej samej długości:
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); // czekaj 100 milisekund
Brush.Style := bsClear; // styl na przezroczysty
Font.Name := 'Courier New'; // czcionka
Font.Color := clWhite; // kolor czcionki ? biały
Font.Size := 16; // rozmiar
{ Wyświetlaj 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;
Chyba najtrudniejszą rzeczą w tej procedurze jest wytyczenie odstępów pomiędzy kolejnymi znakami. Ja wykorzystałem metodę prób i błędów i okazało się, że najoptymalniejszym rozwiązaniem jest mnożenie zmiennej I przez liczbę 16. Wtedy pierwszy znak zostanie umieszczony w punkcie 16, drugi w punkcie 32 itd.
Przed wywołaniem tej procedury dobrze jest odświeżyć obraz znajdujący się na formularzu (metoda Repaint):
procedure TMainForm.btnRunClick(Sender: TObject);
begin
Repaint; // odśwież Canvas
KrokPoKroku(100, 100); // wywołaj procedurę
end;
Animacja na belce tytułowej
Kolejny efekt, jaki chcę zaprezentować, to przemieszczanie się tekstu po pasku tytułowym programu (rysunek 9.17).
Rysunek 9.17. Tekst wyświetlany na pasku tytułowym
Na samym początku będziesz musiał zadeklarować zmienną globalną, która będzie określać, czy animacja jest wciąż uruchomiona:
var Running : Boolean = TRUE;
Kolejny krokiem jest deklaracja w sekcji private procedury Go
:
procedure Go(const PText : TStrings);
Owa procedura jako parametr przyjmuje dane w postaci typu TStrings
. Każdy wiersz tekstu znajdujący się w tym typie będzie osobno pokazywany na pasku tytułowym. Gdy zostaną już pokazane wszystkie wiersze, animacja rozpocznie swe działanie od początku:
procedure TMainForm.Go(const PText: TStrings);
var
PTextLong : Integer; // długość tekstu
I : Integer;
LinesCount : Integer; // ilość wierszy
begin
while (Running) do // dopóki zmienna będzie miała wartość 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 pierwszego wiersza
begin
if not Running then Break; // znów sprawdź...
PTextLong := Length(PText[LinesCount]); // pobierz długość wiersza
Sleep(500); // odczekaj pół sekundy
Caption := ''; // wymaż Caption
for I := 0 to PTextLong do // wykonuj pętlę literka po literce
begin
Application.ProcessMessages;
Sleep(100); // z przerwą 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;
Kod tej procedury może się wydać nieco odstraszający, lecz warto zapoznać się z komentarzami ? wówczas wiele spraw okaże się łatwiejszymi. Aby wszystko dobrze działało, należało umieścić tu aż trzy pętle. Pierwsza ? while
? służy do kontrolowania, czy użytkownik nie chce przypadkiem zamknąć aplikacji. Jeżeli nie ? powtarza cały proces od początku.
Kolejna pętla pobiera kolejne wiersze ze zmiennej typu TStrings
; zagnieżdżona w niej jest kolejna pętla for, wyświetlająca napis litera po literze. W tym wypadku sprawa jest znacznie prostsza, niż w poprzednim przykładzie, gdzie rysowanie kolejnych liter musiało być dokładnie mierzone. W listingu 9.6. znajduje się kod źródłowy modułu.
Listing 9.6. Kod źródłowy 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ść wierszy
begin
while (Running) do // dopóki zmienna będzie miała wartość 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 pierwszego wiersza
begin
if not Running then Break; // znów sprawdź...
PTextLong := Length(PText[LinesCount]); // pobierz dlugość wiersza
Sleep(500); // odczekaj pół sekundy
Caption := ''; // wymaż Caption
for I := 0 to PTextLong do // wykonuj pętlę literka po literce
begin
Application.ProcessMessages;
Sleep(100); // z przerwą 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 wierszy, które po kolei będą wyświetlane }
Add('Delphi 7.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.
Inne płynne animacje
W Delphi istnieje oczywiście możliwość tworzenia innych, płynnych animacji ? niekoniecznie tekstowych. Na rysunku 9.18 przedstawiony jest program, którego działanie polega na przemieszczaniu rysunku po całym oknie programu. Cały problem tkwi w tym, aby rysunek ?odbijał się? od krawędzi okna i wędrował w inne, losowe kierunki okna.
Rysunek 9.18. Rysunek przemieszczający się po oknie programu
Kod źródłowy tego programu zamieszczam na płycie CD-ROM ? zachęcam do samodzielnego przetestowania go (katalog ../listingi/9/ImgPaint/ImgPaint.dpr).
Przemieszczanie obrazków po formularzu opiera się, ogólnie rzecz biorąc, na każdorazowym rysowaniu obrazka w innej pozycji. W przypadku programu przedstawionego na rysunku 9.18 za przemieszczanie obrazka odpowiada osobny wątek (o wątkach była mowa w poprzednim rozdziale). Jeżeli jednak chcemy za każdym razem przerysowywać obrazek, należy uprzednio odświeżyć obraz (metoda Repaint). W wielu przypadkach może to powodować migotanie okna, dające nieprzyjemny dla oka efekt.
W takich przypadkach dobrze jest stosować tzw. podwójne buforowanie.
DoubleBuffered := True;
W przypadku, gdy właściwość (nie jest to właściwość klasy TCanvas
, ale klasy TWinControl
? patrz rozdział 14.) ma wartość False
, rysowanie odbywa się bezpośrednio na formularzu, co może rzeczywiście powodować migotanie obrazu. Zmiana wartości na True
może częściowo poprawić efekt wizualny.
W przykładowym programie z rysunku 9.18 rozwiązałem ten problem nieco inaczej, umieszczając na formularzu komponent TPaintBox
i rysując bezpośrednio na nim.
Odtwarzanie dźwięków
Obecnie istnieje wiele formatów dźwiękowych ? od tych najprostszych (jak WAV) po najpopularniejsze, typu mp3. Na razie zajmiemy się odtwarzaniem tych najmniej skomplikowanych plików dźwiękowych ? WAV (fala ? ang. wave). Może nie jest to bardzo popularny format (pliki tego rodzaju często mają duże rozmiary), lecz znakomicie nadaje się do odtwarzania prostych dźwięków.
Funkcja PlaySound
W module MMSystem
zadeklarowana jest funkcja API PlaySound
, dzięki której odtwarzanie prostych dźwięków jest czynnością raczej nieskomplikowaną ? oto jej deklaracja:
function PlaySound(pszSound: PChar; hmod: HMODULE; fdwSound: DWORD): BOOL; stdcall;
Pierwszy parametr musi zawierać ścieżkę do odtwarzanego dźwięku; drugi to wskazanie modułu ? w naszym przypadku w tym miejscu wystarczy wstawić cyfrę 0. Ostatni parametr to flaga. Najprostsze odtworzenie dźwięku *.wav może wyglądać tak:
PlaySound('C:\plik.wav' 0, SND_FILENAME);
W tabeli 9.13 umieściłem możliwe do zastosowania flagi.
Tabela 9.13. Flagi polecenia PlaySound
Flaga | Opis |
SND_ALIAS | Odtwarzanie dźwięku systemowego |
SND_FILENAME | Odtwarzany plik znajduje się na dysku |
SND_NOWAIT | Flaga nakazuje wstrzymanie odtwarzania, jeżeli jest już odtwarzany jakiś inny dźwięk |
SND_NOSTOP | Odtwarzanie nastąpi w przypadku, gdy nie jest odtwarzany inny utwór |
SND_RESOURCE | Plik muzyczny będzie odtwarzany z zasobów |
SND_LOOP | Powoduje odtwarzanie utworu w pętli. Tylko w zastosowaniu z SND_ASYNC |
SND_ASYNC | Odtwarzanie będzie odbywać się w tle |
SND_NODEFAULT | Jeżeli plik do odtworzenia nie istnieje, nie jest generowany dźwięk ostrzegawczy |
SND_PURGE | Zatrzymanie odtwarzania |
Istnieje możliwość połączenia kilku flag za pomocą operatora or.
Przy omawianiu parametru SND_ALIAS wspomniałem o możliwości odtwarzania pliku systemowego. Takie dźwięki są zadeklarowane w rejestrze Windows i generowane w wyniku zajścia jakiś sytuacji ? np. uruchomienia systemu, zamknięcia go, kliknięcia myszą itp.
PlaySound('MailBeep', 0, SND_ALIAS or SND_NODEFAULT);
W tym przypadku odegrany zostanie dźwięk MailBeep, zapisany w rejestrze.
Użycie komponentu TMediaPlayer
Zabawa funkcjami WinAPI nie ma zbytniego sensu, gdy mamy pod ręką komponent TMediaPlayer
, którego użycie wiąże się tylko z wywoływaniem kolejnych metod. Komponent ten znajduje się w palecie komponentów na zakładce System ? umieść go na formularzu. Wygląd kontrolki nie przedstawia się zbyt zachęcająco ? ja zawsze ukrywam widoczną paletę sterowania, zmieniając właściwość Visible
na False
.
Oto przykład rozpoczęcia odtwarzania pliku mp3:
with Player do
begin
FileName := 'C:\Live set at home.mp3'; // określenie ścieżki do pliku
Open; // otwarcie pliku
Play; // rozpoczęcie odtwarzania
end;
Na samym początku konieczne staje się przydzielenie ścieżki pliku do właściwości FileName komponentu. Jeżeli wiemy już, jaki plik chcemy odtwarzać, wystarczy wybrać metodę Open
, a następnie Play.
Ważniejsze metody komponentu TMediaPlayer przedstawione zostały w tabeli 9.14.
Metoda | Opis |
Back | Cofa odtwarzanie o określoną we właściwości Frames liczbę klatek |
Close | Wyłącza urządzenie, co wiąże się ze zatrzymaniem odtwarzania |
Open | Włącza urządzenie i przygotowuje do odtwarzania |
Pause | Wstrzymuje odtwarzanie |
Play | Rozpoczyna odtwarzanie |
Resume | Wznawia odtwarzanie |
Stop | Zatrzymuje odtwarzanie |
Odtwarzanie filmów
Za pomocą komponentu TMediaPlayer
można równie dobrze odtwarzać także filmy. Na dołączonej do książki płycie CD-ROM (../lisingi/9/Player/Player.dpr) znajduje się program (odtwarzacz), który napisałem dość dawno, bo 3 lata temu, lecz jest wciąż aktualny. Program w trakcie działania przedstawiony jest na rysunku 9.19, a jego kod znajduje się w listingu 9.7.
Rysunek 9.19. Odtwarzacz multimedialny
Listing 9.7. Kod źródłowy odtwarzacza
unit MainFrmU;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
MPlayer, StdCtrls, ExtCtrls, ComCtrls, Menus, Buttons, MMSystem, Registry,
ToolWin, ImgList;
type
TMainFrm = class(TForm)
HomeMenu: TMainMenu;
File1: TMenuItem;
FileOpen: TMenuItem;
N1: TMenuItem;
StatusB: TStatusBar;
Open: TOpenDialog;
PP: TPanel;
TextPanel: TPanel;
Scroll: TScrollBar;
Player: TMediaPlayer;
Timer: TTimer;
Sound: TTrackBar;
Image1: TImage;
Exit: TMenuItem;
Close: TMenuItem;
Widok1: TMenuItem;
FullS: TMenuItem;
N2: TMenuItem;
OnTop: TMenuItem;
Bar: TToolBar;
Play: TToolButton;
Pause: TToolButton;
ImageList: TImageList;
Stop: TToolButton;
About1: TMenuItem;
procedure FileOpenClick(Sender: TObject);
procedure ScrollScroll(Sender: TObject; ScrollCode: TScrollCode;
var ScrollPos: Integer);
procedure TimerTimer(Sender: TObject);
procedure PlayerNotify(Sender: TObject);
procedure SoundChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Image1Click(Sender: TObject);
procedure PPClick(Sender: TObject);
procedure CloseClick(Sender: TObject);
procedure FullSClick(Sender: TObject);
procedure OnTopClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure PauseClick(Sender: TObject);
procedure PlayClick(Sender: TObject);
procedure StopClick(Sender: TObject);
procedure About1Click(Sender: TObject);
private
Button: TMPBtnType;
procedure ShowText(Text: String);
public
FName : String;
Tx : String;
end;
var
MainFrm: TMainFrm;
implementation
uses BigFrmU;
{$R *.DFM}
procedure TMainFrm.ShowText(Text: String);
begin
{
Procedura, która wyświetla w Panelu tekst.
}
TextPanel.Caption := Text;
end;
procedure TMainFrm.FileOpenClick(Sender: TObject);
begin
try
if Open.Execute then
FName := Open.FileName; //Przypisanie ścieżki zmiennej
Player.FileName := FName; //Przypisanie odtwarzaczowi zmiennej
Player.Open; // Otwarcie filmu
Player.Display := PP; { Przypisanie odtwarzaczowi obszaru
wyświetlania jako Panel }
Player.DisplayRect := PP.ClientRect; { Dopasowanie rozmiarów
filmu do rozmiarów Panelu }
Scroll.Position := 0; //Wskaźnik na 0
Tx := ExtractFileName(Player.FileName); //Odłączenie nazwy pliku od ścieżki
ShowText(Tx); // i wyświetlenie jej w Panelu "TextPanel"
Caption := Tx;
except
on EMCIDeviceError do
raise Exception.Create(Format(
'Nie mogę otworzyć pliku o rozszerzeniu %s. Sprawdź, czy plik nie '+
'jest uszkodzony lub czy prawidłowy jest jego format danych.',
[ExtractFileExt(Player.FileName)]));
end;
end;
procedure TMainFrm.ScrollScroll(Sender: TObject; ScrollCode: TScrollCode;
var ScrollPos: Integer);
begin
{
Pozycja odtwarzacza równać się będzie pozycji Scrolla.
Jeżeli zmieni się pozycja Scrolla, film zostanie "przewinięty"
}
Player.Position := Scroll.Position;
end;
procedure TMainFrm.TimerTimer(Sender: TObject);
begin
{ j/w }
Scroll.Position := Player.Position;
end;
procedure TMainFrm.PlayerNotify(Sender: TObject);
begin // jeżeli film się skończy
if Player.NotifyValue = nvSuccessful then // Jeżeli operacja się zakończy
begin
Timer.Enabled := False; // wyłączenie Timera
Scroll.Position := Scroll.Position; //Scroll na tej samej pozycji
Player.Stop;
StopClick(Sender);
end;
end;
procedure TMainFrm.SoundChange(Sender: TObject);
begin //Ustawienie głośności...
case Sound.Position of
1:
WaveOutSetVolume(0, $20002000); {2000}
2:
WaveOutSetVolume(0, $60006000); {6000}
3:
WaveOutSetVolume(0, $80008000); {8000}
4:
WaveOutSetVolume(0, $90009000); {9000}
5:
WaveOutSetVolume(0, $FFFFFFFF); {maksymalna głośność}
end;
end;
procedure TMainFrm.FormCreate(Sender: TObject);
var
Reg : TRegistry;
Key : Boolean;
I : Integer;
S : String;
begin
// wczytaj wartości z rejestru
Reg := TRegistry.Create;
try
Key := Reg.OpenKey(
'Software\Player', False);
if Key then
begin
Top := Reg.ReadInteger('Top');
Left := Reg.ReadInteger('Left');
OnTop.Checked := Reg.ReadBool('StayOnTop');
end else
OnTop.Checked := False;
finally
Reg.Free;
end;
end;
procedure TMainFrm.Image1Click(Sender: TObject);
begin
Sound.Position := 1; // Wyciszenie
end;
procedure TMainFrm.PPClick(Sender: TObject);
begin
try
Player.Pause;
ShowText('Wciśnięto pauzę...');
except
ShowText('Brak filmu...');
end;
end;
procedure TMainFrm.CloseClick(Sender: TObject);
begin
Player.Close; // Zamknięcie filmu
ShowText('Film został zamknięty.'); // Wyświetlenie tekstu
Caption := 'Player';
end;
procedure TMainFrm.FullSClick(Sender: TObject);
begin
FullForm.BorderStyle := bsNone; // ukryj pasek
FullForm.WindowState := wsMaximized; // maksymalizacja okna
Player.Display := FullForm.FullPanel;// obraz na Panelu
with FullForm.FullPanel do // rozmiar panelu dopasowany do rozmiaru filmu
Player.DisplayRect := Rect(0, 0, Width, Height);
OnTop.Checked := False;
FullForm.ShowModal; // Wyświetlenie okna 2
end;
procedure TMainFrm.OnTopClick(Sender: TObject);
begin
{ Zawrze na wierzchu }
OnTop.Checked := not OnTop.Checked;
if OnTop.Checked = True then
FormStyle := fsStayOnTop;
end;
procedure TMainFrm.FormDestroy(Sender: TObject);
var
Reg : TRegistry;
begin
Reg := TRegistry.Create;
try
Reg.OpenKey(
'Software\Player', True);
// zapisz pozycję okna
Reg.WriteInteger('Top', Top);
Reg.WriteInteger('Left', Left);
// zapisz pozycję "StayOnTop"
Reg.WriteBool('StayOnTop',OnTop.Checked);
finally
Reg.Free;
end;
end;
procedure TMainFrm.PauseClick(Sender: TObject);
begin // pauza
try
Player.Pause; // zastopuj odtwarzanie filmu
Play.Down := False; // przyciski "odciśnięte"
Stop.Down := False;
Pause.Down := True;
ShowText('Wciśnięto pauzę...'); // zmień tekst
except // wyjątek
Pause.Down := False;
raise Exception.Create(
'Film nie jest odtwarzany!');
end;
end;
procedure TMainFrm.PlayClick(Sender: TObject);
begin
try
Scroll.Max := Player.Length; //Przypisanie maksymalnej wartości do długości filmu
Timer.Enabled := True; // Włączenie Timera
ShowText('Trwa odtwarzanie...');
Player.Play;
Play.Down := True;
Stop.Down := False;
Pause.Down := False;
except
Play.Down := False;
raise Exception.Create(
'Najpierw musisz wybrać film do otwarcia!');
end;
end;
procedure TMainFrm.StopClick(Sender: TObject);
begin
try
Player.Stop;
Play.Down := False;
Pause.Down := False;
Stop.Down := True;
ShowText('Zatrzymano film...');
except
Stop.Down := False;
raise Exception.Create(
'Nie można wyłączyć filmu, ponieważ nie jest on odtwarzany!');
end;
end;
procedure TMainFrm.About1Click(Sender: TObject);
begin
MessageDlg(
' Player v. BETA '+#13+#13+
'Autor: Adam Boduch '+#13+
'E-mail: adam@boduch.net '+ #13+
' http://4programmers.net',mtInformation,
[mbOK], 0);
end;
end.
Przy okazji analizowania kodu możesz sobie przypomnieć funkcje związane z zapisem danych do rejestru ? program bowiem zapisuje w nim aktualne ustawienia oraz położenie okna.
Odtwarzanie filmu
Odtwarzanie filmu przebiega w podobny sposób, jak odtwarzanie dźwięku ? na początku należy przypisać wartość zmiennej FileName, później otworzyć film (Open), a na końcu rozpocząć odtwarzanie (Play
). Jedyna różnica jest taka, że odtwarzany film będzie wyświetlany na komponencie TPanel
? odpowiada za to taki kod:
Player.Display := PP;
Player.DisplayRect := PP.ClientRect;
Oprócz określenia obszaru na którym zostanie wyświetlony film, następuje tu także dopasowanie rozmiarów wyświetlanego filmu do rozmiarów panelu (właściwość DisplayRect
).
Pozycja odtwarzanego filmu
Jak pewnie zauważyłeś, oprócz zwykłego odtwarzania pasek (komponent TScrollBar
) pokazuje pozycję odtwarzanego filmu. Można to zrealizować za pomocą komponentu TTimer
. Zadaniem komponentu TTimer jest wykonywanie jakiejś czynności w pewnych odstępach czasu (w naszym wypadku ? co 1 sekundę).
procedure TMainFrm.TimerTimer(Sender: TObject);
begin
Scroll.Position := Player.Position;
end;
Co 1 sekundę pozycja scrolla (paska przewijania) jest uaktualniania w stosunku do pozycji odtwarzanego filmu.
Próba przewinięcia tego paska spowoduje ustawienie nowej pozycji dla odtwarzania filmu:
procedure TMainFrm.ScrollScroll(Sender: TObject; ScrollCode: TScrollCode;
var ScrollPos: Integer);
begin
Player.Position := Scroll.Position;
end;
Ustawianie głośności
Program posiada także opcje ustawiania głośności filmu. Wszystko to dzięki funkcji API ? WaveOutSetVolume
:
WaveOutSetVolume(0, $90009000);
W drugim parametrze należy podać wartości dla prawego oraz lewego głośnika ? w tym wypadku wartość wynosi $9000.
Niektóre odtwarzacze filmów umożliwiają również wyświetlanie napisów w przypadku, gdy film nie jest w polskiej wersji językowej. Jak to zrobić? Pokażę to w 13. rozdziale książki.
Kontrolka Flash
Format plików Flash zyskał sobie dużą popularność dzięki prostocie tworzenia animacji oraz dużej efektywności, jaką zapewnia. W Delphi istnieje możliwość odtwarzania animacji *.swf tworzonych za pomocą programu Flash, lecz będzie do tego potrzebna kontrolka ActiveX dołączona do tego programu.
ActiveX jest to plik OCX, który można nazwać komponentem. Jest to standard opracowany przez Microsoft. O kontrolkach ActiveX będzie mowa w rozdziale 13. niniejszej książki.
Plik SWFLASH.OCX znajdziesz w katalogu C:\Windows\System\Macromed\Flash (przynajmniej tak jest w moim przypadku).
Instalacja kontrolki
Z menu Component wybierz polecenie Import ActiveX Control. Okno to przedstawiono na rysunku 9.20.
Rysunek 9.20. Importowanie nowej kontrolki
Naciśnij przycisk Add i znajdź wśród katalogów szukaną kontrolkę ActiveX. Później na liście kliknij pozycję Shockwave Flash. Naciśnij przycisk Install, aby zainstalować kontrolkę. Zostanie wyświetlone okno, w którym powinieneś jeszcze raz nacisnąć OK. Zobaczysz wówczas takie okno, jakie przedstawiono na rysunku 9.21.
Rysunek 9.21. Instalacja kontrolki ActiveX
Zostaniesz zapytany, czy kontynuować instalację kontrolki w palecie komponentów. Naciśnij Yes. Następnie powinieneś ujrzeć informację o prawidłowym zainstalowaniu kontrolki. Naciśnij Ctrl+S, aby zapisać wszystko. Teraz możesz już otworzyć nowy projekt. Nowy komponent do wyświetlania animacji Flash znajduje się na palecie ActiveX.
Wykorzystanie komponentu
Przede wszystkim musisz dysponować jakąś animacją w formacie Flash! Jeżeli już takową masz, to na formularzu umieść komponent TShockwaveFlash
z palety ActiveX. Wygeneruj zdarzenie OnCreate
formularza ? do wyświetlenia filmu będą potrzebne tylko dwa wiersze kodu.
procedure TMainForm.FormCreate(Sender: TObject);
begin
Flash.Movie := ExtractFilePath(Application.ExeName) + 'banner.swf';
Flash.Play;
end;
W komponencie tym wymagane jest podanie dokładnej ścieżki do pliku SWF. Musimy posłużyć się funkcją Aplication.ExeName
, która zwraca pełną ścieżkę dostępu do naszej aplikacji. Zakładając, że film znajduje się w katalogu z programem, musimy jeszcze użyć funkcji ExtractFilePath
. Funkcja ta z parametru wyodrębnia jedynie ścieżkę. Przykładowo jeśli wywołamy funkcję w ten sposób:
ExtractFilePath('C:\Moje dokumenty\app\mój.exe');
zostanie zwrócona wartość: C:\Moje dokumenty\app
Działanie programu zaprezentowano na rysunku 9.22.
Rysunek 9.22. Wykorzystanie kontrolki TShockwaveFlash
Podsumowanie
Być może nie wyczerpałem do końca tematu i coś pominąłem, ale starałem się zaprezentować główne aspekty wykorzystania grafiki oraz operowania dźwiękiem. Mam nadzieję, że przedstawione tu informacje przydadzą Ci się w przyszłości?! A może napiszesz odtwarzacz filmów lepszy niż ten, który tu zaprezentowałem?
Załączniki:
Więcej informacji Delphi 2005. Kompendium programisty Adam Boduch Format: B5, stron: 1048 oprawa twarda Zawiera CD-ROM |
czy możecie dać do pobrania plików ten odtwarzacz filmów???
Spoko, dodałem formatowanie do tabelek (mam nadzieję, że żadnej nie pominąłem) :)
Smialo - edytuj tekst i dodaj tabelke.
Po prostu jest to najbardziej czasochlonne (montowanie tabelki ;)) a w tym rozdziale jest ich duzo wiec sobie dalem sopkoj. Ale fajnie gdybys Ty dodal.
hmm... Tabela 9.7. Wartości typu TPenMode - może dać tę tabelkę w i tak dalej?