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.

5 komentarzy

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.

A w jaki sposób można powiązać swój komponent z jakimś komponentem (na przykład typu TWebBrowser)?