Piątek 11 Kwiecień 2025r. Godz 00:00:00      
Postów: 251      

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.