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