Excel ile çalışmak

Excel obje modelini ve 4 temel nesneyi iyi bildiğinizi varsayarak başlıyorum. Bilmiyorsanız önce bu linklere bakın, zira bu sayfada bunların ne anlama geldiği açıklanmayacak.

Temel kaynağınız: Understanding the Excel Object Model from a .NET Developer's Perspective dokümanı ama içindeki bazı bilgiler eski, özellikle c#'ın desteklemediğini söylediği şeylerin çoğu artık destekleniyor. O yüzden bu linki bi yere kaydedin ve benim sitedeki okumalarınızı bitirdikten ve biraz pratik yaptıktan sonra mutlaka bu linke tekrar bakın ve konuyu içselleştirin.

Şimdi .Net dünyasında Excel ile çalışırken iki yolumuz bulunmakta. Bunlar;

  • Excel API'si kullanmak. Bunun için bir VSTO projesinde(Excel Add-in) hazır olarak gelen Microsoft.Office.Interop.Excel kütüphanesi kullanılır.(Bir de Office.Tools.Excel var ama bunu hiç kullanmayacağız)
  • 3rd Party kütüphanelerin kullanımı.

Biz burada ağırlıklı olarak ilk yöntemi kullanacağız. Ama ikinci yöntem hayat kolaylaştırıcı yöntemdir, o yüzden bunlara da değineceğiz. Bu kütüphaneler o kadar güçlüdür ki, Excel'iniz kapalıyken de işlem yaparlar, hatta Excel kurulu olmasa dahi excel dosyaları yaratabilirler. Hayat kolaylaştırıcılıkları ise 1.yöntemde belli işler için uzun uzun yazdığınız kodlar yerine tek satırlık kodlarla işinizi halletmenizi sağlamasındandır.

NOT: Şimdilik kodları sadece takip edin. Sonraki sayfadaki İlk VSTO Add-inimizi yaparken uygun yerlere bunları kendiniz de koyup deneyebileceksiniz.

Bu yöntemde Excel nesneleriyle çalışabilmemiz için kodunuzun tepedeki using directivelerinin olduğu kısımda şu satırın yazması gerekir.

using Excel = Microsoft.Office.Interop.Excel;

Vb.Net'te Imports kısmında aynısını yapabileceğiniz gibi, MyProject>References içinde aşağıdaki gibi ekleyerek de bunu yapabilirsiniz.

post-thumb

Bu arada VBA'de kullandığımız MsgBox, InputBox gibi fonksiyonlar VB.Net'te yine kullanılabilir durumdadır. Ancak bir şekilde c#'ta da bunları kullanmak isterseniz, projenize Microsoft.VisualBasic kütüphanesini eklemek gerekecektir. Özellikle InputBox kullanımı için bu kütüphaneye başvuracağız, zira c#'ta böyle bir fonksiyon(metod) yok.

Globals sınıfı

Bir sonraki sayfada göreceğimiz gibi, bir Add-in projesi yarattığımızda VS'nin bazı sınıfları otomatik oluşturacaktır. Bunlardan biri de Globals sınıfıdır. Bu sınıf ThisAddIn.Designer.cs dosyası içinde yer alır. Kod bloğunun içinde ne yazdığını bilmemize gerek yok. Bu sınıfla ilgili bilmemiz gereken şey şu: Bunun sayesinde aşağıdaki nesnelere erişim sağlayabiliyoruz.

  • Document Level projelerde ThisWorkbook ve Sheetn sınıflarına. Ör: Globals.ThisWorkbook
  • Application level projelerde ThisAddin sınıfı. Globals.ThisAddin
  • Ribbon Designerda tasarladığımız Ribbonlara. Globals.Ribbons.Ribbon1

Bu, şu demek oluyor. VBA'de doğrudan kullandığımız ActiveCell veya ActiveSheet gibi nesneler vardı. Document Level bir projede ThisWorkbook sınıfı dışından, Application Level bir projede de ThisAddin sınıfı dışından bunlara direkt ulaşamazsınız. Mesela bir Ribbondan (veya bir başka sınıf içinden) ulaşmak için bunlara Globals sınıfı üzerinden erişmeniz gerekir. Aşağıda çeşitli örnekler var(basitlik adına şuan için conversion yapmıyorum)

//Doc level proje ThisWorkbook içi
                    ThisWorkbook.ActiveSheet //veya this.ActiveSheet
                    ThisApplication.... //veya this.Application

                    //Doc level proje Ribbon
                    Glboals.ThisWorkbook.ActiveSheet....
                    Globals.ThisWorkbook.Application

                    //App level proje ThisAddin içi
                    this.Application.ActiveCell....

                    //App level proje Form1 sınıfı
                    Globals.ThisAddIn.Application.ActiveCell....

4 Temel Sınıf/Nesne

Application

