Delphi Komponenti Yazma
February 07, 2012 Tuesday

Delphi komponentleri yazabilmek için Delphi programlama dilinin komut setine geniş ölçüde hakim olmak ve özellikle OOP (Nesneye Dayalı Programlama) hakkında yeterli bilgiye sahip olmak gereklidir.

Eğer kendinizi komponent yazma konusuna hazır hissediyorsanız, haydi ilk komponentimizi yazmak için hazırlıklara başlayalım.

Komponent yazmaya başlamadan önce, karar vermeniz gereken bir kaç nokta var. Bunlardan bazıları:

  • Yazdığınız komponent hangi tür programlarda kullanılabilecek? (Genel bir komponent mi yoksa sadece bir programa özel bir komponent mi?)

  • Yazdığınız komponent, standard bir komponentten türetilecekse, bu komponent yeni özellik ve olaylara sahip olacak mı?

  • Yazdığınız komponent görsel bir komponent mi (Label, Edit vs), yoksa görsel olmayan bir komponent mi (TQuery, TTable vs.) olacak?

  • Komponentiniz olay güdümlü özelliklere (event driven) sahip olacak mı?

Görüldüğü gibi komponent yazmaya başlamadan önce karar verilmesi gereken soruların cevapları hazır olmalıdır. Bu sorular daha artırılabilir. Onun için önce çok iyi bir planlama, gerekiyorsa sınıfların organizeli olarak planlanması gerekir.

Biz burada görsel olmayan ve en temel özelliklere sahip bir komponent yazmak için gereken özellikleri göstereceğiz. Bu dökümanda aşağıdaki konular anlatılacaktır:

  1. VCL (Görsel komponent Kütüphanesi) içerisinde var olan bir komponenti, tüm özelliklerini devralarak yeni bir komponent oluşturacağız.

  2. VCL içerisinde bulunmayan ve görsel olmayan, başlangıç ve bitiş zamanları arasındaki süreyi veren bir komponent yazacağız.

  3. Oluşturduğumuz yeni komponentimizden türeteceğimiz ve sadece çıktı formatını değiştirmek için yeni bir komponent yazacağız.

  4. Oluşturduğumuz ikinci komponentten yine bir komponent türeterek bu yeni komponente olaylar ekleyeceğiz.

VCL'den komponent türetme

