30.09.2018 tarihinde Excel bölümüne Dış Verilerle çalışmak sayfası eklenmiştir

04.08.2018 tarihinde VBA bölümüne ObjelerDünyası sayfası eklenmiştir

25.07.2018 tarihinde VBA bölümüne Outlook programlama sayfası eklenmiştir

13.07.2018 tarihinde VBA bölümüne Formlar-Kontroller sayfası eklenmiştir

25.05.2018 Hosting şirketi dğeiştirmekten kaynaklı bir hata nedeniyle Excelent add-ini indirirken hata alınmaktaydı. Bu hata düzeltilmiştir. İki ayrı download alternatifi sunulmuştur. Kurumunuzun BT politikalarının veya şahsi PC’nizdeki güvenlik ayarlarının izin vermesi durumunda yöntemlerden biriyle kurulum yapabilmelisiniz. Bi sorun olursa bana iletebilirseniz sevinirim.

VBAMakro İleri seviye konular 1

Nesneler Dünyası

VBA konularının başında nesne kavramına biraz girmiş ve Excel'in nesne modelinden bahsetmiştik. O bölümü okumayanların önce orayı okumasını tavsiye ederim.

Bu bölümde ise nesnelere biraz daha yakında bakıcaz. Burada Türkçe'nin avantajını kullanarak bu kavrama Türkçe adıyla hitap edicem, zira bir de değişken tipi olarak Object var elimizde. İkisi birbirine karışmasın diye genel nesne kavramını Nesne olarak, tip olanı ise Object olarak belirticem.

Giriş

Nedir?

Nesnelerin ne olduğuna bakmadan önce nesnelerin ne olmadıklarına bakalım. Basit değişkenlerle nesnelerin birbirinden çok temel bir farkı vardır. Basit değişkenlerin tek bir amacı vardır: Bir değer depolamak.

Dim i As Integer
Dim ad As String

i=10
ad="Volkan"

Nesneler ise, bir değer depolamaktan daha fazlasını yaparlar. Nesneler, hem çoklu veri tutarlar hem de bir eylem icra ederler.

Dim ws As Worksheet
ws.Name="Kredi" 'veri
Debug.Print ws.Index 'veri
ws.Add 'eylem

Nesnelerin bileşenleri

Artık bildiğiniz üzere, Excel'de herşey bir nesnedir, hatta nesneler topluluğu olan collectionlar(sonu "s" ile biten) da nesnedir(Ör:Workbook da nesne, Workbooks collection'ı da).

Bu nesnelerin bazıları sadece veri tutarlar, bu verilere özellik(property) denir, bazı nesneler belirli eylemleri(metod) de icra ederler. Bazıları ise ayrıca kendileriyle ilgili bir eylem olduğunda bir olay(event) meydana getirirler.

Nesne kavramı olmasaydı herşeyi değişkenlerle yönetmemiz gerekirdi ki bu inanılmaz karmaşık bir dünyaya neden olurdu. Düşünsenize, 30 propertysi olan bir obje içi 30 ayrı değişken tanımlamanız gerekirdi, ki bu sadece değişken tanımlamayla ilgili endişemiz, diğer dezavantajlarını sanymıyorum bile.

NOT:VBA, tam anlamıyla bir Nesne Yönelimli Programlama(OOP) dili değildir, ama bu kavramı destekler. Excelent eklentisini yazdığım dil olan VB.NET ise tam bir OOP dilidir.

Property'ler

Bazı propertyler Readonly'dir(salt okunur), yani bunlara değer atayamazsınız, bazıları ise hem okunabilir hem yazılabilirdir.

MsgBox Activecell.Address 'bu readonlydir
ActiveCell.Value = 1 'bu hem okunur hem yazılabilirdir.
MsgBox ActiveCell.Value

Property'lere değer atamak, eğer dönüş değerleri basit data tiplerindeyse, aynı bunlar gibi atanır. i=1 ile ActiveCell.Value = 1 örneğindeki gibi

Ancak dönüş değeri nesne olan propertylere nesne atamalarındaki Set ifadesi ile atama yaparız.

 Set ilkkolon = ActiveCell.CurrentRegion.Columns(1)

Metodlar

Metodlar, nesnelerin eylem icra eden üyeleridir. Sub olarak da Function olarak da tanımlanmış olabilirler. Mesela Workbook nesnesinin Add metodu bir Function'dır, zira geriye birWorkbook nesnesi döndürür. Ancak Save metodu bir Sub'dır, zira geriye birşey döndürmez, sadece bir eylem icra eder.

