Custom Control Oluşturmak 3: Klasör Görüntüleyici (TreeView)

Selam dostlar! Sanırım, bu başlığı okuduktan sonra, kesin “şişman adam’ın TreeView’a karşı takıntısı var” diye düşüneceksiniz. Tamam kabul ediyorum. Şu ListDisplay kontrollere karşı büyük sempatim var. Hem kullanması hem de (elbette) kodlaması çok eğlenceli.

Read more…

Custom Control Oluşturmak 2:Resim Önizleme Kontrolü

Merhaba yazılım dostu… Bu yazımızda da, kendi kontrollerimizi oluşturmaya devam edeceğiz. Bu kez, verilen klasör içindeki .JPG uzantılı resimlerin önizlemesini (thumbnail) asenkron olarak gösterecek bir kontrol yazıyor olacağız. Bu uygulamamıza, bir önceki kontrolümüzden farklı olarak Windows Forms Control Library açarak başlıyoruz:

 

 

Projemizi açtığımızda, Class’ın adı, default olarak “UserControl1.cs” olarak gelecektir. Bu ismi, “ResimGoruntuleyicisi.cs” olarak değiştirerek kontrolümüze ilk adımı atıyoruz. Projemizin tasarım aşamasında, ihtiyacımız olan tek şey, bir adet FlowLayoutPanel kontrolü. Bu kontrolü formumuza sürükleyerek, ismini flpResimler ve Dock özelliğini Fill olarak ayarlıyoruz. Ayrıca resimlerin kontrol üzerine sığmayabileceğini düşünerek AutoScroll özelliğini de True yapalım

 

Sevgili dostlar; bu kontrolün amacı, verilen klasör içindeki resimleri okumak. Ama bunu yapabilmek için yalnızca imajların önizlemelerinin alınması yeterli değil. Ayrıca, dosya üzerinde işlem yapılabilmesi için dosya adını da tutabilmeli. Öyleyse, biz de bu bilgileri tutması için ResimInfo isimli bir class oluşturuyoruz.

 

//ResimInfo classı, resmin Image’ini tutmanın yanı sıra, dosya ismini de tutabilen bir class.

    class ResimInfo

    {

       public string ResimAdi { get; set; }

       public Image Resim { get; set; }

    }

Bu class’ı oluşturduktan sonra, ResimGoruntuleyicisi classının kod tarafına dönebiliriz.

//Kontrolümüzün; verdiğimiz klasörden aldığı resimleri ve bu resimlerin dosya isimlerini resimler isimli bir GenericList içinde tutacağız:

    private List<ResimInfo> resimler = new List<ResimInfo>();

//Kontrolümüzü kullanırken, resmimizin ön izlemesinin boyutunu da belirleyebilmek için aşağıdaki özelliği tanımlıyoruz:

    public int Boyut { get; set; }

Bu noktada dikkat çekmem gereken bir şey olduğunu düşünüyorum. Kontrolümüze, resimleri çekeceği klasör yolunu bir özellik üyeden değil, bir metod aracılığıyla vereceğim. Çünkü, resimleri klasörden asenkron bir işlemle çekmek istiyorum. Bunun sebebi de, klasör içindeki resim sayısını bilmiyor oluşumuz. Eğer verilen klasör içinde çok fazla sayıda resim varsa, kontrolün çalışma esnasında açılması oldukça gecikecektir. Bunun yerine; kontrol resmi okuduğu anda önizlemesini gösterebilmek için Thread yapısından faydalanacağım. Fakat, herşeye rağmen; kontrole verilmiş olan klasörü, daha sonra okuma ihtiyacı duyabileceğimizden dolayı read-only bir özellik yazıyorum.

    private string klasor;

    public string Klasor

    {

       get { return klasor; }

    }

Şimdi, yukarıda bahsetmiş olduğum Thread’in kullanacağı DosyadanResimOku() metodunu yazalım:

    private void DosyadanResimOku()

   {

     //lock yapısı, verilen nesneyi, blok için yürütülen koddan bağımsız tutar. Böylece nesnenizin bu kritik koddan etkilenmemesini sağlarsınız.

     lock (resimler)

     {

       //GenericList’i temizle

       resimler.Clear();

       //eğer klasör boş değilse

       if (Klasor != string.Empty)

       {

          //DirectoryInfo nesnesini kullanarak, verilen klasör altındaki jpg uzantılı dosyalara ulaşıyorum:

          DirectoryInfo klasorBilgisi = new DirectoryInfo(Klasor);

          foreach (FileInfo dosya in klasorBilgisi.GetFiles(“*.jpg”))

         {

             resimler.Add(new ResimInfo { Resim = Bitmap.FromFile(dosya.FullName).GetThumbnailImage(Boyut, Boyut, null, IntPtr.Zero), ResimAdi = dosya.Name });

         }

      }

     }

   flpResimler.Invoke(new MethodInvoker(GoruntuyuGuncelle));

   }

