Delphi, cz. 11
Adam Boduch
Nie omówiłem jeszcze ważnych zagadnień z grafiki. Nadrobię to tu i teraz :)
Regiony
Regiony stanowią obszar, w którym mają być wykonywane operacje graficzne. ( np. wyświetlenie bitmapy ). Regiony wywodzą się z funkcji Windows API. Co to takiego? Są to funkcje Microsoftu, które nie są umieszczone w bibliotekach wchodzących w skład Windowsa. Nie musimy tych funkcji ładować 'ręcznie'. Dobra. Regiony. A więc możesz zdefiniować region ( np. elipsę ), w którym będzie umieszczona bitmapa. Tak służy do tego funkcja SelectClipRgn. Oto przykład załadowanie obrazka, zdefiniowania regionu w kształcie elipsy, a następnie do tej elipsy załadowanie obrazu:
var Bitmap: TBitmap; begin Bitmap := TBitmap.Create; Bitmap.LoadFromFile('C:\Moje dokumenty\obrazek.BMP'); // zładuj obraz // zdefiniuj region.... SelectClipRgn(Canvas.Handle, CreateEllipticRgn(20, 20, 190, 190)); Canvas.Draw(10, 10, Bitmap); // wyswietl region w punkcie 10, 10 Bitmap.Free; // zwolnij pamiec end;
Efekt jest dość ładny. Pierwszym parametrem funkcji SelectClipRgn jest uchwyt okna. O uchwytach mówiłem w poprzednim rozdziale. Płótno ( Canvas ) także ma swój uchwyt. ( w każdej funkcji API należy podawać uchwyt ). Drugi parametr to sam region. W tym wypadku jest to region w kształcie elipsy. W nawiasach podawane są współrzędne regionu. Uruchom program i sprawdź jego działanie. Istnieją jeszcze inne regiony: CreatePolygonRgn - wielokąt; CreateRectRgn - prostokąt; CreateRoundRectRgn - prostokąt z zaokrąglonymi narożnikami; Polecam poczytać na temat tych funkcji w systemie pomocy Delphi. Ja podam przykład z jeszcze dwoma regionami. Po pierwsze CreateRoundRecrRgn.
SelectClipRgn(Canvas.Handle, CreateRoundRectRgn(20, 20, 190, 190, 40, 40));
Gdy wpiszesz w poprzedni przykład taką linie to zamiast elipsy ujrzysz prostokąt z zaokrąglonymi narożnikami. Ostatnie dwa parametry tej funkcji to "siła" zaokrąglenia narożników :) Jeszcze jeden przykładzik z wielokątem:
var Bitmap: TBitmap; const Points : array[0..3] of TPoint = // współrzędne punktów ((X:100; Y:0), (X:0; Y:100), (X:100; Y:200), (X:200; Y: 100)); begin Bitmap := TBitmap.Create; Bitmap.LoadFromFile('C:\Moje dokumenty\obrazek.BMP'); // zładuj obraz // zdefiniuj region.... SelectClipRgn(Canvas.Handle, CreatePolygonRgn(Points, 4, ALTERNATE)); Canvas.Draw(10, 10, Bitmap); // wyswietl region w punkcie 10, 10 Bitmap.Free; // zwolnij pamiec
Żeby stworzyć region w kształcie wielokąta należy zadeklarować tablicę zawierająca punkty. W naszym wypadku są to 4 punkty. Następnie funkcja "CreatePolygonRgn". Pierwszym parametrem tej funkcji jest tablica zawierająca współrzędne punktów, kolejny to ilość punktów, a ostatni to dodatkowy parametr. Zamiast niego można wstawić WINDING.
Funkcja "DrawText"
Funkcja "DrawText" jest kolejna fajna funkcja WinAPI. Służy ona do wpisywania tekstu. Daje ona jednak duże możliwości w stosunku do "TextOut". Umożliwia ona np. wyświetlenie tekstu wycentrowanego w pionie, w poziomie w jakimś prostokącie. Ten przykład wyświetla obrazek, a na nim wyświetla tekst, który jest wycentrowany w pionie i poziomie:
var B : TBitmap; R : TRect; begin R := Rect(20, 20, 200, 200); // określ rozmiary i położenie B := TBitmap.Create; // stwórz bitmape B.LoadFromFile('C:\Moje dokumenty\obrazek.bmp'); Canvas.StretchDraw(R, B); // wyświetl obrazek Canvas.Font.Color := clWhite; // kolor czcionki Canvas.Brush.Style := bsClear; // tlo przezroczyste Canvas.Font.Size := 12; // rozmiar DrawText(Canvas.Handle, 'Funkcja DrawText', -1, R, DT_CENTER or DT_VCENTER or DT_SINGLELINE); B.Free; end;
Być może nie jest to zbyt trafny przykład jak na pierwsze zetknięcie z funkcją "DrawText". No, ale po kolei. Na samym początku zadeklarowaliśmy zmienną typu "TRect". Określa ona położenie prostokąta. Następnie załadowanie obrazka. Ale cóż to? Gdzie jest funkcja "Draw"? Nie ma :) Zamiast tego użyłem funkcji "StretchDraw". Ta funkcja wyświetla obrazek według własnych pozycji tzn., że TY ustalasz jakie mają być wymiary i położenie obrazka, a system albo rozciąga, albo zwęża obrazek w zależności od Twoich upodobań. Funkcja ta posiada dwa parametry: zmienna typu "TRect", która określa położenie i wymiary obrazka, a drugim parametrem jest sam obrazek do wyświetlenia. Później następuje odpowiednie dopasowanie czcionki i wreszcie użycie funkcji "DrawText". Pierwszym jej parametrem jest uchwyt do płótna. Drugi to tekst, który ma być wyświetlony. Trzeci to liczba wyświetlonych znaków - cyfra -1 oznacza wszystkie znaki. Kolejny parametr to obszar, w który ma być wpisany tekst ( musi być to zmienna typu "TRect" ). Ostatni parametr to tzw. flagi. Określają one styl wyświetlania tekstu. W tym wypadku ma być tekst wycentrowany w poziomie ( DT_CENTER ), wycentrowane w pionie ( DT_VCENTER ) oraz ma być to jedna linia ( DT_SINGLELINE ). Funkcja "DrawText" ma wiele flag ( ich znaczenia poszukaj w systemie pomocy ). Jeszcze dwa fajne przykłady z użyciem tej funkcji. Po pierwsze flaga DT_END_ELLIPSIS. Jeżeli użyjesz tej flagi to jeżeli tekst będzie za długi, aby zmieścić się w prostokącie Windows utnie go i na końcu postawi trzykropek. Oto przykład użycia tej funkcji:
var R : TRect; begin R := Rect(20, 20, 250, 100); // określ rozmiary i położenie
DrawText(Canvas.Handle, 'Funkcja DrawText. Za długi tekst, aby się ' + 'tutaj zmieścić', -1, R, DT_END_ELLIPSIS);
Czy do tej funkcji wymagany jest jakiś komentarz? Chyba nie. Jeszcze jeden przykład z użyciem flagi DT_WORDBREAK. Jeżeli tekst jest dłuższy niż szerokość prostokąta to tekst zostanie odpowiednio dopasowany, aby jeden wyraz zapisany był pod drugim:
var R : TRect; begin R := Rect(20, 20, 250, 50); // określ rozmiary i położenie Canvas.Rectangle(R.Left, R.Top, R.Right, R.Bottom);
DrawText(Canvas.Handle, 'Funkcja DrawText. Za długi tekst, aby się ' + 'zmieścić w jednej linii', -1, R, DT_WORDBREAK);
Kreślenie linii
Delphi umożliwia także kreślenie linii. Grubość i kolor linii ustalana jest na podst. ustawien pióra ( była o tym mowa w poprzednich rozdziałach ). Zanim zakreślimy linie należy ustawić początek linii.
Canvas.MoveTo(20, 100); // ustawienie poczatku w punkcie 20, 100 Canvas.LineTo(500, 100); // rysowanie od punktu 20 do 500. Pozycja Y to 100
Nie jest to skomplikowane. W powyższym przykładzie kreślona jest pozioma linia. Na początku należy ustawić początek ( punkt, w którym linia będzie rysowana ). Przypominam, ze pierwszy parametr oznacza pozycje X, a drugi Y. Kolejna funkcja rysuje linie. Pierwszym parametrem jest liczba na której linia się zakończy. Drugim to parametr Y.
Przechwytywanie obrazu ( Screen )
Podam jeszcze jeden przykład, w którym przechwytywany będzie obraz tj. pulpit. Inaczej mówiąc zrobimy program, który będzie robił zrzut z ekranu. Do tego celu wykorzystamy do tego klasę "TScreen". Ta klasa posiada wymiary pulpitu, wysokość pulpitu, szerokość itp. Oto kod:
var Can: TCanvas; B : TBitmap; begin
{ tworzenie zmiennej } Can := TCanvas.Create; { przechwycenie uchwytu ekrnau } Can.Handle := GetWindowDC(GetDesktopWindow);
{ tworzenie bitmapy } B := TBitmap.Create; B.Width := Screen.Width; B.Height := Screen.Height; B.Canvas.CopyRect(Rect(0, 0, Screen.Width, Screen.Height), Can, Rect(0, 0, Screen.Width, Screen.Height));
Canvas.StretchDraw(Rect(0, 0, Width, Height), B); // wyświetl na formie B.SaveToFile('C:\ekran.bmp'); // zapisz pulpit na dysk Can.Free; B.Free;
end;
Musisz wiedzieć, że "Canvas" to także zmienna typu "TCanvas". Możesz więc samemu taką zadeklarować. Tak też zrobiłem. Stworzyłem zmienną. Ale to nie wszystko bo trzeba pobrać uchwyt pulpitu. Dokonuje tego kolejna linia. W podobny sposób możesz także pobierać uchwyty komponentów:
var C:TCanvas; begin C:=TCanvas.Create; C.Handle:=GetDC(Panel1.Handle); // na Panelu { Tutaj mozesz rysowac jak po zwyklym Canvasie } C.Free;
Jak widzisz najpierw stworzona została zmienna typu "TCanvas", następnie została stworzona, a później pobrany został uchwyt komponentu i dopisany został do zmiennej "C". Uchwyt komponentu pobiera funkcja "GetDC". No, ale powróćmy do poprzedniego kodu. Następnie utworzona zostaje zmienna typu "TBitmap". Jej rozmiary zostają dopasowane do rozmiarów ekranu. Kolejna funkcja to "CopyRect". Kopiuje ona bitmapę z obszaru źródła do obszaru przeznaczenia. W tym wypadku określa ona obszar pulpitu, przypisuje go do zmiennej "Can", a następnie 'kopiuje' ten obszar także na obszar pulpitu. Później następuje wyświetlenie bitmapy na formie i odpowiedniej jej dopasowanie. Później już tylko zapisanie na dysk tej bitmapy. Cóż może nie jest to łatwe, ale kto powiedział, że programowanie jest łatwe?:)
Edytor zasobów
Pewnie denerwuje Cię to, że bitmapki, które chcesz załadować do programu muszą być oddzielnie, a nie w jednym pliku EXE. Cóż jest na to sposób.
Ładowanie kursorów
Normalnie możesz załadować kursor z dysku. Oto jak to zrobić:
Screen.Cursors[1] := LoadCursorFromFile('cursor.ani'); Screen.Cursor := 1;
Już wyjaśniam. Normalnie Delphi numeruje swoje kursory ok -17 do 0; dlatego też żeby włączyć swój kursor do tablicy kursorów. W naszym wypadku będzie to cyfra 1. Następnie następuje załadowanie kursora z pliku. Kursor może być animowany. Następnie do formy przypisany zostaje nasz kursor. Można to robić indywidualnie do osobnych komponentów: Button1.Cursor := 1; Delphi oprócz tego posiada zestaw wbudowanych kursorów. Jeżeli klikniesz na jakiś komponent możesz w Inspektorze Obiektów rozwinąć właściwości "Cursor" aby poznać możliwe kursory. Zróbmy prosty test. Umieść na formie Edit'a i Button. W "OnClick" Button'a wpisz taki tekst:
var I, Czas: Integer; begin Czas := StrToInt(Edit1.Text); // przekształć String'a na Integer Screen.Cursor := crHourGlass; // zmień kursor for i := Czas downto 0 do // odliczaj do tyłu begin Application.ProcessMessages; Edit1.Text := IntToStr(i); // wynik pętli w Edicie end; Screen.Cursor := crDefault; // przywróć domyślny kursor end;
W tym wypadku następuje odliczanie od cyfry, która została wpisana w Edit do zera. W czasie działania pętli kursor zmienia swój kształt na klepsydrę dzięki czemu nie można wykonać żadnych operacji aż do zakończenia pętli kiedy przywracany zostaje domyślny kursor. No dobra, ale ja miałem mówić o edytorze zasobów. Otóż Delphi oferuje takie narzędzie dzięki któremu można wsadzić do EXE kursory, bitmapy i ikony. Nazywa się to edytor zasobów. Edytor ten uruchamia się poprzez wybranie z menu "Tools" -> "Image Editor". Jest to standardowe narzędzie Delphi. Moim zdaniem jest one dość toporne. Do zasobów można dodawać tylko bitmapy 256 - kolorowe; nie można dodawać kursorów animowanych. Możesz oczywiście namalować kursor czy bitmapę w tymże edytorze lecz nie będzie to ładnie wyglądało. Dobra otwórz jakąś bitmapę 256 kolorową. ( polecenie "File" -> "Open" ). W oknie na liście filtrów wybierz *.bmp! Jeżeli udało Ci się taką otworzyć to OK. Kliknij na szarym polu prawym przyciskiem myszy i z menu wybierz "Properties". Zapamiętaj rozmiary bitmapy, które podane są w oknie, które się pojawi. Z menu wybierz "File" -> "New" -> "Resource File". W nowym okienku prawym przyciskiem kliknij ( ale składnia :)) i wybierz "New" -> "Bitmap". W nowym okienku wpisz rozmiary Twojej bitmapy. Aha, nie zapomnij zaznaczyć opcji "Super VGA". No dobra, stworzona została nowa bitmapa. W okienku pojawiła się nowa gałąź. Teraz musisz skopiować bitmapę, która otworzyłeś ( Ctrl + A, a następnie Ctrl + C ). To okno możesz już zamknąć. Zostało tylko jedno. Powinna się tam znaleźć gałąź "Bitmap". W tej gałęzi znajdują się wszystkie bitmapy. Powinna się tam znaleźć Twoja stworzona bitmapa ( Bitmap1 ). Kliknij na nią - pojawi się nowe okienko. Wklej do niego skopiowaną wcześniej bitmapę ( Ctrl + V ). Możesz to okno zamknąć. Zmień nazwę tej bitmapy ( prawy przycisk myszy -> "Rename" ). Wpisz "MAINBITMAP". Dobra! Gotowe! Stworzyłeś bitmapę. Teraz musisz to wszystko zapisać. ( "File" -> "Save As" ). Zapisany zostanie plik RES, który zawiera Twoją bitmapę. Ten plik podczas kompilacji zostanie włączony do programu. Zapisz go pod nazwą: "BITMAPS.RES". To wszystko, teraz przejdź do Delphi. Trzeba będzie napisać trochę kodu:
1. W sekcji "Interface" dodaj taką linię:
{$R BITMAPS.RES}
Ta linia powoduje dołączenie zasobu do EXE. Teraz musisz napisać procedurę.
procedure TForm1.Button1Click(Sender: TObject); var Bitmap : TBitmap; begin Bitmap := TBitmap.Create; Bitmap.LoadFromResourceName(hInstance, 'MAINBITMAP'); Canvas.Draw(0, 0, Bitmap); Bitmap.Free; end;
Nic specjalnego. Załadowanie bitmapy z zasobów następuje poprzez polecenie "LoadFromResourceName". Pierwszym parametrem tej funkcji jest określenie, że bitmapa będzie ładowana z zasobów. Jest to zawsze takie same słowo "hInstance". Drugim parametrem jest nazwa bitmapy do załadowania. Tak samo ma się sprawa z załadowaniem kursora:
Screen.Cursors[1] := LoadCursor(hInstance, 'MAINCURSOR'); Screen.Cursor := 1;
Jeżeli chcesz załadowac ikonę to umieść na formie komponent "Image" i napisz:
Image1.Picture.Icon.Handle := LoadIcon(hInstance, 'OCZKO');
Nic specjalnego. Delphi domyślnie nazywa swoją ikonę ( ikone EXEka ) jako "MAINICON" także Ty musisz nazwać swoją ikonę na literę rozpoczynająca się na literę 'dalszą' niż M - np: O. :))
Cóż, w tym rozdziale chyba nie przedstawię tworzenia animacji poprzez ładowanie bitmap z zasobów. Ale to jest wyzwanie dla Was! Tak, tak. Na podst. wiedzy, która teraz osiągnąłeś spróbuj napisać program, który ładował będzie z zasobów kilka bitmapek po kolei tworząc w ten sposób efekt animacji. Rozwiązanie w kolejnej części kursu! Jeżeli macie ochotę to zajrzyjcie na stronę: www.programowanie.of.pl - tam znajdziesz wiele programów, kodów źródłowych, artykułów, FAQ-ów, wszystko dotyczące Delphi.
Adam Boduch
e-mail: boduch@poland.com http://www.programowanie.of.pl
|