Eventler

Belli nesnelerin de belirli eylemler olduğunda meydana gelen olayları vardır. Bu konuyu olaylar bölümünde genişçe ele almıştık.

Nesne türleri

Ben nesneleri 4 gruba ayırıyorum(bu sınıflama tamamen bana aittir, resmi bir gruplama değildir)

İlk grupta Excelin nesne modelindeki nesneler var. Range, Cell, Worksheet gibi. Bunlar Excel librarysinde yer alırlar. Tümüne buradan ulaşabilrisiniz

İkinci grupta, default gelen librarylerdeki nesneler bulunuyor. Yani ilave herhangi bir library'yi reference olarak eklemeden yaratacağımız nesneler. Collection gibi; bu nesne VBA librarysinde bulunur.

Üçüncü grupta, bir library ekleyerek yaratılan nesneler var. Dictionary, FileSystemObject gibi. Bunlar da Scripting Runtime library'sinde bulunurlar.

Excel'in nesne modeli dışında kendi nesnelerimizi de yaratabiliriz, bunlar da dördüncü grup oluyor. Tabi bunun için önce bu nesnenin taslağını oluşturan Class yaratmamız gerekiyor. Classlara bu bölümde değinmeyeceğiz, onlarla ilgili bilgiye buradan ulaşalabilirsiniz.

Nesne üyelerine erişmek

Klasik yöntem

Bir nesne üyesine erişmek için en bilinen yol, nesne adını yazıp sonra "." koymaktır. Ör: Workook.Name

With - End With

Bir diğer yöntem de daha önce gördüğümüz With kalıbı. Bu kalıbı şurada anlatmıştık, sadece kısa bir örnek verelim.

Dim fd As FileDialog
Set fd = Application.FileDialog(msoFileDialogFolderPicker)
With fd
  .Title = "Klasör seçin"
  If .Show = True Then
    lblKlasör.Caption = .SelectedItems(1)
  End If
End With

Me

Bir class modülündeyken(workbook, worksheet modülleri dahil, veya kendinize ait bir calass) kendisine Me ifadese ile erişebiliyorsunuz. Burada unutulmaması gereken şudur; Me, her zaman o an içinde bulunulan classa başvurur. Ör: Thisworkbook modülündeyken: Me.Save

Nesne tanımlama ve yaratma

Genel tanımlama ve yaratım teknikleri

New ifadesi

Excel nesne modelinde bulunan nesneleri tanımlarken New ifadesini kullanmayız. Zira Excel açıldığında bunlar otomatikman yaratılmış olurlar, o yüzden sadece değişken tanımlamak yeterlidir.

Bunlara atama yapmak için ise Set ifadesini kullanıyoruz.

Dim rng As Range
Dim ws As Worksheet

Set rng = Activecell
Set ws = Activesheet

Excel nesne modeli dışındaki nesneleri yaratmak için ise New ifadesini kullanmak zorundayız. Bu şekilde nesne yaratmanın da iki yolu vardır.

Yöntem 1:Tek satırda

İlk yöntemde Dim ve New ifadelerini aynı satırda kullanırız, yani tanımlama ve yaratım aynı anda olur. Sonra nesnenin üyelerini hemen kullanmaya başlayabilirz. Aslında yaratım aynı anda olmamaktadır, bu nesne ilk nerede görülürse işte o sırada yaratım olmaktadır.

