Cuma, Aralık 02, 2005

Derinlemesine TField kullanımı

Veri ile çalışan Delphi uygulamalarında sıkça kullandığımız TBDEDataset, TADODataset, TIBDataset gibi bileşenlerin tümü TDataset sınıfından türetilmiştir. Gerçekte TDataset sınıfı satırlar ve sütunlardan oluşan her tür veriyle çalışabilecek özelliklerle donatılmıştır. Programlarımız çeşitli veritabanı sistemleriyle çalıştığı için Delphi içinde TDataset sınıfından türetilmiş birçok özel bileşen bulunmaktadır. TDataset sınıfının özelliklerini bildiğimiz zaman diğer TDataset türevlerini de aynı şekilde kullanabiliriz. Mesela SQL Server kullanacaksak TADODataset bileşenini, Interbase kullanacaksak TIBDataset bileşenini kullanırız. Delphi ile gelen TDataset bileşenleri haricinde üçüncü parti birçok özel TDataset bileşeni de internette bulunabilmektedir. Örneğin sadece hafızada bir tablo oluşturup kullanabileceğimiz TMemoryDataset bileşeni veya Outlook programına bağlanarak Outlook adres defterini bir tablo gibi kullanmamızı sağlayan TOutlookDataset bileşeni gibi özel TDataset bileşenleri de vardır. Çok özel durumlar için biz de TDataset sınıfını özelleştirerek özel TDataset bileşenleri yazabiliriz ancak bu konu ayrı bir yazı konusu olabilecek kadar geniş bir konudur.

Bir veritabanı sistemine bağlı çalışan TDataset bileşeni veritabanına gerekli bağlantıyı kurabilmek için o veritabanı için özel yazılmış TConnection bileşeninden yardım alır. Mesela bir TADODataset bileşeni kullanıyorsak veritabanına bağlanabilmek için ADO için yazılmış TADOConnection, eğer interbase’e bağlanacaksak bu sefer TIBConnection bileşeni kullanırız. Veritabanlarına bağlı çalışan TDataset’ler tutmasını istediğimiz veriyi veritabanından çekme işini de yaparlar. Bu veri bir tablo olabileceği gibi birkaç tablonun birleşiminden oluşmuş da olabilir. TTable tek bir veritabanı tablosuyla çalışmak üzere tasarlanmış özel bir TDataset türevidir. Bu bileşen gerekli tabloyu veritabanından çekmek için sadece o tablonun adına ihtiyaç duyar. Bu tablo adını bileşenin TableName özelliği ile tanımlarız. TQuery ise SQL cümlesi kullanarak bir veya birçok tablonun birleştirilmesinden oluşan bir verikümesi üzerinde çalışan bir bileşendir. Bu nedenle TQuery TableName özelliği içermez. Onun yerine SQL özelliği içerir. Kısacası TDataset bileşenleri çalışacakları verikümesini veritabanından nasıl çekeceği ile ilgili bilgiye ihtiyaç duyar. Bu bilgiyi o bileşene özgü özellikler ile tanımlayabiliriz.

İlgili tanımları yaptıktan sonra TDataset bileşenimiz ihtiyacımız olan verikümesini veritabanından nasıl çekeceğini ve yaptığımız değişiklikleri nasıl geri göndereceğini öğrenir. Bu tanımlamaları nasıl yapabileceğimize dair ADO ile ilgili bir örneği bir önceki yazımda anlatmıştım. Bu işlemler genellikle her TDataset türü için ilgili veritabanının özelliklerine göre farklı şekillerde yapılır.

Bir TDataset’i Active özelliğini True yaparak veya kod ile Open metodunu çağırarak açarız. TDataset direk veritabanındaki tablolar üzerinden çalışmaz. Onun yerine tablonun bir eşini bilgisayarın hafızasında oluşturur. Bu hafızadaki tablo üzerinde yaptığımız değişiklikler daha sonradan veritabanına yansıtılır. Bu özellikle büyük boyutlu tabloları açarken dikkat etmemiz gereken bir konudur. Tüm tablo olduğu gibi veritabanından bilgisayarın hafızasına çekileceği için yavaş çalışma veya bilgisayarın hafızasının tükenmesi gibi sorunlar ortaya çıkabilir. Genellikle kullanılan yöntem tablonun üzerinde çalışacağımız kadarını kullanmaktır. Örneğin bir müşterinin siparişlerini göstermek için tüm siparişleri çekmenin bir anlamı yoktur. Sadece ilgili müşterinin sipariş kayıtlarını çekmek yeterli olacaktır. Kimi veritabanı sistemleri tablonun tümünü çekmeden sadece gerektiği kadar bilginin çekilmesi özelliğini destekler. Mesela bir TDBGrid ile listelediğimiz kayıtlar listeyi aşağıya kaydırdıkça ekranda göründüğü kadar satır veritabanından çekilecektir. Ancak bu özellik kullanıcı sayısı çok olan sistemlerde veritabanı sunucusunun yükünü normalden daha fazla arttıracağı için performans düşüklüğüne neden olabilir. Özel durumlar haricinde bu tür özellikler kullanılırken dikkat edilmelidir.