Bu metod’da bazı şeyleri ilk kez görüyor olabilirsiniz. Bunların üzerinden biraz geçelim. Öncelikle şu lock yapısını ele alalım. Şimdi, bu metod, birazdan hazırlayacağımız bir Thread altında çalışacaktı öyle değil mi? Fakat, “resimler” isimli GenericList, Class içinde bir alan (field) olarak tanımlı. DosyadanResimOku() metodunun amacı, klasör içindeki resimleri, image ve dosya ismi olarak alıp, ResimInfo classını oluşturarak, tek tek “resimler” field’ine eklemek. Bu metod asenkron çalışacağına göre, her bir ResimInfo nesnesi eklendiğinde, “resimler” GenericList’i önceki halini korumalıdır. İşte bunu yapabilmek için bu nesneyi lock yapısı ile kilitleyerek bozulmasını engellemiş oluyorum.

Metodda, muhtemelen yeni karşılaştığınız bir başka kod ise, Image.GetThumbNailImage() metodudur. Bu metod, bir Image instance’inin önizlemesini oluşturan bir metoddur. Bunun sayesinde, resimlerimizi belirli bir standartta görebileceğiz.

lock () işlemi bittiğinde, flpResimler (FlowLayoutPanel) kontrolü üzerinden birazdan yazacağım GoruntuyuGuncelle() metodunu fırlatacağım.

Artık, Thread’ın kullanacağı metodu yazdığımıza göre, Thread’i çalıştıracak metodu da yazabiliriz.

    private void ResmiAl()

    {

       Thread tr = new Thread(new ThreadStart(DosyadanResimOku));

       tr.Start();

     }

Kontrolümüzü bir Windows Application’da kullanırken, bir metod vasıtasıyla klasör bilgisini vereceğimizi söylemiştik. İşte o metod:

     public void ResimYuklemeyeBasla(string klasor)

     {

       this.klasor = klasor;

       ResmiAl();

     }

Verilen klasördeki her resmin kontrolümüz üzerinde karşılığı bir PictureBox kontrolü olacaktır. resimler GenericList’i içindeki her bir ResimInfo nesnesinin Resim özelliğini, çalışma zamanında oluşturulacak PictureBox’ın Image özelliğine aktaracak bir metod yapıyoruz (Bu metodu DosyadanResimOku() metodunun sonunda çağırmıştık ).

     private void GoruntuyuGuncelle()

    {

      flpResimler.SuspendLayout();

      flpResimler.Controls.Clear();

      foreach (ResimInfo resim in resimler)

      {

         PictureBox foto = new PictureBox();

         foto.Image = resim.Resim;

         foto.Tag = resim.ResimAdi;

         foto.Size = new Size(Boyut, Boyut);

         foto.BorderStyle = BorderStyle.FixedSingle;

         foto.SizeMode = PictureBoxSizeMode.StretchImage;

         foto.Click += new EventHandler(foto_Click);

         flpResimler.Controls.Add(foto);

     }

   flpResimler.ResumeLayout();

   }

Ayrıca, çalışma zamanında oluşturulmuş bu PictureBox’ların Click olaylarını da yakalıyorum ki; kontrol üzerindeki resme tıkladığımızda bir olay fırlatsın ve bu olayda tıklanan resmin dosya ismini alabileyim (foto.Tag = resim.ResimAdi satırına dikkat). Bu cümleden de anlaşıldığı üzere kendi Olay argümanlarımı (Eventargs) tutacak bir classa ihtiyacım var.

    public class ResimSecildiEventArgs : EventArgs

   {

      public Image Resim { get; set; }  

      public string ResimAdi { get; set; }

   }

Bunu da hallettikten sonra delegate metod ve olayımı yazabilirim

    public delegate void ResimSecildiEventHandler(object sender, ResimSecildiEventArgs e); 

    public event ResimSecildiEventHandler ResimSecildi;

GoruntuyuGuncelle() metodunda oluşturduğumuz PictureBox’ların click olayına gelince, burada kendi yazdığımız ResimSecildi eventini fırlatacağız.Ama önce, SecilenResim isminde bir PictureBox alan tanımlıyorum. Böylece, bir seçim yapılıp yapılmadığını kontrol edeceğim.

    private PictureBox secilenResim;

    void foto_Click(object sender, EventArgs e)

    {

       if (secilenResim != null)

      {

         secilenResim.BorderStyle = BorderStyle.FixedSingle; 

      }

       secilenResim = sender as PictureBox;

       secilenResim.BorderStyle = BorderStyle.Fixed3D;

       ResimSecildiEventArgs arg = new ResimSecildiEventArgs { Resim = secilenResim.Image, ResimAdi = (string)secilenResim.Tag };

       ResimSecildi(secilenResim, arg);

    }