VCL içerinde bulunan bir komponentin tüm özelliklerine sahip yeni bir komponent oluşturmak için Delphi Menüsünden "komponent/New Component" komutunu verin. Biz TLabel sınıfından yeni bir komponent oluşturup adını "TGokLabel" koyacağız.(Delphi'de kullanılan tüm sınıfların başında her zaman "T" olması gerektiğini unutmayın.)

"Ancestor Type" Kısmına "TLabel" yazın. Ancestor Type ile özellikleri devralınacak sınıfın tip tanımı kastedilmektedir.

"Class Name" alanına ise oluşturacağımız komponent için seçtiğimiz ismi yazmalıyız. Bizim komponentimizin adı "TGokLabel" olacaktır.

"Palette Page" alanına komponentimizin, Delphi komponent paletinin hangi kısmında gösterilmesini istiyorsanız onun adını yazınız. (Örn : "Samples")

"Unit File Name" alanına yeni komponentimiz için oluşturulacak olan ".PAS" dosyasının dizinini ve dosyanın adını yazınız. (Unit adı ile komponent adının aynı olmasına dikkat ediniz. Örn: "C:\mycomp\GokLabel.pas")

Gerekli Bilgileri girdikten sonra "New Component" penceresinin görünümü Şekil 1 deki gibi olmalıdır.

Şimdi "Install" butonuna basın ve Komponentin hangi kütüphaneye ekleneceğiniz soran pencerede "Into New Package" kısmını seçip "File Name" kısmına "c:\mycomp\mycomp.dpk" (C:\mycomp dizininin var olduğunu varsayıyoruz. Eğer başka bir dizin ve dosya adı belirtmek isterseniz "Browse" butonuna basarak dizin ve dosya seçebilirsiniz.) ve "Description" kısmına yeni paket ile ilgili kısa bir açıklama yazın. Örnek için Şekil 2'ye bakınız.

"OK" butonuna bastığınız zaman Delphi yeni bir unit oluşturacak ve "mycomp.dpk" adlı paketi derleyecektir. Şimdi komponent paletinin "Samples" kısmında üzerinde "A" harfi bulunan "GokLabel" isimli bir komponent görebilirsiniz ve bu komponentin "Label" komponentinden hiç bir farkı yoktur.

Şimdi GokLabel isimli komponentimize yeni bir özellik olarak Copyright Bilgisi ekleyelim. Bunun için (eğer açık değilse) "GokLabel.pas" dosyasını açın ve Type bloğunu aşağıdaki gibi değiştirin.

type
  TGokLabel = class(TLabel)
  private
    { Private declarations }
    FCopyright : String;

  protected
    { Protected declarations }
    Function GetCopyright : String;

  public
    { Public declarations }

  published
    { Published declarations }
    Property Copyright  : String read GetCopyright Write FCopyRight;

end;

procedure Register;

Görüldüğü gibi private deklarasyonu içerisinde FCopyRight adlı bir ön değişken, protected alanında String bir değer üreten GetCopyright fonksiyonu ve Published alanında Copyright adlı bir özellik tanımlıyor ve Copyright özelliğinin değerini GetCopyrgiht adlı fonksiyondan alacağını ve FCopyright değişkenine aktaracağını belirliyoruz.

Şimdi de "GokLabel.pas" unitimizin implemantation kısmına aşağıdaki kodu ekleyin:

Function TGokLabel.GetCopyright:String;
begin
     Result := 'Copyright by Mustafa GOKMEN, Konya, 2002';
end;

GetCopyright fonksiyonu her zaman aynı sonucu döndürür. Bu nedenle programcı Copyright özelliğinin değerini kod ile değiştirmeye çalışsa bile Copyright özelliğinin değeri aynı kalacak ve sabit bir değer döndürecektir.

Implemantation kısmında, komponent yazma ile uğraşırken her zaman göreceğiniz bir prosedür daha var.

procedure Register;
begin
  RegisterComponents('Samples', [TGokLabel]);
end;

Register adlı bu prosedür, sınıfımızı VCL Kütüphanesine kaydettirir ve komponentin hangi komponent paletinde gösterileceğini belirtir. Örneğimizde TGokLabel Sınıfı komponent paletinde "Samples" adlı grubu altına kaydettirilmektedir. Eğer bir unit içerisinde birden fazla komponent (sınıf) tanımlamışsanız, Register prosedürü altında bunları da VCL kütüphanesine kaydettirmeniz gereklidir.

"GokLabel.pas" dosyasının son hali aşağıdaki gibi olmalıdır.

unit GokLabel;

interface

uses
  Windows, Messages, SysUtils, Classes, Controls, StdCtrls;

type
  TGokLabel = class(TLabel)
  private
    { Private declarations }
    FCopyright : String;
  protected
    { Protected declarations }
    Function GetCopyright : String;
  public
    { Public declarations }
  published
    { Published declarations }
    Property Copyright  : String read GetCopyright Write FCopyRight;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TGokLabel]);
end;

Function TGokLabel.GetCopyright:String;
begin
     Result := 'Copyright by Mustafa GOKMEN, Konya, 2002';
end;

end.

Şimdi, "GokLabel.pas" dosyasını kaydedin ve "mycomp.dpk" dosyasını tekrar derleyip onu da kaydedin. Yeni bir proje başlatıp Form üzerine "GokLabel" komponentinden bir tane indirdiğiniz zaman, Object Inspector'da Copyright alanını ve bu bilginin değiştirilemez olduğunu göreceksiniz. Şekil 3'te Object Inspector'daki Copyright alanını görebilirsiniz.

Yeni komponentimiz

Şimdi kendimize ait olan ve VCL içerisinde bulunmayan bir komponent yazalım. TLabel komponentinden bir başka komponent olarak TGokLabel komponentini oluştururken Type bloğu altında dört ayrı kısım vardı ve biz bunlardan bahsetmemiştik. Bunlar OOP ile ilgili Private, Protected, Public ve Published kısımlarıdır.Şimdi bunları kısaca hatırlayalım.

Private  

