Delphi, cz. 17
Tym razem coś bardziej skomplikowanego. Najpierw zapoznam Ciebie z dynamicznym przydziałem pamięci, omówię także wskaźniki, a pod koniec zaprezentuje aplikację, która wymieniała będzie dane ze schowkiem na podstawie swojego własnego formatu. Aplikacja ta będzie kopiowała dane z rekordu do pamięci komputera, następnie do schowka.
Wskaźniki
Wskaźniki to chyba najtrudniejsza do opanowania cześć programowania w Delphi. Tak mi się zdaje, bo pamiętam jak mi sprawiało trudności opanowanie tego.
Normalnie pisząc program i deklarując zmienne pamięć dla nich przydzielana jest przy uruchamianiu program. Ty nie musisz się o nic martwić - Delphi sam sobie pamięć dla programu zarezerwuje. Takie coś nazywa się alokacją statyczną.
Stos to cały obszar pamięci, który rezerwowany jest dla aplikacji w czasie jej uruchamiania.
A więc obszar pamięci ( Stos ) nie może być w żaden sposób modyfikowany. Jeżeli piszesz mały programik to taka alokacja ( statyczna ) jest wystarczająca.
Sterta to cała dostępna pamięć komputera. + całe dostępne miejsca na dysku komputera.
Teraz jeżeli w swoim programie masz dużo rekordów i zmiennych to przydział statyczny może być niewystarczający. Wówczas możesz skorzystać ze wskaźników przydzielając im pamięć dynamicznie, czyli jawnie. Musisz o to zadbać sam... Tak jak mówiłem. Alokując pamięć statycznie nie możesz korzystać z przestrzeni dyskowej, a tylko z pamięci fizycznej. Jeżeli masz w swoim dużo tablic, rekordów ta pamięć może być niewystarczająca. Trzeba wówczas dynamicznie zwalniać i alokować pamięć. Popatrz na poniższy przykład.
procedure TForm1.Button1Click(Sender: TObject);
type
TRec = record // rekord
Imie: String;
Nazwisko: String;
Kod: Integer;
end;
PRec = ^TRec; // wskazanie na rekord "TRec"
var
Rec : PRec; // zmienna na wskaźnik
begin
New(Rec); // przydział pamięci
Rec^.Imie := 'Adam'; // wypełnienie rekordu
Rec^.Nazwisko := 'Boduch';
Rec^.Kod := 54150;
Dispose(Rec); // zwolnienie pamięci
end;
Pewnie nie po raz pierwszy spotykasz się z taką konstrukcją. Nowy typ "PRec" wskazuje na rekord "TRec". Wskazanie następuje poprzez znak '^'. Następnie jako zmienna zadeklarowany zostaje typ "Rec". Następnie przydział pamięci następuje poprzez polecenie "New". Po tym można zacząć wypełnienie rekordu ( także z wykorzystaniem znaku '^' ). Tak więc operacja została wykonana ( na końcu nastąpiło zwolnienie pamięci ).
Tak więc dzięki wskaźnikom pamięć może być modyfikowana podczas działania programu.
Istnieje kilka sposobów na alokację pamięci. Możesz to np. zrobić poprzez polecenie "AllocMem":
type
PRec = ^Trec;
Trec = record
S : PCHar;
I : Double;
end;
var
Rec : PRec;
begin
Rec := AllocMem(SizeOf(Trec));
Teraz żeby zwolnić pamięć należy wywołać polecenie:
FreeMem(Rec);
Kolejny sposób:
type
PRec = ^Trec;
Trec = record
S : PCHar;
I : Double;
end;
var
Rec : PRec;
begin
GetMem(Rec, SizeOf(TRec));
Przy zwalnianiu także należy wywołać polecenie "FreeMem".
Przydział dynamiczny ma swoje wady i zalety, ale przy wielkich tablicach takie zastosowanie jest konieczne.
Tak jak mówiłem - wskaźniki przechowują informacji o komórce pamięci w jakiej przechowywana jest dana zmienna. Popatrz na poniższy przykład:
var
A : Integer;
B : ^Integer; // wskaznik na typ integer
begin
A := 11; // przypisanie wartosci do zmiennej
// wyswiet zmiena...
Canvas.TextOut(10, 10, IntToStr(a));
B := @A; // przypisz adres komorki do zmiennej
// wyswietl dotychczasowa wartosc komorki
Canvas.TextOut(10, 30, IntToStr(B^));
B^ := 14; // przypisz nowa wartosc dla zmiennej
// wpisz wartosc zmiennej "A"
Canvas.TextOut(10, 50, IntToStr(A));
W powyższym przykładzie następuje przypisanie zmiennej "A" odpowiedniej wartości. Następnie do wskażnika "B" zostanie przypisany adres pamięci pod którym znajduje się zmienna "A" ( operator "@" ). Następnie na ekranie wyświetlona zostaje dotychczasowa wartość komórki. Później ta wartość zostaje zmodyfikowna. Inaczej mówiąc komórce pamięci, która przechowuje wartość zmiennej "A" zostaje przypisana nowa wartość ( cyfra 14 ).
Wykorzystanie schowka
O schowku trochę pisałem podczas odpisu budowania edytora tekstu TeXT Edit. W Delphi. schowek ma dużo większe zastosowanie. Do listy uses dodaj słowo "Clipbrd". Teraz możesz wykorzystać inne jego elementy. Np. żeby do schowka dodać tekst piszesz:
ClipBoard.AsText := 'Nowy tekst w schowku!';
Możesz także kopiować inne elementy - np. zawartość komponentu "Image":
ClipBoard.Assign(Image.Picture);
Żeby teraz do "Image'a" wstawić obrazek ze schowka piszesz:
Image.Picture.Assign(ClipBoard);
Polecenie "Assign" pobiera strumień danych. Inaczej mówiąc robi kopie jakiegoś obiektu.
Tak jak mówiłem wcześniej stworzymy aplikację rejestrującą nowy format danych w schowku. Windows rozpoznaje takie typy:
CF_TEXT - zwykły tekst;
CF_BITMAP - bitmapa Windows;
CF_PALETTE - paleta Windows;
Żeby sprawdzić jaki typ danych znajduje się w schowku stosujesz polecenie "HasFormat":
if ClipBoard.HasFormat(CF_TEXT) then { jakieś czynności }
Na potrzeby naszej aplikacji będziemy musieli zarejestrować nowy format danych. Stworzymy także nowy moduł. Stwórz więc nowy projekt i zapisz go w jakimś katalogu. W programowaniu ciągle korzystasz z modułów napisanych przez programistów Borlanda. Przykładowo: chcesz użyć funkcję "ShellExecute". Jeżeli do listy uses nie dodasz słwa "ShellAPI" to Delphi nie rozpozna polecenia "ShellExecute". Ty także możesz stworzyć nowy moduł. W Delphi z menu "File" wybierz "New". W oknie, które się pojawi wybierz ikonę z napisem "Unit". Delphi otworzy nowy moduł. Zapisz go pod nazwą "DataUnit.pas". Ok teraz do tego modułu należy dodać potrzebne nam procedury i funkcje.
W sekcji "Interface" wpisz słwo "uses" i pod nim wypisz wszystkie potrzebne nam moduły. Będą to: Windows, Classes, Clipbrd;
Teraz należy stworzyć nowy rekord, klasę. Oto jak powinno to wyglądać:
unit DataUnit;
interface
uses
Classes, Windows, { wykorzystanie schowka ->> }Clipbrd;
var
CF_USER : WORD; // zmienna zawiera wynik rejestracji nowego formatu
{
Ten nowy rekord zawiera informacje o wypozyczonej kasecie video :)
Pierwszym jego parametrem jest nazwa filmu, ktora skladac sie bedzie
z max. 20 znakow. Kolejnym parametrem jest numerek wypozyczajacego,
a ostatnim data i czas wypozyczenia filmu.
}
type
TVideoRec = packed record
Film: String[20];
Numer: Integer;
Data : TDateTime;
end;
{
Ta klasa zawiera dwie wazne procedury. Pierwsza z nich kopiuje
dane z rekordu do schowka, a druga pobiera te dane ze schowka i
przypisuje do rekordu.
}
type
TVideo = class
public
Rec : TVideoRec;
procedure CopyToClipBoard;
procedure GetFromClipBoard;
end;
implementation
Zacznijmy od góry. Nowa zmienna, która przechowuje w sobie informacje o tym czy rejestracja nowego formatu powiodła się, czy też nie. Następnie nowy rekord. Zauważ słowo "packed" przy rekordzie. Oznacza ono, że rekord zostanie 'spakowany', czyli zawierać będzie mniej miejsca w pamięci. Jeżeli opatrzymy rekord tą klauzurą to rekord w pamięci zawierać będzie sumę podporządkowanych mu zmiennych. Jeżeli nie opatrzyłbyś rekordu tą klauzurą to pamięć zajmowana przez rekord zaokrąglana by była do 8 bajtów, czyli 8 lub 16 lub 32. Pierwsza zmienną wchodzącą w skład rekordu jest nazwa filmu. Będzie ona typu "String". W nawiasach klamrowych można określić ile znaków zawierać będzie ta zmienna ( czyli max. długość nazwy filmu ). Kolejna zmienna to informacja o numerze klienta, który wypożycza film. No i ostatni parametr to data wypożyczenia filmu.
Następnie rejestracja nowej klasy. Nasza klasa zawiera tylko sekcje "public" by zawarte w niej pola mogły być wykorzystane przez inny moduł. Klasa ta zawiera zmienną która wskazuje na rekord oraz dwie procedury: kopiująca i wklejająca tekst.
No. Teraz w sekcji "Implementation" należy zapisać deklarację dwóch procedur: kopiującej i wklejającej tekst:
procedure TVideo.CopyToClipBoard;
var
Data : THandle;
PData : Pointer; // wskaznik!
begin
{ Zarezerwuj pamiec o rozmiarze rekordu }
Data := GlobalAlloc(GMEM_MOVEABLE, SizeOf(TVideoRec));
try
PData := GlobalLock(Data); // przeksztalc we wskaznik
Move(Rec, PData^, SizeOf(TVideoRec)); // skopiuj do pamieci
ClipBoard.Open; // otworz schowek
ClipBoard.SetAsHandle(CF_USER, Data); // dodaj do schowka
ClipBoard.Close; // zamknij schowek
finally
GlobalUnlock(Data); // uwolnij pamiec
end;
end;
Na pierwszy rzut okna procedura może wydać się skomplikowana. Na samym początku następuje rezerwacja pamięci potrzebnej do przechowania rekordu. Funkcja "SizeOf" oblicza ile miejsca w pamięci zajmuje dany typ - w tym wypadku rekord. Zauważ, że zadeklarowałem jako zmienną wskaźnik. Następnie przekształciłem pamięć we wskaźnik ( GlobalLock ). Wiadomo, że wskaźnik ma zawierać tylko adres pamięci która zadeklarowaliśmy. Następnie polecenie "Move", które przenosi dane z jednego miejsca na drugie. Pierwszym jej parametrem jest źródło - w tym wypadku rekord - drugim wskaźnik, czyli miejsce do którego dane mają być przeniesione. Ostatni parametr to porcja przenoszonych danych. Tak jak poprzednio oblicza się ją za pomocą polecenia "SizeOf".
Kolejne polecenia to operacje na schowku. Następuje jego otworzenie, czyli teraz żadna aplikacja nie może ze schowka korzystać. Następuje do schowka wstawienie nowych danych. Pierwszy parametr to informacja o tym w jakim formacie mają być informacje wstawione. W końcu schowek zostaje zamknięć, a pamięć zwolniona.
Teraz druga procedura wklejająca informacje ze schowka do rekordu:
procedure TVideo.GetFromClipBoard;
var
Data : THandle;
PData : Pointer;
begin
Data := ClipBoard.GetAsHandle(CF_USER); // pobierz dane ze schowka
try
PData := GlobalLock(Data); // przeksztakc we wskaznik
Move(PData^, Rec, SizeOf(TVideoRec)); // skopiuj do rekordu
finally
GlobalUnlock(data); // uwolnij pamiec;
end;
end;
Ta procedura jest krótsza. Najpierw następuje pobranie formatu ze schowka. Później przekształcenie tego na wskaźnik. Z pamięci dane mają być zapisane do rekordu.
To jeszcze nie wszystko. Przed zakończeniem modułu dodaj takie linie:
{ Rejestracja nowego formatu w schowku }
initialization
CF_USER := RegisterClipBoardFormat('CF_DGUSER');
To jest kolejna sekcja! Tak po słwie "Initialization" możesz zawrzeć instrukcje które będą wykonywane na samym początku uruchomienia programu. Następuje tutaj zadeklarowanie nowego formatu wymiany danych ze schowkiem. Tak jak mówiłem: zmienna "CF_USER" zawiera informacje o zarejestrowanym formacie.
W module możesz także umieścić linię "finalization" Po tej linii możesz zawrzeć instrukcję, które będą wykonywane podczas zakończenia pracy programu.
Moduł mamy już gotowy:
(********************************************************)
(* *)
(* DataUnit for Borland Delphi 5 *)
(* Copyright (c) 2001 by Adam Boduch *)
(* Build: 04.03.2001 r. *)
(* HTTP://WWW.PROGRAMOWANIE.OF.PL *)
(* E - mail: boduch@poland.com *)
(* *)
(********************************************************)
unit DataUnit;
interface
uses
Classes, Windows, { wykorzystanie schowka ->> }Clipbrd;
var
CF_USER : WORD; // zmienna zawiera wynik rejestracji nowego formatu
{
Ten nowy rekord zawiera informacje o wypozyczonej kasecie video :)
Pierwszym jego parametrem jest nazwa filmu, ktora skladac sie bedzie
z max. 20 znakow. Kolejnym parametrem jest numerek wypozyczajacego,
a ostatnim data i czas wypozyczenia filmu.
}
type
TVideoRec = packed record
Film: String[20];
Numer: Integer;
Data : TDateTime;
end;
{
Ta klasa zawiera dwie wazne procedury. Pierwsza z nich kopiuje
dane z rekordu do schowka, a druga pobiera te dane ze schowka i
przypisuje do rekordu.
}
type
TVideo = class
public
Rec : TVideoRec;
procedure CopyToClipBoard;
procedure GetFromClipBoard;
end;
implementation
{ TVideo }
procedure TVideo.CopyToClipBoard;
var
Data : THandle;
PData : Pointer; // wskaznik!
begin
{ Zarezerwuj pamiec o rozmiarze rekordu }
Data := GlobalAlloc(GMEM_MOVEABLE, SizeOf(TVideoRec));
try
PData := GlobalLock(Data); // przeksztalc we wskaznik
Move(Rec, PData^, SizeOf(TVideoRec)); // skopiuj do pamieci
ClipBoard.Open; // otworz schowek
ClipBoard.SetAsHandle(CF_USER, Data); // dodaj do schowka
ClipBoard.Close; // zamknij schowek
finally
GlobalUnlock(Data); // uwolnij pamiec
end;
end;
procedure TVideo.GetFromClipBoard;
var
Data : THandle;
PData : Pointer;
begin
Data := ClipBoard.GetAsHandle(CF_USER); // pobierz dane ze schowka
try
PData := GlobalLock(Data); // przeksztakc we wskaznik
Move(PData^, Rec, SizeOf(TVideoRec)); // skopiuj do rekordu
finally
GlobalUnlock(data); // uwolnij pamiec;
end;
end;
{ Rejestracja nowego formatu w schowku }
initialization
CF_USER := RegisterClipBoardFormat('CF_DGUSER');
end.
Teraz na formularzu umieść dwa Buttony odpowiadające za kopiowanie i wklejanie informacji. Umieść także dwie kontrolki typu "Edit" i nazwij je odpowiednio "FileName" i "UserNumber". Umieść na formie także komponent "DateTimePicker". Pokazuje on aktualną datę. Teraz do listy modułów uses twojego modułu dodaj nazwę stworzonego przed chwilą modułu ( DataUnit ). Teraz wygeneruj procedurę "OnClick" pierwszego przycisku.
procedure TMainFrm.btnCopyClick(Sender: TObject);
var
Video : TVideo; // utworz klase...
begin
Video := TVideo.Create;
try
with Video.Rec do
begin
{ Przypisz dane z kontrolek na formie do rekordu w module "DataUnit" }
Film := FilmName.Text;
Numer := StrToInt(UserNumber.Text);
Data := Dates.DateTime;
Video.CopyToClipBoard; // wywyolaj procedure...
end;
finally
Video.Free; // zwolnij pamiec...
end;
end;
Konstrukcja nie jest trudna. Na początku utworzona została klasa ( przydzielona została pamięć ), następnie do rekordu wchodzącego w skład tej klasy przypisane zostały pola z komponentów. No i na końcu oczywiście wywołanie procedury kopiującej.
Wygeneruj procedurę "OnClick" dla przycisku wklejającego tekst:
procedure TMainFrm.btnPasteClick(Sender: TObject);
var
Video : TVideo;
begin
Video := TVideo.Create;
try
{ Sprawdz jaki format danych znajduje sie w schowku. Jezeli to nasz... }
if ClipBoard.HasFormat(CF_USER) then
with Video.Rec do
begin
Video.GetFromClipBoard; //... wywolaj procedure
FilmName.Text := Film; // do kontrolek przypisz dane z rekordu
UserNumber.Text := IntToStr(Numer);
Dates.DateTime := Data;
end;
finally
Video.Free; // zwolnij pamiec
end;
end;
Działanie jest odwrotne. Najpierw następuje sprawdzenie formatu znajdującego się w schowku, a następnie do kontolek przypisane zostały dane z rekordu.
Oto cały program:
(********************************************************)
(* *)
(* Test programme for Borland Delphi 5 *)
(* Copyright (c) 2001 by Adam Boduch *)
(* Build: 04.03.2001 r. *)
(* HTTP://WWW.PROGRAMOWANIE.OF.PL *)
(* E - mail: boduch@poland.com *)
(* *)
(********************************************************)
//
// Program ten wykorzystuje schowek i pamiec oraz wskazniki.
// Ma on za zadanie zarejestrowac nowy format wymiany danych
// ze schowkiem.
// Nastepnie kopuiuje on dane z rekordu do schowka.
unit MainForm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls, ClipBrd, { Nasz modul ->> }DataUnit;
type
TMainFrm = class(TForm)
FilmName: TEdit;
UserNumber: TEdit;
Dates: TDateTimePicker;
btnCopy: TButton;
btnPaste: TButton;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
procedure btnCopyClick(Sender: TObject);
procedure btnPasteClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainFrm: TMainFrm;
implementation
{$R *.DFM}
procedure TMainFrm.btnCopyClick(Sender: TObject);
var
Video : TVideo; // utworz klase...
begin
Video := TVideo.Create;
try
with Video.Rec do
begin
{ Przypisz dane z kontrolek na formie do rekordu w module "DataUnit" }
Film := FilmName.Text;
Numer := StrToInt(UserNumber.Text);
Data := Dates.DateTime;
Video.CopyToClipBoard; // wywyolaj procedure...
end;
finally
Video.Free; // zwolnij pamiec...
end;
end;
procedure TMainFrm.btnPasteClick(Sender: TObject);
var
Video : TVideo;
begin
Video := TVideo.Create;
try
{ Sprawdz jaki format danych znajduje sie w schowku. Jezeli to nasz... }
if ClipBoard.HasFormat(CF_USER) then
with Video.Rec do
begin
Video.GetFromClipBoard; //... wywolaj procedure
FilmName.Text := Film; // do kontrolek przypisz dane z rekordu
UserNumber.Text := IntToStr(Numer);
Dates.DateTime := Data;
end;
finally
Video.Free; // zwolnij pamiec
end;
end;
end.
Pozdrawiam! |