Çok büyük bir kısmını hallettik. Geriye iki metodumuz kaldı. Birincisi, kontrolü kullanırken klasörde bir değişiklik olursa kullanılabilecek ResimleriYenile metodu ve kontrolün boyutları değiştiğinde, resimleri tekrar düzenleyecek olan Control classından alıp ezeceğimiz “OnSizeChanged” metodu.

    public void ResimleriYenile()

   {

     ResmiAl();

     GoruntuyuGuncelle();

   }

   protected override void OnSizeChanged(EventArgs e)

   {

      GoruntuyuGuncelle();

      base.OnSizeChanged(e);

   }

Evet.. Kontrolümüz bitti. Şimdi test etme zamanı. Bunun için, solution’umuza bir proje ekleyelim (Bunun için File/Add/New project menüsünü kullanabileceğiniz gibi Solution Explorer’dan Solution’a sağ tıklayıp Add/New Project seçeneğini de kullanabilirsiniz).

 

Eklediğiniz projede Form1 üzerine, ToolBox’da ResimGoruntuleyici Components altında bulunan ResimGoruntuleyicisi kontrolünü ekleyin ve Dock özelliğini fill yapın. Hemen ardından Properties penceresinden event’lerine gelerek “ResimSecildi” olayını yakalayın. Form’un Load olayını da yakaladıktan sonra koda geçebiliriz.

    private void Form1_Load(object sender, EventArgs e)

    {

       resimGoruntuleyicisi1.ResimYuklemeyeBasla(@”C:\Users\Public\Pictures\Sample Pictures”);

    }

    private void resimGoruntuleyicisi1_ResimSecildi(object sender, ResimGoruntuleyici.ResimSecildiEventArgs e)

   {

     MessageBox.Show(e.ResimAdi);

   }

Ve işte testimizin sonuçları:

 

Ve…

 

Bir sonraki yazımızda görüşmek üzere hoşçakalın.

Türkay Ürkmez

turkay@turkayurkmez.com

Custom Control Oluşturmak 1: Otomatik Progress Bar

Merhaba dostlar.

Uzun zamandır yazmak istediğim bir konuydu bu aslında. Kısmet bugüneymiş. C#.NET ile Windows uygulaması geliştirirken; bazen, “yahu şöyle bir kontrol olsaydı ne de güzel olurdu” dediğimiz olmuştur. İşte bu cümleyi sarf ettiğiniz anda, Custom Control kavramı devreye girer ve bize “kendi kontrolünü kendin yap” der. Bunu yapabilmemiz için de, bize birkaç farklı seçenek sunar.

Bu seçenekler tamamen, custom control’ü ne için kullanacağımla ilgilidir. Genel olarak bu seçenekleri üçe ayırabiliriz:

User Control : Var olan kontrolleri bir araya getirerek yeni kontroller üretmemizi sağlayan tekniktir. Örneğin, kullanıcı giriş ekranını tasarlamak amacıyla,her seferinde tek tek iki Label iki TextBox bir Button kontrolü atmak yerine tüm bunları bir araya getirerek tek bir kontrol gibi kullanabilirsiniz. Bu sayede daha az efor harcamış olursunuz.

Derived Control: Bu teknik ise, Control classından türemiş olan herhangi bir kontrolü miras alarak, o kontrolün üyelerini geliştirmek ya da değiştirmek suretiyle yeni bir kontrol sınıfı yaratmaktır. Örneğin bu teknikle, yalnızca sayısal değer içeren bir TextBox kontrolü yapabiliriz.

Owner-drawn Control: Bu seçenek ise, GDI+ kullanarak kendine has bir tasarımları olan kontroller yapmanızı sağlayacaktır.

Lafı çok uzatmadan, bir User Control örneği yapalım. Bu örnekte, kendi ProgressBar kontrolümüzü yapacağız. Bu progressbar asenkron işlemlerde kullanılabilecek türden bir kontrol olsun. Yalnız, belirtmem gereken nokta şu; bazı asenkron işlemlerin (örneğin web servislerinden sorgu çekmek ) ne zaman biteceği belli olmaz. Bu nedenle, kontrolümüzde belirli bir yüzde ifadesi göstermeyeceğiz. Yalnızca kullanıcıya “yükleniyor” uyarısı yapacak.

Öncelikle, standart bir Windows Application projesi açalım. Ardından da projeye sağ tık, Add New Item’dan User Control template’i seçerek, Progress.cs ismiyle ekleyelim:

 

 