Bu kısımda tanımlanan metodlar, olaylar ve özellikler SADECE AYNI UNIT İÇERİSİNDE tanımlanan komponentler ve sınıflar tarafından kullanılabilir. Aynı unit içerisindeki bütün sınıf ve komponentler, birbirlerinin private alanına erişim yetkisine sahiptir ve birbirlerine referans olarak gösterilebilir.

Protected  

Bu kısımda tanımlanan metodlar, olaylar ve özellikler, SADECE BU SINIFTAN TÜRETİLMİŞ DİĞER SINIFLAR tarafından kullanılabilir.

Public  

Bu kısımda tanımlanan metodlar, olaylar ve özellikler, her yerden erişilip kullanılabilir.

Published  

Bu kısımda Object Inspector'da görülüp değiştirilebilen Tasarım anındaki özellikler ile olayların tanımlandığı bölümdür ve çalışılan proje kaydedildiği zaman bu özellikler de <proje_adi>.dfm dosyasına kaydedilirler. Bu kısımda her özellik kullanılamaz. Buna örnek olarak ARRAY türündeki özellikler verilebilir.

Yazacağımız yeni komponent, Win32 API aracılığı ile iki zaman dilimi arasındaki süreyi hesaplayacak. Bunun için başlangıç ve bitiş zamanlarını değişken olarak tanımlayacağız. Şimdi "TComponent" sınıfından yeni bir komponent türetip "C:\mycomp\mycomp.dpk" kütüphanesi içerisine TMyFirst adı ile ekleyin ve dosyaya aşağıdaki kodu ekleyin.

type
  TMyFirst = class(TComponent)

private
        { Private declarations }
        FStart, FStop : DWord;

protected
        { Protected declarations }
        function GetElapsedTime : String; virtual;

public
        { Public declarations }
        procedure Start; virtual;
    procedure Stop ; virtual;

        property StartTime : DWord   read FStart;
        property StopTime : DWord    read FStop;
        property ElapsedTime : String     read GetElapsedTime;
published
        { Published declarations }

end;

Private kısmında Başlangıç ve Bitiş zamanlarını tanımlayan FStart ve FStop adlı iki ön değişken (Sınıf deklarasyonlarında kullanılan değişkenlere ön değişken denmektedir. Ön değişken isimleri Standart olarak F harfi ile başlar ve komponent özelliklerine değer atama işlemlerinde kullanılırlar.) ile public kısmında, çalışma anında süreci başlatma ve durdurma işlemlerini yapabilmek için Baslat ve Durdur isimli iki prosedür tanımlıyoruz. Protected kısmında, sürecin başlaılması ile durdurulması arasındaki zamanı hesaplamak için GetElapsedTime isimli bir fonksiyon tanımlıyoruz. Peki bu fonksiyonu neden protected delarasyonu içerisinde tanımladık? Çünkü bu fonksiyonun sadece, daha sonra TMyFirst sınıfından türetilecek TMySecond ve TMyThird sınıfları tarafından kullanılmasını istiyoruz. Yine public kısmında, çalışma anında kullanılabilecek olan StartTime, StopTime ve ElapsedTime adlarında Salt-Okunur üç özellik tanımlıyoruz. ElapsedTime özelliği değerini GetElapsedTime fonksiyonundan alacaktır.

Şimdi de implementation kısmına aşağıdaki kodları ekleyin.

function TMyFirst.GetElapsedTime: String;
begin
    result := IntToStr(FStop - FStart);
end;

procedure TMyFirst.Start;
begin
    FStart := GetTickCount;       //GetTickCount Bir Windows API fonksiyonudur.
end;

procedure TMyFirst.Stop;
begin
    FStop := GetTickCount;        //GetTickCount Bir Windows API fonksiyonudur.
end;

(GetTickCount fonksiyonu bir Win32 API fonksiyonudur ve Windows başlatıldığından itibaren geçen süreyi tutar.) GetElapsedTime fonksiyonu her zaman Durdurma anındaki değer ile Başlatma anındaki değeri String olarak döndürür ve bu değer ElapsedTime özelliğine aktarılır.

"MyFirst.pas" adlı dosyayı kaydederek "mycomp.dpk" dosyasını tekrar derleyin ve kaydedin.

İlk Komponentimizin tam kodu

Değişikliklerden sonra "MyFirst.pas" dosyasının son hali şu şekilde olmalıdır.

unit MyFirst;

