WinApi - Lekcja 4
Przede wszystkim tworzenie bardziej zaawansowanych komponentow takich jak ProgressBar, TrackBar czy inne jest takze mozliwe w API. Trzeba przede wszystkim dodac do listy uses slowo CommCtrl. Tym sposobem wlaczyles do programu plik CommCtrl.pas, ktory umozliwia tworzenie bardziej zaawansowanych konponentow. Sterowanie tymi komponentami odbywa sie oczywiscie za pomoca komunikatow. Opis wszystkich mozliwych do stworzenia kontrolek mozesz znalezc wlasnie w pliku CommCtrl.pas, ktory masz w katalgu z Delphi. My nie bedziemy omawiac tutaj wszystkich tych komunikatow gdyz zajeloby to wiele miejsca, a ich znaczenia mozna sie latwo domyslec. Na przyklad tworzenie komponentu ProgressBar:
CreateWindow('msctls_progress32', '', WS_CHILD or WS_VISIBLE, 0, 10, 350, 20, H, 0, hInstance, nil);
msctls_progress32 to wlasnie komponent ProgressBar. SKad to wiemy? Poszperalismy w pliku CommCtrl.pas!
Teraz jak wprawic w urch ten komponent? Co zrobic zeby zaczol dzialac? Przede wszystkim stworzyc przycisk po ktorego nacisnieciu program wykona petle, ktora spowoduje wyslenie do komponentu odpowiedniego komunikatu. Komunikat, ktory ustawia wartosc komponentu na wybranej pozycji to:
SendMessage(Status, TBM_SETPOS, 10, 10); // ustaw pozycje na progressbar
Status to uchwyt komponentu ProgressBar. W powyzszym przykladzie program ustawia komponent na pozycji 10. Sprawdz. Tak wyglada caly kod programu:
program kontrolki;
uses
Windows,
Messages,
CommCtrl;
const
Caption : PChar = 'Inne kontrolki';
var Status : HWND;
function Window(H : HWND; Msg: UINT; wPar : WPARAM; lPar: LPARAM):
LRESULT; stdcall;
var i : Integer;
begin
Result := 0;
case Msg of
WM_DESTROY: PostQuitMessage(0);
WM_CREATE:
begin
{ stworz progressbar'a i umiesc go w dogodnej pozycji... }
Status := CreateWindow('msctls_progress32', '', WS_CHILD or WS_VISIBLE, 0, 10, 350, 20, H, 0, hInstance, nil);
{ stworz przycisk }
CreateWindow('BUTTON', 'Click', WS_CHILD or WS_VISIBLE, 100, 100, 120, 25, H, 101, hinstance, nil);
end;
WM_COMMAND:
begin
case wPar of
101: // podczas nacisniecia przycisku...
begin
{ wykonaj petle... }
for I := 0 to 10 do
begin
Sleep(100); // czkeja 100 milisekund
SendMessage(Status, TBM_SETPOS, i, i); // ustaw pozycje na progressbar
end;
end;
end;
end;
else
Result := DefWindowProc(H, Msg, wPar, lPar);
end;
end;
procedure InitApp;
var
WNDCLASS : TWndClass;
WND : HWND;
begin
WndClass.style := CS_CLASSDC; // styl okna
WndClass.lpfnWndProc := @Window; // adres funkcji okienkowej
WndClass.cbClsExtra := 0;
WndClass.cbWndExtra := 0;
WndClass.hInstance := hInstance; // zasoby
WndClass.hIcon := LoadIcon(0, IDI_APPLICATION); // ikona
WndClass.hCursor := LoadCursor(0, IDC_ARROW); // kursor
WndClass.hbrBackground := COLOR_WINDOW; // kolor tla
WndClass.lpszMenuName := nil;
WndClass.lpszClassName := Caption; // nazwa ("Caption")
RegisterClass(WNDCLASS); // rejestracja klasy
Wnd := CreateWindow(Caption, // stworz okno
Caption,
WS_SYSMENU,
120, // x
120, // y
360, // width
270, // height
0, 0,
hInstance, nil);
ShowWindow(wnd, SW_SHOW); // pokaz okno
end;
procedure Run;
var
Msg: TMsg;
begin
while GetMessage(msg, 0, 0, 0) do // petla komunikatow
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
Halt(msg.wParam);
end;
begin
InitApp;
Run;
end.
Tworzenie menu
function FunOkna( ...
label default;
var menu,submenu,submenu2: HMENU; pt:TPoint;
begin
{ ... }
WM_CREATE: begin
menu:=GetSystemMenu(hOkno,FALSE);
AppendMenu(menu,MF_SEPARATOR,-1,nil);
AppendMenu(menu,MF_STRING,101,'&Dodaj');
AppendMenu(menu,MF_STRING,102,'&Odejmij');
menu:=CreateMenu;
submenu:=CreatePopupMenu;
AppendMenu(submenu,MF_String,111,'Opcja 1.1');
AppendMenu(submenu,MF_String,112,'Opcja 1.2');
AppendMenu(submenu,MF_String,113,'Opcja 1.3');
AppendMenu(menu,MF_POPUP,submenu,'Opcja 1');
submenu:=CreatePopupMenu;
AppendMenu(submenu,MF_String,121,'Opcja 2.1');
AppendMenu(submenu,MF_String,122,'Opcja 2.2');
AppendMenu(submenu,MF_String,123,'Opcja 2.3');
submenu2:=CreatePopupMenu;
AppendMenu(submenu2,MF_String,131,'Opcja 2.4.1');
AppendMenu(submenu2,MF_String,132,'Opcja 2.4.2');
AppendMenu(submenu,MF_POPUP,submenu2,'Opcja 2.4');
AppendMenu(menu,MF_POPUP,submenu,'Opcja 2');
AppendMenu(menu,MF_STRING or MFT_RIGHTJUSTIFY,999,'Help');
SetMenu(hOkno,menu);
end;
WM_RBUTTONDOWN: begin
menu:=GetMenu(hOkno);
submenu:=GetSubMenu(menu,1);
GetCursorPos(pt);
TrackPopupMenu(submenu,0,pt.x,pt.y,0,hOkno,nil);
end;
WM_COMMAND:
case wParametr of
111: MessageBox(hOkno,'Wybrano opcje 1.1','Info',64);
132: MessageBox(hOkno,'Wybrano opcje 2.4.2','Info',64);
999: MessageBox(hOkno,'Pomocy!!!!!','Ludzie!!!',48);
end;
WM_SYSCOMMAND:
case wParametr of
101: MessageBox(hOkno,'Co dodajemy?','Pytanie',32);
102: MessageBox(hOkno,'Co odejmujemy?','Pytanie',32);
else
goto default;
end;
WM_DESTROY: PostQuitMessage(0);
else
default: {swiadomie uzywam 'goto' aby nie powtarzac DefWindowProc}
Result := DefWindowProc(hOkno, uKomunikat, wParametr, lParametr);
end;
end;
Wklej powyzszy kod do programu i zaobserwuj jego dzialanie. Tworzenie menu w WinAPI jest dosc monotonne. Funkcje tworzenia menu sa dosc dobrze opisane w systemie pomocy. Funkcja AppendMenu dodaje nowa funkcje do okna okreslonego pierwszym parametrem.
Piszemy instalator
Podsumowaniem tego poradnika bedzie napisanie instalatora. Bedzie to najzywklejszy mini-instalator. Bedzie zawieral dwa przyciski, pole edycyjne, ikonke. Po nacisnieciu jednego z przyciskow pobierana bedzie sciezka gdzie uzytkownik chce zainstalowac program. Potem tworzony bedzie katalog i do tego katalogu z zasobow beda "wyciagane" pliki. W naszym przypadku instalowane beda dwa pliki *.txt i *.jpg.
Pierwszym krokiem bedzie przygotowanie odpowiednich zasobow. Potrzebny nam bedzie program brcc32.exe. Program ten masz u siebie na dysku. Jezeli masz Delphi to ten program powinien byc w katalogu ..bin. Utworz sobie jakis ktalog - np. instalator i skopiuj do niego program brcc32.exe. Teraz do tego katalogu skopiuj dwa pliki jakie chcesz umiescic w zasobach. Ja skopiowalem pliki sfp.jpg oraz !info!.txt. Teraz stworz plik files.rc i otworz go w Windowsowym Notatniku. Umiesc w tym pliku takie linie:
SFP RCDATA "sfp.jpg"
TXT RCDATA "!info!.txt"
Te linie to pliki, ktore beda wlaczone do zasobow. Pierwszy czlon to nazwa wlaczonego zasobu, kolejny element to typ zasobu. Jezeli jest to nieokreslony typ pliku, ktory chcesz "wyrzucic" na zewnatrz to wpisujesz wlasnie RCDATA. Jezeli chcesz, aby to byl plik *.wav wpisujesz WAVE.
Kolejny czlonem wpisanym w cudzyslow jest nazwa pliku, ktory bedzie wlaczony do zasobow.
Dobrze to juz mamy gotowe. Teraz nalezy ten plik files.rc skompilowac do postaci pliku RES za pomoca wlasnie programu brcc32.exe. Jest to program DOS wiec musisz go odpalic spod okienka DOS. Teraz poleceniami cd, cd.. przechodzisz do katalogu w ktorym masz plik files.rc ( u mnie jest to katalog instalator ). Teraz w oknie MS-DOS wpisujesz takie komendy:
brcc32.exe fiiles.rc
Po tym plik zostanie skompilowany do postaci files.RES. Gotowe! Mamy juz zasoby, ktore beda wlaczone do pliku EXE. Teraz pozostalo juz tylko napisanie samego instalatora.
Na poczatek musisz zadeklarowac taki oto rekord:
type
{
rekord zawiera dwa elementy. Pierwszym jest nazwa pliku umieszczonego w zasobach
- np. SFP, a drugim elementem jest nazwa pliku, ktory zostanie zapisany na
dysku - np: sfp.jpg
}
_DATA = packed record
Files: String;
FName: PChar;
end;
Po co on sluzy? Zostalo opisane to w komentarzu. Nastepnie taka tablice:
{ oto elementy umieszczone w zasobach }
const Tablica : array[0..1] of _DATA =
((Files: 'sfp.jpg'; FName: 'SFP'),
(Files: '!info!.txt'; FName: 'TXT'));
{ moga w tej tablicy byc ine pliki }
W zasobach mamy dwa pliki wiec tablica bedzie dwu elementowa. W tej tablicy musimy wpisac dane, ktore oznaczaja umieszczone w zasobach pliki. Pierwszym czlonem ma byc plik, ktory bedzie zapisany na dysku ( nazwa tego pliku ), a drugi czlon to nazwa pliku umieszczonego w zasobach.
Najwazniejsza jest procedura, ktora bedzie po kolei wyciagac pliki z zasobow i umieszczac je na dysku. Najpierw jej sie przypatrz, a pozniej ja omowimy:
begin
GetWindowText(Edit, Buff, SizeOf(Buff)); // pobierz tekst z komponentu Edit
MkDir(Buff); // utworz nowy katalog
{ instaluj poszczegolne elementy }
For I:= Low(Tablica) to High(Tablica) do
begin
AssignFile(Ouff, Buff + '' + Tablica[i].Files); // stworz plik...
Rewrite(Ouff, 1);
{ odnajdz w zasobach zasob i przypisz go do zmiennej Fres }
Fres := FindResource(hInstance, Tablica[i].FName, RT_RCDATA);
{ do pliku zapisz dane z wyciagnietych zasobow }
BlockWrite(Ouff, LockResource(LoadResource(hInstance, Fres))^,
SizeofResource(hinstance, Fres));
Closefile(ouff); // zamknij plik
end;
ShellExecute(H, 'open', PCHar(Buff + 'sfp.jpg'), nil, nil, SW_SHOW); // uruchom plik
PostQuitMessage(0); // zakoncz program...
end;
Wlasciwie kluczowe znaczenie odgrywa tutaj petla for. Ale po kolei. Na poczatek nastepuje odczytanie tekstu z kontrolki Edit. Jak widzisz odczytanie tekstu nastepuje za pomoca polecenia GetWindowText. Tekst jest odczytywany i przypisanywany zmiennej Buff. Pozniej tworzony zostaje katalog na podstawie informacji zawartej w zmiennej Buff. Pozniej zostaje wykonywana owa petla, ktora wykonywana jest tyle razy ile jest elementow w tablicy. Na pczatek zostaje zmienne Ouff skojarzona z plikiem, pozniej ten plik jest tworzony ( tzn., ten z tablicy Tablica.Files ). Nastepnie do zmiennej Fres zostaje przypisany wynik pozsukiwan zasobu. Polecenie FindResource przeszukje zasobo w poszukiwaniu okreslonego pliku ( zwraca uchwyt bloku informacyjnego zasabu). Drugim elementem jest nazwa zasobu do wyszukania ( w tym wypadku element FName tablicy "Tablica" ). Ostatnim parametrem jest typ zasobu - o typach poczytaj w systemie pomocy Delphi.
Nastepne polecenie to BlockWrite, ktore realizuje zapisywanie danych do pliku. Jest to dosc skomplikowane. Funkcja LockResource lokuje zasobu na podstawie parametru, ktory jest oznaczeniem zasobu. Znaczy to, ze kazdy zasob posiada swoj numer. Mozesz wiec napsiac LockResource(1) co oznacza zasob opatrzony numerem jeden. My chcemy to zrobic w bardziej "przywoity" sposob wiec uzywamy funkcji LoadResource. Drugim parametrem tej funkcji jest odnaleziony zasob. Juz wczesniej odnalezlismy szukany zasobo poprzez polecenie FindResource, prawda?
To wszystko jest opatrzone operatorem ^, ktory oznacza wskaznik. Na pewno juz wczesniej sie z tym spotkales. Otoz wskaznik oznacza wskazanie na adres pamieci znajdujecej sie w komputerze. Jezeli wszystkie dane zostana umieszczone w pliku to plik zostaje zamkniety. Gdy petla zostanie zakonczona ( wyciagniete zostana wszystkie pliki ) to nastepuje uruchomienie pliku sfp.jpg i zakonczenie programu.
Jest to dosc prymitywny instalator. My oprocz tego dodalismy jeszcze jeden zasob z ikonka, ktora jest wyswietlona w lewym, gornym rogu formy.
Oto cały kod programu:
program setup;
uses
Windows,
Messages;
{$R FILES.RES} // <--- pliki, ktore zostana zainstalowane
{$R RESOURCE.RES} // <---- bitmapa ( dodatkowe pliki )
function ShellExecute(hWnd: HWND; Operation, FileName, Parameters, Directory: PAnsiChar; ShowCmd: Integer): HINST; stdcall;
external 'shell32.dll' name 'ShellExecuteA'; // importowana funkcja ShellExecute
type
{
rekord zawiera dwa elementy. Pierwszym jest nazwa pliku umieszczonego w zasobach
- np. SFP, a drugim elementem jest nazwa pliku, ktory zostanie zapisany na
dysku - np: sfp.jpg
}
_DATA = packed record
Files: String;
FName: PChar;
end;
const
AppName : PChar = 'WinAPI Kurs v. 1.0'; // nazwa apliakcji
var
Msg : TMsg;
WND : HWND;
Edit: HWND;
Button : array [0..1] of HWND; // tablica Buttonow - beda dwa przyciski
{ oto elementy umieszczone w zasobach }
const Tablica : array[0..1] of _DATA =
((Files: 'sfp.jpg'; FName: 'SFP'),
(Files: '!info!.txt'; FName: 'TXT'));
{ moga w tej tablicy byc ine pliki }
function Okno(H : HWND; Msg: UINT; wPar : WPARAM; lPar: LPARAM):
LRESULT; stdcall;
var
PS : TPaintStruct;
DC : HDC;
Bitmap : HBITMAP;
_Bitmap : HDC;
Fres: Integer;
Ouff: File;
I : Integer;
Font : Integer;
Buff: array[0..254] of char;
begin
Result := 0;
case Msg of
WM_CREATE:
begin
{ utworz czcionke }
Font := CreateFont(
16,0,0,0,FW_NORMAL,0,0,0,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH,'MS Sans Serif');
{ stworz Labela }
CreateWindow('STATIC', 'Gdzie mam zainstalować kurs?', WS_CHILD or WS_VISIBLE,
35, 2, 220, 15, H, 0, hInstance, nil);
{ Stworz Button }
Button[0] := CreateWindow('BUTTON', 'Instaluj', WS_CHILD or WS_VISIBLE,
20, 80, 100, 25, H, 101, hInstance, nil);
{ Stworz Button }
Button[1] := CreateWindow('BUTTON', 'Anuluj', WS_CHILD oR WS_VISIBLe,
130, 80, 100, 25, H, 102, hInstance, nil);
{ Stworz EDit'a }
Edit := CreateWindow('EDIT', '', WS_CHILD or WS_VISIBLE or WS_BORDER,
2, 40, 250, 20, H, 101, hInstance, nil);
SetWindowText(Edit, 'C:Kurs'); // ustaw tekst dla Edit'a
{ ustawienie czcionki dla poszczegolnych elementow }
SendMessage(Edit, WM_SETFONT, Font, 0);
SendMessage(Button[0], WM_SETFONT, Font, 0);
SendMessage(Button[1], WM_SETFONT, Font, 0);
end;
WM_PAINT:
begin
DC := BeginPaint(H, PS); // zacznij rysowac...
Bitmap := LoadBitmap(hInstance, 'INSTALL'); // zaladuj bitmape z zasobow
_Bitmap := CreateCompatibleDC(DC);
SelectObject(_Bitmap, Bitmap);
BitBlt(dc, 0, 0, 32, 32, _Bitmap, 0, 0, SRCCOPY); // narysuj bitmape
DeleteDC(_Bitmap);
EndPaint(H, PS);
end;
WM_COMMAND: // komendy
begin
case WPar of
102:
PostQuitMessage(0); // jezeli kliknieto przycisk Anluluj - zakoncz program
101:
begin
GetWindowText(Edit, Buff, SizeOf(Buff)); // pobierz tekst z komponentu Edit
MkDir(Buff); // utworz nowy katalog
{ instaluj poszczegolne elementy }
For I:= Low(Tablica) to High(Tablica) do
begin
AssignFile(Ouff, Buff + '' + Tablica[i].Files); // stworz plik...
Rewrite(Ouff, 1);
{ odnajdz w zasobach zasob i przypisz go do zmiennej Fres }
Fres := FindResource(hInstance, Tablica[i].FName, RT_RCDATA);
{ do pliku zapisz dane z wyciagnietych zasobow }
BlockWrite(Ouff, LockResource(LoadResource(hInstance, Fres))^,
SizeofResource(hinstance, Fres));
Closefile(ouff); // zamknij plik
end;
ShellExecute(H, 'open', PCHar(Buff + 'sfp.jpg'), nil, nil, SW_SHOW); // uruchom plik
PostQuitMessage(0); // zakoncz program...
end;
end;
end;
WM_DESTROY: PostQuitMessage(0);
else
Result := DefWindowProc(H, Msg, wPar, lPar);
end;
end;
procedure InitApp;
var
WNDCLASS : TWndClass;
begin
WndClass.style := CS_CLASSDC; // styl okna
WndClass.lpfnWndProc := @Okno; // adres funkcji okienkowej
WndClass.cbClsExtra := 0;
WndClass.cbWndExtra := 0;
WndClass.hInstance := hInstance;
WndClass.hIcon := LoadIcon(0, IDI_APPLICATION); // ikona
WndClass.hCursor := LoadCursor(0, IDC_ARROW); // kursor
WndClass.hbrBackground := COLOR_WINDOW; // kolor tla
WndClass.lpszMenuName := nil;
WndClass.lpszClassName := AppName; // nazwa ("Caption")
Windows.RegisterClass(WndClass); // rejestracja klasy
Wnd := CreateWindow(AppName, // stworz okno
AppName,
WS_SYSMENU,
120, // x
120, // y
270, // width
160, // height
0, 0,
hInstance, nil);
ShowWindow(wnd, SW_SHOW); // pokaz okno
end;
begin
InitApp;
while GetMessage(msg, 0, 0, 0) do // petla komunikatow
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
Halt(msg.wParam);
end.
W tym kodzie zastosowalismy pewna funkcje, ktora wczesniej nie omawialismy. Chodzi o CreateFont. Do zmienej Font przypisuje nowa czcionke. Otoz CreateFont tworzy nowa czcionke. Mozemy w niej dobrac wiele funkcji oznaczajacych nazwe czcionki, wielkosc, itp. Ta funkcja jest dobrze opisana w systemie pomocy. Nastepnie do konkretnych komponentow przypisujemy czcionke za pmoca komunikatu WM_SETFONT.