Eklediğimiz UserControl’ün Design ekranını tasarlayacağımız kontrolün taşıyıcısı (container) olarak düşüneceğiz. Öyleyse, bu ekrana bir Timer bileşeni, bir ProgressBar ve bir de Label kontrolü atalım:

 

Kontrol Tipi

Adı

ProgressBar

Bar

Label

lblSonuc

Timer

Timer1

Tasarımı da hallettikten sonra koda geçebiliriz.

İlk önce, lblSonuc kontrolüne “Yükleniyor. . . ” uyarısı yapan metodu yazmakla işe başlayalım. Yalnız bu uyarı, kuru kuru bir yazı da olmasın. Asenkron işlem devam ettiği sürece, uyarı kelimesinin yanına “.” işareti gelerek küçük bir atraksiyon olsun (gerçi bu bile kuru ama olsun).

private void LabeliGuncelle()

{

lblSonuc.Text = “Yükleniyor”;

int sayi = Bar.Value % 5;

for (int i = 0; i < sayi ; i++)

{

lblSonuc.Text += “.”;

}

}

Tasarladığımız bu kontolün, belirsiz bir süre için çalışacağını daha önce belirtmiştim. Bu durumda, progressBar kontrolüm, maksimum değere ulaştıktan sonra eğer işlem bitmemişse tekrar başa dönmeli ve yine dolmaya başlamalı. Dolayısıyla kontrolü kullanırken, ProgressBar’ın saniyede ne kadar değer artacağını belirleyerek, bu dolma hızını ayarlayabilmeliyim. Yani dilersem, saniyede 10’ar 10’ar ya da 5’er 5’er artmasına karar verebilmeliyim. Bu nedenle kontrolüme SaniyeBaşınaYuzde isimli bir özellik ekliyorum.

int saniyeBasinaYuzde = 5;

public int SaniyeBasinaYuzde

{

get { return saniyeBasinaYuzde; }

set

{

//progressBar’ın değerinin artabilmesi için, bu özelliğin değeri 0’dan büyük olmak zorunda

if (value <= 0)

{

throw new ArgumentException(“Değer, 0 ya da daha küçük bir sayı olamaz”);

}

saniyeBasinaYuzde = value;

}

}

Kontrolümüzü çalıştıracak olan Başlat metodunu yapalım şimdi de:

public void Baslat()

{

Bar.Maximum = 200;

decimal step = Math.Round((decimal)Bar.Maximum * SaniyeBasinaYuzde / 1000);

Bar.Step = (int)step;

Bar.Value = 0;

timer1.Start();

}

UserControl’ün Başlat metodu çağırıldığında, Timer componenti çalışmya başlayacak. Ama, uygulamam çalışırken, bu kontrolü durdurmak istersem ne olacak? Madem öyle ona da bir metod yazalım:

public void Durdur()

{

timer1.Stop();

Bar.Value = 0;

}

Şimdi, gelelim başka bir senaryoya… UserControl’ümüz çalışırken, asenkron işlemimiz sonlandı. Yani işimiz bitti. Fakat bu esnada, ProgressBar’ımızın değeri, maksimumda olmayabilir değil mi? Yani asenkron işleminiz bitmiş ama sizin ProgressBar’ınız işin “bitmemiş” olduğunu gösteriyor. Bu durumda, program çalışırken istediğimiz zaman ProgressBar’ın değerinin maksimum olmasını sağlamam gerekir. İşte o nedenle bir metot daha yazıyoruz..

public void Bitir()

{

timer1.Stop();

Bar.Value = Bar.Maximum;

lblSonuc.Text = “Tamamlandı…”;

}

ProgressBarın ilerlemesini sağlayacak metodda sıra

public void Ilerle()

{

Bar.PerformStep();

LabeliGuncelle();

}

Ve son olarak, timer bileşeninin Tick olayı:

private void timer1_Tick(object sender, EventArgs e)

{

Ilerle();

if (Bar.Value==Bar.Maximum )

{

Bar.Value = 0;

}

}

Buraya kadar sorun yok. Artık, kontrolümüzü test etmemizin vakti geldi. Öncelikle UserControl’ümüzü Build edelim. Ardından uygulamamızın ana formunun design ekranına dönelim ve ToolBox’dan “Progress” isimli user kontrolü sürükleyip bırakalım

 

Uygulamamı test edebilmek için iki adet de buton atıyorum.

 

 

Kodları ise:

private void btnBaslat_Click(object sender, EventArgs e)

{

progress1.Baslat();

}

private void btnBitir_Click(object sender, EventArgs e)

{

progress1.Bitir();

}

…Ve çalıştırıyorum:

 

 

 

Evet.. Basit ve kullanışlı bir userControl oldu. Bir sonraki makalede görüşmek üzere

Öneri ve istek için: turkay@turkayukmez.com

Kendinize iyi bakın