interface

uses
  Windows, Messages, SysUtils, Classes;

type
  TMyFirst = class(TComponent)

private
        { Private declarations }
        FStart, FStop : DWord;

protected
        { Protected declarations }
        function GetElapsedTime : String; virtual;

public
        { Public declarations }
        procedure Start; virtual;
    procedure Stop; virtual;

        property StartTime: DWord read FStart;
        property StopTime: DWord read FStop;
        property ElapsedTime: String  read GetElapsedTime;
published
        { Published declarations }

end;

procedure Register;

implementation

procedure Register;
begin
    RegisterComponents('Samples', [TMyFirst]);
end;

function TMyFirst.GetElapsedTime: String;
begin
    result := IntToStr(FStop - FStart);
end;

procedure TMyFirst.Start;
begin
    FStart := GetTickCount;       //GetTickCount Bir Windows API fonksiyonudur.
end;

procedure TMyFirst.Stop;
begin
    FStop := GetTickCount;        //GetTickCount Bir Windows API fonksiyonudur.
end;

end.

komponentin Test Edilmesi

Yeni proje başlatın. Ana Formun üzerine komponent paletinin "Samples" kısmından "Myfirst" isimli komponentimizden bir tane indirin. Daha sonra iki adet Button komponenti indirip butonların adlarını btnBaslat ve btnDurdur olarak, Caption özelliklerini sırasıyla "BAŞLAT" ve "DURDUR" olarak değiştirip aşağıdaki kodları ilgili butonların "OnClick" olaylarına ekleyin.

procedure TForm1.btnBaslatClick(Sender: TObject);
begin
   MyFirst1.Start;
end;
procedure TForm1.btnDurdurClick(Sender: TObject);
begin
   MyFirst1.Stop;
   Caption := MyFirst1.ElapsedTime;
end;

Projeyi derleyip çalıştırın ve önce "BAŞLAT" butonuna basın. Biraz bekledikten sonra "DURDUR" butonuna basıp Formun başlık çubuğunda iki butona basma arasında geçen sürenin milisaniye olarak gösterildiğini gözleyin.

Virtual, Dynamic ve Override

TMyFirst isimli komponenti yazarken Start, Stop ve GetElapsedTime tanımlarının sonlarındaki virtual kelimesi dikkatinizi çekmiş olmalı. Bu tür tanımlayıcılar bir temel sınıftan devralınan bir metodun,  yeni sınıf içerisinde hangi yöntemle çalışacağını belirlemek içindir. Bu tanımlayıcılar aşağıdaki gibi kısaca özetlenebilir:

Virtual ve Dynamic tanımlayıcıları oluşturulacak temel sınıftaki metodun yeni metoda hangi şekilde devralınacağını belirlemek için kullanılırlar. Aralarında sadece kullanılacak RAM miktarının büyüklüğü ve işlem hızı açısından bir fark vardır. Dynamic olarak tanımlamada daha az RAM kullanımının yanısıra işlem hızının yavaşlaması, Virtual olarak tanımlamada ise kullanılan RAM miktarının artmasına karşın işlem hızının artması ön plana çıkar.

Override tanımlayıcısı ile temel sınıftaki metodlardan birinin yerine çalıştırılacak yeni bir metod tanımlamak için kullanılır. Yeni metod temel sınıftaki metod yerine çalıştırılacak olmasına rağmen temel sınıftaki metoda bağlı olarak çalıştırılabilir. Buna Inheritance denir ve temel sınıf içerisindeki metodu çağırdıktan sonra buna bağlı olarak yeni işlemler de yapılabilir.

Şimdi yukarıda anlatılanları bir örnek ile açıklamaya çalışalım.

"TMyFirst" sınıfından yeni bir komponent türetin ve bu yeni komponentin adını da "TMySecond" olarak atadıktan sonra ilk komponentte yaptığımız işlemleri aynı şekilde yaparak "install" edin. Bu şekilde TMyFirst sınıfının tüm özelliklerine (FStart ve FStop ön değişkenleri, Start ve Stop prosedürleri, GetElapsedTime fonksiyonu ile StartTime, StopTime ve ElapsedTime özelliklerinin tamamına) sahip olan TMySecond adlı yeni bir sınıf türetmiş oluyoruz. 

