Pazar, Ağustos 13, 2006

Delphi'de TListView bileşeni

Öncelikle TListView bileşenin ne olduğunu iyice açıklayalım. Bu bileşen Explorer penceresinde gördüğünüz, dosyaların, klasörlerin gösterildiği yerle aynı özelliklere sahip olan bir bileşen. Yani bu bileşenin içinde çeşitli alt birimler oluşturup bunlara bir simge ve başka alt bilgiler ekleyip, şık bir şekilde ekrana yansıtabiliriyorsunuz. Bu bileşen "Win32" kategorisi altında bulunuyor.

Hemen söyleyelim öğelerin yönetimi oldukça kolay. TStringList'ten bildiğimiz Add, Delete gibi metodların biraz değiştirilmiş hallerini burada da kullanabiliyoruz. Bir farkla, burada ekleme fonksiyonu parametre almıyor ve dönüş değeri olarak da eklenen öğeye ait bir adres ya da daha anlaşılır bir şekilde TListItem tipinde bir değişken döndürüyor. Bu değişken sayesinde yeni eklenen öğeyi rahatça düzenleyebiliyoruz. Bu bileşeni simgelerle birlikte kullanmak isterseniz(ki başka türlü çok da güzel görünmüyor zaten :)) formunuza bir "TImageList" bileşeni eklemeniz gerekiyor. Hatta iki tane eklemelisiniz. Bunlardan biri 32x32'lik büyük simgeleri taşırken diğeri de 16x16'lık küçük simgeleri taşıyacak. TListView bileşeninin "LargeImages" ve "SmallImages" özelliklerini ilgili ImageList'lerle ilişkilendirmeyi unutmayın.(Özellikler bölümünden ilgili ImageList'i seçin.)

Gelelim TListItem'ın nasıl birşey olduğuna. Bunu iyi bilmelisiniz çünkü TListView bileşeninin tüm öğeleri bu tipte. Hemen özelliklerini tek tek inceleyelim:

Caption(string): Bu özellik adından da anlaşılacağı üzere öğenin başlık bilgisini, yani ekranda görüntülenecek olan metni tutuyor.(yenioge.Caption:='Adı';)

Checked(Boolean): Bu özellik -eğer TListView'un "checkboxes" özelliği true ise- öğenin tikli olup olmadığını belirtiyor.

Cut(Boolean): Bu özellik simgenin kesme işlemi yapıldığı sıradaki gibi yarı saydam görüntülenip görüntülenmeyeceğini belirtiyor.

DropTarget(Boolean): Bu özellik true yapıldığında, öğe bırakma-bölgesi olarak gösterilir ve ListView içindeki diğer tüm öğelerin bu özelliği false yapılır.

ImageIndex(Integer): Bu özellik öğenin ilişkilendirilmiş ImageList içindeki kaç numaralı resmi simge olarak kullanacağını belirtir. Değeri -1 olduğunda bir simge gösterilmez.

Index(Integer): Bu özellik öğenin ListView içindeki yerini belirtir. İlk öğenin Index'i 0'dır.

OverlayIndex(Integer): Bu özellik kulanılacak üst katman resminin(overlay image) ImageList içindeki yerini belirtir.

Selected(Boolean): Bu özellik öğenin seçili olup olmadığını belirtir. ListView içinde birden fazla öğe seçildiğinde öğenin seçili olup olmadığını bu özelliğine bakarak anlayabiliriz.

SubItemImages(Array of Integer): Bu özellik öğenin alt öğelerinin resim numaralarını tutar. Eğer alt öğelerin yanında resim görüntülemek istiyorsanız "SubItemImages[1]:=5" gibi bir ifadeyle bunu yapabilirsiniz. Burada köşeli ayraçlar arasındaki "1" alt öğenin indexini ve "5" de resim indexini belirtiyor.

SubItems(TStrings): Bu özellik alt öğeleri tutar. Alt öğe eklemek, silmek veya yönetmek oldukça basittir. Bir Memo ya da ListBox bileşeninin öğelerini yönetmekle hemen hemen aynıdır. TStrings tipini biraz bilmeniz yeterli.

Evet işimize yarayan özellikler bunlar. Şimdi ListView'a geri dönelim isterseniz. ColumnClick ve Columns adı altında 2 özelliğimiz var dikkat ettiyseniz. Bunlardan "Columns" ayrıntılı gösterimdeki sütunların isimlerini temsil ediyor. Yani örneğin her öğenizin 2 alt öğesi(subitem) var. O zaman "columns" bölümüne toplam 3 başlık eklemeniz gerekiyor. Birincisi öğenin adının bulunduğu sütunun adı, diğerleri de sırasıyla birinci ve ikinci alt öğelerin başlığı olacak. ColumnClick özelliğini true yaparsanız bu sütunlara tıklanabilecek ve siz bu tıklama olaylarını yönetebileceksiniz, ki bu iş genelde o sütundaki alt öğeye göre sıralama yapmak için kullanılır.

Ve son 2 önemli özelliğimiz, SortType ve ViewStyle. Sort type alfabetik sıralamanın neye göre yapılacağını belirtir. stNone bir sıralama yapmazken stName sadece başlıklara göre, stData alt öğelere göre, stBoth ise her iki bilgiye göre sıralama yapar. Ayrıca bunu stNone yapıp kendi sıralama sisteminizi de geliştirebilirsiniz. Bunun için ListView bileşeninizin onCompare olayına bir kaç kod yazmanız gerekiyor. Ayrıca sıralama fonksiyonunun çalışması için de "AlphaSort" metodunu çağırmalısınız.(Listview1.AlphaSort;) onCompare olayında size 2 tane TListItem öğesi veriliyor ve siz buraya yazacağınız kodla hangisinin daha önce gelmesi gerektiğini belirtiyorsunuz. Eğer Item1 daha önce gelmeliyse Compare değerini sıfırdan küçük, daha sonra gelmeliyse Compare değerini sıfırdan büyük yapmalısınız. Eğer ikisi de eşitse o zaman bu değeri 0 yapmanız gerekiyor. ViewStyle özelliği de aktif olan gösterim tipini belirtiyor. vsIcon bildiğiniz "Büyük Simge" gösterimi, ve burada LargeImages'a bağlı ImageList kullanılıyor. Diğer tüm modlarda SmallIcons'a bağlı ImageList kullanılıyor. vsSmallIcons, küçük simgeler; vsList, liste ve vsReport, ayrıntılı gösterimi temsil ediyor. Bu değerleri program çalışırken de değiştirip farklı görünümler arasında geçiş yapabilirsiniz.

Evet, bu kadar ön bilgiden sonra sanırım ListView bileşenimize çalışma zamanı nasıl öğe ekleyip, bunları nasıl yöneteceğimizi öğrenmeye hazırız. Öncelikle TListItem tipinde bir değişken tanımlamalısınız. Daha sonra TListView.Items.Add fonksiyonuyla oluşturmalı, değişkenimize bağlamalı ve gerekli düzenlemeleri yapmalısınız.

Bir ListView bileşeninde toplu ve büyük değişiklikler yapmadan önce "BeginUpdate" ve bu işlemlerin sonunda da "EndUpdate" metodlarını çağırmak işlem hızını oldukça arttıracaktır. Bu komutlar her değişiklik olduğunda ekranın güncellenmesini engeller ve sonra işiniz bittiğinde tüm değişikliğin bir defada gösterilmesini sağlar. ListView'un "Clear" metodu, ListView'un içini güzelce temizler :). Ayrıca istediğiniz öğeyi silebilir, öğelerin yerini değiştirebilir ve daha başka birçok şey yapabilirsiniz. Hepsini burada anlatmam inanın çok zor. Yalnız yazıyı bir örnekle bitirmek istiyorum.

