Własne komponenty
Adam Boduch
Zanim zaczniesz czytać ten artykuł poczytaj inny potrzebny artykuł klasy.
Jednocześnie podczas nauki będziemy pisać komponent. Będzie to prościutki komponencik sprawdzający, czy jest połączenie z Internetem. Na początek z menu Component wybierz pozycje New Component. W pierwszym polu z listy rozwijalnej musisz wybrać komponent, który będzie bazowym dla naszego, który teraz stworzymy. Wybierz z listy TComponent. Oznacza to, że nasz komponent będzie bazował na klasie TComponent. W kolejnym polu wpisz nazwę komponentu. Wpisz "TIsConnected". W kolejnym polu możesz wybrać miejsce, gdzie ma być zapisany plik z komponentem. Jeszcze z jednej listy wybierz paletę gdzie komponent ma być umieszczony. Wybierz zakładkę Standard. Możesz nacisnąć OK - zostanie wygenerowany plik z modułem, który wygląda tak:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TIsConnected = class(TComponent)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Standard', [TIsConnected]);
end;
end.
Zwróć uwagę na procedurę Register, które powoduje rejestracje komponentu na palecie komponentów. Przejdźmy do omówienia klasy. Zauważ, że zawiera ona dodatkową pozycje - published. W tej sekcji zamieszczone będą właściwości, które będą dodane do Inspektora Obiektów.
Tak więc jeżeli chcesz umieścić jakąś właściwość w Inspektorze Obiektów to robisz to w sekcji published - np. tak:
private
FText : String;
protected
{ Protected declarations }
public
{ Public declarations }
published
property Text : String read FText;
Na początek przyzywaczajaj się do specyficznego typu zapisu. W sekcji private zapisuje się zmienne z literą F na początku. W sekcji published właściwość wpisuje się przed słowem property. Najpierw następuje nazwa komponentu, później typ zmiennej. Właściwość może być tylko do odczytu lub do odczytu i do zapisu. Po słowie read zmienna, która ma przypisze wartość do właściwości. Jeżeli chcesz, aby do właściwości można było zapisywać wartości piszesz:
property Text : String read FText write FText;
Oczywiście oprócz właściwości możesz zamieszczać w komponentach również zdarzenia - oto przykład ( zdarzenia będą umieszczone na zakładce Events ):
TIsConnected = class(TComponent)
private
FTrue, FFalse : TNotifyEvent;
public
{ ... }
published
property OnTrue : TNotifyEvent read FTrue write FTrue;
property OnFalse : TNotifyEvent read FFalse write FFalse;
end;
Zdarzenia muszą być typu TNotifyEvent. Dobrze. To na razie ABSOLUTNE podstawy dotyczące pisania komponentów. Te informacje, która zostały tutaj podane wystarczą do napisania prościutkiego komponentu. Klasa wygląda tak:
type
TIsConnected = class(TComponent)
private
FTrue, FFalse : TNotifyEvent;
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
function Connected : Boolean; // sprawdza, czy jest polaczenie z netem
procedure ShowAbout; // wyswietla informacje o autorze...
published
property OnTrue : TNotifyEvent read FTrue write FTrue;
property OnFalse : TNotifyEvent read FFalse write FFalse;
end;
Kluczową funkcjom w tej klasie jest Connected, która zwraca TRUE jeżeli jest połączenie, a FALSE jeżeli połączenia nie ma. Oto treść tej procedury:
function TIsConnected.Connected: Boolean;
var
Flags: DWORD;
begin
Flags := INTERNET_CONNECTION_MODEM or INTERNET_CONNECTION_LAN or
INTERNET_CONNECTION_PROXY or INTERNET_CONNECTION_MODEM_BUSY;
Result := InternetGetConnectedState(@Flags, 0); // sprawdz polaczenie.
if Result then
begin
{ jezeli procedura OnTrue jest wygenerowana uruchom ja }
if Assigned(FTrue) then OnTrue(Self);
end else if Assigned(FFalse) then OnFalse(Self);
end;
UWAGA. Do listy uses musisz dodać słowo WinInet. Tak więc funkcja sprawdza, czy jest połączenie z netem. Jeżeli tak jest to następuje sprawdzenie, czy zdarzenie OnTrue jest oprogramowane ( Assigned ). Jeżeli tak to zostaje ona wykonana.
I to właściwie całość komponentu. Pozostało jeszcze napisanie konstruktora i destruktora dla komponentu. Nic nadzwyczajnego:
constructor TIsConnected.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
end;
destructor TIsConnected.Destroy;
begin
inherited Destroy;
end;
Oto cały kod komponentu:
{---------------------------------------------------------------}
{ IsConnected v. 1.0 [03.06.2001] }
{ Copyright (c) 2001 by Adam Boduch }
{ http://4programmers.net }
{ boduch@poland.com }
{---------------------------------------------------------------}
unit IsConnected;
interface
uses
Windows, Classes;
{$R COMPONENTRES.DCR}
type
TIsConnected = class(TComponent)
private
FTrue, FFalse : TNotifyEvent;
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
function Connected : Boolean; // sprawdza, czy jest polaczenie z netem
procedure ShowAbout; // wyswietla informacje o autorze...
published
property OnTrue : TNotifyEvent read FTrue write FTrue;
property OnFalse : TNotifyEvent read FFalse write FFalse;
end;
procedure Register;
implementation
uses WinInet;
procedure Register;
begin
RegisterComponents('Standard', [TIsConnected]);
end;
procedure TIsConnected.ShowAbout;
begin
{ wyswietl informacje o autorze }
MessageBox(0, 'TIsConnected' + #13#13+
'Copyright (c) 2001 by Adam Boduch ' + #13+
'http://4programmers.net'+#13+
'boduch@poland.com', '', MB_OK);
end;
function TIsConnected.Connected: Boolean;
var
Flags: DWORD;
begin
Flags := INTERNET_CONNECTION_MODEM or INTERNET_CONNECTION_LAN or
INTERNET_CONNECTION_PROXY or INTERNET_CONNECTION_MODEM_BUSY;
Result := InternetGetConnectedState(@Flags, 0); // sprawdz polaczenie.
if Result then
begin
{ jezeli procedura OnTrue jest wygenerowana uruchom ja }
if Assigned(FTrue) then OnTrue(Self);
end else if Assigned(FFalse) then OnFalse(Self);
end;
constructor TIsConnected.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
end;
destructor TIsConnected.Destroy;
begin
inherited Destroy;
end;
end.
Na początek możesz niektórych rzecz nie rozumieć. Z czasem jednak się z tym oswoisz. W razie problemów - pisz. Zwróć uwagę na dyrektywę u góry tego komponentu. Na początek można by pomyśleć, że ta dyrektywa włącza zasoby do projektu. Nie mylisz się za bardzo. Ta dyrektywa włącza plik zasobów. ( UWAGA! Dla komponentów zasoby tworzy się także w edytorze graficznym tyle, ze zapisuje się je z rozszerzeniem DCR, a nie RES ). Normalnie Delphi komponent przyozdabia swoją standardową ikoną. Jeżeli chcesz stworzyć własną ikonę to tworzysz BITMAPĘ w edytorze zasobów o rozmiarze 24x24 i rysujesz co zechcesz. Bardzo ważne jest nazewnictwo tej bitmapy. Otóż bitmapa musi mieć nazwę taką jak nazwa klasy tego komponentu! Tzn., że dla komponentu TIsConnected bitmapa w zasobach musi mieć właśnie nazwę TISCONNECTED. Plik z zasobami może mieć obojętnie jaką nazwę.
Dodawanie komponentu do palety
Dodawanie komponentu do palety nie jest niczym nadzwyczajnym. Z menu Component wybierasz Install Component. Pokaże się okno. W polu Unit File Name musisz wybrać gdzie znajduje się komponent. Teraz naciskasz ok - Delphi skompiluje komponent i doda go do palety komponentów. Wraz z komponentem, który możesz ściągnąć tutaj dostarczony jest program - demo wykorzystujący ten komponent.
Pisanie komponentu cd.
Często podczas używania jakiegoś komponentu można napotkać na właściwości, które są wybierane w postaci listy rozwijalnej. Jeżeli chcesz stworyć taką właściwość należy zastosować taką kontukcję:
type
TCars = (tcFord, tcFiat, tcBMW);
TTest = class(TAbstractSocket)
private
FCar : TCars;
protected
{ Protected declarations }
public
{ Public declarations }
published
property Car : TCars read FCar write FCar;
end;
W tym wypadku stworzyliśmy nową właściwość, nowego typu, która zostanie dodana do inspektora obiektów.
Często również spotykamy się z konstrukcją w postaci drzewa innych właściwości ( dobrym przykładem może być właściwość Font w Inspektorze Obiektów ). Jeżeli chcesz zrobić właśnie taką właściwość to stosujesz taką konstrukcję:
type
TSetCars = (tcFord, tcFiat, tcBMW);
TCars = set of TSetCars;
TTest = class(TComponent)
private
FCar : TCars;
protected
{ Protected declarations }
public
{ Public declarations }
published
property Car : TCars read FCar write FCar;
end;
Zwróć uwagę na specyficzny typ kodowania programów. Jeżeli nazwa typu nosi nazwę: TCzescKomputera, to elementy tego typu będą zaczynać się od liter: ck ( od: część komputera ) - np: ckRAM, ckProcesor.
Wartości domyślne
Istnieje możliwość nadawania właściwościom wartości domyślnych, które będą automatycznie umieszczane w Inspektorze Obiektów.
published
property Text : String default 'Adam';
Istnieje możliwość NIE domyślnych. Jeżeli jedna klasa przedstawia się następująco:
TFirst = class
{...}
published
property MyProp : Integer default 100;
end;
Nadaliśmy właśnie właściwości domyślną wartość 100. Gdy teraz umieścimy klasę dziedziczącą z dotychczasowej i chcielibyśmy, aby ta sama właściwość MyProp nie miała już wartości domyślnej. Co robić? Zastosować dyrektywę nodefault:
TSecond = class(TFirst)
{ ... }
published
property MyProp : Integer nodefault;
end;
Piszemy kolejny komponent...
Komponent, który teraz napiszemy będzie dziedziczny dla komponentu TImage. Jeżeli użytkownik nasunie kursor nad komponent to obrazek zmieni się na jakiś inny - jeżeli się odsunie to powróci do poprzedniego.
Nasz komponent nazywać się będzie TImagePlus...
Jak tworzyć nowy komponent? Już wiesz. Doprowadź klasę nowego komponentu do takiej postaci:
type
TImagePlus = class(TImage)
private
FEnter, FLeave : String;
FOnEnter, FOnLeave : TNotifyEvent;
protected
{ komunikaty realizujace wejscie i wyjscie kursora w obszar komponentu }
procedure CMMouseEnter(var Msg: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
published
{ wlasciwosci, ktore beda dodane do Inspektora Obiektow }
property EnterImage : String read FEnter write FEnter;
property LeaveImage : String read FLeave write FLeave;
{ zdarzenia dla komponentu. Mozemy dodatkowo kontrolowac wejscie i
wyjscie kursora w obszar komponentu }
property OnMouseEnter : TNotifyEvent read FOnEnter write FOnEnter;
property OnMouseLeave : TNotifyEvent read FOnLeave write FOnLeave;
end;
Kluczowym elementem są dwa komunikaty CM_MOUSEENTER i CM_MOUSELEAVE. Dzięki nim możemy kontrolować czas wejścia i wyjścia kursora w obszar komponentu. Cóż po za tym? Dwie właściwości, w których będzie musiała być wpisana ścieżka obrazka. Dodatkowo dwa zdarzenia OnMouseEnter oraz OnMouseLeave, które będą wykonywane w czasie wejścia/wyjścia kursora w obszar komponentu.
Oto treść dwóch komunikatów:
procedure TImagePlus.CMMouseEnter(var Msg: TMessage);
begin
// laduje do komponentu obrazek ze zmiennej
if FEnter <> '' then Picture.LoadFromFile(FEnter);
{ jezeli wlasciwosc OnMouseEnter jest oprogramowana uruchom ja }
if Assigned(FOnEnter) then OnMouseEnter(Self);
Msg.Result := 1;
end;
procedure TImagePlus.CMMouseLeave(var Msg: TMessage);
begin
if FLeave <> '' then Picture.LoadFromFile(FLeave); // zaladuj bitmape
{ jezeli wlasciwosc OnMouseLeave jest oprogramowana uruchom ja }
if Assigned(FOnLeave) then OnMouseLeave(Self);
Msg.Result := 1;
end;
Co robią te procedury? Na początek następuje sprawdzenie, czy wartość FEnter jest uzupełniona. Jeżeli tak to do komponent zostaje załadowany obrazek. Następnie następuje sprawdzenie, czy wygenerowane jest zdarzenie FOnEnter - jeżeli tak to jest ona wykonywana. Tak samo ( podobnie ) ma się sprawa z drugą procedurą. To właściwie wszystko co związane z komponentem! Oto kod całego komponentu:
(****************************************************************)
(* *)
(* TImagePlus v. 1.0.0 *)
(* Copyright (c) 2001 by Service for programmers *)
(* Adam Boduch; e-mail: boduch@poland.com *)
(* http://4programmers.net *)
(* 03.06.2001 r. *)
(* *)
(****************************************************************)
unit ImagePlus;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls;
type
TImagePlus = class(TImage)
private
FEnter, FLeave : String;
FOnEnter, FOnLeave : TNotifyEvent;
protected
{ komunikaty realizujace wejscie i wyjscie kursora w obszar komponentu }
procedure CMMouseEnter(var Msg: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
published
{ wlasciwosci, ktore beda dodane do Inspektora Obiektow }
property EnterImage : String read FEnter write FEnter;
property LeaveImage : String read FLeave write FLeave;
{ zdarzenia dla komponentu. Mozemy dodatkowo kontrolowac wejscie i
wyjscie kursora w obszar komponentu }
property OnMouseEnter : TNotifyEvent read FOnEnter write FOnEnter;
property OnMouseLeave : TNotifyEvent read FOnLeave write FOnLeave;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Win32', [TImagePlus]); // rejestracja komponentu
end;
procedure TImagePlus.CMMouseEnter(var Msg: TMessage);
begin
// laduje do komponentu obrazek ze zmiennej
if FEnter <> '' then Picture.LoadFromFile(FEnter);
{ jezeli wlasciwosc OnMouseEnter jest oprogramowana uruchom ja }
if Assigned(FOnEnter) then OnMouseEnter(Self);
Msg.Result := 1;
end;
procedure TImagePlus.CMMouseLeave(var Msg: TMessage);
begin
if FLeave <> '' then Picture.LoadFromFile(FLeave); // zaladuj bitmape
{ jezeli wlasciwosc OnMouseLeave jest oprogramowana uruchom ja }
if Assigned(FOnLeave) then OnMouseLeave(Self);
Msg.Result := 1;
end;
constructor TImagePlus.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
end;
destructor TImagePlus.Destroy;
begin
inherited Destroy;
end;
end.
Wiem że od ostatniego komentarza mineło sporo czasu, ale dopiero teraz przeglądam sobie ten artykul i
odpowiem, że przykład jak skonstruuować zdarzenie dla komponnetu z jakimiś zmiennymi opisano tutaj
http://4programmers.net/Delphi/Gotowce/Dwa_w_jednym_czyli_jak_napisać_komponent_i_wysłać_pinga - wystarczy przejrzeć kod źródlowy.
Jest w tym komponencie zdarzenie OnReply ktore posiada dodatkowe zmienne. To jakby ktos też szukał.
A co jeśli chcemy do Eventu dodac jakieś zmienne? Np.
procedure OnKlick(Sender:TObject; X,Y:Integer);
?
owe "powiązanie" nazywa się dziedziczeniem i następuje w tym miejscu : TImagePlus = class(TImage) co onacza, że komponent TimagePlus dziedziczy (przejmuje) właściwości i zdarzenia od komponentu TImage.
Powiązać?
A w jaki sposób można powiązać swój komponent z jakimś komponentem (na przykład typu TWebBrowser)?