Rozdział 12. WinAPI
Adam Boduch
Nie sposób było nie wspomnieć w tej książce o WinAPI. Z tym terminem zetknąłeś się już wielokrotnie podczas lektury niniejszej publikacji. Teraz chciałbym omówić ten temat nieco dogłębniej. Nie jest możliwe bowiem zawarcie wszystkich informacji dotyczących WinAPI w jednym rozdziale ? temat ten jest na tyle rozległy, że można by mu poświęcić osobną książkę. Więcej o WinAPI możesz dowiedzieć się ze strony http://msdn.microsoft.com lub z systemu pomocy Delphi (znajdziesz tam dokładny opis funkcji i procedur).
1 Czym tak naprawdę jest WinAPI?
1.1 Zasady tworzenia programów za pomocą WinAPI
1.2 Brak zdarzeń
1.3 Brak komponentów
1.4 Zalety wykorzystania WinAPI
2 Pierwszy program
3 Funkcja okienkowa
4 Rejestracja klasy
5 Tworzenie formularza
6 Komunikaty i uchwyty
7 Łańcuchy
7.5 Konwersja łańcuchów
7.6 Funkcje operujące na łańcuchach
7.6.1 CharLower, CharUpper
7.6.2 lstrlen
7.6.3 lstrcpyn
8 Tworzenie kontrolek
8.7 Umieszczanie kontrolek przy starcie programu
8.8 Flagi kontrolek
9 Obsługa zdarzeń
10 Uchwyty do kontrolek
11 Tworzenie bardziej zaawansowanych kontrolek
11.9 Pozostałe kontrolki
12 Wyświetlanie grafiki
12.10 Rysowanie w WinAPI
12.11 Kontekst urządzenia graficznego
12.12 Obsługa WM_PAINT
12.12.4 Zmiana koloru tła
12.13 Ładowanie i wyświetlanie bitmapy
13 Ładowanie zasobów
13.14 Skompilowane zasoby
13.15 Wykorzystanie zasobów
13.15.5 Wyświetlenie formularza
13.15.6 Ustawianie wartości komponentów formularza
13.16 LockResource, LoadResource, FindResource
13.17 Zapisywanie plików na dysku
14 Podsumowanie
Można powiedzieć, że ten rozdział przeznaczony jest dla ?maniaków? Delphi w pozytywnym tego słowa znaczeniu. Programowanie w WinAPI nie jest bowiem łatwe i wygodne, ale umożliwia zachowanie większej kontroli nad programem.
Czym tak naprawdę jest WinAPI?
Pełna nazwa tego skrótu to Windows Application Programming Interface . Dla osób, które wcześniej tworzyły swoje programy w Turbo Pascalu, biblioteka wizualna i klasy mogą wydać się dość niezrozumiałe. Z kolei dla niektórych prostsze może okazać się rozpoczęcie pisania programów metodą API. Ty jednak jesteś już zapewne przyzwyczajony do stosowania klas i komponentów, a WinAPI może Ci się wydać trudne lub po prostu nieciekawe. Problem, którego rozwiązanie przy użyciu komponentów wymagało jednego wiersza kodu, przy wykorzystaniu WinAPI może oznaczać konieczność napisania nawet kilkudziesięciu wierszy! Dlatego nie zdziwię się, jeżeli ominiesz ten rozdział i przejdziesz od razu do kolejnego.
Zasady tworzenia programów za pomocą WinAPI
Wyobraź sobie pisanie programów bez wykorzystania formularzy, komponentów i wszystkich innych udogodnień oferowanych przez Delphi. Nasze programy będą oparte jedynie na podstawowych modułach Windows.pas i Messages.pas. Wszystkie funkcje, z których będziemy korzystać, zawarte są w bibliotekach DLL systemu Windows. Ich załadowanie do programu odbywa się w module Windows.pas.
Nam, użytkownikom tych bibliotek, potrzebna jest wiedza o ich budowie ? liczbie i typie parametrów, wartościach zwracanych przez funkcję itp. Stąd miej na uwadze perspektywę częstego zaglądania do pomocy Delphi.
Przy tej okazji warto wspomnieć o możliwości choć częściowego nauczenia się języka C. System Windows był pisany w tym języku, stąd opisy wszystkich funkcji API (deklaracje) są również w nim przedstawione. Jest to pewna okazja do poznania choćby w małym stopniu budowy języka C.
Gdy kiedyś zaczniesz pisać programy w języku C++, znajomość WinAPI bardzo Ci się przyda! Nazwy funkcji są takie same ? jedynie składnia nieco się różni.
Brak zdarzeń
Podczas pisania programów w ?czystym? API będziemy pozbawieni wygodnego mechanizmu, jakim są zdarzenia. Jak pamiętasz z rozdziału 5., mechanizm zdarzeń można zastąpić poprzez komunikaty Windows. Ten moment jest więc dobrą okazją, aby cofnąć się do rozdziału 5. i przypomnieć sobie zasadę funkcjonowania komunikatów.
Brak komponentów
W API będziemy musieli obyć się bez komponentów. Te ?klocki?, jakimi są komponenty, w dużym stopniu odciążały nas od mozolnego operowania komunikatami czy pamięcią. Nie będziemy jednak pozbawieni typowych kontrolek Windows, jak przycisk czy lista rozwijalna ? będziemy je tworzyć w kodzie programu za pomocą funkcji CreateWindow
.
Zalety wykorzystania WinAPI
Zastanawiasz się może, co takiego zyskasz, używając mechanizmów WinAPI? Powiem szczerze: niewiele. Dużym plusem jest szybkość działania aplikacji oraz rozmiar. Programy tworzone w Delphi i wykorzystujące VCL mają duże rozmiary. Nawet prosty program w postaci ?czystego? formularza potrafi zajmować grubo ponad 300 kB. Aplikacje wykorzystujące jedynie WinAPI mogą zajmować nawet 16 kB i są wykonywane znacznie szybciej. Różnica jest znaczna, nieprawdaż?
Czytając ten rozdział, masz możliwość zaznajomienia się z dotąd nieznanymi funkcjami, z których być może będziesz musiał skorzystać w swoich programach, gdy VCL okaże się niewystarczający i zbytnio będzie Cię ograniczał.
Głównymi zaletami Delphi są przecież biblioteka VCL, klasy oraz formularze, dzięki którym tworzenie programów trwa znacznie krócej. Jeśli porzucisz te udogodnienia, pisanie aplikacji może zająć więcej czasu, a nie po to chyba tworzono Delphi, prawda?
Podsumowując: WinAPI jest okazją do głębszego zaznajomienia się z tematyką programowania w systemie Windows, lecz nie nadaje się do pisania dużych projektów.
Pierwszy program
Przypomnij sobie rozdział 2. Wówczas poznawałeś dopiero język Object Pascal, ale tworzone przez Ciebie programy także nie zawierały żadnych komponentów czy formularzy. Te programy po skompilowaniu także miały rozmiar kilkunastu kilobajtów ? można zatem powiedzieć, że już wtedy pisałeś programy WinAPI!
Zamknij formularz i Edytor kodu. Następnie z menu Project wybierz polecenie View Source. Kod źródłowy projektu (pliku *.dpr) doprowadź do takiej postaci:
program Project1;
uses
Windows;
begin
end.
Na razie nie potrzebujemy pliku zasobów, więc usunąłem także dyrektywę {$R}. Ikonę naszego programu możemy dodać w następnej kolejności. Tak powstały kod źródłowy zapisz gdzieś na dysku. Następnie z menu Project wybierz polecenie Build, co spowoduje skompilowanie aplikacji i utworzenie pliku *.exe. Spójrz teraz na rozmiar aplikacji ? u mnie jest to 14 kB! Na razie co prawda program jest ?pusty?, ale już wkrótce co nieco do niego dodamy.
Funkcja okienkowa
Dotąd nasz program kończył pracę zaraz po uruchomieniu go ? w bloku begin
nie ma przecież żadnej instrukcji. Naszym celem jest napisanie takiego programu w WinAPI, który zakończyłby pracę po interwencji użytkownika ? zamknięciu okna. Musimy więc napisać kod, który spowodowałby wyświetlenie formularza. Jednym z etapów tworzenia takiego formularza jest napisanie funkcji okienkowej. Funkcja taka będzie odpowiedzialna za odbieranie wszystkich komunikatów, które docierają do okna i ewentualną reakcję na dany komunikat. Zadeklaruj więc w programie taką funkcję:
function WndProc(Wnd: HWND; uMsg: UINT; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
begin
end;
Znaczenie parametrów tej funkcji jest następujące:
*Wnd ? uchwyt do okna.
*uMsg ? komunikat.
*wPar ? pierwsza wartość komunikatu.
*lPar ? druga wartość komunikatu.
Taka budowa jest nieprzypadkowa ? aby cały program został prawidłowo skompilowany, funkcja okienkowa musi wyglądać tak, jak to przedstawiłem powyżej. Pierwszym komunikatem, jaki będzie obsługiwany przez funkcję okienkową, jest WM_DESTROY
. Program musi odpowiednio zareagować na próbę zamknięcia programu.
function WndProc(Wnd: HWND; uMsg: UINT; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
begin
{ na początek zwracamy wartość 0 ? meldunek jest przetwarzany }
Result := 0;
case uMsg of
{ w tym miejscu należy obsłużyć należne komunikaty }
{ w funkcji DefWindowProc przekazujemy takie same parametry, jak w funkcji okienkowej }
WM_DESTROY: PostQuitMessage(0);
else Result := DefWindowProc(Wnd, uMsg, wPar, lPar);
end;
end;
Jak widzisz, instrukcja case
sprawdza, jaki komunikat został odebrany przez funkcję okienkową. W przypadku odebrania komunikatu WM_DESTROY
program kończy pracę ? PostQuitMessage
. Na samym jednak początku przypisujemy funkcji wartość zwrotną ? cyfrę 0. W przeciwnym wypadku ? jeżeli nadesłany komunikat ?nas interesuje? ? przekazujemy go dalej, do domyślnego okna. Realizuje to funkcja DefWindowProc
; parametry muszą być identyczne z parametrami funkcji okienkowej.
Aby program został prawidłowo skompilowany, na liście uses musi znaleźć się moduł Messages.pas
.
Rejestracja klasy
Aby cały formularz mógł zostać stworzony, uprzednio należy zarejestrować klasę. Rejestracja klasy następuje poprzez wywołanie funkcji RegisterClass
z modułu Windows.pas
.
function RegisterClass(const lpWndClass: TWndClass): ATOM; stdcall;
W parametrze owej funkcji należy podać zmienną wskazującą rekord TWndClass
:
TWndClass = packed record
style: UINT;
lpfnWndProc: TFNWndProc;
cbClsExtra: Integer;
cbWndExtra: Integer;
hInstance: HINST;
hIcon: HICON;
hCursor: HCURSOR;
hbrBackground: HBRUSH;
lpszMenuName: PAnsiChar;
lpszClassName: PAnsiChar;
end;
Powyższy rekord określa wygląd formularza, kolor tła, styl i kursor. Znacznie parametrów jest następujące:
*style ? parametr ów określa styl wyświetlanego okna. Możliwe jest mieszanie stylów za pomocą operatora or.
*lpfnWndProc ? jest to wskazanie na funkcję okienkową.
*cbClsExtra ? liczba dodatkowych bajtów alokowanych wraz z rekordem.
*cbWndExtra ? liczba dodatkowych bajtów alokowanych wraz z instancją okna.
*hInstance ? uchwyt do zasobów.
*hIcon ? identyfikacja formularza.
*hCursor ? kursor używany w czasie wyświetlania formularza.
*hbrBackground ? tło formularza. Możliwe jest zastosowanie jednej z poniższych wartości: COLOR_ACTIVEBORDER, COLOR_ACTIVECAPTION, COLOR_APPWORKSPACE, COLOR_BACKGROUND, COLOR_BTNFACE, COLOR_BTNSHADOW, COLOR_BTNTEXT, COLOR_CAPTIONTEXT, COLOR_GRAYTEXT, COLOR_HIGHLIGHT, COLOR_HIGHLIGHTTEXT, COLOR_INACTIVEBORDER, COLOR_INACTIVECAPTION, COLOR_MENU, COLOR_MENUTEXT, COLOR_SCROLLBAR, COLOR_WINDOW, COLOR_WINDOWFRAME, COLOR_WINDOWTEXT.
*lpszMenuName ? wskazanie na łańcuch określający menu używane w programie.
*lpszClassName ? wskazanie na nazwę klasy (wartość typu PChar
).
Rejestracja nowej klasy może być wykonana w poniższy sposób:
var
Wnd: TWndClass; // klasa okna
begin
with Wnd do
begin
lpfnWndProc := @WndProc; // funkcja okienkowa
hInstance := hInstance; // uchwyt do zasobów
lpszClassName := 'My1stApp'; // klasa
hbrBackground := COLOR_WINDOW; // kolor tła
end;
RegisterClass(Wnd); // zarejestruj nową klasę
end;
W moim przypadku nie było konieczne wypełnianie wszystkich pól rekordu TWndClass
. Przypisałem jedynie te pola, które wydawały się konieczne do uzyskania przynajmniej podstawowego wyglądu formularza.
Tworzenie formularza
Na szczęście tworzenie samego formularza nie jest czynnością zbytnio skomplikowaną. Realizuje to bowiem jedna instrukcja ? CreateWindow
:
function CreateWindow(lpClassName: PChar; lpWindowName: PChar;
dwStyle: DWORD; X, Y, nWidth, nHeight: Integer; hWndParent: HWND;
hMenu: HMENU; hInstance: HINST; lpParam: Pointer): HWND;
Przyznasz, że ilość parametrów jest spora:
*lpClassName ? nazwa klasy (wartość PChar). Wartość ta musi się równać wartości wpisanej w rekordzie TWndClass.
*lpWindowName ? łańcuch określający tekst, który będzie wyświetlany na formularzu. Znaczenie parametru można porównywać do właściwości Caption komponentów.
*dwStyle ? styl okna (tabela 12.1).
*x ? położenie formularza w poziomie. Wstawienie w to miejsce stałej CW_USEDEFAULT powoduje automatyczne dopasowanie położenia przez system (nowe okno będzie przesunięte lekko w lewą stronę).
*y ? położenie formularza w pionie. Wstawienie w to miejsce stałej CW_USEDEFAULT powoduje automatyczne dopasowanie położenia przez system.
*nWidth ? szerokość formularza. Tutaj także stała CW_USEDEFAULT powoduje automatyczne dopasowanie szerokości.
*nHeight ? wysokość formularza. Stała CW_USEDEFAULT powoduje dopasowanie wysokości formularza.
*hWndParent ? uchwyt do okna rodzica.
*hMenu ? wskazanie do menu, które ma być użyte w programie.
*hInstance ? określa instancję modułu, który ma być kojarzony z programem.
Na podstawie tych danych utworzenie formularza może wyglądać tak:
CreateWindow('My1stApp', 'Pierwszy program w WinAPI',
WS_VISIBLE or WS_TILEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NIL);
Tabela 12.1. Najczęstsze wartości określające styl okna
Wartość | Opis |
---|---|
WS_OVERLAPPED | Okno posiada pasek tytułowy oraz obramowanie |
WS_CHILD | Potomne okno, które nie może ?wyjść? poza okno rodzicielskie |
WS_POPUP | Okno dialogowe |
WS_CAPTION | Okno ma pasek tytułu |
WS_SYSMENU | Okno ma menu systemowe |
WS_MINIMIZEBOX | Okno ma przycisk minimalizacji |
WS_MAXIMIZEBOX | Okno ma przycisk maksymalizacji |
WS_VISIBLE | Okno jest widoczne |
WS_HIDE | Okno jest ukryte |
WS_DISABLED | Nieaktywne okno ? nie reaguje na zdarzenia |
WS_BORDER | Okno posiada ramkę |
Listing 12.1. Pierwszy program napisany w WinAPI
{
Copyright (c) 2002 by Adam Boduch
}
program WndApp;
uses
Windows,
Messages;
function WndProc(Wnd: HWND; uMsg: UINT; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
begin
{ na początek zwracamy wartość 0 ? meldunek jest przetwarzany }
Result := 0;
case uMsg of
{ w tym miejscu należy obsłużyć należne komunikaty }
{ w funkcji DefWindowProc przekazujemy takie same parametry, jak w funkcji okienkowej }
WM_DESTROY: PostQuitMessage(0);
else Result := DefWindowProc(Wnd, uMsg, wPar, lPar);
end;
end;
var
Wnd: TWndClass; // klasa okna
Msg: TMsg;
begin
with Wnd do
begin
lpfnWndProc := @WndProc; // funkcja okienkowa
hInstance := hInstance; // uchwyt do zasobów
lpszClassName := 'My1stApp'; // klasa
hbrBackground := COLOR_WINDOW; // kolor tła
end;
RegisterClass(Wnd); // zarejestruj nową klasę
CreateWindow('My1stApp', 'Pierwszy program w WinAPI',
WS_VISIBLE or WS_TILEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
0, 0, hInstance, NIL);
while GetMessage(msg, 0, 0, 0) do DispatchMessage(msg);
end.
W listingu 12.1 zaprezentowano cały kod źródłowy programu. Nie omawiałem jeszcze ostatnich instrukcji tego listingu. Są one bardzo ważne ? bez nich program nie będzie mógł zostać uruchomiony. Operacje te muszą być wykonane, aby funkcja okienkowa otrzymała potrzebne meldunki. Podczas uruchamiania program musi wejść w tzw. fazę meldunków. Funkcja GetMessage pobiera kolejno meldunki, wpisując je do struktury TMsg
(parametry są nieistotne), a następnie przekazuje funkcji DispatchMessage
, która z kolei przekazuje meldunek funkcji okienkowej.
Komunikaty i uchwyty
Pisząc programy w WinAPI, będziemy posługiwali się wyłącznie komunikatami jako formą zastępującą zdarzenia (funkcje SendMessage i PostMessage). Warto więc przypomnieć sobie informacje na temat wysyłania komunikatów. Ich odbieranie będzie następowało tylko w funkcji okienkowej.
Odświeżmy zatem pamięć ? komunikaty można podzielić na następujące kategorie:
*komunikaty klawiaturowe (użytkownik nacisnął lub zwolnił jakiś klawisz),
*komunikaty myszy (użytkownik wykonał jakąś czynność myszą),
*komunikaty zegara, oznaczające upływ określonego odcinka czasu,
*komunikaty systemu ? tworzenie okna, zmiana jego rozmiaru i położenia, zwijanie i rozwijanie okna, zmiana kolorów systemowych itp.,
*komunikaty wewnętrzne ? wysyłane przez inne okna utworzone w naszym programie.
W każdym komunikacie należy podać uchwyt okna docelowego (lub kontrolki docelowej). Uchwyt jest liczbą 32-bitową, która identyfikuje kontrolkę w systemie Windows. To właśnie Windows przydziela uchwyty różnym kontrolkom. Nazwa typu reprezentującego uchwyt zaczyna się od litery H, czyli np. HWND
, HBRUSH
, HFONT
czy HBITMAP
. Dzięki temu łatwo jest rozpoznać, czy dana zmienna jest uchwytem.
Łańcuchy
Podczas pisania programów w WinAPI będziemy używali jedynie łańcuchów typu PChar
lub łańcuchów w formie tablicy. Stosowanie typu String
powoduje spowolnienie działania programu i zużywanie większej ilości pamięci.
Typ PChar
ma jeszcze jedną zaletę ? można dokonywać na nim takich operacji:
P2 := 'To jest Delphi';
P1 := P2 + 8;
Pozornie wygląda to tak, jakby do zmiennej P2 dodawana była cyfra 8. W rzeczywistości do typu P1 przypisujemy wartość zmiennej P2, tyle że bez pierwszych 8 znaków. Podczas lektury dalszej części książki możesz spotkać się także z deklaracjami zmiennych w postaci tablicy:
Variable : array[0..255] of char;
Do takiej zmiennej można następnie przypisać dane w zwykły sposób, tyle że ich wielkość będzie ograniczona do 255 znaków.
Variable := 'Adam Boduch';
Konwersja łańcuchów
W VCL ten problem nie istniał ? moduł SysUtils
posiadał odpowiednie funkcje, umożliwiające konwersję typów. Pisząc programy API, nie będziemy mogli z nich skorzystać ? pozostaje nam użycie funkcji zastępczych, np. wvsprintf
.
Oto możliwy sposób wykonania funkcji zastępczej:
function IntToStr(Value : Integer) : String;
var
Buffer : array[0..255] of char; // bufor, w którym przechowywać będziemy dane
begin
wvsprintf(Buffer, '%d', @Value); // tu następuje funkcja konwersji
Result := Buffer; // zwracamy rezultat
end;
Funkcja wvsprintf
służy do konwertowania tekstu. Pierwszym parametrem musi być wskazanie zwracanego przez funkcję ciągu. Ja zadeklarowałem Buffer ? 256-elementową tablicę typu Char
. Drugi parametr to tzw. maska. Jeśli wstawimy w to miejsce znak %d
, zostanie on zastąpiony liczbą typu Integer
. Owa liczba to zmienna Value, przekazywana jako trzeci parametr. Przykładowy program prezentujący działanie łańcuchów, zamieściłem w listingu 12.2.
W powyższej funkcji IntToStr
zadeklarowałem tablicę 256-elementową (standardowo), chociaż tak naprawdę aż tak duża wartość nie jest konieczna.
Wyjątkowo w powyższej funkcji jako zwracanego rezultatu użyłem typu String
. Zrobiłem to tylko po to, aby upodobnić budowę funkcji do rzeczywistego wyglądu funkcji IntToStr
z modułu SysUtils
.
Listing 12.2. Pełny kod źródłowy programu
{
Copyright (c) 2002 by Adam Boduch
}
uses Windows;
function IntToStr(Value : Integer) : String;
var
Buffer : array[0..255] of char; // bufor, w którym przechowywać będziemy dane
begin
wvsprintf(Buffer, '%d', @Value); // tu następuje funkcja konwersji
Result := Buffer; // zwracamy rezultat
end;
begin
MessageBox(0, PChar('Witaj w ' + IntToStr(12) + ' części książki!'), 'Witaj!', MB_OK); // wyświetl wartość zmiennej
end.
Inny przykład wykorzystania funkcji wvsprintf
:
Buffer : array[0..255] of char;
Format : packed record // deklaracja rekordu danych do konwersji
Int : Integer;
Fl : String;
end;
begin
{ wypełnienie danych do konwersji }
Format.Int := 11;
Format.Fl := 'Adam Boduch';
wvsprintf(Buffer, 'Witaj w %d części kursu, ja nazywam się %s!', @Format);
MessageBox(0, Buffer, '', 0);
end.
W tym wypadku w drugim parametrze w łańcuchu znajdują się dwa znaki ? %s
i %d
. Zostaną one po konwersji zastąpione danymi w postaci liczby Integer
oraz łańcucha String
.
Funkcje operujące na łańcuchach
Poniżej przedstawiam kilka funkcji WinAPI operujących na łańcuchach. Mogą one okazać się przydatne podczas pisania aplikacji w API.
CharLower, CharUpper
function CharLower(lpsz: PChar): PChar; stdcall;
function CharUpper(lpsz: PChar): PChar; stdcall;
Obie funkcje powodują zamianę znaków odpowiednio na małe lub wielkie litery. Pierwsza z nich (CharLower
) zamienia litery z wielkich na małe, a CharUpper
? z małych na wielkie.
program main;
uses Windows;
begin
MessageBox(0, CharLower('TO JEST PROGRAM W WINAPI'), '', MB_OK);
MessageBox(0, CharUpper('to jest program w winapi'), '', MB_OK);
end.
Warto się zainteresować także funkcją CharLowerBuff
i CharUpperBuff
. Obie także powodują zamianę znaków, lecz posiadają także dodatkowy parametr, który określa liczbę znaków, które mają zostać zamienione.
lstrlen
function lstrlen(lpString: PChar): Integer; stdcall;
Funkcja lstrlen
podaje długość łańcucha określonego w parametrze lpString
. Długość podawana jest w znakach.
Writeln(lstrlen('Adam'));
Powyższa instrukcja wyświetli na ekranie liczbę 4.
lstrcpyn
function lstrcpyn(lpString1, lpString2: PChar; iMaxLength: Integer): PChar; stdcall;
Funkcja służy do kopiowania części łańcucha do drugiej zmiennej. Pierwszy parametr musi być wskazaniem łańcucha, do którego zostaną skopiowane dane. Drugi parametr ? lpString2 ? to miejsce, z którego dane zostaną pobrane. Ostatni parametr ? iMaxLength ? określa liczbę znaków do skopiowania:
program main;
uses Windows;
{$APPTYPE CONSOLE}
var
P1 : array[0..50] of char;
begin
lstrcpyn(P1, 'Delphi jest narzędziem typu RAD', 7);
Writeln(P1);
Readln;
end.
Powyższy kod źródłowy spowoduje wyświetlenie na ekranie napisu Delphi.
Tworzenie kontrolek
Zarówno tworzenie komponentów, jak i różnych kontrolek odbywa się za pośrednictwem funkcji CreateWindow
. W przypadku komponentów nie będzie konieczna rejestracja nowych klas itp. elementów. Do stworzenia nowego komponentu wystarczy napisanie jednego wiersza kodu. Komponent należy utworzyć z flagą WS_CHILD
oraz WS_VISIBLE
. Stworzenie przycisku będzie więc wyglądało następująco:
CreateWindow('BUTTON', 'Przycisk', WS_CHILD or WS_VISIBLE, 100, 100, 120, 25, Wnd, 0, hInstance, nil);
Jedyną charakterystyczną cechą jest pierwszy parametr tej funkcji. Jeżeli chcesz stworzyć przycisk, musisz w to miejsce wpisać słowo BUTTON
. Drugi parametr to tekst, który będzie widniał na przycisku. Trzeci parametr to flagi komponentu. Dalsza część jest już taka sama, jak w przypadku tworzenia formularza. W tabeli 12.2 umieściłem wartości, jakie może przyjmować pierwszy parametr funkcji CreateWindow
.
Tabela 12.2. Możliwe wartości pierwszego parametru funkcji CreateWindow
Wartość | Opis |
---|---|
BUTTON | Przycisk ? odpowiednik komponentu TButton |
COMBOBOX | Lista rozwijalna ? odpowiednik TComboBox |
EDIT | Kontrolka edycyjna ? jedno liniowa. Odpowiednik komponentu TEdit |
LISTBOX | Kontrolka wielowierszowa. Odpowiednik TListBox |
MDICLIENT | Okno potomne ? MDI |
SCROLLBAR | Pasek przewijania ? inaczej TScrollBar |
STATIC | Etykieta tekstowa. Odpowiednik TLabel |
Taka kontrolka będzie więc ?dzieckiem? w stosunku do formularza (WS_CHILD
) i będzie także widoczna (WS_VISIBLE
).
Jeżeli chcesz, aby kontrolka na starcie była niewidoczna, użyj flagi WS_HIDE
.
Pamiętaj, aby podczas tworzenia nowej kontrolki w parametrze hWndParent (czwarty od końca) podać uchwyt okna głównego. Parametr ów określa uchwyt okna ?rodzica? ? w tym wypadku formularza.
Umieszczanie kontrolek przy starcie programu
Jeżeli chcemy, aby kontrolki były tworzone na starcie programu, kod należy umieścić w funkcji okienkowej. Konieczne jest także obsłużenie komunikatu WM_CREATE
. Oto kod:
function WndProc(Wnd: HWND; uMsg: UINT; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
begin
{ na początek zwracamy wartość 0 ? meldunek jest przetwarzany }
Result := 0;
case uMsg of
WM_CREATE:
CreateWindow('BUTTON', 'Przycisk', WS_CHILD or WS_VISIBLE, 100, 100, 120, 25, Wnd, 0, hInstance, nil);
WM_DESTROY: PostQuitMessage(0);
else Result := DefWindowProc(Wnd, uMsg, wPar, lPar);
end;
end;
W przypadku zastosowania takiej funkcji okienkowej, jaką przedstawiono powyżej, na formularzu w punkcie 100,100 zostanie umieszczony przycisk. W listingu 12.3 znajduje się kod źródłowy programu, którego efektem jest umieszczenie 5 przycisków na raz.
Listing 12.3. Umieszczanie kilku przycisków
{
Copyright (c) 2002 by Adam Boduch
}
program ChildApp;
uses
Windows,
Messages;
function IntToStr(Value : Integer) : String;
var
Buffer : array[0..255] of char; // bufor, w którym przechowywać będziemy dane
begin
wvsprintf(Buffer, '%d', @Value); // tu następuje funkcja konwersji
Result := Buffer; // zwracamy rezultat
end;
function WndProc(Wnd: HWND; uMsg: UINT; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
var
i : Integer;
begin
{ na początek zwracamy wartość 0 ? meldunek jest przetwarzany }
Result := 0;
case uMsg of
WM_CREATE:
begin
for I := 1 to 5 do
CreateWindow('BUTTON', PCHar('Przycisk nr: ' + IntToStr(i)), WS_CHILD or WS_VISIBLE, 100, 100 + i * 30, 120, 25, Wnd, 0, hInstance, nil);
end;
WM_DESTROY: PostQuitMessage(0);
else Result := DefWindowProc(Wnd, uMsg, wPar, lPar);
end;
end;
var
Wnd: TWndClass; // klasa okna
Msg: TMsg;
begin
with Wnd do
begin
lpfnWndProc := @WndProc; // funkcja okienkowa
hInstance := hInstance;
lpszClassName := 'My1stApp'; // klasa
hbrBackground := COLOR_WINDOW; // kolor tła
end;
RegisterClass(Wnd); // zarejestruj nową klasę
CreateWindow('My1stApp', 'Pierwszy program w WinAPI',
WS_VISIBLE or WS_TILEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
0, 0, hInstance, NIL);
while GetMessage(msg, 0, 0, 0) do DispatchMessage(msg);
end.
W kodzie wykorzystaliśmy wcześniej napisaną funkcję IntToStr
. Umieszczenie 5 kontrolek następuje w pętli, dlatego też za każdą iteracją należy zmieniać położenie przycisku w pionie, wykonując takie działanie:
100 + I * 30;
Powoduje to dodanie do liczby 100 wartości z mnożenia ? np. 30, 60, 90 itd. Działanie programu prezentuje rysunek 12.1.
Rysunek 12.1. Działanie programu
Flagi kontrolek
Poszczególne kontrolki umieszczone na formularzu mogą posiadać dodatkowe flagi, określające zachowanie lub wygląd komponentu. Owe flagi można podawać jako trzeci parametr polecenia CreateWindow
, łącząc je operatorem or. W tabelach 12.3 ? 12.6 przedstawiam najczęściej używane flagi.
Tabela 12.3. Flagi używane z kontrolką BUTTON
Flaga | Krótki opis |
---|---|
BS_3STATE | Kontrolka (przycisk) stanie się komponentem ? la TCheckBox |
BS_AUTO3STATE | Flaga podobna do BS_3STATE, tyle że komponent może przybierać wartość ?zaznaczony? |
BS_AUTORADIOBUTTON | Kontrolka (przycisk) stanie się komponentem ? la `TRadioButton `(rysunek 12.2) |
BS_DEFPUSHBUTTON | Powoduje, że przycisk zostanie wyświetlony z czarną, pogrubioną obwódką |
BS_GROUPBOX | Komponent zostanie wyświetlony z obwódką (rysunek 12.3) |
BS_BITMAP | Umożliwia wyświetlanie bitmapy na kontrolce |
BS_BOTTOM | Ustawia tekst na samym dole komponentu |
BS_CENTER | Centruje tekst w poziomie |
BS_ICON | Umożliwia wyświetlanie ikony na komponencie |
BS_LEFT | Tekst będzie wyrównany do lewej strony |
BS_MULTILINE | Flaga umożliwia wyświetlanie kilku wierszy tekstu |
BS_RIGHT | Tekst będzie wyrównany do prawej strony |
BS_TOP | Tekst zostanie umieszczony u góry kontrolki |
BS_VCENTER | Tekst zostanie wyśrodkowany w pionie |
Rysunek 12.2. Przyciski w formie komponentu TRadioButton
Rysunek 12.3. Przyciski w formie kontrolek TGroupBox
Tabela 12.4. Flagi używane z kontrolką COMBOBOX
Flaga | Krótki opis |
---|---|
CBS_DISABLENOSCROLL | Pasek przewijania zostanie zablokowany |
CBS_DROPDOWN | Lista rozwijalna zostanie aktywna |
CBS_DROPDOWNLIST | Nie będzie możliwe edytowanie listy rozwijalnej (zaznaczona pozycja nie będzie mogła być zmieniana) |
CBS_LOWERCASE | Konwertuje tekst wpisany w kontrolce na małe litery |
CBS_UPPERCASE | Konwertuje tekst wpisany w kontrolce na wielkie litery |
CBS_SORT | Automatyczne sortowanie danych wpisanych w kontrolce |
Tabela 12.5. Flagi używane z kontrolką EDIT
Flaga | Krótki opis |
---|---|
ES_AUTOHSCROLL | Automatycznie przewiń tekst w kontrolce w poziomie, jeżeli użytkownik wpisał więcej znaków niż może być w niej wyświetlone |
ES_CENTER | Wyśrodkuj tekst, jeżeli kontrolka zawiera wiele wierszy |
ES_LEFT | Wyrównaj tekst do lewej strony |
ES_LOWERCASE | Konwertuj wpisany tekst na małe litery |
ES_MULTILINE | Flaga umożliwia wpisywanie w kontrolce wielu wierszy tekstu |
ES_NUMBER | Zezwalaj na wpisywanie jedynie liczb |
ES_PASSWORD | Tekst wpisany w kontrolce zostanie zastąpiony znakami * |
ES_READONLY | Tekst wpisany w kontrolce będzie przeznaczony jedynie do odczytu |
ES_RIGHT | Tekst zostanie wyrównany do prawej strony, jeżeli kontrolka została stworzona z flagą ES_MULTILINE |
ES_UPPERCASE | Wpisane w kontrolce litery konwertuj na wielkie |
ES_WANTRETURN | Dotyczy kontrolek wielowierszowych. Po zastosowaniu tej flagi naciśnięcie klawisza Enter przenosi kursor do kolejnego wiersza |
Tabela 12.6. Flagi używane z kontrolką LISTBOX
Flaga | Krótki opis |
---|---|
LBS_DISABLENOSCROLL | Wyświetla nieaktywny, pionowy pasek przewijania |
LBS_EXTENDEDSEL | Zezwala na zaznaczenie wielu wierszy z użyciem klawisza Shift |
LBS_MULTICOLUMN | Zezwala na wyświetlanie w komponencie kilku kolumn |
LBS_SORT | Automatyczne sortowanie kolumn |
Więcej informacji na temat flag znajdziesz w pomocy WinAPI pod hasłem CreateWindow
.
Obsługa zdarzeń
Umiemy już tworzyć formularze i umieszczać na nich kontrolki WinAPI. Kolejnym krokiem jest obsługa zdarzeń (np. kliknięcia obiektu). Pierwszym krokiem będzie nadanie kontrolce jakiegoś unikalnego identyfikatora.
CreateWindow('BUTTON', PCHar('Przycisk nr: ' + IntToStr(i)), WS_CHILD or WS_VISIBLE, 100, 100 + i * 30, 120, 25, Wnd, 100,
hInstance, nil);
W tym wypadku nadaliśmy kontrolce identyfikator nr 100. Od tego momentu podczas kliknięcia przycisku będziemy musieli odbierać komunikat WM_COMMAND
i ? zależnie od numeru ID ? odpowiednio reagować:
WM_COMMAND:
if wPar = 100 then MessageBox(Wnd, 'Nacisnąłeś!', '', MB_OK);
Na takiej samej zasadzie możesz kontrolować naciśnięcie wszystkich przycisków ? ważne jest tylko, aby numery ID różniły się.
W powyższym przykładzie skorzystałem z instrukcji if, lecz przy większej liczbie instrukcji wygodniej będzie zastosować case
.
Oto zmodyfikowana procedura okienkowa z poprzedniego programu:
function WndProc(Wnd: HWND; uMsg: UINT; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
var
i : Integer;
begin
{ na początku zwracamy wartość 0 ? meldunek jest przetwarzany }
Result := 0;
case uMsg of
WM_CREATE:
begin
{ w pętli umieszczamy kilka przycisków, każdemu z nich nadając kolejny identyfikator - poczynając od 100 }
for I := 1 to 5 do
CreateWindow('BUTTON', PCHar('Przycisk nr: ' + IntToStr(i)), WS_CHILD or WS_VISIBLE, 100, 100 + i * 30, 120, 25,
Wnd, 100 + i, hInstance, nil);
end;
WM_COMMAND: // obsługa kliknięcia przycisku
begin
case wPar of // sprawdź, czy w Par jest od 101 do 105
101..105: MessageBox(Wnd, PChar('Witaj!, nacisnąłeś przycisk nr ' + IntToStr(wPar ? 100) + '!'), ':?)',
MB_OK + MB_ICONINFORMATION);
end;
end;
WM_DESTROY: PostQuitMessage(0);
else Result := DefWindowProc(Wnd, uMsg, wPar, lPar);
end;
end;
Jak widać, za każdą iteracją pętli nowa kontrolka zostaje utworzona ze zmienionym numerem ID. Po uruchomieniu aplikacji i naciśnięciu przez użytkownika przycisku do programu zostaje wysłany komunikat WM_COMMAND
z parametrem wPar, który zawiera numer ID przycisku. Na tej podstawie możemy odpowiednio zareagować ? w tym wypadku poprzez wyświetlenie komunikatu.
Pełen kod źródłowy powyższego programu możesz znaleźć na płycie CD-ROM w katalogu ..listingi/12/Wm_Command.
Uchwyty do kontrolek
Po prawidłowym utworzeniu kontrolki funkcja CreateWindow
zwraca jej uchwyt w postaci typu HWND
. Np.:
Edit := CreateWindow('EDIT', '', WS_CHILD or WS_VISIBLE or WS_BORDER, 10, 10, 100, 25, Wnd, 0, hInstance, nil);
Teraz mając uchwyt takiej kontrolki, możemy wysyłać do niej komunikaty. Przykładowo chcąc pobrać tekst z kontrolki EDIT
, musimy skorzystać z funkcji GetWindowText
:
GetWindowText(Edit, Buffer, SizeOf(Buffer)); // pobierz tekst z edita
Pierwszym parametrem tej funkcji musi być uchwyt kontrolki, z której chcemy pobrać tekst. Drugi parametr ? Buffer ? to np. łańcuch o takiej postaci:
var
Buffer : array[0..128] of char;
Pełny kod programu znajduje się w listingu 12.4.
Listing 12.4. Pełny kod programu
{
Copyright (c) 2002 by Adam Boduch
}
program PMsg;
uses
Windows,
Messages;
var
Edit : THandle;
function WndProc(Wnd: HWND; uMsg: UINT; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
var
Buffer : array[0..128] of char;
begin
{ na początku zwracamy wartość 0 ? meldunek jest przetwarzany }
Result := 0;
case uMsg of
WM_CREATE:
begin
Edit := CreateWindow('EDIT', '', WS_CHILD or WS_VISIBLE or WS_BORDER, 10, 10, 100, 25,
Wnd, 0, hInstance, nil);
CreateWindow('BUTTON', 'OK', WS_CHILD or WS_VISIBLE, 150, 10, 120, 25, Wnd, 101, hInstance, nil);
end;
WM_COMMAND:
if wPar = 101 then
begin
GetWindowText(Edit, Buffer, SizeOf(Buffer)); // pobierz tekst z parametru Edit
MessageBox(Wnd, Buffer, 'EDIT', MB_OK); // wyświetl w okienku
SendMessage(Wnd, WM_SETTEXT, 0, Longint(@Buffer)); // ustaw nową wartość Caption
end;
WM_DESTROY: PostQuitMessage(0);
else Result := DefWindowProc(Wnd, uMsg, wPar, lPar);
end;
end;
var
Wnd: TWndClass; // klasa okna
Msg: TMsg;
begin
with Wnd do
begin
lpfnWndProc := @WndProc; // funkcja okienkowa
hInstance := hInstance;
lpszClassName := 'My1stApp'; // klasa
hbrBackground := COLOR_WINDOW; // kolor tła
end;
RegisterClass(Wnd); // zarejestruj nową klasę
CreateWindow('My1stApp', 'Server App',
WS_VISIBLE or WS_TILEDWINDOW,
300, 300, 300, 70,
0, 0, hInstance, NIL);
while GetMessage(msg, 0, 0, 0) do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
end.
W pierwszej kolejności po naciśnięciu przycisku pobierana zostaje wartość wpisana w kontrolce EDIT
. Teraz wystarczy już tylko wyświetlić zawartość zmiennej Buffer. Następnie program ustawia nową wartość dla okna formularza (można powiedzieć, że to jest właściwość Caption
):
SendMessage(Wnd, WM_SETTEXT, 0, Longint(@Buffer));
Chciałem przy okazji zaprezentować sposób wysyłania komunikatów do kontrolek. W tym celu do okna należy przekazać komunikat WM_SETTEXT
. Drugi parametr natomiast musi być wskazaniem tekstu, który ma zostać umieszczony w oknie.
Tworzenie bardziej zaawansowanych kontrolek
Aby możliwe było tworzenie bardziej zaawansowanych kontrolek (takich, jak komponenty typu TProgressBar
czy TListView
), należy do listy uses
dodać moduł CommCtrl
. Operować tymi komponentami możemy tylko poprzez komunikaty. Ich spis możesz znaleźć w pliku CommCtrl.pas
. Znaczenie poszczególnych komunikatów jest bardzo intuicyjne. Cóż bowiem wykonuje komunikat PBM_SETPOST
? Można się domyśleć, że ustawia nową pozycję w komponencie.
Jeżeli uruchomisz program z użyciem biblioteki CommCtrl
, a na ekranie nadal widnieć będzie ?czysty? formularz (tzn. komponent nie zostanie utworzony), to wówczas konieczne będzie wywołanie procedury InitCommonControls
. Procedura ta inicjuje odpowiednią bibliotekę DLL. Najlepiej tę procedurę wywołać tuż po utworzeniu nowej klasy w sekcji begin
..end
.
Przykładowo ? utworzenie nowej kontrolki ? la TProgressBar
wygląda następująco:
CreateWindow('msctls_progress32', '', WS_CHILD or WS_VISIBLE, 100, 10, 350, 20, Wnd, 0, hInstance, nil);
Decydujące znaczenie ma tutaj parametr msctls_progress32
. Spis wszystkich parametrów kluczowych dla utworzenia komponentu możesz znaleźć w pliku CommCtrl.pas
. Po utworzeniu komponentu można wysyłać do niego komunikaty ? np. dotyczące zmiany pozycji:
for i := 0 to 100 do
begin
Sleep(50);
SendMessage(ProgressBar, PBM_SETPOS, i, 0);
end;
Jest to zatem pętla od jednego do stu z przerwami pomiędzy kolejnymi iteracjami, wynoszącymi 50 milisekund. Podczas każdorazowego wykonania pętli do komponentu jest wysyłany komunikat PBM_SETPOS
. Parametr lParam funkcji SendMessage
zawiera nową wartość (pozycję) paska postępu. Cały ten kod umieścimy w programie obsługi komunikatu WM_PAINT (listing 12.5.)
Listing 12.5. Pełny kod programu
{
Copyright (c) 2002 by Adam Boduch
}
program Ctrl;
uses
Windows,
CommCtrl,
Messages;
var ProgressBar : HWND;
function WndProc(Wnd: HWND; uMsg: UINT; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
var
i : Integer;
begin
{ na początku zwracamy wartość 0 ? meldunek jest przetwarzany }
Result := 0;
case uMsg of
WM_CREATE:
begin
// umieść komponent ProgressBar i zwróć uchwyt
ProgressBar := CreateWindow('msctls_progress32', '', WS_CHILD or WS_VISIBLE, 100, 10, 350, 20, Wnd,
0, hInstance, nil);
end;
WM_PAINT: // obsługa komunikatu WM_PAINT
begin
for i := 0 to 100 do
begin
Sleep(50); // odczekaj 50 milisekund
SendMessage(ProgressBar, PBM_SETPOS, i, 0); // wyślij komunikat do komponentu
end;
Halt(1); // zamknij program
end;
WM_DESTROY: PostQuitMessage(0);
else Result := DefWindowProc(Wnd, uMsg, wPar, lPar);
end;
end;
var
Wnd: TWndClass; // klasa okna
Msg: TMsg;
begin
with Wnd do
begin
lpfnWndProc := @WndProc; // funkcja okienkowa
hInstance := hInstance; lpszClassName := 'My1stApp'; // klasa
hbrBackground := COLOR_WINDOW; // kolor tła
hIcon := LoadIcon(0, IDI_APPLICATION); // domyślna ikona
hCursor := LoadCursor(0, IDC_ARROW); // domyślny kursor
end;
RegisterClass(Wnd); // zarejestruj nową klasę
InitCommonControls;
// stwórz formularz...
CreateWindow('My1stApp', 'Aplikacja z wykorzystaniem modułu CommCtrl.pas',
WS_VISIBLE or WS_TILEDWINDOW,
300, 300, 500, 300,
0, 0, hInstance, NIL);
while GetMessage(msg, 0, 0, 0) do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
end.
Pozostałe kontrolki
Nazwy pozostałych kontrolek, z jakich możesz skorzystać w swoich programach, zawarte są w pliku CommCtrl.pas. Tam również możesz znaleźć listę komunikatów związanych z konkretnym komponentem. W tabeli 12.7 prezentuję komponenty, których możesz użyć w swoich programach w API, wraz z ich odpowiednikami w VCL.
Tabela 12.7. Kontrolki z biblioteki CommCtrl.dll
Identyfikator kontrolki | Nazwa kontrolki | Odpowiednik VCL |
---|---|---|
ToolbarWindow32 | Pasek narzędziowy | TToolBar |
msctls_statusbar32 | Pasek aplikacji | TStatusBar |
msctls_trackbar32 | Pasek przewijania | TTrackBar |
msctls_updown32 | Pasek góra-dół | TUpDown |
msctls_progress32 | Pasek postępu | TProgressBar |
SysListView32 | Lista pozycji | TListView |
SysTreeView32 | Drzewo obiektów | TTreeView |
ComboBoxEx32 | Kontrolka Combo | TComboBoxEx |
SysTabControl32 | System zakładek | TTabControl |
SysAnimate32 | Animacje systemowe | TAnimate |
SysMonthCal32 | Kalendarz | TMonthCalendar |
SysDateTimePick32 | Prezentuje datę i czas | TDateTimePicker |
SysIPAddress32 | Podaje adres IP komputera. | brak odpowiednika |
SysPager | System stron | TPageControl |
Wyświetlanie grafiki
We wcześniejszych rozdziałach miałeś okazję zapoznać się z funkcjami operującymi na grafice czy też wyświetlającymi tekst. Poznałeś także klasę TCanvas
, która owe zadanie znacznie upraszczała. Funkcje API umożliwiające rysowanie lub wyświetlanie grafiki są bardzo podobne do funkcji z klasy TCanvas
. Różnica polega jedynie na liczbie parametrów.
Rysowanie w WinAPI
Przypominam, że rysowanie czegokolwiek powinno odbyć się po wywołaniu komunikatu WM_PAINT
. Wiadomo, że okno w Windows może podlegać różnym zdarzeniom, takim jak: minimalizacja, możliwość zasłonięcia przez inne okno itp. System nie przechowuje obrazu ekranu w pamięci, lecz odpowiedzialna jest za to sama aplikacja.
Po odsłonięciu okna i przywróceniu go na pierwszy plan do aplikacji wysyłany jest komunikat WM_PAINT
? do nas należy obsłużenie tego komunikatu.
Kontekst urządzenia graficznego
Wszystkie funkcje operujące na WinAPI wymagają podania pierwszego parametru, który jest tzw. kontekstem urządzenia (Device Context). Jest to pewna struktura danych, opisująca różne parametry rysowania ? czcionkę, grubość i kolor linii itp. Takie ustawienia są różne dla każdego programu uruchomionego w systemie, a my w swoich aplikacjach będziemy musieli pobierać uchwyt do owego kontekstu:
DCHandle := GetDC(Wnd);
Od tej pory mamy już uchwyt, który będzie trzeba podawać przy każdej funkcji operującej na grafice. Po zakończeniu malowania należy ten uchwyt zwolnić, korzystając z polecenia ReleaseDC
:
ReleaseDC(Wnd, DCHandle);
Za pomocą funkcji GetDC
uzyskujemy uchwyt, dzięki któremu możemy malować po całym obszarze roboczym programu. Nie mamy natomiast możliwości rysowania na pasku tytułowym okna ? do tego będzie nam potrzebna funkcja GetWindowDC
.
Obsługa WM_PAINT
Istnieje lepsza metoda dostarczania kontekstu urządzenia ? rekord TPaintStruct
. Rekord TPaintStruct
dostarcza oprócz kontekstu urządzenia także informacje, które można wykorzystać podczas obsługi komunikatu WM_PAINT
. Namalowanie czegoś na formularzu opiera się na wywołaniu metody BeginPaint
oraz ? po zakończeniu ? EndPaint
:
WM_PAINT:
begin
DC := BeginPaint(Wnd, PS);
TextOut(DC, 10, 10, 'Delphi 7', Length('Delphi 7'));
EndPaint(Wnd, PS);
end;
Wcześniej jednak należy zadeklarować zmienne PS oraz DC:
var
PS : TPaintStruct;
DC : HDC; // uchwyt
Funkcja TextOut
realizuje ? podobnie jak funkcja o tej samej nazwie z klasy TCanvas
? rysowanie tekstu. Pierwszym parametrem musi być uchwyt do kontekstu urządzenia. Kolejne dwa parametry to współrzędne rysowanego tekstu. Trzeci parametr to tekst, który zostanie narysowany na formularzu, a ostatni ? długość tekstu. Rysunek 12.4 prezentuje program po uruchomieniu.
Rysunek 12.4. Tekst narysowany metodą TextOut
Zmiana koloru tła
Na rysunku 12.4 widzisz, że dotychczasowy efekt działania funkcji TextOut
nie jest zbyt interesujący. Napis jest wyświetlany na białym tle. Aby to zmienić, można ustawić przezroczyste tło, używając w tym celu funkcji SetBkMode
:
SetBkMode(DC, TRANSPARENT);
Pierwszy parametr musi być kontekstem urządzenia, a drugi to flaga informująca o tym, że tło będzie przezroczyste (TRANSPARENT
).
W dość prosty sposób można zmienić również kolor wyświetlanego tekstu. Wystarczy zastosować funkcję SetTextColor
. Pierwszym parametrem tej funkcji musi być oczywiście kontekst urządzenia. Drugi parametr musi określać kolor tekstu w postaci RGB (Reed Green Blue), czyli kombinacji trzech kolorów: czerwonego, zielonego i niebieskiego ? np.:
SetTextColor(DC, RGB(0, 100, 150));
Ładowanie i wyświetlanie bitmapy
Załóżmy, że w zasobach programu umieszczona jest bitmapa o nazwie ID_BITMAP
. Przy wykorzystaniu VCL załadowanie bitmapy do komponentu TImage zajęłoby chwilę ? wystarczyłby jeden wiersz kodu:
Image.Picture.Bitmap.LoadFromResourceName(hInstance, 'ID_BITMAP');
Chcąc skorzystać z funkcji API, musimy poświęcić na to nieco więcej czasu i napisać więcej kodu:
WM_PAINT
:
begin
DC := BeginPaint(Wnd, PS);
Bitmap := LoadBitmap(hInstance, 'ID_BITMAP');
_Bitmap := CreateCompatibleDC(DC);
SelectObject(_Bitmap, Bitmap);
BitBlt(dc, 10, 10, 14, 14, _Bitmap, 0, 0, SRCCOPY);
DeleteDC(_Bitmap);
EndPaint(Wnd, PS);
end;
Samo załadowanie jest proste, gdyż używamy tutaj funkcji LoadBitmap
, która zwraca bitmapę w postaci zmiennej HBITMAP
(odpowiednik klasy TBitmap
). Następnym krokiem jest stworzenie pamięciowego kontekstu urządzenia (CreateCompatibleDC
), na którym zostanie narysowana bitmapa. Kolejny krok to wybranie właściwego kontekstu za pomocą funkcji SelectObject. Wreszcie samo narysowanie bitmapy następuje poprzez funkcję BitBlt
. Pierwszy parametr owej funkcji to uchwyt kontekstu, na którym zostanie narysowana bitmapa. Kolejne dwa parametry to X i Y, współrzędne miejsca wyświetlania obrazka. Rozmiar rysowanej bitmapy określają dwa kolejne parametry. Jeszcze inne dwa parametry określają pozycję X i Y lewego górnego rogu obrazka oraz stopień jego wyświetlania. Ostatni parametr określa sposób przedstawienia bitmapy ? w tym wypadku kopiowanie ze źródła do miejsca przeznaczenia.
Najlepszym sposobem sprawdzenia działania owych parametrów jest przetestowanie ich w praktyce.
Nim skompilujesz swój program, zadeklaruj w nim następujące zmienne:
var
Bitmap : HBITMAP;
_Bitmap : HDC;
Ładowanie zasobów
W rozdziale 10. była mowa o wykorzystaniu zasobów do przechowywania w pliku wykonywalnym różnych danych ? począwszy od grafiki, a na innych plikach wykonywalnych skończywszy. W tym podpunkcie pokażę, w jaki sposób skorzystać z tych zasobów, nie używając przy tym klasy TResourceStream
.
Korzystając z zasobów, można w WinAPI napisać swój własny instalator, który będzie ?przechowywał? w sobie pliki instalacyjne. Taki przykładowy instalator możesz znaleźć na płycie CD-ROM w katalogu ../listingi/12/Install. W katalogu z projektem znajdziesz również skompilowaną wersję projektu ? plik Install.exe (rysunek 12.5). Po uruchomieniu programu instalacyjnego na dysku zostanie zainstalowany program, który kiedyś napisałem (lecz to jest w tej chwili nie istotne).
Rysunek 12.5. Instalator wykonany w WinAPI
Skompilowane zasoby
Tworzenie zasobów za pomocą programu brcc32.exe wygląda tak samo, jak to przedstawiałem w rozdziale 10. ? np. w przypadku mojego instalatora plik files.rc wygląda tak:
MAILBOXES RCDATA "Mailboxes.exe"
SETUP RCDATA "setup.dll"
MAILCNT RCDATA "Mailboxes.cnt"
MAILHLP RCDATA "Mailboxes.hlp"
README RCDATA "Readme.html"
SAMPLE RCDATA "sample.txt"
DEFAULT RCDATA "default.mbx"
UNINSTALL RCDATA "Odinstaluj.exe"
Tak skonstruowany plik .rc pozwoli na włączenie do gotowego zasobu (.res) powyżej przedstawionych plików.
W rozdziale 10. nie wspomniałem o jednej kwestii ? mianowicie o możliwości tworzenia bardziej zaawansowanych zasobów, np. formularza:
LICENCJA DIALOGEX 42, 8, 271, 133
STYLE DS_MODALFRAME | DS_CENTER | DS_3DLOOK | DS_SETFOREGROUND | WS_POPUP |
WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_CLIENTEDGE
CAPTION "Instalacja programu"
FONT 8, "MS Sans Serif"
BEGIN
ICON "a", a, 11, 9, 21, 20
LTEXT "Najnowszą wersję programu MailBoxes możesz zawsze znaleźć na stronie www.4programmers.net",
a, 37, 11, 221, 20
DEFPUSHBUTTON "Instaluj", 102, 101, 100, 101, 14
PUSHBUTTON "Wyjście", 103, 208, 100, 47, 14
GROUPBOX "Gdzie zainstalować program?", a, 10, 59, 247, 32
EDITTEXT 106, 16, 72, 233, 14, ES_AUTOHSCROLL
CONTROL "BAR", BAR, "msctls_progress32", 0x0 | WS_CLIPSIBLINGS,
14, 42, 241, 11
END
Pisanie tego ręcznie raczej nie ma sensu ? ja używałem pakietu Microsoft Visual Studio, który oferuje tworzenie zasobów. Ty jednak możesz skorzystać z darmowych narzędzi dostępnych w Internecie.
Wykorzystanie zasobów
Ażeby skorzystać z naszych zasobów, będziemy zmuszeni zastosować funkcje WinAPI, których do tej pory nie używaliśmy ? np. LoadResource
i FindRecource
. Najpierw jednak należy zająć się przedstawieniem formularza instalacyjnego.
Wyświetlenie formularza
Za wyświetlenie formularza znajdującego się w zasobach odpowiada funkcja DialogBox
:
function DialogBox(hInstance: HINST; lpTemplate: PChar;
hWndParent: HWND; lpDialogFunc: TFNDlgProc): Integer;
Pierwszy parametr musi być wskazaniem modułu, w którym znajdują się zasoby ? my w tym miejscu podajemy wartość hInstance
. Drugi parametr musi być nazwą szablonu, który chcemy wyświetlić. Parametr trzeci (hWndParent
) stanowi uchwyt do okna macierzystego; jako że nasze okno będzie oknem macierzystym, wpisujemy w tym miejscu cyfrę 0. Ostatni parametr to wskazanie procedury, która będzie obsługiwać zdarzenia naszego formularza:
DialogBox(hInstance, 'LICENCJA', 0, @DlgWindowProc);
Funkcja DlgWindowProc
, którą podałem w ostatnim parametrze, jest zwykłą funkcją okienkową ? musi odpowiadać na przychodzące do aplikacji komunikaty.
Zamknięcie okna (zakończenie działania aplikacji) zrealizujemy za pomocą polecenia EndDialog
:
EndDialog(wnd,0);
Ustawianie wartości komponentów formularza
Mimo że formularz wykorzystany w programie jest jedynie skryptem, posiada on także komponenty. Podczas ustawiania tych komponentów przydzieliłem każdemu z nich osobny identyfikator (ID), do którego będziemy się odwoływać podczas wykonywania funkcji:
SetDlgItemText(wnd, 106, 'C:\Mailboxes 1.53');
Przykładowo funkcja SetDlgItemText
powoduje ustawienie nowej wartości kontrolki. W pierwszym parametrze wpisujemy uchwyt okna, a w drugim ID kontrolki. Parametr ostatni to tekst, który ma zostać umieszczony w obiekcie.
LockResource, LoadResource, FindResource
Załadowanie zasobów (tekstu licencji) do kontrolki może się wydać trochę skomplikowane, gdyż trzeba się posłużyć aż trzema funkcjami naraz: LockResource, LoadResource
i FindResource
.
SetDlgItemText(wnd, 101, LockResource(LoadResource(hinstance,FindResource(hinstance,'LICENCJA','TXT'))));
Pierwsza funkcja ? LockResource
? zwraca wskaźnik do pierwszego bajtu zasobów, jednak wymaga podania uchwytu do zasobów. Ten globalny uchwyt jest natomiast zwracany przez funkcję LoadResource
, która w drugim parametrze wymaga podania wskazania ładowanego zasobu. Tutaj pomoże nam funkcja FindResource
, w której parametrach wystarczy podać typ oraz nazwę zasobu.
Zapisywanie plików na dysku
Chcąc zastąpić klasę TResourceStream
, jesteśmy zmuszeni skorzystać z funkcji LoadResource
oraz FindResource
. Najpierw w swoim instalatorze zadeklaruj tablicę plików, które są instalowane:
{ oto elementy umieszczone w zasobach }
const Tablica : array[0..7] of _DATA =
((Files: 'Mailboxes.exe'; FName: 'MAILBOXES'),
(Files: 'setup.dll'; FName: 'SETUP'),
(Files: 'Mailboxes.cnt'; FName: 'MAILCNT'),
(Files: 'Mailboxes.hlp'; FName: 'MAILHLP'),
(Files: 'Readme.html'; FName: 'README'),
(Files: 'Sample.txt'; FName: 'SAMPLE'),
(Files: 'default.mbx'; FName: 'DEFAULT'),
(Files: 'Odinstaluj.exe'; FName: 'UNINSTALL'));
Pierwszy element tej tablicy to nazwa pliku, który ma zostać utworzony na dysku; drugi to wskazanie nazwy zasobu. Instalacja (wyodrębnianie) poszczególnych elementów może wyglądać tak:
For I:= Low(Tablica) to High(Tablica) do
begin
AssignFile(Ouff, PC + Tablica[i].Files); // stwórz plik...
Rewrite(Ouff, 1);
{ odnajdź w zasobach zasób i przypisz go zmiennej Fres }
Fres := FindResource(hInstance, Tablica[i].FName, RT_RCDATA);
{ do pliku zapisz dane z wyciągniętych zasobów }
BlockWrite(Ouff, LockResource(LoadResource(hInstance, Fres))^, SizeofResource(hinstance, Fres));
Closefile(ouff); // zamknij plik
end;
Posłużyłem się tutaj funkcjami operującymi na plikach, które szczegółowo omówiłem w rozdziale 7. Po zlokalizowaniu konkretnego zasobu (FindResource
) następuje jego zapisanie do pliku (BlockWrite
). Pełny kod źródłowy znajduje się w listingu 12.6.
Listing 12.6. Kod źródłowy instalatora
{
Copyright (c) 2001 by Adam Boduch [http://programowanie.of.pl]
}
program setup;
uses
Windows,
Messages;
{$R FILES.RES} // <--- pliki, które zostaną zainstalowane
{$R RESOURCE.RES} // <---- bitmapa ( dodatkowe pliki )
type
{
rekord zawiera dwa elementy. Pierwszym jest nazwa pliku umieszczonego w zasobach
? np. SFP, a drugim elementem jest nazwa pliku, który zostanie zapisany na
dysku ? np: sfp.jpg
}
_DATA = packed record
Files: String;
FName: PChar;
end;
{ oto elementy umieszczone w zasobach }
const Tablica : array[0..7] of _DATA =
((Files: 'Mailboxes.exe'; FName: 'MAILBOXES'),
(Files: 'setup.dll'; FName: 'SETUP'),
(Files: 'Mailboxes.cnt'; FName: 'MAILCNT'),
(Files: 'Mailboxes.hlp'; FName: 'MAILHLP'),
(Files: 'Readme.html'; FName: 'README'),
(Files: 'Sample.txt'; FName: 'SAMPLE'),
(Files: 'default.mbx'; FName: 'DEFAULT'),
(Files: 'Odinstaluj.exe'; FName: 'UNINSTALL'));
var KeyHandle : HKEY;
Uninstall : PChar;
const Name : PChar = 'Mailboxes v. 1.5.3';
(*************************************************************************)
function DlgWindowProc (Wnd: hWnd; Msg: UINT; DlgWParam: WPARAM; DlgLParam: LPARAM): boolean; stdcall;
var
Fres: Integer;
Ouff: File;
Buff: array[0..254] of char;
PC : String;
I : Integer;
label Next;
begin
result := true;
case Msg of
WM_INITDIALOG:
begin
SetDlgItemText(wnd, 101, LockResource(LoadResource(hinstance,FindResource(hinstance,'LICENCJA','TXT'))));
SetDlgItemText(wnd, 106, 'C:\Mailboxes 1.53');
end;
WM_CLOSE: EndDialog(wnd,0);
wm_activate: SendDlgItemMessage(wnd, 101, EM_SETSEL, ?1, 0);
WM_COMMAND:
begin
if LOWORD(DlgWParam) = 103 then EndDialog(wnd,0);
if LOWORD(DlgWParam) = 102 then
begin
{ pobranie ścieżki, w której ma zostać zainstalowany program }
GetDlgItemText(wnd, 106, Buff, SizeOf(Buff));
PC := Buff;
{ sprawdzenie, czy na końcu znajduje się znak \ }
if PC[Length(PC)] <> '\' then PC := PC + '\' else PC := PC;
Uninstall := PChar(PC + 'Odinstaluj.exe');
{ otwarcie rejestru i klucza }
RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar('Software\Microsoft\Windows\CurrentVersion\Uninstall'), 0,
KEY_ALL_ACCESS, KeyHandle);
{ stworzenie nowej wartości }
RegCreateKey(KeyHandle, 'Mailboxes', KeyHandle);
RegSetValueEx(KeyHandle, 'DisplayName', 0, REG_SZ, Name, SizeOf(Name));
RegSetValueEx(KeyHandle, 'UninstallString', 0, REG_SZ, Uninstall, SizeOf(Uninstall));
{ sprawdzenie, czy użytkownik nie wpisał tylko litery partycji ? jeżeli nie, należy utworzyć dodatkowo katalog }
if Length(PC) <= 3 then goto Next else CreateDirectory(PChar(PC), nil);
Next:
{ instaluj poszczególne elementy }
For I:= Low(Tablica) to High(Tablica) do
begin
AssignFile(Ouff, PC + Tablica[i].Files); // stworz plik...
Rewrite(Ouff, 1);
{ odnajdź w zasobach zasób i przypisz go do zmiennej Fres }
Fres := FindResource(hInstance, Tablica[i].FName, RT_RCDATA);
{ do pliku zapisz dane z "wyciągniętych" zasobów }
BlockWrite(Ouff, LockResource(LoadResource(hInstance, Fres))^, SizeofResource(hinstance, Fres));
Closefile(ouff); // zamknij plik
end;
MessageBox(0, 'Instalacja przebiegła pomyślnie!', ':)', MB_OK + MB_ICONINFORMATION);
PostQuitMessage(0); // zakończ program...
end;
end;
else result:=false;
end;
end;
begin
if MessageBox(0, 'Za chwilę odbędzie się instalacja programu Mailboxes v. 1.53. Czy chcesz kontynuować?',
'Mailboxes ? instalacja', MB_YESNO + MB_ICONINFORMATION) = ID_Yes then
begin
DialogBox(hInstance, 'LICENCJA', 0, @DlgWindowProc);
end;
end.
Podsumowanie
Programowanie w czystym WinAPI może być niewygodne, a nawet dość trudne, ale nie da się ukryć, że zapewnia większą kontrolę nad programem i daje większe możliwości, których niekiedy brak w bibliotece VCL.
Załączniki:
Więcej informacji Delphi 2005. Kompendium programisty Adam Boduch Format: B5, stron: 1048 oprawa twarda Zawiera CD-ROM |