Yukarıda  gördüğümüz gibi Application nesnesine Globals sınıfı üzerinden ulaşacağız. Size tavsiyem, her defasında Application'ı bu şekilde uzun uzun yazmak yerine bunu ilgili sınıfta public variable(Ör:app) olarak tanımlayıp sonra bunu kullanmanızdır. Veya çok fazla sınıfı olan bir uygulamanız olacaksa her sınıfta ayrı ayrı değişkenler tanımlamak yerine static bir sınıf oluşturun ve bu sınıfın içine bir kez tanımlayın ve her defasında bunu çağırın. Vb.Net'te çalışıyorsanız "genel modül" ismini vereceğiniz bir modülde de yapabilirsiniz. Burada Vb.Net'in küçük bi avantajı var; bu değişkenin önünde modül adı gibi birşey belirtmeye gerek olmuyor.

Statik sınıf detaylarını ve neden static sınıf kullandığımızı şuradan görebilirsiniz. (static detayını bilenler bu parantezli kısmı geçebilir. Özetlemek gerekirse statik sınıflardan biz nesne yaratmayız, ona ait metod ve propertyleri doğrudan kullanabiliriz. En bilinen örneği Math sınıfıdır. Çeşitli matematik fonksiyonlarının bulunduğu bu sınıfı kullanmak için bu sınıftan bi nesne yaratmanın bi esprisi yok, sınıfı doğrudan kullanabilmeliyizdir. Halbuki form gibi bir nesnede ise, Form sınıfından bir nesne yaratıyor(aslında Form sınıfını inherit eden Form1 veya başak bir isim verdiğiniz bir form sınıfı), sonra bu nesnenin Show metodunu kullanıyorduk, doğrudan Form(Form1) sınıfını kullanamıyorduk. Bizim örneğimizde de bi utility sınıf oalrak MyStatik adında bi sınıf yaratacağız ve bundaki app değişkenini kullanacağız)

//Statik sınıfı ve değişkeni tanımlama
                    using Excel = Microsoft.Office.Interop.Excel;

                    namespace VSTOcsharp
                    {
                        static class MyStatik
                        {
                            public static Excel.Application app = Globals.ThisAddIn.Application;
                        }
                    }

                    //projede herhangi bir yerde kullanımı
                    MyStatik.app.ScreenUpdating=false; //Statik sınıf adını da öne koyuyoruz

VB.Net tarafında yukarıdaki kullanıma ek olarak aşağıdaki modül kullanımına bakalım.

'Genel modül içi
                    Public app As Excel.Application = Globals.ThisAddIn.Application

                    'projede herhangi bir yerde kullanımı
                    app.ScreenUpdating=False 'Modül adını öne koymaya gerek yok

Workbook

Buradan itibaren yukarıdaki app değişkenini kullanacağız. VBA'de buna ihtiyacımız yoktu, zira Application nesnesi default nesneydi. VSTO'da ise işler değişiyor. Ancak bir kez app nesnesi elimizdeyken wb ve ws nesnelerine erişim kolay olacaktır.

Yeni bir dosya açıp bunu bi değişkene atayalım. Bunu tek satırda yapabileceğimizi biliyorsunuz artık.


                    Excel.Workbook wb = app.Workbooks.Add();

VBA'de bunu şöyle yapardık


                    Dim wb As Workbook
                    Set wb = Workbooks.Add

VSTO'daki farkı özetleyecek olursak

  • Tek satırda değişken tanımlayıp atama yaptık
  • Set ifadesi kullanmadık, zaten bu ifade artık yok. Ayrı satırlarda yapsaydık bile kullanmazdık
  • Add metodu sonunda parantez kullandık
  • Workbooks collectionu önünde app nesnesini kullandık.

Bir dosyayı açmak için;

MyStatik.app.Workbooks.Open(@"E:\OneDrive\Uygulama Geliştirme\web sitelerim\Yeni Efendi\Ornek_dosyalar\CF.xlsx");

NOT: Buradaki @ işareti, klasör ayracı olan \ işaretini bir kez kullanmamızı sağlar, aksi halde \\ yazmak lazımdı. Zira c#'ta \ işareti özel bir karekter(‘escape character’ denir) olup takip eden başka karakterlere özel anlam katar. Örneğin \n satırbaşı anlamında, \t tab sekmesi anlamındadır. O yüzden gerçekten \ işaretini kullanmak istediğimizde önüne bir \ daha koyarak onun gerçek \ işareti olduğunu vurgularız. Ama bu çok zahmetli olabileceği için @ karakteri ile bu zahmetten kurtuluruz. Vb.Net'te ise buna gerek yoktur, VBA'de olduğu gibi normal bir şekilde \ kullanımı yapılabilir.

Şimdi bir de c# 4.0 öncesinde, optional parametrelerin desteklenmediği döneme bakalım; korkmayın, artık bu kabus bitti :)