TDataset ile ilgili anlaşılması gereken önemli konulardan biri dataset’in aktif kayıt özelliğidir. Bir TDataset’e ilk veri çektiğimizde gelen listenin ilk kaydı dataset’ in aktif kaydı olur. TDataset’in First, Next, Prior, Last metodlarını kullanarak aktif kaydı değiştirebiliriz. TDataset’ ten veri okuduğumuzda aktif kayıtla ilgili bilgileri okuruz. TDataset’ in Fields koleksiyonu tablonun alanlarıyla ilgili TField nesnelerini içerir. Eğer tablomuzda 3 sütun varsa Fields koleksiyonunda her bir sütuna karşılık gelen 3 adet TField nesnesi bulunmaktadır. Herhangi bir koleksiyonda olduğu gibi Fields[0] birinci TField nesnesine, Fields[1] ikinci TField nesnesine ulaşmamızı sağlar. TField nesnesi TDataset’ in aktif kaydındaki herhangi bir değeri okuyabileceğimiz bir Value özelliği içerir. Örneğin:

textbox1.text := MyDataset.Fields[0].Value;

dediğimizde datasetin aktif kaydının birinci sütunundaki değeri textbox’a aktarmış oluruz. Veritabanında tabloyu oluştururken birinci sütuna herhangi bir veritipini tanımlayabiliriz. Yani birinci sütun bir string, integer, double, vb.. tipinde değer içerebilir. Bu nedenle Value özelliği herhangi bir tipin yerine geçen Variant tipindedir. Variant tipler herhangi bir tip barındırabildiği için biraz karmaşık yapıdadırlar ve yavaş çalışırlar. Eğer tablonun birinci sütununun hangi tipte veri içerdiğini biliyorsak TField’in örneğin string için AsString, integer için AsInteger özelliklerini kullanabiliriz. TField hemen hemen her tür veri için AsXXX özelliği içerir. Yani bir değer okurken sütunun içerdiği veri tipine karşılık gelen AsXXX özelliğini kullanmak faydalı olacaktır. Yukarıdaki örneğimizi:

textbox1.Text := MyDataset.Fields[0].AsString;
dateTimePicker.Date := MyDataset.Fields[1].AsDateTime;

olarak yazabiliriz. Dikkat edilirse dateTimePicker bileşeninin Date özelliği TDateTime tipinde olduğu için TField’in AsDateTime metodu ile veriyi TDateTime tipinde okuduk. Eğer ilgili tarihsel değeri bir TextBox ile göstermek istersek TextBox’ın tipi string olduğu için AsDateTime yerine AsString kullanarak tarihsel değeri otomatik olarak TextBox ile kullanabileceğimiz string tipine çevirmiş oluruz.

İlgili sütuna referans eden TField nesnesine ulaşmak için kullanabileceğimiz ikinci yöntem TDataset’in FieldByName fonksiyonudur. FieldByName fonksiyonu ile TField’in FieldName özelliğine göre arama yapabiliriz. TField’in FieldName özelliği tablomuzu oluştururken ilgili sütuna verdiğimiz addır. Yani “Adres” adında bir sütun tanımladıysak dataset açılırken otomatik olarak “Adres” sütununa karşılık gelen bir TField nesnesi oluşturulur ve dataset’in fields koleksiyonuna eklenir. Bu TField nesnesinin FieldName özelliği “Adres” olacaktır. Bu TField nesnesini:

Var MyAdresField: TField;
MyAdresField := MyDataset.FieldByName(“Adres”);
textBox1.Text := MyAdresField.AsString;


veya

textBox1.Text := MyDataset.FieldByName(“Adres”).AsString;

gibi kullanabiliriz.

