C#’ta Özyenilemeli Algoritmalar (Recursion)
C#’ta Özyenilemeli Algoritmalar (Recursion)
Özyenilemeli algoritmalar tüm dünyada bilgisayar bilimleriyle ilgili bölümlerde veri yapıları ve algoritmalar derslerinde detaylı olarak incelenir. Bu bağlamda biz de makalemizde özyenilemeli algoritmaları geliştirmeyi ve C# ile kodlamayı öğreneceğiz. Önce konunun teorik temelleri üzerinde duracağız. Daha sonra daha iyi anlaşılabilmesi için konu ile ilgili örnekler yapacağız. Makaleyi bitirmeden önce ise klasik döngüler ve özyenilemeli algoritmaları karşılaştıracağız.
Bir alogirtma geliştirirken genelde döngüleri ve karar mekanizmalarını metodların içinde kullanırız. Fakat bazı durumlarda döngüler yerine özyenilemeli algoritmalar kullanmak daha kolay ve anlaşılır olabilir. Özyenilemeli (recursive) metodların püf noktası bu tür metodların bir şekilde tekrar tekrar kendilerini çağırmalarıdır.
Özyenilemeli algoritmalarda problemin en basit hali için çözüm bulunur. Bu en basit duruma temel durum (base case) denir. Eğer metod temel durum için çağırılırsa sonuç dönderilir Daha karmaşık durumlar için metod, temel durumdan yararlanılarak, problemi çözmeye çalışır yani kendini çağırır. Karmaşık durumlar için yapılan her çağrı recursion step olarak adlandırılır.
İsterseniz konunun kafanızda daha iyi canlanması için klasik faktoriyel örneğiyle devam edelim. Sıfırdan büyük herhangi bir n tamsayısının faktoriyelinin formülü şudur:
|
n! = n * (n-1) * (n-2) * …. * 3 . 2 . 1 |
Ayrıca 0! ve 1!’in değerleri bir olarak tanımlanmıştır. Mesela 5! = 4*3*2*1 = 120‘dir.
Bir sayının, mesela n, faktoriyelini özyenilemeli değilde döngü kullanarak bulmak istersek aşağıdakine benzer bir kod kullanabiliriz:
| int faktoriyel = 1;
for( int i = n; i >= 1; i– ) |
Kod 1: Döngü ile Faktoriyel hesabı
Eğer bu problemi özyenilemeli algoritma yardımıyla çözmek istersek şu noktaya dikkat etmemiz gerekiyor:
|
n! = n * (n-1)! |
Daha açık bir yazım ile;
| 5! = 5 * 4 * 3 * 2 * 1
5! = 5 * (4 * 3 * 2 * 1) 5! = 5 * (4!) |
Aşağıda şekilde 5!in özyenilemeli bir algoritmada nasıl hesablanacağını görüyoruz. Şeklin solunda 5!’den 1!’le kadar her özyenilemeli çağrıdaki neyin çağrılacağı sağda ise sonuca ulaşılana kadar her çağrıda dönen değerler yeralıyor.
C# diliyle özyenilemeli biçimde Faktoriyel hesabı yapan bir metodu aşağıdaki gibi yazabiliriz. Bu fonksiyona int tipinde sayi isimli bir değişken geçiriyoruz. Eğer sayi 1’den küçük veya eşit ise, ki bu temel durumdur, fonksiyon 1 değerini dönderiyor. Diğer durumlarda ise fonksiyonumuz
sayi * Faktoriyel(sayi-1)
değerini dönderiyor.
| private static long Faktoriyel(int sayi) { if( sayi <= 1 ) // Eğer temel durumsa 1 dönder return 1; else // Temel durum değilse n * (n -1)! bul. return sayi * Faktoriyel( sayi-1 ); } |
Kod 2: Özyenileme ile Faktoriyel hesabı
Sıra örneğimizi bir Windows uygulaması olacak biçimde programlayalım. Bunun için öncelikle aşağıda gördüğümüz Form’u tasarlayalım. Metin kutusuna txtSayi ve düğmeye btnHesapla isimleri vermeyi unutmayalım.
Formdaki btnHesapla isimli düğmeye çift tıklayalım düğmenin Click olayına cevap veren metodu yazalım.
| private void btnHesapla_Click(object sender, System.EventArgs e) { string sonucMetin=””; // metin kutusundan değeri al ve int tipine çevir: for(int i=1; i<= sayi; i++) MessageBox.Show(sonucMetin.ToString(),”Faktoriyel Hesabı”); |
Kod 3: Örnek programda btnHesapla_Click() metodu
Yukarıdaki metod içinde metin kutusuna girilen sayi değerine kadar olan tüm faktoriyeller hesaplanıp ekrana mesaj kutusu içinde yazdırılıyor. Programımızı çalıştırmadan önce programımıza Kod 2‘de yeralan metodu da eklemeyi unutmayınız. Örneğimizi 10 değeri için çalıştırırsak aşağıdaki sonucu elde ederiz:
Özyenilemeli Algoritma Örneği: Fibonacci Serisi
Lise ve üniversitelerde matematik derslerinde seriler konusunda gösterilen Fibanocci serilerini programlamak için döngüler yerine özyenilemeli algoritma kullanmak daha kolay ve anlaşılır oluyor. Bu serinin tanımı ise herhangi bir n sayısı için Fibonacci(n)’nin değeri Fibonacci(n-1) ve Fibonacci(n-2)’nin şeklindedir. Tabiki Fibonacci(0) ve Fibonacci(1)’in değeri 1 olarak alınıyor.
Bu serideki sayılar doğada çok sayıda bulunur ve spiral şeklini oluştururlar. Ayrıca art arda gelen iki Fibonacci değerinin oranı 1.68.. şeklindedir. Bu değer altın oran olarak adlandırılır. Özellikle kart postallar, tablolar vb nesnelerin boy ve enlerinin oranları da 1.68.. gibidir. Çünkü insan gözüne çok hoş görünürler. Neyse konumuza devam edelim isterseniz…
Yukarıdaki tanımlardan yola çıkarak Fibonacci serisini hesaplayan metod’ta iki temel durum olacaktır. Bunlar Fibonacci(0) = 1 ve Fibonacci(1) = 1. Ayrıca diğer durumlar için dönen değer, herhangi bir n için, Fibonacci(n-1) ve Fibonacci(n-2)’nin toplamıdır. O zaman metodumuz aşağıdaki gibi olacaktır:
| private static long Fibonacci(int sayi) { if( sayi == 0 || sayi == 1) // Eğer temel durumlardan biriyse 1 dönder return 1; else // Temel durum değilse (n-1) + (n -2) bul. return Fibonacci( sayi-1 ) + Fibonacci( sayi-2 ); } |
Kod 4: Özyenileme ile Fibonacci serisi hesabı
Fibonacci serisi ile ilgili aşağıdaki küçük formu tasarlayalım. Sonra gerekli kodları yazıp örneğimizi deneyelim. Formdaki metin kutusunun ismi yine txtSayi olsun. Ayrıca Hesapla etiketine sahip düğmenin ismi btnHesapla olsun. Son olarak arka planı koyu kırmızı olan etiketin ismi de label2 olacak.
Programı tamamlamak için Hesapla düğmesinini tıklandığında gerekli işleri yapacak kodu yazmaya geldi. Ayrıca programın kodunun içine Kod 4 yeralan fonksiyonu da ekleyiniz.
| private void btnHesapla_Click(object sender, System.EventArgs e) { // Kullanıcını girdiği değeri al ve int tipine çevir. int sayi = Convert.ToInt32(txtSayi.Text); // Fibonacci Hesabı yapan fonksiyonu girilen sayi değeri ile çağır. |
Kod 5: Fibonacci örneğinde btnHesapla_Click() metodu
Programı test etmek için Fibonacci(15) değerini bulmak istersek aşağıdaki sonucu elde ederiz.
Özyenilemeli Algoritma Örneği: Fibonacci Serisi
Makalemizi bitirmeden önce özyenilemeli algoritmalar ile döngülerden oluşan algoritmaların aralarındaki farklara ve benzerlikte gözatmakta yarar olduğu kanısındayım. İki algoritma türü de akış kontrol mekanizmlarını kullanır. Döngülerde for, while ve do while yapıları kullanılırken özyenilemeli algoritmalarda if, if/else ve switch yapıları yeralır. Aslında hem döngüler de hem de özyenilemeli metodlarda itereasyonlar bulunur. Döngüler doğaları gereği açık bir biçimde iteratiftirler. Fakat özyenilemeli algoritmalarda iterasyon aynı metodun tekrar tekrar kendi içinden çağrılması ile gerçekleşir. Döngülü ve özyenilemeli algoritmalarda göze çarpan diğer bir benzerlikte sonladırıcı testlerin bulunmasıdır. Döngülerde sayacın(counter) belli bir değere ulaşıp ulaşmadığı kontrol edilir ve gerekirse döngü sonlanır. Özyenilemeli algoritmalarda o andaki durumun temel durum olup olmamasına göre işlemler devam edebilir veya sonlanabilir. Son olarak hem döngülerle hem de özyenilemeli algoritmalar ile bilgisayarı sonsuz döngüye(infinite loop) sokabiliriz. Birincisi döngünün sonlanacağı sayaca ulaşmanın imkansız olmasından ikincisi ise temel duruma ulaşamamaktan kaynaklanır.
Aslında özyenilemeli algoritmaları kullanırsak hem daha yavaş hem de hafızada daha çok yer kaplayan programlar yazmış oluruz. Fakat çoğu zaman aynı problemin çöümünü özyenilemeli olarak bulmak daha kolaydır. Ya da döngülerle aynı sonuca varacak algoritmayı düşünmek zor olur. Ayrıca özyenilemeli algoritmaları inceleyince anlamak ve hata ayıklamak daha kolaydır. Bu durumda seçim programcıya kalmıştır. Yalnız işlemcilerin giderek hızlanması, hafıza fiyatlarındaki düşüşü ve programın daha kolay yönetilebilmesinin getirdiği avantajları göz önüne almanızı tavsiye ederim.
Bu makalede özyenilemeli algoritmaları detayları ile inceledik. Konu ile ilgili sorularınızı rahatlıkla sorabileceğinizi belirterek makalemize son verelim.
Struct(Yapı) Kavramı ve Class(Sınıf) ile Struct(Yapı) Arasındaki Farklar
Bu makalemizde struct kavramını incelemeye çalışacağız. Hatırlayacağınız gibi, kendi tanımladığımız veri türlerinden birisi olan Numaralandırıcıları (Enumerators) görmüştük. Benzer şekilde diğer bir veri tipide struct (yapı) lardır.
Yapılar, sınıflar ile büyük benzerleklik gösterirler. Sınıf gibi tanımlanırlar. Hatta sınıflar gibi, özellikler,metodlar,veriler, yapıcılar vb… içerebilirler. Buna karşın sınıflar ile yapılar arasında çok önemli farklılıklar vardır.
Herşeyden önce en önemli fark, yapıların değer türü olması ve sınıfların referans türü olmasıdır. Sınıflar referans türünden oldukları için, bellekte tutuluş biçimleri değer türlerine göre daha farklıdır. Referans tiplerinin sahip olduğu veriler belleğin öbek(heap) adı verilen tarafında tutulurken, referansın adı stack(yığın) da tutulur ve öbekteki verilerin bulunduğu adresi işaret eder. Ancak değer türleri belleğin stack denilen kısmında tutulurlar. Aşağıdaki şekil ile konuyu daha net canlandırabiliriz.
Şekil 1. Referans Tipleri
Aşağıdaki şekilde ise değer tiplerinin bellekte nasıl tutulduğunu görüyorsunuz.
Şekil 2. Değer Tipleri
İşte sınıflar ile yapılar arasındaki en büyük fark budur. Peki bu farkın bize sağladığı getiriler nelerdir? Ne zaman yapı ne zaman sınıf kullanmalıyız? Özellikle metodlara veriler aktarırken bu verileri sınıf içerisinde tanımladığımızda, tüm veriler metoda aktarılacağını sadece bu verilerin öbekteki başlangıç adresi aktarılır ve ilgili parametrenin de bu adresteki verilere işaret etmesi sağlanmış olur. Böylece büyük boyutlu verileri stack’ta kopyalayarak gereksiz miktarda bellek harcanmasının önüne geçilmiş olunur. Ancak küçük boyutlarda veriler ile çalışırken bu verileri sınıflar içerisinde kullandığımızda bu kezde gereksiz yere bellek kullanıldığı öbek şişer ve performans düşer. Bu konudaki uzman görüş 16 byte’tan küçük veriler için yapıların kullanılması, 16 byte’tan büyük veriler için ise sınıfların kullanılmasıdır.
Diğer taraftan yapılar ile sınıflar arasında başka farklılıklarda vardır. Örneğin bir yapı için varsayılan yapıcı metod (default constructor) yazamayız. Derleyici hatası alırız. Ancak bu değişik sayıda parametreler alan yapıcılar yazmamızı engellemez. Oysaki sınıflarda istersek sınıfın varsayılan yapıcı metodunu kendimiz yazabilmekteyiz.
Bir yapı içersinde yer alan constructor metod(lar) içinde tanımlamış olduğumuz alanlara başlangıç değerlerini atamak zorundayız. Oysaki bir sınıftaki constructor(lar) içinde kullanılan alanlara başlangıç değerlerini atamaz isek, derleyici bizim yerimize sayısal değerlere 0, boolean değerlere false vb… gibi başlangıç değerlerini kendisi otomatik olarak yapar. Ancak derleyici aynı işi yapılarda yapmaz. Bu nedenle bir yapı içinde kullandığımız constructor(lar)daki tanımlamış olduğumuz alanlara mutlaka ilk değerlerini vermemiz gerekir. Ancak yinede dikkat edilmesi gereken bir nokta vardır. Eğer yapı örneğini varsayılan yapılandırıcı ile oluşturursak bu durumda derleyici yapı içinde kullanılan alanlara ilk değerleri atanmamış ise kendisi ilk değerleri atar. Unutmayın, parametreli constructorlarda her bir alan için başlangıç değerlerini bizim vermemiz gerekmektedir. Örneğin, aşağıdaki Console uygulamasını inceleyelim.
| using System;
namespace StructSample1 public string Kosucu public int Saat class Class1 |
Yukarıdaki kod derlenmeyecektir. Nitekim derleyici “Use of unassigned local variable ‘z'” hatası ile z yapısı için ilk değerlerin atanmadığını bize söyleyecektir. Ancak z isimli Zaman yapı türünü new anahtarı ile tanımlarsak durum değişir.
Zaman z;
Satırı yerine
Zaman z=new Zaman ();
yazalım . Bu durumda kod derlenir. Uygulama çalıştığında aşağıdaki ekran görüntüsü ile karşılaşırız. Görüldüğü gibi z isimli yapı örneğini new yapılandırıcısı ile tanımladığımızda, derleyici bu yapı içindeki özelliklere ilk değerleri kendi atamıştır. Kosucu isimli özellik için null, diğer integer özellikler için ise 0.
Şekil 3. New yapılandırıcısı ile ilk değer ataması.
Yine önemli bir farkta yapılarda türetme yapamıyacağımızdır. Bilindiği gibi bir sınıf oluşturduğumuzda bunu başka bir temel sınıftan kalıtım yolu ile türetebilmekteyiz ki inheritance olarak geçen bu kavramı ilerliyen makalelerimizde işleyeceğiz. Ancak bir yapıyı başka bir yapıyı temel alarak türetemeyiz. Şimdi yukarıda verdiğimiz örnekteki yapıdan başka bir yapı türetmeye çalışalım.
struct yeni:Zaman
{
}
satırlarını kodumuza ekleyelim. Bu durumda uygulamayı derlemeye çalıştığımızda aşağıdaki hata mesajını alırız.
‘Zaman’ : type in interface list is not an interface
Bu belirgin farklılıklarıda inceledikten sonra dilerseniz örneklerimiz ile konuyu pekiştirmeye çalışalım.
| using System;
namespace StructSample1 /* Yapı için parametreli bir constructor metod tanımladık. Yapı içinde yer alan kosucuAdi,saat,dakika,saniye alanlarına ilk değerlerin atandığına dikkat edelim. Bunları atamassak derleyici hatası alırız. */ public Zaman(string k,int s,int d,int sn) /* Bir dizi özellik tanımlayarak private olarak tanımladığımız asıl alanların kullanımını kolaylaştırıyoruz. */ public string Saat public string Dakika public string Saniye class Class1 Zaman Baslangic=new Zaman (“Burak”,1,15,23); Zaman Bitis=new Zaman (“Burak”,2,20,25); /* Zaman yapısı içinde tanımladığımız özelliklere erişip işlem yapıyoruz. Burada elbette zamanları birbirinden bu şekilde çıkarmak matematiksel olarak bir cinayet. Ancak amacımız yapıların kullanımını anlamak. Bu satırlarda yapı içindeki özelliklerimizin değerlerine erişiyor ve bunların değerleri ile sembolik işlemler yapıyoruz */ int saatFark=Bitis.Saat-Baslangic.Saat; int dakikaFark=Bitis.Dakika-Baslangic.Dakika; int saniyeFark=Bitis.Saniye-Baslangic.Saniye; Console.WriteLine (“Fark {0} saat, {1} dakika, {2} saniye”,saatFark,dakikaFark,saniyeFark); |
Bir sonraki makalemizde görüşmek dileğiyle. Hepinize mutlu günler dilerim
Windows 98’de C# Kodu Derleyin
Windows 9x işletim sistemlerine dotNET Framework kurularak, dotNET platformunda yazılmış programlar çalıştırılabiliyor, fakat siz 9x işletim sistemlerinde dotNET programı yazıp derleyemiyorsunuz, çünkü .NET programlarını yazabilmek için Microsoft 2 araç sunuyor.
1 ) .NET Framework SDK: Yükleyebilmek için en az windows NT 4.0 SP6 kurulu 32 MB RAM’li bir sistem gerekmektedir.
2 ) Visual Studio .NET: Yükleyebilmek icin en az Windows 2000 Professional SP3 ve 96 MB RAM içeren bir sistem gerekmektedir.
Diyelim ki elimizde bir Windows 98 işletim sistemi yüklü sistem var. Bu sistemin belleği de sadece 32 MB olsun. Bu özelliklere sahip bir sistem üzerinde .NET programları yazmak isteyelim. Bunu yapabilir miyiz?
Eğer .NET programlarını yazmanın tek yolu yukaridaki araçlari kullanmak olsaydı bu sorunun yanıtı “hayır” olacaktı. Fakat .NET programlarını yazmanın birkaç yolu daha var. Bunlardan bir tanesi Mono projesi dahilinde geliştirilen C# derleyicisidir. http://www.go-mono.com/ adresinden projeyle ilgili bilgilere ve gerekli tüm programlara ücretsiz ulaşabilir bilgisayarınıza indirebilirsiniz. mono’nun bugünku tarih itibariyle Windows icin 0.28 sürümü mevcut. Mono’nun CLR altyapısı ile basit bir uygulamayi “mint den1.exe” şeklinde çalıştırmak istediğimde benim bilgisayarımda “bellek yetersiz” gibi bir hata verdi. Bu yüzden Microsoft .NET Framework kurmanızı da tavsiye ederim. Microsoft’un sitesinden son .net framework kurulum dosyasını indirrip kurduktan sonra Mono’nun “mcs” derleyicisi ile derlediginiz programları normal Windows uygulaması çalıştırıyormuş gibi çift tıklayarak çalıştırabilirsiniz. Aşağıda basit bir örnek görülüyor.
| class den1{ public static void Main(){ System.Console.WriteLine(“denemedir.”); System.Console.ReadLine(); } } |
Bu örnek uygulamayı mcs derleyicisi ile aşağıdaki gibi derliyoruz.
| D:\Program Files\Mono-0.28\bin\>mcs den1.cs Compilation succeeded |
Oluşan dosyaya Çift tıklayarak ya da exe’nin adını yazarak uygulamayı çalıştırıyoruz.
| D:\Program Files\Mono-0.28\bin\>den1.exe Denemedir. |
İsterseniz daha karmaşık bir uygulama ile mcs derleyicisinin yeteneklerini test edelim.
Matematik’te satır, sütun veya diyagonallerindeki sayıların toplamının hep aynı sayıya eşit olduğu karelere “sihirli kare” denir. Verilen bir tek sayılı boyut icin sihirli kare oluşturan algoritma uygulaması C# ile verilmistir. Program Visual Studio .NET 2003 ortamında yazılımıştır ve derlenmistir. VS.NET ile oluşturulan calıştırılabilir dosyanın adı WindowsApplication6.exe’dir. Aynı kaynak kod (Form1.cs) hicbir değişikliğe ugratılmadan Windows 98 uzerine kurulu Mono-0.28 ve .NET Framework 1.1 yuklu makinede Mono derleyicisiyle “mcs Form1.cs -r:System.Windows.Forms -r:System.Drawing -r:System.Data” komutuyla derlenmiştir. Derleme başarıyla sonuclanmış ve Form1.exe adli dosya oluşmuştur. Aşağıdaki masaustu görüntüsunde sol tarafta çalıştırılan uygulama Mono ile derlenen, sağ tarafta çalıştırılan uygulama ise (aynı kaynak koddan derlenmiştir) VS.NET 2003’te derlenmiştir. İki dosya da çift tıklanarak çalıştırılmıştır.
Sihirli Karelerin Oluşturulması – Basamak Yöntemi
“Sihirli Kare” oluşturmak icin kullanılan yöntem La Loubere’in bulduğu “Basamak” adı verilen yontemdir.
Aşağıda genel kuralı verilen “Basamak” yöntemi her tek boyutlu sihirli kareyi oluşturabilir. Aşagidaki anlatım “Yaşayan Matematik” adli kitabin 53. sayfasından alınmıştır, bu konu hakkında daha detayli bilgi ve buna benzer keyifli matematik eğlencelerini öğrenmek için bu kitaba başvurmanız tavsiye edilir. Bu yöntemin 3×3’lük bir sihirli kareye uygulanışı aşağıdaki şekilde gösterilmiştir.
Şekil 2: Sihirli kare algoritması
”
1) 1 sayısını en üst satırın ortasındaki kareye yerleştirerek başlayalım.
2) Her koyduğumuz sayının sağ üst çaprazına bir sonraki sayıyı koyalım. Eğer burası sihirli karenin dışındaki hayali bir kareyse (a,b,…,g diye isimlendirdiklerimizden biriyse) sihirli karede bu yere denk gelen kutuyu bulup buraya sayımızı yerleştirelim.
3) Eğer sihirli karedeki sağ üst çapraz doluysa, o zaman sayıyı bir onceki sayının altındaki kutuya yerleştirelim (4 ve 7 sayılarında olduğu gibi).
4) 2. ve 3. basamakları uygulamayı sürdurerek sihirli karedeki diğer sayıların yerlerini bulalım.”
Windows XP Stillerinin Kontrollere Uyarlanması
Merhaba, bu makalede Windows XP sitillerinin form kontrollerine herhangi bir Manifest dosyası olmadan uygulanmasını tartışacağız. Ben uygulama örneği olarak bir ProgressBar kontrolünü seçtim. Çalıştırılabilir örnek kod bu siteden indirilebilir.
Herşeyden önce bu isteğimizi gerçekleştirmek için isteğimizi uygulayacağımız bir sınıfa sahip olmamız gerekir. Ne yazık ki standart ProgressBar sınıfı “sealed” olduğu için ben sınıfımı Control sınıfından türettim:
public class XPProgressBar:Control
{
}
Daha sonra yapmamız gereken standart ProgressBar özelliklerini (Maximum, Minimum, Value, Step) özelliklerini sınıfımıza uygulamaktır. Bu özelliklerin hepsi integer’dir. Kolay olduğu için bu kısmı geçiyorum. Dikkat edilmesi gereken nokta Value özelliğine bir değer geçerken sınıf örneğimizin yeniden boyanmasını sağlamaktır. Bunu da Refresh() fonksiyonu ile yapabiliriz. Uygulamamızın amacı XP sitillerinin uygulanması olduğu için Style isminde bir özellik tanıtırız. Bu özellik Normal ve System isimlerinde iki enum değeri tutar. Eğer özelliğimizin değeri System ise XP sitili uygulanır.
public Styles Style
{
get {<return FStyle;}
set
{
FStyle=value;
Refresh();
}
}
Şimdi sınıfımızın boyanması için OnPaint metoduna geçebiliriz. İlkönce Normal değerini görelim.
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rect = ClientRectangle;
rect.Width-=6;
rect.Height-=6;
if(Maximum!=Minimum)
{
rect.Width =(Value-Minimum)*100/(Maximum-Minimum)*rect.Width/100;
}
if(Style==Styles.Normal)
{
ControlPaint.DrawBorder3D(e.Graphics,ClientRectangle,Border3DStyle.Sunken);
rect=new Rectangle(new Point(rect.X+3,rect.Y+3),rect.Size);
e.Graphics.FillRectangle(Brushes.Blue,rect);
}
else
{
//Burada Styles.System değerini uygulayacağız.
}
}
Yukarıdaki kodda yapılan Maximum ve Minimum değerlerine göre boyanacak alanın bulunup daha sonra boyanmasıdır. Dikkat edeceğiniz gibi segment şeklinde değil de düz boya şeklinde boyanmıştır. Eğer isterseniz bu aşamada bileşenimizi test edebilirsiniz.
Dikkat edeceğiniz gibi standart bir ProgressBar’ın yapacağı işlemleri gerçekleştirdik. Şimdi asıl amacımız XP sitillerinin uygulanması olduğuna göre bu aşamaya geçelim.
Öncelikle kullandığımız işletim sisteminin yapacağımız bu işlemi desteklemesi için Windows XP olması lazım. Bunu nasıl anlayacağımızı görelim:
bool IsThemedos()
{
if(Environment.OSVersion.Platform != PlatformID.Win32NT
|| Environment.OSVersion.Version.Major < 5
|| Environment.OSVersion.Version.Minor < 1)
return false;
return true;
}
Yukarıdaki fonksiyonda yapılan kullanılan işletim sisteminin version numaralarına bakarak Windows XP olup almadığını anlamaktır. Eğer değilse program XP sitillerinin kullanımına izin vermeyecektir.
Şimdi burada bir ara verip XP sitillerinin kullanımına olanak veren WinApi leri tanıtmak istiyorum. Öncelikle kullanacağımız dll “UxTheme.dll” dir. Bizim bu programda kullanacağımız 4 tane WinApi fonksiyonu var. Şimdi bunları tanıyalım:
HTHEME OpenThemeData( HWND hwnd,LPCWSTR pszClassList);
Bu fonksiyon bir window için ilgili sınıfın datasını açar. Dönüş değeri IntPtr dir. “pszClassList” parametresi ise kullanacağımız sınıfın string değeridir. Bizim örneğimizde bu değer “PROGRESS” dir.
BOOL IsAppThemed(VOID);
Bu Api bir kontrol fonksiyonudur. uygulamamızın visual sitilleri uygulayıp uygulamayacağını sorgular.
BOOL IsThemeActive(VOID);
Bu da başka bir kontrol fonksiyonudur. Visual sitillerin uygulamamız için aktif olup olmadığını denetler.
HRESULT DrawThemeBackground(
HTHEME hTheme,
HDC hdc,
int iPartId,
int iStateId,
const RECT *pRect,
const RECT *pClipRect
);
Uygulamamızın belki de en can alıcı Api si budur. Bu fonksiyonla visual sitillerin çizim işlemini gerçekleştiriyoruz. Dönüş değeri integer dir. Parametrelerine gelince:
hTheme: OpenThemeData fonksiyonu ile elde ettiğimiz IntPtr değerini kullanacağız.
hdc: Controlümüzün hdc değeri. CreateGraphics().GetHdc() ile elde edilir.
iPartId ve iStateId: Bu parametreler çizeceğimiz kontrolün bölümlerini ifade eder. Visual Studio nun yardım indeksi bölümüne “Parts and States” yazarsak konuyla ilgili dökümanı bulabiliriz. Bu durumda Controlümüzün arkaplanını çizeceksek iPartId 1 değerini, eğer öndeki ilerleme bölümünü çizeceksek 3 değerini almalıdır. StateId değeri ise ProgressBar için kullanılmaz. Eğer dökümanı iyice incelersek yapacağımız diğer uygulamalarda XP sitillerini nasıl uygulayacağımız kolaylıkla anlaşılır.
pRect ve pClipRect: Bu parametreler çizeceğimiz dörtgen bölümü ifade eder. ClipRect “null” değerini alabilir.
Şimdi programımıza geri dönüp anlattığımız Api lerin kullanımına geçebiliriz. Öncelikle sınıfımızın yapıcısında OpenThemeData apisini kullanarak XP sitilimizin değerini tutabilirz. Daha sonra sınıfımızın OnPaint metodunda boş bıraktığımız “else” ifadesini dolduralım.
if(IsThemedos() && IsAppThemed() && IsThemeActive())
{
DrawThemeBackground(e.Graphics,1,1,ClientRectangle);
DrawSystemSegments(e.Graphics,rect);
}
private void DrawSystemSegments(Graphics g, Rectangle rc)
{
int segwidth = 8;
int Count = ((rc.Width)/(segwidth+2))+((rc.Width<=0)?0:1);
Rectangle rect = new Rectangle(3,3,Count*(segwidth+2),rc.Height);
if (rect.Width>(Width-2*3))
{
rect.Width = rc.Width;
}
DrawThemeBackground(g,3,1,rect);
}
Öncelikle kontrolümüzün arka planını çizdik. Daha sonra DrawSystemSegments fonksiyonu ile segmentleri hesaplayıp sonra da bunu çizdirdik.
Bu konu hakkında anlatacaklarım bitti. Eğer örnek kod incelenirse anlaşılmayan bölümlerin anlaşılmasına yardımcı olacaktır. Hepinize iyi çalışmalar.
Not : XP stilindeki kontrolleri görmeniz için işletim sisteminizin temasını XP Stil olarak değiştirmeniz gerekmektedir.