MyStatik.app.Workbooks.Open(@"E:\OneDrive\Uygulama Geliştirme\web sitelerim\Yeni Efendi\Ornek_dosyalar\CF.xlsx",
                        Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                        Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                        Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                        Type.Missing, Type.Missing);

Worksheet

app nesnesini elde edikten sonra worksheet kullanımı da kolaydır.


                    Excel.Worksheet ws = MyStatik.app.Worksheets[1]; //index'e dikkat
                    MessageBox.Show(ws.Name); 

Ancak worksheets collection'ının dönüş tipi Sheets olup, Sheets'in de dönüş tipi VBA'den bildiğimiz üzere object olduğundan eğer bir değişkene atama yapmadıysak doğrudan kullanımda takip eden üyelerini hemen göstermez. Yani aşağıdaki gibi kullanırken [1] yazıp noktaya basınca intellisense çıkmaz, çünkü tipi henüz belli değil, worksheet mi charts mı bilinmiyor.


                    MessageBox.Show(MyStatik.app.Worksheets[1].Name); 

Bu aslında garip bir durum, zira her ne kadar Worksheets(Sheets değil) collection'ınını kullanmış olsak bile dönüş tipini Sheets yapmışlar. Gerçi bu, VSTO'ya özgü bir durum değil, VBA'de çalışırken de aynı durum sözkonusudur. Neyse bunun sebebine takılmayalım. Intellisense çıkmasını istiyorsanız bunu değişkene atamanızda fayda var. Ama diyelim ki bunu bi değişkene atamak istemiyorsunuz, zira bu nesneyi başka bir yerde kullanmayacaksınız, sadece bir kereliğine bir özelliğine erişeceksiniz, ama yine de intellisensin de çıkmasını istiyorsunuz. Bu da mümkün, ama bu örneği Range nesnesinde göreceğiz, oradaki ilgili açıklama worksheet'te (ve tüm diğer nesnelerde de) aynen geçerlidir.

Range

Workbook ve Worksheet biraz kolaydı. Range'de bazen casting yapmak durumunda kalacağız. Şimdi ilk olarak basit bir örnekle başlayalım.

Mesela aktif hücrenin adresini ve değerini öğrenmek istiyoruz. Bunun için yazacağımız kod aşağıdaki gibi olabilir.


                    Excel.Range hucre = MyStatik.app.ActiveCell;
                    double deger = hucre.Value2;
                    MessageBox.Show(hucre.Address + " adresindeki hucrenin değeri:" + deger.ToString());
                    

Bu kodda çok kompleks birşey yok aslında. İlk satırda statik sınıfımızdaki app değişkeni üzerinden ActiveCell'e ulaşıyoruz, ikinci satır gayet açık. Son satırdaki MessageBox.Show yazabilmek için de tepede using System.Windows.Forms olması gerektiği aşikar.

Şimdi biraz daha şık bir kod nasıl yazılır, ona bakalım. Üstelik biraz daha teferruatlı konulara da girelim. Böylece hem biraz daha c# pratiği hem de VSTO pratiği yapmış olalım.

Öncelikle, bir hata bloğu koyacağız, ki bir hücre seçilmemişken uyarı versin, ayrıca seçilen hücre nümerik bişey içermediğinde de farklı bi uyarı versin, yani aslında iki hata bloğumuz olacak. Bir de, ölçeceğimiz değerin tipini integer yapalım istiyoruz.

