Bileşen Yazım Klavuzu – 1
Delphi, gerçekten güçlü özelliklerle donatılmış bir geliştirme ortamıdır. Bir çok sınıflar ve yapılar ile IDE özelleştirilebilir. Kendi yazacağınız ya da dışarıdan kullanacağınız bileşenler ile programlama alanınızı özelleştirebilirsiniz. Hatta daha da ileri gidip Expert dediğimiz IDE eklentileri yazarak, IDE’ye olmayan yeni özellikler ekleyebilirsiniz. Bu makale serisinde VCL ve VCL.NET için bileşen yazımını göstereceğiz. Tabi ki, bileşen yazımına değinirken Delphi’nin sahip olduğu belli başlı Nesne Programlama tekniklerine de değinmeden geçmeyeceğiz. Tercih ettiğimiz dil Object Pascal’dır. Ama C++’a da kodlar çevrilebilir.
Ve tavsiyem başka bir bileşen kullanacağınıza kendi bileşeninizi kendiniz yazın! Nasıl mı? Cevabı işte bu makalede…
Öyle bir yazılım geliştirme ortamı düşünün ki, kendi yazdığınız sınıfları tanıyıp kendi bünyesine alabiliyor. Öyle ki, kendi editörleri ile, bilinen bazı tiplerde sınıfınızı daha kolay kullanır hale getiriyor. Ve hatta kendi görsel dizayn aracı ile, sizin sınıfınızı bütünleşik olarak kullanabiliyor. İşte bu geliştirme ortamlarından bir tanesi ve belki de en güçlüsü Delphi’dir.
VCL’de bileşen dediğimiz zaman akla gelen ilk şey, VCL’in tanıyabildiği sınıflardır. VCL’in bir bileşeni tanıyabilmesi için belli başlı kriterler mevcuttur. Bu kriterlerden en önemlisi şüphesiz, nesnenin TComponent sınıfından ya da alt sınıflarından türemesidir.
Öyle ise, VCL’de bir bileşen, sınıf hiyerarşisi TComponent ata sınıfına dayanan nesnelerdir, diyebiliriz. VCL.NET’de de bu olay böyledir. Tek fark TComponent sınıfının nereden türediğidir. Ama bileşen yazıyor isek bu bizi o kadar da çok ilgilendirmiyor. Çünkü TComponent sınıfının ataları ile pek uğraşmayacağız.
DesignTime ve RunTime
Bileşen yazımında bilmemiz gereken iki kavram. DesignTime yani tasarım zamanı, programı derleyip çalıştırmadan önceki tasarım aşamasıdır. Bileşenlerin, özelliklerini değiştirip düzenleyebildiğiniz bu aşamada, bileşenler IDE tarafından kısmen çalıştırılırlar. RunTime yani çalışma zamanı ise, programı derleyip çalıştırdıktan sonraki kısımdır. Bu aşamada IDE, devreden çıkmıştır ve bileşenleriniz çalışmaya başlamıştır. Bu aşamda IDE’nin tek müdahale edebildiği kısım debug işlemleridir.
İleride göreceğimiz belli özellikler ile, DesignTime ve RunTime üzerinde ayrı ayrı işlem yapabileceksiniz. Böylece tasarım zamanında bileşeniniz farklı davranacak, çalışma zamanında farklı davranacaktır.
Bileşen Hiyerarşisi
VCL’in bileşen olarak tanıdığı sınıfların TComponent sınıfından türemesi gerektiğini gördük. Peki bundan sonra olaylar nasıl gelişiyor?
Bildiğiniz gibi iki tip bileşen vardır. Görülebilen(Görsel) ve Görünmeyen(Görsel olmayan) bileşenler.(Türkçeyi –sala bindirip –sele verdiler. Y. Bülent Bakiler)
Görülebilen bileşenler, çalışma zamanında form üzerinde görülebilen bileşenlerdir. Bu bileşenler TControl sınıfından türerler.
Görünmeyen bileşenler ise TControl sınıfından türemeyen herhangi bir TComponent bileşenidir. Normalde sadece DesignTime’da görünürler. Fakat bazı görünmeyen bileşenler, RunTime’da etkileri bulunmaktadır. Ama bunlar çalışma zamanında görünseler bile görülebilen bileşen sınıfına girmezler. Mesela TPopupMenu ve TMainMenu, bu tarz bileşenlerdendir. Her ne kadar bazı makalelerde bu tarz bileşenler kısmi görünür olarak nitelendirilseler de biz bunları da görünmeyen olarak nitelendireceğiz.
Kısacası bileşenleri Görülebilen(Visible) yani TControl sınıfından türeyen bileşenler ve Görünmeyen(Non-Visible) bileşenler yani TControl’den türemeyen bileşenler olarak iki kısma ayırıyoruz.
Tasarım zamanında görülebilen ve görünmeyen bileşenleri ayırmak kolaydır. Böylece hangisi TControl’den türemiş hangisi türememiş anlaşılabilir. Mesela TImage, TButton gibi görülebilen bileşenleri tasarım zamanında görünen şekli üzerinde oynama yapabiliyorsunuz. Ama TTimer bileşeni form’un üzerine eklendikten sonra, bileşeni temsil eden sadece bir kutucuk göreceksiniz.
TWinControl ve TGraphicControl
Dedik ki, TComponent sınıfından türeyen nesneleri, VCL bileşen olarak tanıyabilir. Ayrıca İşletim Sistemi’nin tanıyabileceği sınıflar da mevcuttur. İşletim Sistemi tanıdığı sınıflara kolay ulaşmak için onlara birer numara atar. Bu namaraya Handle diyoruz. Eğer bir bileşenin Handle numarası var ise, bu bileşen İşletim Sistemi tarafından tanınabilmektedir. Herhangi bir programdan bu Handle numarası bilindiği takdirde, ilgili sınıfa ulaşilabilir.
TControl sınıfının iki önemli çocuk sınıfı vardır. Bunlar TWinControl ve TGraphicControl. TWinControl,yukarıda bahsettiğimiz gibi, İşletim Sisteminin tanıyabildiği bileşen türlerindendir. Ve Handle numarasına sahiptir. Bu sınıftan türeyen bileşenler de Handle numarasına sahip olacaklardır. TForm, TButton, TPanel, TCheckBox, TComboBox, TStaticText bu tarz bileşenlerdendir.
TGraphicControl sınıfları ise aksine Handle numarasına sahip değillerdir. Ve İşletim Sistemi, bunları sadece resim olarak görür. TImage, TBevel, TLabel bu tarz bileşenlerdendir. TControl sınıfının, TGraphicControl sınıfından tek farkı, TGraphicControl’de çizim yapabilmeniz için bir Canvas nesnesi tanımlanmıştır.
Öyle ise eğer Görülebilen bileşen yapmak istiyorsak önümüzde iki seçenek bulunmakta. Handle numarası alacak ya da almayacak. Handle numarası alması bileşene ek külfet getirecektir. Eğer Windows API’leri ile bileşen üzerinde işlem yapmayacaksanız, ya da diğer bileşenler ile bu bileşeni etkileşimli bir şekilde kullanmayacaksanız her zaman için TGraphicControl seçiminiz olsun. Ama az önce saydığımız işlemeleri yapacaksanız, bileşeninizin bir Handle numarasına ihtiyacı olacaktır.
Mesela TPanel gibi bir bileşen içine diğer bileşenleri gömecekseniz, bileşeniniz TWinControl‘dan türemelidir. Ya da bileşeninize SetWindowRgn gibi bir api ile değişik şekiller verecekseniz, yine TWinControl sınıfını ebeveyn sınıf olarak kabul etmelisiniz.
Bu örnekler artırılabilir. Bileşen yazımında mesafe katettikçe bu ayrımı yapmak sizin için zor olmayacaktır.
Ayırca TGraphicControl nesneleri, işletim sistemi tarafından tanınmadığı için, Handle numarası olan bir container yani bir taşıyıcı nesneye ihtiyaç duyarlar. Mesela form üzerine bir adet Image ve bir adet Panel ekleyin. Image nesnesi Handle numarasına sahip olmadığından hiç bir zaman panel’in üzerine geçemiyecektir, daima altında kalacaktır. Ta ki, Handle numarası olan bir taşıyıcı nesne içine konulana kadar. Çünkü Handle numarası olmadan işletim sistemi bileşeni tanıyamaz ve hangisinin altta hangisinin üstte olacağını belirleyemez. Aynı şekilde bir Label da Panel, Button gibi bir bileşenin üstüne çıkamaz. Ama StaticText eklediğinizde button’unun üzerine çıkabileceğini göreceksiniz. Eğer altında ise sağ tuşa tıklayıp “Bring to Front” yapabilirsiniz. TStaticText, TWinControl’den türemiştir, TLabel ise TGraphicControl’den türemiştir. Ek olarak, çok luzumu olmadıkça TStaticText kullanılmamalıdır. Çünkü TWinControl sınıfları daha fazla hafıza ve daha fazla işlem gücü gerektirirler.
Bu iki sınıf dışında, bu iki sınıftan türeyen başka sınıflardan da bileşen oluşturabilirsiniz. Mesela TMemo sınıfından türeyen bir başka sınıf yazarak, TMemo’nun mevcut özelliklerinin üzerine yenilerini ekleyebilirsiniz. Ya da TLabel gibi bir GraphicControl sınıfından türeyen bir bileşen yaparak TLabel’a mesela link özelliği ekleyebilirsiniz. Yoksa önceden tanımlanmış bir şeyi tekrar tanımlamanıza gerek yoktur.
Nesne Tabanlı Programlama
Burada bahsedeceğimiz kısım, bileşen yazımında lüzumlu olan bazı nesne işlemleridir. Bu konu hakkında daha fazla bilgi alabilmek için başka makalelere göz atabilirsiniz.
Genel olarak bir sınıfın yapısı şu şekildedir.
TMyClass = class(TAnchestorClass) private protected public published end;
Bu tanımlama, type bloğunda bulunmalıdır. Implementation alanında ise bu sınıfın tanımlamaları bulunacaktır.
Bu örnekte TAnchestorClass sınıfından türeyen bir TMyClass sınıfı tanımladık. Bunun dışında herhangibir method ya da özellik eklemedik.
private alanında, sadece sınıfımızın için kullanılacak olan ve dışarı ile bağlantısı olmayacak olan değişkenler ve metodlar bulunacaktır. Gerek nesne tanımlandıktan sonra, gerekse TMyClass sınıfından türeyen başka bir sınıf içinden, private alanı erişilebilir olmayacaktır.
protected alanı ise yine private alana benzemektedir. Tek fark bu alana sadece çocuk sınıflar erişebilir. Yani TMyClass sınıfından türeyen herhangi bir sınıf protected alanında tanımlanmış olan özellik ve metodlara erişebilecektir.
public alanı, gerek nesne tanımlandıktan sonra, gerekse çocuk sınıf tanımlamalarında görülebilen alandır. Yani private alanın tam ters özellikte bir alandır.
published alanı ise Delphi için özel bir alandır. Bu alan IDE tarafından kullanılabilir olan özellikleri içerir. Bu alana eklediğiniz özellikler IDE’de Object Inspector’da görünecektir. Ayrıca eğer özelliğin kalıcılığı sağlanmış ise, IDE bu özelliğe ait değerleri ve verileri Dfm ya da Nfm dosyaları içine kaydedebilir. Özelliklerin kalıcılığını gelecek bölümlerde bahsedeceğiz.
Constructor ve Destructor
Bir fonksiyon ya da prosedür tanımlamayı biliyoruz. Sınıf içinde bu tanımlamalar şu şekilde olacaktır.
interface type TMyClass = class(TAnchestorClass) private protected public procedure MyProc(Param: string); published end; implementation procedure TMyClass.MyProc(Param: string); begin ShowMessage(Param); end;
Sınıfımızın oluşturma zamanını ya da yok edilme zamanını kontrol edebilmemiz için iki ayrılmış kelime bulunmaktadır. Constructor ve Destructor. Bunlarda aynen procedure ve function gibi çalışırlar. Tek fark, constructor’lar çağrıldığında sınıfımızından yeni bir nesne oluşturulacaktır. Aynı şekilde destructor çağrıldığında ise nesne yok edilecektir. Object Pascal, istediğiniz kadar constructor ve destructor oluşturmanıza izin vermektedir. Örnek bir tanımlamayı şimdi görelim.
interface
type
TMyClass = class
private
protected
public
constructor Olustur(Parametre: string);
destructor YokEt;
published
end;
implementation
constructor TMyClass. Olustur (Parametre: string);
begin
ShowMessage('Nesne Oluşturuldu. Oluşturma parametresi :' + Parametre);
end;
destructor TMyClass.YokEt;
begin
ShowMessage('Nesne yok edildi');
end;Daha sonra bu sınıfımızdan bir nesne oluşturmak için:
var
MyObject: TMyClass;
begin
MyObject := TMyClass.Olustur('Nesneden merhabalar');
MyObject.YokEt;
ShowMessage('Nesne yok edildi mi ' + BoolToStr(Assigned(MyObject), True));
end;Genel olarak mecbur olmasanız da standartlaşmış olan bazı isimlendirmeler mevcuttur. Nesneleri oluşturmak için kullanacağınız constructor için “Create” ve yok etmek için kullanacağınız destructor için “Destroy” tabirleri kullanılmaktadır. Bu isimlendirmeler VCL’de önceden beri kullanıldığı için, oluşturulan diğer sınıflarda da bu isimlendirmeler kullanılmıştır. Hem ileride başka bileşenler ile sorun yaşamamak için hem de başka programcılar’ın bileşeninizi rahat kullanabilmesi için standartları takip etmek faydalıdır. Bu yüzden biz de makalemizde constructor için Create ve destructor için Destroy isimlerini kullanacağız.
Özellikler
Delphi’de çok öncelerden beri var olan ve çoğu programlama dillerinin bile yeni yeni sahip oldukları “özellikler”, değişkenlerin farklı bir çeşitidir.
Öyle bir değişken düşünün ki, değişkene veri atamaya kalktığınızda bir metod çalışsın ve değişkenden veri çekmeye çalıştığınızda ise başka bir metod çalışsın yada belirleyeceğiniz bir başka değişkenden veriyi okusun. İşte bu tarz işlemlere izin veren özel değişkenlere “özellik”(property) diyoruz.
Genel olarak bir özellik aşağıdaki gibi tanımlanır:
property MyProp: Integer read FMyProp write SetMyProp;
Burada bilmemiz gereken 3 önemli alan vardır.
Birincisi özelliğin tipini belirlediğimiz kısımdır. Özelliğe atayacağınız tip, diğer bölümlerde kullanılacak olan metod ve değişkenlerin tipleri ile aynı olmalıdır.
İkinci alan olan read alanına, veri okunacağı zaman nereden okuyacağını bildiriyoruz. Bu örnekte özelliğimizden veri çekilmeye çalışıldığında, veri otomatik olarak FMyProp değişkeninden çekilecektir.
Üçüncü alanımız olan write alanında ise, özelliğimize veri yazılacağı vakit nereye yazılacağını belirliyoruz. Örneğimizde veri, SetMyProp metoduna yollanacaktır.
Önceden de bahsettiğimiz gibi read ve write alanlarına yazacağınız metod ya da değişkenler, özellik ile aynı tipte olmalıdır. Ve özellik tanımlanmadan önce bunların tanımlanması gerekir. Bu tanımlama kısmını örneğimizde göstermedik.
Şimdi aşağıdaki örneğe bakalım
TAraba = class
private
FMarka: string;
function GetMarka: string;
procedure SetMarka(const Value: string);
public
property Marka: string read GetMarka write SetMarka;
end;
implementation
function TAraba.GetMarka: string;
begin
Result := FMarka;
end;
procedure TAraba.SetMarka(const Value: string);
begin
if Value = 'Toyota' then
begin
ShowMessage('Toyota markası kabul edilmedi!!');
end
else
FMarka := Value;
ShowMessage('Şuan ki Marka = ' + FMarka);
end;
end;“Marka” isimli özelliğimizde read ve write metodlarında iki adet metod bulunmaktadır. Nesne tabanlı programlamada bu metodlara Getter ve Setter denilmektedir. Önceki örnekten farklı olarak burada hem read için hem de write için metod kullanıldı.
Getter ve Setter metodlarının tanımlamasının illaki private alanda olmasına gerek yok. Ama tavsiye edilen tanımlama, private ve protected alanlarında yapılmasıdır. Eğer alt sınıflarda bu getter ve setter metodlarının üzerine yazmayı planlamıyorsanız, private kısmında bırakabilirsiniz.
Örneğimizi incelediğinizde, Marka özelliğimizden bir değer “okunmaya” çalışıldığında GetMarka fonksiyonu çalışıyor. Ve bu fonksiyonun çıkış değeri Marka özelliğinin de çıkış değeri oluyor. Aynı şekilde Marka özelliğine bir değer atılmaya kalkıldığında, SetMarka prosedürüne değer parametre olarak yollanır. Bu örneğimizde yeni değer Value parametresi olarak SetMarka prosedürüne gelecektir.
“F”, “Get” ve “Set” öneklerini kullanmanız sizin avantajınızadır. Siz bunlara farklı şeyler de verebilirsiniz. Ama bu önekleri kullanarak IDE editörü ile etkileşiminizi artırabilirsiniz. Mesela sadece alttaki tanımlamayı sınıfın public alanına yazın:
property BirOzellik: Integer read FBirOzellik write SetBirOzellik;
Şimdi bu satırıda iken Ctrl+Shift+C tuşlarına basın. Editör otomatik olarak read ve write kısmında yazılı olan değişken ya da metodu sizin yerinize tanımlayacaktır. Editörün otomatik olarak tanımlayabilmesi için değişken(ya da diğer tabir ile “alan”)’ler için “F” öneki, Getter yani read fonksiyonları için “Get” öneki ve Setter yani write prosedürleri için “Set” önekini kullanmanız yeterlidir.
Yukarıdaki örneği şu şekilde çalıştırın ve sonucu gözlemleyin:
var BirAraba: TAraba; ArabaninMarkasi: string; begin BirAraba := TAraba; BirAraba.Marka = 'Subaru'; BirAraba.Marka = 'Toyota'; ArabaninMarkasi = BirAraba.Marka; ShowMessage(ArabaninMarkasi); end;
Özelliklerde son olarak değinmek istediğim nokta, sadece okunabilir olan ve sadece yazılabilir olan özelliklerdir.
Sadece okunabilir olan bir özellik için, write alanını kaldırmamız yeterlidir. Aynı şekilde sadece yazılabilir olan bir özellik için de, read alanını kaldırmanız yeterlidir.
property ReadOnlyProp: Integer read FReadOnlyProp; // Sadece okunabilir property ReadOnlyProp: Integer write FReadOnlyProp; // Sadece yazılabilir
Türeme, Kalıtım ve Miras Alımı
Bundan önce bir sınıftan türetmeyi göstermiştik. Tak yapmamız gereken sınıf tanımlamasında ata sınıfı belirtmektir. Eğer belirtmezseniz, sınıfınız TObject sınıfından türeyecektir.
TCokOzelButton = class(TWinControl) TCookDegisikResim = class(TImage) TJanjanliLabel = class(TLabel)
gibi…
Bildiğiniz kadarı ile, türettiğiniz ata sınıfına ait private alanı hariç tüm alanlardan faydalanabilirsiniz. Bu nesne özelliğine kalıtım diyoruz.
Bir sınıftan türetme yaptığınız vakit, ata sınıfa ait belli bazı metodları tekrar tanımlamak isteyebilirsiniz. Ya da protected olan bazı özellikleri public ya da published yapmak isteyebilirsiniz. Bu durumda, bu özellik ya da metodu kalıtım yolu ile alıp tekrar tanımlamanız gerekmektedir. Metodlar için bu işleme, üzerine yazma(override) ya da inherit(miras alımı) denilmektedir.
Mesela iki adet sınıfımız olsun:
TAnchestorClass = class protected procedure BirSeylerYap(Param1: string); virtual; end; TMyClass = class(TAnchestorClass) public procedure BirSeylerYap(Param1: string, Param2: Integer); override; end; implementation procedure TMyClass. BirSeylerYap(Param1: string, Param2: Integer); begin //Ata sınıftaki metodu çağırmadan önce yapılacaklar. inherited BirSeylerYap(Param1); //Ata sınıftaki metod çağrıldıktan sonra yapılacaklar. end;
Burada dikkat etmemiz gereken üç kısım var.
Birincisi, ata sınıfta tanımlayacağımız metodun üzerine yazılabilir olabilmesi için virtual ya da dynamic tanımlıyoruz. Aksi halde metodumuz static olacaktır. Ama bu konuya fazla girmiyoruz. Bu kısımda tek söylemek istediğim şey virtual ile dynamic arasındaki farktır. Dynamic metodlar virtual metodlara nazaran daha yavaş çalışırlar ama hafıza kullanımı olarak virtual metodlardan daha az yer tutarlar. Şimdiki işletim sistemlerinde bu fark yok denecek kadar az olduğu için, genelde virtual metodlar kullanılır. Dynamic ve virtual metodların tarihi gelişmesini öğrenmek istiyorsanız ve ayrıntılı bilgi almak istiyorsanız buradan ulaşabilirsiniz.
İkinci olarak, çocuk sınıflarda üzerine yazımak istediğimiz ya da tekrar tanımlamak istrediğimiz metodun tanımlamasından sonra override alan tanımlayıcısını getiriyoruz. Örneğimizde gördüğünüz gibi ata sınıfa nazaran fazladan bir parametre daha eklenmiştir.
Üçüncü olarak, çocuk sınıfımızda metodun kodlarında inherited ayrılmış kelimesi ile ata sınıfın metodunu çağırıyoruz. inherited kelimesinden sonra metodun ata sınıftaki tanımlanmış haline göre metodu çağırıyoruz. Yukarıda örnekteki inherited kısmı TAnchestorClass.BirSeylerYap(Param1) gibi bir manaya gelmektedir.
Eğer inherited ile ata sınıfın metodunu çağırmazsanız, BirSeylerYap metodu çağrıldığında sadece sizin metodunuz çalıştırılacaktır. Ata sınıfın metodu hiç bir zaman çalıştırılmayacaktır. Bunun için, ileride yapacağınız bileşenlerde, ata sınıfın bir metodunu override yaptığınızda bu noktaya dikkat ediniz. Mesela TPanel’den türeyen bir sınıfınızda Paint metodunu override yaptığınızda ve inherited ile TPanel’in Paint metodunu kendi metodunuz içinde bir yerde çağırmadığınızda, TPanel ekrana çizilmeyecektir. Programın çökmesine bile sebebiyet verebilirsiniz.
Ek olarak override kelimesini sadece virtual ve dynamic metodlarda kullanabiliyoruz. Static metodlarda override kelimesini kullanamazsınız.
Aynen metodlar gibi constructor ve destructor’lar da override edilebilir. Ve genelde constructor’lar virtual ve destructor’lar da static olarak tanımlanmışlardır. Aşağıdaki örneğe göz atalım:
TMyComponent = class(TComponent) public constructor Create(AOwner: TComponent); override; destructor Destroy; end; implementation constructor TMyComponent.Create(AOwner: TComponent); begin inherited Create(AOwner); // nesne oluşturulduktan sonraki işlemler end; destructor TMyComponent.Destroy; begin //nesne yok edilmeden hemen önceki işlemler inherited; end;
Metodların inherit edilmesinden farklı değil, değil mi? Ama ata sınıfın constructor ve destructor’ını nerede çağıracağınız önemlidir. Mesela Destroy ‘da inherited kelimesinden sonraki kodlar çalıştırılmayacaktır. Çünkü ata nesnenin Destroy destructor’ı nesneyi yok edecektir. Farkında iseniz ata nesnenin Destroy’unda parametre olmadığından, sadece inherited; şeklinde kullandık.
Son olarak bu konuda değinmek istediğim metodların ve özelliklerin kalıtım yolu ile görünürlüğünün değiştirilmesidir.
Bir önceki örnekte göreceğiniz gibi TAnchestorClass sınıfında protected olan metod TMyClass çocuk sınıfında public olarak tanımlanmıştır. Yani ata sınıftan oluşturulan bir nesne BirSeylerYap metodunu göremezken, çocuk sınıfta bu public olduğu için, çocuk sınıftan oluşturulan bir nesne BirSeylerYap metodunu görüp erişebilecektir.
Aynı şekilde protected olan bir özelliği zahmetsiz bir şekilde görünür yani public yapabilirsiniz. Yani:
// Ata sınıf tanımlaması... protected property BirOzellik: Integer read FBirOzellik write SetBirOzellik; //ÇocukSınıf tanımlaması public property BirOzellik;
Görüldüğü gibi tekrar özelliğin read ve write metodlarını yazmamıza gerek kalmadan, ata sınıfta gizli olan bir özelliği çocuk sınıfta görünür hale çevirdik.
Peki özellikler, inherit yapılamaz mı? Yapılabilir ama override kelimesi ile değil. Ata sınıftaki bir özelliği çocuk sınıfta tekrar tanımlamak istiyorsak:
//Ata sınıf tanımlaması protected FColor: TColor; procedure SetColor(const Value: TColor); property Color: TColor read FColor write SetColor; //Çocuk sınıfı tanımlaması protected FColor: TColor; procedure SetColor(const Value: TColor); public property Color: TColor read FColor write SetColor;
Peki bu bize ne kazandıracak? Ata sınıfa ait bir özelliği çocuk sınıfta da yönetmemize imkan sağlayacaktır. Ayrıca özelliğin getter ya da setter metodu çalıştırılmak istendiğinde, ata sınıfın getter ve setter’ı yerine çocuk sınıfın getter veya setter’ı çalıştırılacaktır. Çocuk sınıf içinde ata sınıfın SetColor metoduna erişmek için de inherited kelimesini kullanabiliriz. Yani ata sınıfa ait özelliğin getter ve setter’ını inherited ile çağırabilirsiniz. Tabi ata sınıftaki setter’ın protected ya da public olması kaydı ile.
Buaraya kadar nesne tabanlı programlamanın bileşen yazımında en çok işimize yarayan kısımlarına değindik. Başka kaynaklar ile daha ayrıntılı bilgiler edinebilirsiniz. Eğer bunları nasıl ve nerede kullanacağımızı merak ediyorsanız sabredin. Çünkü yapacağımız çoğu işlem bu bilgiler üzerine olacaktır.
Bileşenlerde Özellikler ve Event(Olay)’lar
Sınıflarda özelliklerin nasıl tanımlandığını gördük. Bir bileşen sınıfında bazı durumlarda özellikler özel anlam taşırlar. IDE, bileşen sınıfımıza ait özellikleri değiştirip düzenleyebilmek için bu özellikleri tanıyabilmesi lazımdır. Aynı şekilde, bu özellikleri form’a kaydedip daha sonra bu özelliği formdan tekrar okuyabilmesi gerekmektedir. Bu işlem sadece tasarım zamanında değil, çalışma zamanında da yapılmalıdır.
IDE’nin özelliklerimizi tanıyabilmesi için özellikleri, published alanında tanımlamamız gerekmektedir. Ayrıca özelliklerin kaydedilip tekrar yüklenebilmesi için kalıcılığının da sağlanması gerekmektedir.
Basit veri tipleri için kalıcılık, IDE tarafından otomatik olarak sağlanır. Mesela Integer, string, Byte, Boolean, sıralı tipler bu tip basit tiplerdendir. Ve bazı daha kompleks tipler için de özel fonksiyonlar tanımlanmıştır. Mesela TImage bileşeninde bulunan TPicture verilerini okuyup yazabilmek için LoadFromStream ve SaveToStream gibi bazı metodlar mevcuttur. TMemo’da bulunan Lines özelliği de bu tarz bir özelliktir. Kendi veri tiplerinizin kalıcılığının sağlanması, daha sonraki bölümlerde bahsedeceğimiz konulardandır. Şimdilik yapacağımız örnekler, hazır kalıcılığı sağlanmış olan kompleks tipler ile, basit veri tipleri ile ilgili olacaktır.
Event (Olay)
Bir Event (Olay) ise bir çeşit özelliktir. Event’ların tipi metod işaretçileridir. Yani eğer bir özelliğin tipi metod işaretçisi ise bu özellik, event(olay) olarak bilinir.
Metod İşaretçileri, sınıf içinde bulunan fonksiyon ve prosedürelerin işaretçilerini tutan değişkenlerdir. Normal fonksiyon ve prosedür işaretçilerinden farklı olarak metod işaretçilerinin tanımlanma şekli biraz değişiktir. Bir metod işaretçisi tanımlayabilmek için ilk başta metod işaretçisinin tipini tanımlamamız gerekmektedir. Ardından, bu tip ile oluşturulmuş herhangi bir değişken, aynen metod çağırır gibi çağrılabilir.
İlk olarak bir metod işaretçi tipi belirleyelim.
type TMyEvent = procedure(Param1: string) of object;
Ardından bu metod işaretçi tipini kullanan bir event tanımlayalım
TMyClass = class private FOnMyEvent: TMyEvent; published property OnMyEvent: TMyEvent read FOnMyEvent write FOnMyEvent; end;
Artık OnMyEvent adında yeni bir event’ımız mevcut. Burada getter ve setter olarak girdiğimiz FOnMyEvent değişkeni ise metod işaretçisidir. Daha sonradan bu sınıfımız içinde bir yerde bu metod işaretçisini çağırabiliriz.
if Assigned(FOnMyEvent) then
FOnMyEvent('Param1 parametresi');Bu işlemden sonra, FOnMyEvent’a atanmış olan metod çağırılacaktır. Mesela :
type TForm1 = class(TForm) .... procedure MyObjectMyEvent(Param1: string); .... end; .... var MyObject: TMyClass; begin MyObject := TMyClass.Create; MyObject.OnMyEvent := MyObjectMyEvent; end;
Yukarıdaki işlemde, MyObject nesnesinin OnMyEvent olayına Form1’de bulunan gerçek bir metod atanmıştır. Böylece TMyClass sınıfındaki FOnMyEvent metod işaretçisi, Form1’de bulunan MyObjectMyEvent gerçek metoduna işaret eder olmuştur. TMyClass içinde herhangi bir yerdeki FOnMyEvent() çağırımı, Form1’deki atadığımız metodu yani bu örneğimizde MyObjectMyEvent metodunu çağıracaktır. Bu işleme Olay Tetikleme ya da Event Triggering denilmektedir.
Mesela Form üzerine eklediğiniz bir TButton bileşeninin OnClick olayına çift tıklayın. IDE, otomatik olarak Form’un sınıfı içinde yeni bir metod oluşturacaktır. Buraya bir kaç kod yazın ve kaydedip kapatın. Ardından Unit’e ait “dfm” yada .net için “nfm” dosyasına notepad gibi bir programla baktığınızda aynen yukarıdaki örneğimizdeki gibi, Button’nun OnClick olayına IDE’nin oluşturduğu metoda eşitlendiğini göreceksiniz. TButton, kendi içinde bunu FOnClick gibi gizli bir metod işaretçisine bu metodu atayacaktır.
İleride Event’lar ile ilgili daha fazla örnek yapacağız. Şu an için bu kadar teorik bilgi yeterli diye düşünüyorum.
Register İşlemi
Hatırlayacağınız gibi, makalenin başında bahsettiğimiz gibi, IDE’nin bileşenimizi tanıyabilmesi için belli kriterler vardır. Bunlardan en birincisinin, bileşenin TComponent ve alt sınıflardan türemesi gerektiğini gördük.
TComponent sınıfından türeyen bir sınıf oluşturduktan sonra, IDE’nin bu bileşeni kullanabilmesi için kayıt ettirmeliyiz. Bunun için Register prosedürü kullanılmaktadır. Basit bir Register işlemi şu şekildedir.
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Bileşen Kategorisi', [TMyComponent]);
end;IDE, bileşenimizin paketini kayıt ederken, otomatik olarak Register prosedürünü çağıracaktır. Bunun içine yazdığımız RegisterComponents prosedürü de sınıfımızı bileşen olarak kaydettirecektir. İlk parametre, bileşenin araç paletinde hangi kategoriye gireceğidir. İkinci parametre ise array tipinde, kaydettirilecek olan bileşen sınıflarıdır. Bu parametreye [TBilesen1, TBilesen2] şeklinde girerek birden fazla kayıt yaptırabilirsiniz.
İlk Bileşenimizi Yazalım
Bu kadar bilgiden sonra artık ilk bileşenimizi yazabiliriz. Bileşenimiz, Caption özelliğine girilen değerin geçerli bir sayı olmaması durumunda bir hata gösteren ve değeri kaydetmeyen bir bileşen hazırlayalım.
Bunun için ata sınıf olarak TLabel sınıfını seçiyoruz. Ve bu sınıfa ait Caption özelliğinin üzerine yazalım. Bileşenimiz şu şekilde olacaktır.
interface
uses
SysUtils, Classes, Controls, StdCtrls, Dialogs;
type
TNumberLabel = class(TLabel)
private
function GetCaption: TCaption;
procedure SetCaption(const Value: TCaption);
protected
public
constructor Create(AOwner: TComponent); override;
published
property Caption: TCaption read GetCaption write SetCaption;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TNumberLabel]);
end;
{ TNumberLabel }
constructor TNumberLabel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Caption := '0';
end;
function TNumberLabel.GetCaption: TCaption;
begin
Result := inherited Caption;
end;
procedure TNumberLabel.SetCaption(const Value: TCaption);
var
E, Number: Integer;
begin
Val(Value, Number, E);
if E <> 0 then
ShowMessage('Bir sayı girmelisiniz!')
else
inherited Caption := Value;
end;Bu bileşenin nasıl kurulacağına sonra değineceğiz. Şimdi kodları açıklayalım.
Sınıf tanımlamasına baktığımızda bir adet constructor, bir adet property ve bu property’e ait olan setter ve getter’ı görüyoruz. Caption özelliği burada gördüğünüz gibi ata sınıfın Caption özelliğinin üzerine yazılmış durumda. Ata sınıfın bir özelliğine ya da bir metoduna ulaşmak için “inherited” kelimesini kullandığımızı görmüştük.
Bu özelliğin GetCaption getter metoduna baktığımızda, çıkış olarak ata sınıfın Caption özelliğinin çıktı olarak verildiğini görüyoruz. Aynı şekilde SetCaption setter metodunda ise Caption’a atanan değerin geçerli bir sayı olup olmadığını kontrol ediyoruz. Eğer geçerli değilse hata mesajını gösteriyoruz, eğer geçerli bir sayı ise, ata sınıfın Caption özelliğine bu yeni değeri atıyoruz.
Bildiğiniz gibi Delphi, forma yeni eklenen bir bileşenin Caption ya da Text özelliğini, bileşenin ismi olarak belirliyor. Ve bu bileşeni yeni eklediğinizde Caption olarak “NumberLabel1” gibi bir şey atayacak. Ki bu, geçerli bir sayı değildir. Bu yüzden ilk değer olarak Caption özelliğine bir sayı atamalıyız. Bunun gibi ilk değerleri bileşenin Create constructor’ında vermeliyiz. Zaten Create constructor’ına baktığınızda anlayacaksınız. Ayrıca hatırlatmak gerekirse, buradaki constructor virtual olarak tanımlanmıştır. Bu yüzden override; kelimesini kullanmayı unutmuyoruz.
Bileşen Kurulumu
Aslında bileşen yazımı ile alakası olmasa da, çoğu zaman yeni bileşen yazanlar için kafa karıştırıcı bir konudur. Ama yeni bir bileşeni kurmak için gereken şartları sağladığınızda hiç bir sorun ile karşılaşmayacaksınız.
Her bileşen mutlaka bir paket içinde olmalıdır. Delphi 7 ve öncesinde, hazır tanımlanmış olan bir paket bulunuyordu. Ve paket yerine pas uzantılı bir bileşeni kurmak istediğinizde, IDE bunu hazır olan bu paket içine otomatik olarak atıp orada derliyordu. Ama Delphi 8 ve daha sonraki versiyonlarda bu paketten vazgeçildi.
Bir VCL bileşeni kurabilmeniz için ilk başta eğer paket’i yok ise bir paket oluşturuyoruz. Bunun için Menü’den File/New/Other ile açılan pencereden Package’i bulup seçiyoruz. Project Manager bölümünde boş bir paket oluşturulmuş durumda. Contains kısmında paketin içinde bulunacak olan unit’ler, ve Requires kısmında ise bu paketin ihtiyaç duyduğu diğer paketler bulunacaktır. Contains kısmına sağ tuş ile tıklayıp “Add New” ile yeni bir Unit ekleyelim. Ve yukarıdaki TNumberLabel bileşenini buraya yapıştırıp paketle birlikte bir klasöre kaydedelim. Ardından bu paketi Shift+F9 ile derleyelim. Eğer çıkış için bir klasör belirlememiş iseniz, My Documents/Borland Studio Projects/Bpl altında bu paketin derlenmiş dosyasını bpl dosyası şeklinde görebilirsiniz. Bu arada, Bpl dosyaları bir çeşit Dll’dir.
Derleme işleminden sonra Menüden Tools/Options ile açılan pencereden Library – Win32 kısmını bulalım. “Library Path” altına bileşenimizin pas dosyasını kaydettiğimiz klasörü ekleyelim.
En son olarak Menüden “Component/Install Packages…” ile derlenmiş olan bpl dosyasını bulup bileşenimizi kuralım. Bileşenimiz Register prosedüründe belirttiğimiz kategori altına kaydedilmiş olmalıdır.
VCL.NET için de işlemler bu şekildedir. Tek fark, derlediğiniz paket bir Bpl değil, Dll uzantılı bir .NET assembly’si olacaktır. Daha sonra bu dll dosyasını Component/Install .NET Component altından VCL Components kategorisi altına ekleyebilirsiniz.
Bu bölüm buraya kadar yeterli. Buraya kadar öğrendiklerimizi bir bir gözden geçirelim, atlamayalım. Çünkü bu bilgiler, hem bileşen yazımının temelini oluşturmakta, hem nesne programlamanın temelini oluşturmakta hem de Delphi’nin çalışma mantığını göstererek, Delphi programlamada neyin nasıl gerçekleştiği noktasında bizlere fikir vermektedir.
Fikir, eleştiri ve yorumlarınızı bekliyorum.