Örneğimiz bir TStringList içinde depolanmış dosya isimlerini ListView içinde göstermek. Ayrıca bu dosya isimlerinde dosya yolları da var ve biz bunu ayırıp her dosyanın ilk alt öğesinin bu dosyanın yolunu göstermesini de sağlayacağız.

Bunu yapmadan önce ImageList'imize istediğimiz simgeleri eklemeliyiz. Bunu yaptıktan sonra kodlara geçebiliriz:

var
yeni:TListItem;
i:integer;
begin
ListView1.Items.BeginUpdate; //bahsettiğimiz güncelleme başlangıcı
ListView1.Clear; //temizleyelim
for i:=0 to dosyalar.Count-1 do //TStringListimizin adının "dosyalar" olduğunu varsayalım
begin
yeni:=Listview1.Items.Add; //yeni öğeyi ekledik ve adresini değişkenimize atadık.
yeni.Caption:=ExtractFileName(dosyalar[i]); //başlığını dosya adı yaptık
yeni.SubItems.Add(ExtractFilePath(dosyalar[i]); //yolunu alt öğe olarak ekledik
yeni.ImageIndex:=1; //burası size kalmış, resim indexi
end;
ListView1.Items.EndUpdate; //değişiklik yapmayı bitirdik
end;

Evet yukarıdaki örnek kodların da yardımıyla TListView bileşenini daha rahat ve etkin kullanabileceğinizi umuyorum. Kolay gelsin..

Not: Yazı, zamanında ByteOnline için yazdığım "Delphi'de TListView bileşeninin pratik kullanımı" yazısından uyarlamadır.

Delphi'de Dosya Arama Fonksiyonları

Bu yazıda Delphi ile dosya aramayı göreceğiz. Burada yazacağımız fonksiyon bir bilgisayar üzerinde bulunan dosyaları aramaktan çok programınızın bir klasör içindeki dosyaların listesini vermesini sağlamak için kullanılabilir. Çünkü dosya sayısı arttığında oldukça yavaşlıyor. Bu örneğin "plug-in" desteğine sahip bir program yaptığınızda plugin klasörünün içindeki "dll" dosyalarının listesini almak için kullanabilirsiniz.

Delphi'de arama yapmak için "TSearchRec" adındaki bir kayıt tipini ve "FindFirst,FindNext,FindClose" fonksiyonlarını kullanıyoruz. Aynı zamanda burada "repeat-until" döngüsünü de göreceğiz ve kullanacağız. Bu döngü aslında while döngülerinin farklı bir versiyonu ancak okunabilirlik ve anlaşılabilirlik açısından daha pratik.

Öncelikle fonksiyonumuzun tanımını yapalım:

function DosyalariBul(yol, aramametni: string): integer;

Burada yol ve aramametni olmak üzere 2 parametre tanımladık. Bunlardan "yol" tahmin edebileceğiniz gibi arama yapılacak yolu belirtirken "aramametni" de Windows'tan aşina olduğumuz "*" karakterinin de kullanabileceğimiz bir arama metnini temsil ediyor. Bunlara ek olarak fonksiyonun başında "bulunan" adında ve "TSearchRec" tipinde bir değişken daha tanımlıyoruz. Bu değişken bulunan dosyaya ait bilgileri tutacak.

var
bulundu: TSearchRec;

İşte şimdi "FindFirst" fonksiyonunu kullanmamız gerekiyor. Burada ufak bir açıklama yapalım hemen. FindFirst bir aramaya başlarken başlangıçta ve sadece bir kere çağırmamız gerekn bir fonksiyondur. Bu fonksiyon parametre olarak aramametni ve yolun birleştirilmiş bir versiyonunu, bulunacak dosyaların özelliklerini(arşiv, gizli, klasör vs.) ve bulundu adlı kaydımızı parametre olarak alıyor. Dönüş değeri "0" ise kriterlerimize uygun bir veya daha fazla dosya bulunduğunu anlıyoruz. Daha sonra kriterimize uyan bir sonraki dosyayı bulmak için FindNext fonksiyonunu çağırıyoruz. Bu fonksiyon sadece "bulundu" adlı değişkenimizi parametre alıyor. Arama kriterlerini ise FindFirst ile bir kez belirttiğimiz için tekrar vermemize gerek yok. Yine bu fonksiyondan da dönüş değeri olarak "0" değerini alırsak aramamıza devam edebiliriz. O zaman buna uygun bir "repeat-until" döngüsü şu şekilde olacak:

if FindFirst(yol+aramametni,faAnyFile,bulundu) = 0 then
begin
repeat
begin

//buraya bulunan dosyayla ilgili yapılacak işlemler girecek
end;
until (FindNext(bulundu) <> 0);
end; //if'in end'i.

Bu kısmı incelersek, ilk başta ğer kriterimize uygun bir dosya yoksa aramaya girmememizi sağlayan bir IF komutu yer alıyor. Bunun ardından FindNext fonksiyonundan 0'dan farklı bir değer dönene kadar; yani uygun başka dosya kalmayana kadar yukarıda belirtilen(repeat ve end arasında kalan) komutları uygula demiş oluyoruz. Bu arada unutmadan "yol" değişkenine girilen değerin sonunda "\" işaretinin olması gerekiyor ki sonuna arama metnini eklediğimizde sorun yaşanmasın.

Bizim fonksiyonumuzun dönüş değer tipi integer, çünkü biz sadece bulunan dosya sayısını döndüreceğiz. Bu yüzden fonksiyonun başında dönüş değerini sıfırlamamız gerekiyor:

result:=0;

Şimdi yukarıda boş bıraktığımız yere

inc(result);

komutunu eklersek fonksiyonumuz işimizi görecek hale geliyor. Yalnız fonksiyonun bitiminden hemen önce

FindClose(bulundu);

yazmamız gerekiyor ki "bulundu" değişkenimiz için ayrılan bellek serbest bırakılsın.

Tabi ki bu fonksiyonu kullanacak kişiler sadece dosya sayısını istemeyecekler. O zaman biraz daha detaya girelim ve "TSearchRec" tipinin dosyaya ait ne gibi bilgiler içerdiğine bir göz atalım. Bu bir kayıt tipi olduğu için içinde altbirimler var. Bu birimlerin isimleri, tipleri ve ne gibi bilgiler içerdikleri aşağıda:

Time: Integer;
Bu, adından da anlaşılacağı üzere dosyanın zaman ve tarih bilgisini tutuyor. Bu bilgi dosyanın son değiştirilme tarihini Delphi'nin zaman formatında saklıyor.

Size: Integer;
Yine adından anlaşılacağı üzere bu da dosyanın boyut bilgisini içeriyor. Yalnız burada dikkat edilmesi gereken nokta bu bilginin kesin doğru olmayabileceği. Çünkü artık doya boyutları 64-bit'lik sayıalr da olabiliyorlar ve bu "Integer" tipinde yani 32-bit'lik bir değişken.

Attr: Integer;
Bu değişken ise dosyanın öznitelik bilgisini saklıyor. Integer tipinde tanımlanmış olmasına rağmen Delphi size anlaşılır isimlere sahip sabitler sunuyor. Yani gizli dosyanın öznitelik değeri kaçtı diye hatırlamanıza gerek kalmıyor. Buraya gireceğiniz sabitleri veya değerleri "FindFirst" fonksiyonunun dosya tipi böümünde de kullanabilirsiniz. Şimdibu sabitlere bir bakalım:

faReadOnly : Bu "Salt Okunur" özniteliğini temsil ediyor. Sayısal değeri "1".
faHidden : Bu ise "Gizli" özniteliğini temsil ediyor. Sayısal değeri "2".
faSystem : "sistem Dosyası" özniteliğni temsil eder. Sayısal değri "4".
faVolumeID : "Sürücü tanılama dosyası" özniteliğini temsil eder. Sayısal değeri "8".
faDirectory : Bulunan öğenin bir klasör olduğunu belirtir. Sayısal değeri "16".
faArchive : "Arşiv" özniletiliğini belirtir. Sayısal değeri "32".
faSymLink : Bulunan öğrenin sembolikbir bağlantı olduğunu gösterir(ben de bilmiyorum ne olduğunu :)). Sayısal değeri "64"
faAnyFile : Bunu bulunan dosyalara bakarken kullanmaktan çok arama yaparken her tipteki dosyanın buunmasını sağlamak için FindFirst fonksiyonunda kullanmak bir anlam ifade ediyor. Her türlü dosya tpini temsil ediyor. Sayısal değeri "71".