Bu sefer farklı olarak Activecell yerine Selection nesnesini kullanacağız ve kodların nasıl değiştiğini göreceğiz.


                    try
                    {                
                        Excel.Range hucre = MyStatik.app.Selection; //castinge gerek yok, çünkü zaten değişkenin tipini belirtiyoruz
                        int deger = (int)(hucre.Value2); //double'dan integera dönüşüm
                        MessageBox.Show(hucre.Address + " adresindeki hucrenin değeri:" + deger.ToString());
                        //değişken atamasız durum
                        MessageBox.Show(MyStatik.app.Selection.Value2.ToString()); //casting yapmadığımız için intellisense çıkmaz
                        MessageBox.Show(((Excel.Range)MyStatik.app.Selection).Value2.ToString()); //intellisense çıkar
                    }
                    catch (NullReferenceException)
                    {
                        MessageBox.Show("Seçili bir hücre yok, lütfen bir hücre seçip tekrar deneyin.");
                    }      
                    catch (Exception ex)
                    {
                        if (ex.HResult==-2146233088)
                        {
                            MessageBox.Show("Şuan nümerik değeri olan bir hücrede bulunmuyorsunuz.");
                            MessageBox.Show(String.Format("HRresult:{0},\n\nMesaj:{1}", ex.HResult.ToString(), ex.Message));
                        }
                        else
                        {
                            MessageBox.Show(String.Format("HRresult:{0},\n\nMesaj:{1}", ex.HResult.ToString(), ex.Message));
                        }                
                    }
                    

 Şimdi de burayı inceleyelim

  • Selection'ın dönüş tipi Range değildir(Peki object mi? Birazdan göreceğiz). Bunun için yapılabilecek 3 şey var. Aslında bu yazdıklarımı 2010'dan önce yazsaydım farklı bişeyler yazacaktım, şimdi yazdığımda ise durum farklı, bunun sebebi dillerin zaman değişiyor olması
    • İlk olarak herhangi bir farklılığın olmadığı case, burada hücre değişkeninin tipini zaten Excel.Range belirlediğim için ilave bir işleme gerek yok
    • Casting işlemi: (Excel.Range) ifadesini kullanarak casting yapıyoruz. Böylece compliera diyoruz ki  "MyStatik.app.Selection"'dan dönen şey bir objedir ama aslında bu bir Range nesnesidir, hadi bunu Range nesnesine dönüştür. Casting detayı için  için .Net dilleri sayfasına bakın, özellikle orada link verdiğim wordpress sayfasına da bakın. Bu arada bunun Vb.Net karşılığı CType(app.Selection, Excel.Range) olur. Biz bunu yaparak ilgili nesneyi Range'e döndürdüğümüz için range nesnesinin tüm üyelerine erişebiliriz.
    • Gelelim 2010 öncesi ve sonrası duruma: 2010'dan önce Cast etmeden kullanamıyorduk, ancak 2010'dan sonra castinge gerek olmadan da kullanabiliyoruz. Peki neden ve farkı ne? 2010'da c# 4.0 geldikten sonra, dynamic veri tipi diye birşey çıktı. Detayına girmeyeceğiz ancak, bu özellik object tipli bir nesnenin gerçek nesneye dönüştürülmeden de kullanılabilmesi imkanı verdi ve bunu da tipinin runtime sırasında compiler tarafından otomatikman belirlenmesiyle yapmaya başladı. İşte bu Selection nesnesinin dönüş tipi de artık dynamictir. 2010'dan önce object idi ve mutlaka cast edilmesi gerekiyordu. Özetle castinge gerek olmadan kullanabilirsiniz ama bunu tavsiye etmem, hem compilerı yormuş olursunuz, hem de intellisenseden faydalanamazsınız. Bununla birlikte dynamic veri tipinin çok faydalı olduğu yerler de vardır, bunları zaman içinde göreceğiz.
  • Gelelim ikinci satırdaki deger değişkenine. Bunu int tanımladık. Şimdi eğer ilk satırda elde ettiğimiz hucre değişkeni olmasaydı ikinci satırdaki kodu "int deger=((Excel.Range)MyStatik.app.Selection).Value2;" şeklinde yazardık. Kırmızı parantezler içinde yazan kısım zaten bizim hucre değişkeni içn yazdığımızın aynısı. Kırmızı parentez içine alarak bunu ayrı bir nesne haline getiriyoruz. İşte bundan sonra bunun üyelerine ulaşabiliyoruz. Bu kırmızı paranteze almayıp doğrudan Selection'dan sonra Value2 yazsaydık, c# derleyicisi bunu şöyle anlayacaktı: "MyStatik.app.ActiveCell.Selection.Value2.ToString() değerini Range'e dönüştür", ve hata verecekti.
  • 2. catch bloğunda String formatlama da kullandık, bunu kendiniz yorumlamaya çalışın. Bu arada else bloğuna gelecek bir örnek bulamadığım için if kısmına da yazdım, sonucunu görün diye.

Cells collectionı

Cells, collection olarak kullanıldığında dönüş tipi Range olup herhangi bir şekilde castinge gerek yoktur. Ancak parametreli kullanıldığında, yani satır ve sütun verilerek bir hücre elde edilmeye çalışıldığında bunun dönüş tipi dynamic(2010'dan önce object) dönüş tipli olduğu için intellisense çıkmaz, çıkması için (Excel.Range) ile cast edilmelidir, tabi eğer değişkene atama yapılmadan kullanılacaksa. Değişken ataması yapıldığında ise ayrı bir castinge bunda da gerek yoktur, zira zaten değişkenin tipini belirliyoruzdur.

post-thumb
MessageBox.Show(((Excel.Range)MyStatik.app.Cells[1, 1]).Value);

İlk projemize başlamadan daha fazla kod örneği ile sizi boğmak istemedim. Normalde konu bütünlüğü adına burada koymayı tercih ederdim ama sizi bir an önce ilk projenizle de buluşturmak istiyorum. Bu konuya hem VSTO ile hem c# ile haşır neşir olduktan sonra şurada gireceğiz.

Sonraki sayfada görüşmek üzere...