Şimdi de Type bloğu içerisindeki Protected kısmını aşağıdaki gibi değiştirin:

protected
        { Protected declarations }
     function GetElapsedTime : String; override;

Bu şekilde bir ekleme ile TMyFirst.GetElapsedTime adlı metodu yerine, TMySecond sınıfında yeni bir metod tanımlayarak TMyFirst sınıfının GetElapsedTime fonksiyonunu "override" ediyoruz.

Implementation kısmına da aşağıdaki kodu ekleyin:

function TMyFirst.GetElapsedTime: String;
var
    S : String;
begin
    S := inherited GetElapsedTime;
    result := 'Elapsed Time calculated as ' + S + ' milisecond(' + Format('%.2f second).',
                            [(StopTime - StartTime) / 1000]);
end;

Böylece TMySecond.GetElapsedTime metodunun her çağırılışında TMyFirst.GetElapsedTime metodu çalıştırılarak sonuc (S adlı) bir String değişkene aktarılacak ve bu String değişken kullanılarak TMySecond.GetElapsedTime fonksiyonunun ürettiği çıktı belli bir formatta elde edilecektir.

Burada dikkat edilmesi gereken önemli durumlar var.
  • Eğer "TMyFirst" isimli komponent install edilmemişse "TMySecond" isimli komponent çalışmayacaktır. Çünkü "TMySecond" komponentinin bir metodu "TMyFirst" komponentinden override edilerek alınmıştır.
     
  • Doğrudan "TMySecond" install edilirse Delphi "mycomp.dpk" dosyasına "TMyFirst" adlı komponenti de otomatik olarak ekleyip derlemeyi deneyecektir.
     
  • Eğer "TMyFirst" install edilmiş ancak protected kısmındaki GetElapsedTime fonksiyonu virtual olarak belirtilmemişse "TMySecond" komponenti bu fonksiyonu override edemez.

MySecond komponentinin tam kodu

