Şimdi .Net dünyasında Excel ile çalışırken iki yolumuz bulunmakta. Bunlar;
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.
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.
MessageBox.Show(((Excel.Range)MyStatik.app.Cells[1, 1]).Value);