Şimdi burada o kadar sabit değerden bahsettim flan ama biz bulduğumuz öğenin buözelliklere sahip olup olamdığın nasıl anlayacağız? Şöyle ki eğer "Attr" bilgisini istediğimiz özelliğin değeri ile "AND" işleminden geçirdiğimizde sonuç sıfırdan büyük oluyorsa bu özellik öğede var demektir. Bir örnekle daha da açıklığa kavuşturalım olayı:

if (bulundu.Attr AND faDirectory) > 0 then ....

Burada eğer bulunan öğre bir klasör ise "then" kısmından sonragelen komut(lar) çalıştırılır. Sanırım yeteri kada açıklayıcı oldu. Bu işlemi sadece bir sabitle değil birden çok sabitle de yapabilirsiniz. (bulundu.Attr AND faDirectory AND FaArchive; gibi).

Name: TFileName;
Tipinin TFileName olmasına bakmayın o aslında bir string :). Tek farkı bu tipin sadece dosya isimleri için kullanılan özel bir string türü olması. Yani anlayacağınız bulunan öğenin adı burada saklanıyor. Yanlız unutmayın burada SADECE adı var, yol bilgisi yok.

Evet işte bitirdik. Son olarak bu fonksiyon arama yaptığınız klasörün alt klasörlerine bakmaz. Eğer bunu yapmasını istiyorsak o zaman fonksiyonumuzun kendi kendisini çağırmasını sağlamalıyız. Buna dabir örnek yazalım ve bu yazımıza da son noktayı yada noktalı virgülü(!) koyalım :).