Dim coll As New Collection
coll.Add "elma"
Yöntem 2:Ayrı satırlarda(Set'li yöntem)

Bu yöntemde ise tanımlama ile yaratım&atama ayrı satırlarda gerçekleşir. Tanımlama Dim ile, yaratım&atama Set ve New ile yapılır.

Dim coll As Collection 'Tanımlama
Set coll = New Collection 'Set ile atama, New ile yaratım
İstisna

Eğer ki, elde edeceğimiz nesneyi bir fonksiyon veya metod ile elde edeceksek o zaman New ifadesi kullanılmaz.

Dim oApp As New Outlook.Application 'bunda New gerekli
Dim oMail As Outlook.Mailitem 'bunda New kullanılmaz
Set oMail = oApp.CreateItem(0) 'başka bir nesnenin metoduyla elde ettik

Başka bir örnek de veritabanı işlemlerinden olsun. Aşağıdaki iki nesne için de New gerekmedi.

 Dim db As Database
Dim rs As Recordset
Set db = OpenDatabase(....)
Set rs = db.OpenRecordset(.....)
Hangi yöntem ne zaman kullanılır?

İkinci yöntem performans yönetimi açısından tercih edilir.Aslında günümüz bilgisayarları açısından bakıldığında buradaki performans etkisi artık ihmal edilebilir düzeydedir. O yüzden iki yöntem de kullanılabilir. Ancak belli özel durumlarda ikinci yöntemin kullanılması tavsiye edilir.

Eğer, tanımladığımız değişkeni belli bir duruma/şarta göre yaratmamız sözkonusu ise tek satırda değil, iki satırda yani Set'li yöntemle tanımlarız.

Mesela mail gönderimi yapılacak bir durum düşünelim. Eğer B2 hücresinde bir değer varsa o zaman mail gönderilsin, yoksa gönderilmesin. O yüzden ilk başta tanımlamayı yapalım, ama henüz nesneyi yaratmamıza gerek yok.

Dim oApp As Outlook.Application

If Not IsEmpty(Range("F2")) Then
   Set oApp = New Outlook.Application 
End If

Burada, ilgili nesne bir değişkene atanana kadar onu yaratmaz. Diğer yöntemle farkını görmek için aşağıdaki iki kodu çalıştırıp kendiniz görün, gerçi ben yazılacak değerleri yanlarında belirttim, ama kendinizin de görmesinde fayda var.(Outlook libraraysini eklemeyi unutmayın)

Sub setliyöntem()
Dim oApp As Outlook.Application
Dim coll As Collection

Debug.Print TypeName(coll) 'Nothing
Debug.Print coll.Count 'hata

Debug.Print TypeName(oApp) 'Nothing
Debug.Print oApp 'hata alınır

If Not IsEmpty(Range("F2")) Then
   Set oApp = New Outlook.Application
End If

End Sub
--------------------
Sub teksatıryöntemi()
Dim oApp As New Outlook.Application
Dim coll As New Collection

Debug.Print TypeName(coll) 'collection
Debug.Print coll.Count '0

Debug.Print TypeName(oApp) 'Application
Debug.Print oApp 'Outlook

End Sub
	

Tek satırda kullanım yönteminin avantajı ise, ilgili değişkeni Nothing ile yok etseniz bile tekrar kullanabiliyor olmanızdır. Mesela şu kod problemsiz çalışır.

Sub coll1()

Dim coll As New Collection

coll.Add "Apple"
Set coll = Nothing
coll.Add "Pear" 'yeni bir collection yaratılır

End Sub

Ancak aynı kodu Setli yönteme çalışıtırırsak hata alırız.

Sub coll2()

Dim coll As Collection
Set coll = New Collection

coll.Add "Apple"
Set coll = Nothing

coll.Add "Pear" 'hata

End Sub	

Early ve Late Binding

Bir diğer nesne yaratım yöntemi ise CreateObject ile yaratımdır, ki buna LateBinding yöntemi ile yaratım denir.

Bu yönteme daha çok, VBA'nin default libraryleri olan VBA, Excel ve Office libraryleri dışındaki librarylerde bulunan classlardan nesne yaratmak istediğimizde başvururuz. En sık kullanılan classlar şunlardır:

  • Scripting.Runtime librarysi içindeki FileSystemObject
  • Yine Scripting.Runtime librarysi içindeki Dictionary
  • Outlook, Word gibi diğer Ofis uygulamaları

Syntax'ı şu şekildedir: CreateObject("library.class")

Değişken tanımlamayı Object tipli yapıp sonra da Set ile atama ve yaratmayı yaparız.

Dim obj As Object 'tanımlama
Set obj = CreateObject("Outlook.Application") 'yaratma ve atama
Set obj = CreateObject("Scripting.Dictionary")
Set obj = CreateObject("Scripting.FileSystemObject")

NOT: Nasıl olsa LateBinding yapıyorum, o yüzden değişken tanımlamaya gerek yok diye düşünmeyin. Zira object tipli değişkenler hafızda 4 byte yer işgal ederken, değişken tanımlamadığınız durumda bunlar otomatikman Variant algılanacakları için hafızada 16 byte işgal ederler.

Early Binding'te ise tanımlamak istediğimiz nesnenin bulunduğu library'yi Tools>References menüsünden eklememiz gerekir.

Library'yi ekledikten sonra artık klasik yoldan değişken tanımlayabiliriz, nesneyi doğru yazma konusunda Intellisense bize yardımcı olur.

veya bunu 2 satırlık versiyonla da yapabiliriz.

Dim dict As Dictionary
Set dict = New Dictionary

Bu örnekte olduğu gibi aynı classtan başka bir library içinde yoksa library adını yazmamıza gerek yoktur, aksi halde karışıklık olur. Böyle bir durumda library adını da belirtmeliyiz.(Veritabanı işlemlerinde bu durumu görüyoruz)

Early Binding vs Late Binding

  • Late Bindingin avantajı, yazdığınız kodu bir başkasına gönderdiğinizde(veya network üzerinden çalıştırdıklarında) onda da kesinlikle sorunsuz çalışacağını biliyor olmanızdır. Zira herhangi bir kütüphane eklenmesi durumu olmadığı için versiyon farkları problem yaratmayacaktır. Early bindingte ise siz Office 2016 ile çalışıyorken, diğer kişi Office 2013 ile çalışıyorsa sizin eklediğiniz Outlook 16.0 reference'ini bulamayacağı için hata alacaktır. O yüzden eğer yaptığınız çalışmayı başka birinin bilgisayarında çalıştırma durumu varsa Late binding kullanmanız faydalı olackatır, aksi halde Early binding kullanın.
  • Early bindingle çalışmanın avanatjı ise intellisenseten faydalanmaktır. Bu hem daha hızlı hem de hatasız kod yazmanızı sağlayacaktır. LateBinding'te yazdığınız kod Intellisense ile teyit edilmemiş olacağı için tam çalışmadan önce birkaç kez derleme hatası almanız olasıdır.
  • Early bindingle çalışmanın bir diğer faydası da kodun çalışma hızıdır. VBA, ilgili nesnenin ne olduğunu direkt bileceği için arka planda bir dönüştürme işlemi yapmasına gerek kalmayacak ve de kod hızlı çalışcaktır. Zaten Early denmesinin sebebi de budur, öncende/erkenden hangi classla çalışacağımıza karar vermiş oluyoruz. Latebindingte ise sonradan/gecikmeli bir tespit işlemi de olacağı için kod daha yavaş çalışacaktır. Ortalama hız farkı 2 kattır.
  • Bazı nesneler Late bindingle ile yaratılamaz. Mesela CreateObject("DAO.Database") gibi bir kullanım sözkonusu değildir. Bu yöntemin kullanımı için ilgili nesnelerle ilgili bazı teknik önayarların yapılmış olması gereklidir. Burada ve burada teknik detaylar yazılı.

Bu sayfaadan çok daha teknik detaylarına ulaşabilirsiniz.

Benim nihai önerim şudur: Intellisense ve performans sebepleriyle her zaman Early Bindingle başlayın, başkasının bu dosyayı çalıştırması da sözkonusu olacaksa, Early binding olan yerleri latebindige çevirin.

NOT: Bir de GetObject diye bir fonksiyon vardır. "Mevcutta açık olan bir uygulama varken CreateObject ile o nesneyi tekrar yaratmanın anlamı yok, ona GetObject ile ulaşabilrsiniz" amacıyla vardır. Günümüz bilgisayarlarındaki bellek kapasitesi düşünüldüğünde çok gereği olmayan bir fonksiyondur. Yine de görürseniz şaşırmayın diye bahsetmek istedim.

Hafızada neler oluyor

Hafıza adresleri

Basit veri tipleri sözkonusu olduğunda bunlar için hafızada ayrı yerler açılır. Mesela aşağıdaki örnekte X ve Y için hafızada iki ayrı alan açılır.

Dim X As Integer, Y As Integer
X = 20
Y = 20

Bellek gösterimini ise aşağıdaki gibi yapabiliriz. İki değişken de aynı değere sahip olduğu halde iki farklı alan işgal edilir: 2şer byte'tan toplam 4 byte.

Nesnelerde ise durum biraz farklıdır. Nesneler sözkonusu oluğunda değişkenlerde nesnenin kendisi değil, nesnenin işaret ettiği adres depolanır. Buna programlamada Pointer denir.

Aşağıdaki örnekte bellekte sadece bir alan işgal edilir, o da Object tipli değişkenlerin değeri 4 byte olduğu için toplam 4 byte'tır, 8 byte değil. Zira iki değişken de hafızadaki aynı yere işaret ediyorlar.

Dim wb1 As Workbook, wb2 As Workbook
Set wb1 = Workbooks("deneme.xlsx")
Set wb2 = wb1

Bu da şu demek oluyor; nesne değişkeni ile nesnenin kendisi farklı şeylerdir. Aşğağıdaki örnekte VarPtr değişkenlerin bellekteki adresini verirken, ObjPtr nesnelerin kendisinin adresini verir. Adresten kastımız, uzunca bir sayıdır, bu sayının ne olduğu önemli değildir, önemli olan içeriğidir, commentlere bakınız.

Sub hafıza()
Dim wb1 As Workbook
Dim wb2 As Workbook

Set wb1 = ActiveWorkbook
Set wb2 = wb1

Debug.Print wb1.Sheets.Count '1
Debug.Print wb2.Sheets.Count '1

wb1.Sheets.Add

Debug.Print wb1.Sheets.Count '2
Debug.Print wb2.Sheets.Count '2

'wb1 ve wb2 "değişkenlerinin" adresi
Debug.Print "wb1 nesne değişkeninin adresi:" & VarPtr(wb1) 'aşağıdaki ile farklı
Debug.Print "wb2 nesne değişkeninin adresi: " & VarPtr(wb2)

'wb1 ve wb2'nin işaret ettiği yerin adresi
Debug.Print "wb1'in adresi:" & ObjPtr(wb1) 'aşağıdaki ile aynı
Debug.Print "wb2'nin adresi:" & ObjPtr(wb2)
End Sub

Hafızayı temizleme

Otomatik ama gecikmeli temizlik

Bir değişkene bir nesne atadıktan sonra tekrar başka bir nesne atarsak, artık ilk nesne varolmaz, ve bir süre sonra bellekten silinir.

Mesela aşağıdaki örnekte, 1'den 10'a kadar sayıları tutan collection son satırdan itibaren yok olur ve ona erişmenini hiçbir yolu kalmaz.(Bu işlem hemen değil biraz gecikmeli olur)

Dim coll As Collection
Set coll  = New Collection
For i = 1 to 10
  coll.Add i
Next i 
Set coll = New Collection 'ilk nesne yok olur

Burda aslında biz bellekte iki tane collection için yer açtık ama son satıra geldiğimizde artık ilkine hiçbir şey atanmış olmadığı için Garbage Collector denen sistem bir süre sonra bunu bellekten atar, yani özetle bir nesneye hiçbir değişken işaret etmiyorsa bu nesne bellekten silinir.

Manuel ama anında temizlik

Bu yöntemi şimdiye kadar birçok örnekte gördük aslında. Bir değişkene Nothing değerini atayınca o değişkenle onun başvurduğu nesne arasındaki ilişkiyi kopartırız.

Aslında çoğu durumda bu işlem gerekli değildir, zira yukarda gördüğümüz gibi bir nesneye başvuran bir değişken kalmadığında bu nesne bellkten gecikmeli de olsa otomatikman silinir.

Ancak bazı durumlarda, özellikle döngüsel işlemlerde Nothing ataması gerekebilir. Çünkü Garbage Collector'ın ne zaman devreye gireceği belli değildir, ve biz belleği hemen boşaltmak istiyorsak işte o zaman Nothing ataması yaparız.

Mesela toplu mail gönderiminde kullandığımız aşağıdaki koda bakalım. oMail değişkenine Nothing atamak faydalıdır, zira bunu yapmazsak ilk mail nesnesi hala bir süre daha bellekte kalmaya devam edecek, taki GC gelip onu yokedene kadar. Eğer tek seferde yüzlerce mail atacaksanız bu işlemi yapmanızı şiddetle öneririm, aksi halde bellekte yüzlerce mail nesnesi birikebilir ve işlem bellek yetersizliğinden yarıda kesilebilir.

Bununla beraber son satırdaki oApp değişkenine Nothing ataması çok da kritik değildir. Ben yine de alışkanlıkla bunu yapmayı tercih ediyorum. yorum.

Sub çoklumail_Button1_Click()
Dim oApp As Outlook.Application
Dim oMail As Outlook.MailItem
Dim alıcılar As Range, a As Range

Set oApp = New Outlook.Application
Set alıcılar = Range(Range("A2"), Range("A2").End(xlDown))

For Each a In alıcılar
    Set oMail = oApp.CreateItem(olMailItem)
    With oMail
        .Subject = "Doğum günü"
        .To = a.Value
        .Body = a.Offset(0, 3).Value & "Doğum gününüz kutlar, ailenizle birlikte mutlu yıllar dilerim"
        .Body = .Body & vbCrLf & "Gönderenin adı soyadı"
        .Send
    End With
    Set oMail = Nothing 'zorunlu değil ama faydalı
Next a

Set oApp = Nothing 'zorunla da değil kritik faydası da yok
End Sub

YORUMLAR