Normalde TDataset’in Fields koleksiyonu için endişelenmeyiz çünkü bir veritabanına bağlı dataseti açtığımızda delphi otomatik olarak tablomuzdaki alanlara karşılık gelen TField nesnelerini oluşturacaktır. Bildiğimiz gibi TDataset türevi bileşenlerin üzerine çift tıkladığımızda Fields koleksiyonuna ekleme-çıkarma yapabileceğimiz bir liste ekrana gelir. Bu listeye eğer ekleme yapmışsak bu bilgiler kullanılır ve TField listesi tekrardan oluşturulmaz. Tıpkı Ado ile çalışırken TDataset’in TAdoDataset türevi ile çalıştığımız gibi TDateTime tipindeki alanlar için TDateTimeField, string tipindeki alanlar için TStringField gibi TField türevleri ile çalışırız. Hemen her veritipine karşılık gelen bir TField türevi vardır ve o alanın türüne göre bazı ek özellikler içerirler. Mesela TDateTimeField nesnesi tarihin gösterilme şeklini belirleyebileceğimiz bir DisplayFormat özelliği içerir veya bir TBooleanField nesnesi boolean değeri gösterirken True, False değerleri yerine Evet, Hayır veya başka herhangi bir bilgi gösterebileceğimiz bir DisplayValues özelliğine sahiptir. Ancak TDataset’in Fields koleksiyonu üzerinden bu alanlara erişirsek bu alanlar bize TField tipinde görüneceği için TField türevine ait özellikleri göremeyiz. Eğer TDataset listesine alanları elle eklersek o zaman alanın tipine karşılık gelen TField türevi yaratılacağı için bu özelliklere erişebilme şansımız olur. Field listesine “Add All Fields” dediğimizde gelen alanlara tıkladığımızda object inspectordan bakarsak oluşan TField türünün ne olduğunu görebiliriz. Add all fields dediğimizde normalde program çalışırken otomatik olarak oluşacak olan alanları baştan oluşturmuş olacağız. Eğer kullanmayacağımız alanları listeden çıkarırsak bu alanlar bir daha oluşmayacağı için hafızadan yer kazanmış oluruz.

TDataset’in fields listesinden ulaşabileceğimiz alanların veri türüne özel özelliklerine ulaşabilmek için bir tip zorlaması yapmamız gerekecektir. Eğer ilk sütunumuz Date türündeyse ve biz kod içerisinde bu alanın DisplayFormat özelliğini değiştirmek istersek:

MyDataset.FieldByName(‘Tarih’).DisplayFormat = ‘dd.mm.yy’;

yazamayız çünkü FieldByName fonksiyonu Tarih alanını TDateTimeField tipinde değil bir üst tip olan TField tipinde döndürür. Halbuki DisplayFormat TDateTimeField türünün bir özellğidir. O zaman yazmamız gereken:

(MyDataset.FieldByName(‘Tarih’) as TDateTimeField).DisplayFormat = ‘dd.mm.yy’;

olacaktır. Bu sayede elle alan eklemesekte türe özel özelliklere erişebilmemiz mümkün olur. Delphi yardım dosyaları TField türevleri ve bunların spesifik özellikleri ile ilgili geniş bilgi içermektedir.

TField nesnesinin FieldKind özelliği bu nesnenin davranış şekli ile ilgili önemli bir özelliktir. Normalde Fields listesindeki her TField nesnesinin tablodaki bir alana karşılık geldiğinden bahsetmiştim. Bu alanların tümünün FieldKind özelliği fkData olarak gelir. Tablodaki bir alana karşılık gelmeyen TField nesneleri de kullanabiliriz. Bunlar lookup ve calculated alanlardır ve tablodaki bir alana referans etmek yerine ek bilgiler vermek için kullanılabilir. Bir lookup TField alanını içerdiği değeri başka bir datasetteki değerle ilişkilendiren değerler için kullanırız. Örneğin müşteriler ve şehirler gibi iki tablomuz olsun. Müşteri tablomuzda şehir alanı için sayısal bir değer kullanırız ve şehirler tablosuna referans ederiz.

Şehir No Şehir Adı
1Ankara
2İstanbul
3İzmir

Müşteri NoMüşteri AdıMüşteri Adresi Şehir No
1Emre Kızılay 1
2HasanTaksim2
3GülayUlus2
4GülerKonak3
5GülsümUlus1


Yukarıda görüldüğü gibi müşteriler tablosunda şehir değeri için şehirler tablosundaki ŞehirNo değerini kullandık. Normalde Ankara değeri yerine 1 yazdık. Bunu neden yaptık? Normalde sayı tipi 8 harflik bir alan harcarlar. Halbuki şehir adları 8 harften fazla olabileceği için biz şehir adı alanı için 15 harf gibi bir alan ayırırız. Bu şekilde referans alanı kullandığımız için müşteriler tablosunun Şehir adı için kullanacağımız yer daha az olacaktır. Referans alanının bir faydası da birçok alandan oluşan bilgilere bir sayı ile erişebilmemizdir. Bir sipariş tablomuz olduğunu düşünelim

Şipariş No Müşteri No Stok Adı Miktar
11Etek1
21Ceket2
32Etek1
45Yelek3
54Yelek1

Bu tablodan görüleceği üzere siparişin hangi müşteriye ait olduğunu müşteri no alanını kullanarak bulabiliyoruz. Müşteri No değeri Müşteriler tablosundaki Müşteri No alanına referans eder.