function DosyalariBul(yol,aramametni:string):integer;
var
bulundu:TSearchRec;
begin
result:=0;
if FindFirst(yol+aramametni,faAnyFile,bulundu) = 0 then
begin
repeat
begin

if (bulundu.Attr AND faDirectory) > 0 then
result:=result+DosyalariBul(yol+buldu.Name+'\',aramametni)
else
inc(Result);
end;
until
(FindNext(bulundu) <> 0);
end;
FindClose(bulundu);
end;

Fonksiyonumuzun son hali böyle olmalı. Burada gördüğünüz gibi önce öğenin klasör olup olmadığına bakılıyor; eğer klasör ise bu sefer fonksiyonumuzdan bir tane daha çağırıyoruz. Burada fark yeni çağırılan haline parametre olarak ilk yolun değil ilk yol + bulunan klasör'ün verilmesi. Daha sonra buradan dönen dosya sayısını ana sayacımıza ekliyoruz. Burada dikkat etmenizi istediğimbir nokta daha var; fonksiyonumuz bu haliyle bir klasörün içindeki TÜM altklasörlere kadar inebiliyor. Çünkü yeni parametrelerle çağırılan fonkiyonumuz bir klasör bulduğunda kendini daha yeni parametrelerle tekrar çağırıyor. Burada kafanızın karışmaması için fonksiyonu her çağırdığımızda o çağrıya özel bir bellek alanını kullandığını unutmamanız. Yani çağrıyı yapan fonksiyondan bağımsız bir şekilde çalışıyor. Sadece işlettikleri komutlar aynı. Neyse çok dağıldık, en son klasörse bunlar bunları yapıyor dedik, eğer klasör değilse o zaman da sonucmuzu bir arttırıyoruz.

Not: Yazı, zamanında ByteOnline için yazdığım "Delphi-Dosya Arama Fonksiyonları" adlı yazıdan uyarlamadır.