Fatihcim çok güzel bir anlatım birtek Property lerde stored kullanımını eksik geçmişsin sanırım Eline Sağlık…
Haklısın. Ayrıca Index’li property’leri, implements’i ve default kullanımını da unutmuş ve anlatmamıştım. Gerçi indexler ve default kullanımından ikinci yazıda bahsettim, ama nesne programalama başlığının altına girse fena olmazdı. İnş. üçüncü yazıda bu eksikliği de gidermeye çalışacağım. Sağolasın.
gercekten güzel bir calısma tebrik ederim iyi günler bette ve gercek hayta basarılar dileriem
Sağolun, elimizden geldiğince yazmaya çalışıyoruz.
Sevgili Fatih Ellerine sağlık bu zamana kadar okuduğum en güzel bileşen yazma makalesiydi. Makalelerinin devamını bekliyorm sabırsızlıkla. Bildiklerini o kadar güzel aktarmışsın ki tekrar kutluyorum.
Makalen çok güzel olmuş.Teşekkür ederiz.
[...] Bileşen yazımı 1 [...]
Hocam son örnekteki
if E <> 0 then
satırını anlayamadım. bu konuyu açıklarmısınız
kağan bey, makalenin üzerinde çok zaman geçti ama O örnekteki “Val” metodunu ve parametrelerini incelerseniz, daha iyi anlaşılır kanaatindeyim.