TBLMusteri ve TBLSehirler adında iki tane TDataset’imiz olsun. Bu bileşenlerin üzerine çift tıklayarak gelen listeden “Add All Fields” diyerek listelere tüm alanları ekleyelim. TBLMusteri bileşeninin alan listesine “New Field” diyerek yeni bir alan ekleyelim ve Name kısmına uygun bir değer yazalım. Type kısmından string seçelim ve FieldType seçeneklerinden Lookup’ı işaretleyelim. Lookup Definition kısmı etkinleşir. Dataset listesinden TBLSehirler’i seçelim. Key Fields TBLMusteri tablosundaki SehirNo alanı, Lookup Keys ise TBLSehirler tablosundaki SehirNo alanı olacak. Result Field ise Şehir isimlerini görmek istediğimiz için TBLSehirler tablosundaki SehirAdi alanı olacaktır. Bu tanımlamayı yapıp ilgili tabloyu bir DBGrid’e bağlarsak eklediğimiz lookup alanında Şehir numarası yerine Şehir adını görebiliriz. Aslında bu lookup alanı eklerken Delphi nin yardımcı alan ekleme ekranını kullandık. Eğer alan listesine geri döner ve bu yeni eklediğimiz alana tıklarsak Object Inspector da kullandığımız değerlerin tümünü görebiliriz. Yine buradan görebildiğimiz gibi FieldKind özelliği fkLookup olmuştur.

FieldKind özelliğinin bahsetmek istediğim üçüncü değeri fkCalculated’ dir. Calculated alanlar anlık hesaplar yapabileceğimiz ve bunları gösterebileceğimiz alanlardır. Calculated alanları lookup alan gibi ekleriz ancak bu sefer Calculated seçeneğini seçeriz. Örneğin bir sipariş tablomuz olduğunu ve tablomuzda sipariş edilen ürünün fiyatının ve sipariş edilen miktarın yazılı olduğunu düşünelim. Normalde bu bilgileri gösterirken miktar ve fiyat çarpımını da göstermemiz gerekir. Eklediğimiz calculated alana ToplamFiyat adını verdiğimizi düşünürsek datasetin OnCalcFields olayına (event) gideriz ve içine

MyDataset.FieldByName(‘ToplamFiyat’).Value :=
MyDataset.FieldByName(‘Fiyat’).Value * MyDataset.FieldByName(‘Miktar’).Value;


yazarız. Bu sayede ToplamFiyat alanı hesaplanır ve kullanıcıya gösterilir. Normal alanlardaki gibi DisplayFormat özelliklerini kullanarak hesaplanan alanların da görünüm şekillerini değiştirmek mümkündür. Hesaplanan alanlar tablodaki bir sütuna referans etmezler ama diğer sütunları kullanarak hesaplar yapmamızı ve bunları kullanıcıya göstermemizi sağlarlar. Tablodaki bir sütuna referans etme şartı sadece FieldKind özelliği fkData olan alanlar için geçerlidir.

TField nesnesinin OnGetText ve OnSetText adında iki önemli olayı (event) vardır. Bu olaylar kullanıcıya görünen değerlerde değişiklikler yapabilmemiz için çok faydalıdırlar. Tıpkı biribirlerine referans eden tablolarda olduğu gibi kullanıcıya string olarak görünen değerleri tablomuzda sayısal değer olarak tuttuğumuzu düşünelim. Mesela müşteri tablomuzda Müşteri Tipi için bir alanımız olduğunu ve Müşteri Tipi “Normal Müşteri” olanlar için 1 değerini, “Özel Müşteri” olanlar için 2 değerini tuttuğumuzu düşünelim. Yani özel müşteriler için tablomuzda 2 değerini tutacağız ancak bunu kullanıcıya “Özel Müşteri” olarak göstereceğiz. Bu basit bir eşleme olduğu için bu bilgileri ayrı bir tabloda tutmak fazla masraflı olacaktır. Bu isteğimizi basitçe gerçekleştirmek için OnGetText olayına gideriz. Olayın parametre listesine bakarsak “var Text:String“ şeklinde bir parametre olduğunu görebiliriz. Bu Text değişkenine atadığımız değerin kullanıcıya görünecek değer olduğunu belirtir. Sender değişkeni de TField tipindedir ve ilgili alana işaret eder. Örneğin:

if sender.Value = 1 then Text := “Normal Müşteri”;
if sender.Value = 2 then Text := “Özel Müşteri”;


yazarak tablodan okuduğumuz değerleri kullanıcıya farklı göstermek mümkün olacaktır.

Aynı şekilde OnSetText olayını kullanarak kullanıcının alana girdiği değerleri veritabanına farklı değerler olarak kaydedebilmek mümkündür.

2 yorum:

Adsız dedi ki...

Pek bir derinleme göremedim doğrusu.

TField i RunTime da yaratma ve Tabloya ekleme ile ilgili bir yorum da yapilabilinirdi.

Adsız dedi ki...
Bu yorum bir blog yöneticisi tarafından silindi.