MySecond.pas dosyasının son hali aşağıdaki gibi olmalıdır. (Interface kısmındaki uses ifadesine MyFirst adlı unit'in de eklenmiş olduğuna dikkat edin.)

unit MySecond;

interface

uses
  Windows, Messages, SysUtils, Classes, MyFirst;

type
  TMyFirst = class(TComponent)

private
        { Private declarations }
        FStart, FStop : DWord;

protected
        { Protected declarations }
        function GetElapsedTime : String; override;

public
        { Public declarations }

published
        { Published declarations }

end;

procedure Register;

implementation

procedure Register;
begin
    RegisterComponents('Samples', [TMySecond]);
end;

function TMyFirst.GetElapsedTime: String;
var
    S : String;
begin
    S := inherited GetElapsedTime;
    result := 'Elapsed Time calculated as ' + S + ' milisecond(' + Format('%.2f second).',
                            [(StopTime - StartTime) / 1000]);
end;

end.

Yeni "MySecond.pas" dosyasını kaydedip "mycomp.dpk" dosyasını tekrar derleyin. Artık Komponent paletin "Samples" kısmında "MySecond" adlı yeni bir komponent daha göreceksiniz.

Bu komponenti test etmek için ilk komponenti test etmek için kullandığımız projenin aynısını "MySecond" komponentini kullanarak tekrarlayın ve programın başlık çubuğundaki değişimi gözleyin.

Komponente Olay Ekleme

"TMySecond" adlı komponentimizden "TMyThird" adlı yeni bir komponent türeterek "Install" edin ve oluşturulan "MyThird.pas" dosyasını kaydedin.

"TMyThird" adlı komponentimizi olaylar (events) eklemek için kullanacağız. Olaylar Komponentin uygulamalarımızla haberleşebilmesi için gereklidir. Komponentimizle ilgili işlemlerin yapılabilmesi ve yapılan işlemlerin uygulama içerisinde kontrol edilebilmesi amacıyla olaylar kullanılmaktadır.

Yeni TMyThird sınıfımıza ait TYPE Bloğunu aşağıdaki gibi değiştirin ve buradaki sınıf deklarasyonlarını ayrı ayrı açıklayalım.

TYPE
TState = (stStarted, stStopped);
TStateChangeEvent = PROCEDURE(Sender: TObject; State: TState) OF OBJECT;

TMyThird = CLASS(TMySecond)
private
{ Private declarations }
   FState: TState;
  FOnStart, FOnStop: TNotifyEvent;
  FOnStateChange: TStateChangeEvent;
    FVersion: String;
    FCopyright : String;

protected
{ Protected declarations }

public
{ Public declarations }
    CONSTRUCTOR Create(AOwner: TComponent); override;
   DESTRUCTOR Destroy; override;
   PROCEDURE Start; override;
   PROCEDURE Stop; override;
   FUNCTION GetVersion: String;

   PROPERTY State: TState read FState;

published
{ Published declarations }
    PROPERTY OnStart: TNotifyEvent read FOnStart write FOnStart;
   PROPERTY OnStateChange: TStateChangeEvent read FOnStateChange write FOnStateChange;
   PROPERTY OnStop: TNotifyEvent read FOnStop write FOnStop;
    PROPERTY Copyright: String read FCopyright write FCopyright;
    PROPERTY Version: String read GetVersion write FVersion;

END;

Tip tanımlarımız içerisine, Yalnızca  stStarted ve stStopped değerlerine sahip olabilecek "TState" sınıfını ve "TState" sınıfından olan "State" değişkenin değerini döndürecek olan "TStateChangeEvent" adlı bir olay (Event) prosedürü tanımlıyoruz.

PRIVATE deklarasyonu

Görüldüğü gibi "TState" sınıfına ait "FState" adlı bir ön değişken tanımladık. Bunun anlamı "FState" ön değişkenin değeri sadece "stStarted" ve "stStopped" değerlerinden birine sahip olabilir ve biz bu değerlere atama işlemlerini "Start" ve "Stop" prosedürleri içerisinde yapacağız.

"FOnStart" ve "FOnStop" ön değişkenleri ise "TNotifyEvent" sınıfındandır. Komponentimize "OnStart" ve "OnStop" olaylarını eklemek için kullanıyoruz. "OnStart" ve "OnStop" olayları "Published property" olarak (Object Inspector'da görülecek şekilde) tanımlanacak ve eğer kullanıcı bu olaylar için bir kod atamış (Assigned) ise, public olarak deklare edilmiş olan "Start" ve "Stop" prosedürleri içerisinden çağırılarak çalıştırılacaktır. 

"FOnStateChange" ön değişkeni de "TStateChangeEvent" sınıfından bir değişkendir. Ama "TStateChangeEvent" bir prosedür olarak tanımlanmış olduğundan, "State" değişkeninin durumunu parametre olarak kullanabilecek olan ve Bizim "Published Property" olarak tanımladığımız "OnStateChange" olayına "State" parametresine değer olarak atar. (Kullanıcı Object Inspector'daki OnStateChange olayına kod yazma için çift tıkladığında aşağıdakine benzer bir prosedür tanımlaması oluşturulur:

procedure TForm1.MyThird1ChangeState(Sender: TObject; State: TState);
begin

end;

Böylece kullanıcı isterse "State" değişkeninin değerini "OnStateChange" olayı içerisinde kontrol ederek kullanabilir. Buna örnek olarak şöyle bir kod yazılabilir.

procedure TForm1.MyThird1ChangeState(Sender: TObject; State: TState);
begin
    btnStart.Enabled := State = stStopped; //Sonuç False olacaktır. Buton Disable Edilir.
    Label1.Caption := 'Sayma işlemi başlatıldı.';
end;

"FCopyright" ve "FVersion" adlı iki String değişken tanımlıyoruz. Bu değişkenlerin kullanımını published deklarasyonu kısmında göreceğiz.

PUBLIC deklarasyonu

komponentimizin oluşturulabilmesi (create) için gereken constructor ve işlemin sona ermesiyle yok edilebilmesi (Destroy) için gereken destructor prosedürleri "TComponent" sınıfından override edilerek tanımlanıyor.

"Start" ve "Stop" prosedürleri de "TMySecond" sınıfından override edilerek alınıyor.

Komponentimizin sürüm bilgisini döndürecek "GetVersion" isimli bir fonksiyon tanımlıyoruz.

Çalışma anında değeri yalnızca okunabilecek (Read-Only) şekilde "TState" sınıfının değerlerinden (stStarted veya stStopped) sadece birine eşit olacak "State" adlı özelliği (Property) tanımlıyoruz. (Public deklarasyonların sadece çalışma anında kullanılabildiğini hatırlayın.) Bu değerin değiştirilebilmesi yine çalışma anında "Start" ve "Stop" prosedürleri aracılığı ile gerçekleştirilebilecektir. "State" özelliği tasarım anında bir değere sahip olamayacağı için published kısmında da tanımlanamaz.

PUBLISHED deklarasyonu

Published deklarasyonları, hatırlayacağınız gibi, tasarım anında tanımlanabilen ve değiştirilebilen komponent özellikleri için kullanılıyordu. Komponentimiz, "OnStart", "OnStop" ve "OnStateChange" adlı üç olaya sahip ve bunlardan "OnStart" ve "OnStop" özellikleri, değerlerini "TNotifyEvent" sınıfının özelliklerine sahip "FOnStart" ve "FOnStop" ön değişkenlerinden alıyor ve veriyor. Yani "TNotifyEvent" sınıfını tetikleyerek "TMyThird" sınıfı ile ilgili bir olay meydana geldiğini bildiriyor ve gerekli kodun çalıştırılmasını sağlıyor.

"OnStateChange" ise "TStateChangeEvent" ile "State" özelliğini  değer olarak göndererek bir prosedür tanımlıyor ve "State" özelliğinde bir değişiklik olduğu zaman tetikleniyor. 

Unutmayınız ki, olay türündeki özellikler, sadece kendileri için tanımlanmış bir kod varsa çalıştırılırlar. Kendileri için bir kod yazılıp yazılmadığını "Assigned" fonksiyonu ile kontrol edilmeli ve varsa çalıştırılmalıdır.

"Copyright" özelliği hem okunabilir, hem yazılabilir (kullanıcı tarafından değiştirilebilir) bir özelliktir. Değerini "FCopyright" ön değişkeninden alır ve yine "FCopyright" değişkenine yazar. Böylece, başlangıç değeri constructor içinde atanmasına rağmen, kullanıcı hem tasarım hem de çalışma anında bu özelliği istediği gibi değiştirebilir.

"Version" özelliği ise read ve write olarak tanımlanmış string değerleri olmasına rağmen sadece okunabilir (Read-Only) durumdadır. Çünkü kullanıcı tasarım anında veya çalışma anında değiştirmek istese bile değeri hep aynı kalacaktır. Değer atama işlemi GetVersion fonksiyonu çağırılarak yapılır ve elde edilen değer tekrar "FVersion" ön değişkenine aktarılır. Ve her değer okuma işleminde "GetVersion" fonksiyonu çağırılacağı için değer hep aynı kalacaktır.

IMPLEMENTATION

"Constructor" ile "TMySecond" Sınıfı override edilerek oluşturulma anındaki tüm özellikleri inherited ile devralınmakta ve yeni başlangıç değerleri tanımlanmaktadır. ("TMySecond" ve "TMyFirst" sınıfları için "Constructor" yazmamıştık. Ancak bu iki sınıf herhangi bir olaya sahip olmadığı için gerek yoktu ve onlar doğal olarak "TComponent" sınıfının constructor'ünü devralıyorlardı.) Yeni değerler "FState" ön değişkenine "stStopped" değerinin atanması (Sınıfın oluşturulması anında bir değer verilmesi olası hataları engellemek içindir. Çünkü "FState" değişkeni, sınıfın oluşturulması anında boş bir değere sahiptir ve bu bir istisna (exception) oluşturur.) ve "FCopyright" ön değişkenine kopya hakları bilgisinin (String) atanması işlemidir.

"Destructor" ile yine "TMySecond" komponenti override edilerek inherited ile yok edilme (destroy) bilgisi alınmakta ve değişiklik yapılmadan kullanılmaktadır. Böylece "TComponent" sınıfının yok edilme işlemi aynen uygulanmaktadır.

"TMyThird" sınıfının "Start" ve "Stop" prosedürleri de "TMySecond" sınıfının aynı adlı prosedürlerinden override edilerek inherited ile çalıştırmaktadır. Ancak bu kez prosedürlerin çağırılmasından sonra "FState" ön değişken değeri (Dolayısıyla "State" özelliğinin değeri) değiştirilmekte ve olaylar için kod yazılıp yazılmadığına bakılmakta, kod varsa olayları tanımlayan kodlar çağırılmaktadır.

Olaylar için kod yazılıp yazılmadığı "Assigned" fonksiyonu ile kontrol edilmekte, eğer sonuç True (kod yazılmış) ise olay, kendisine ait olduğunu kabul ettiği bu kodları "self" parametresi ile çağırmaktadır.

"OnStateChange" olayının çağırılması ise "self" parametresine ek olarak "State" değerini de koda parametre olarak aktarılarak yapılmaktadır.

"GetVersion" fonksiyonu hep aynı sonucu döndürecek şekilde tasarlanmıştır ve döndürülen sonuç "Version" özelliğine değer olarak verilmektedir.

"TMyThird" komponentinin Object Inspector'daki Properties paletinin görünümü Şekil 4'te, Events paletinin görünümü Şekil-5'te verilmiştir.


Şekil 4 - TMyThird Properties
 
Şekil 5 - TMyThird Events

TMyThird komponentinin tam kodu

"TMyThird.pas" dosyasının son hali aşağıda görüldüğü gibidir.

UNIT MyThird;
 
INTERFACE
 
USES
   Windows, Messages, SysUtils, Classes, MyFirst, MySecond;
 
TYPE
TState = (stStarted, stStopped);
TStateChangeEvent = PROCEDURE(Sender: TObject; State: TState) OF OBJECT;

TMyThird = CLASS(TMySecond)
private
{ Private declarations }
   FState: TState;
  FOnStart, FOnStop: TNotifyEvent;
  FOnStateChange: TStateChangeEvent;
    FVersion: String;
    FCopyright : String;

protected
{ Protected declarations }

public
{ Public declarations }
    CONSTRUCTOR Create(AOwner: TComponent); override;
   DESTRUCTOR Destroy; override;
   PROCEDURE Start; override;
   PROCEDURE Stop; override;
   FUNCTION GetVersion: String;

   PROPERTY State: TState read FState;

published
{ Published declarations }
    PROPERTY OnStart: TNotifyEvent read FOnStart write FOnStart;
   PROPERTY OnStateChange: TStateChangeEvent read FOnStateChange write FOnStateChange;
   PROPERTY OnStop: TNotifyEvent read FOnStop write FOnStop;
    PROPERTY Copyright: String read FCopyright write FCopyright;
    PROPERTY Version: String read GetVersion write FVersion;

END;
 
PROCEDURE Register;
 
IMPLEMENTATION
 
PROCEDURE Register;
BEGIN
   RegisterComponents('Samples', [TMyThird]);
END;
 
CONSTRUCTOR TMyThird.Create(AOwner: TComponent);
BEGIN
    INHERITED;
  FState := stStopped;
    FCopyright := '©Copyright by Mustafa GÖKMEN, Konya, 2002';
END;
 
DESTRUCTOR TMyThird.Destroy;
BEGIN
    INHERITED;
END;

PROCEDURE TMyThird.Start;
BEGIN
    INHERITED;
  FState := stStarted;
   IF Assigned(OnStart) THEN 
        OnStart(Self);
   IF Assigned(OnStateChange) THEN 
        OnStateChange(Self, State);
END;
 
PROCEDURE TMyThird.Stop;
BEGIN
    INHERITED;
  FState := stStopped;
   IF Assigned(OnStop) THEN 
        OnStop(Self);
   IF Assigned(OnStateChange) THEN 
        OnStateChange(Self, State);
END;

FUNCTION TMyThird.GetVersion: String;
BEGIN
    result := 'TMyThird Component v1.0';
END;

END.

Test Drive

Aşağıda Demo bir programın çalışma anında üç farklı çıktısı alınmıştır. Her bir resimdeki Formun başlık çubuğu ile Durum çubuğu üzerindeki bilgilere dikkat edin.


Şekil 6 - Başlangıç anı. Herhangi bir butona basılmadı.

Şekil 7 - "Start" butonuna basıldı.

Şekil 8 - "Stop" butonuna basıldı.

©Copyright by Mustafa GÖKMEN, S.Ü. Engineering Faculty, Dept. of Computer Eng, Konya/TURKIYE, 2002. All rights reserved.
Visit Gokmen Portal to get information.