13.07.2018 tarihinde 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.

25.04.2018 tarihinde,VBA konularına Formlar-Temeller sayfası eklenmiştir.

VBAMakroTemeller5

Biraz daha terminoloji

Yeni başlayanlar isterse bu kısmı şimdilik geçebilir. Burayı biraz ilerleme kaydedip de ilave teknik bilgiye ve aslına bakılacak olursa daha sağlam bir altyapıya sahip olmak isteyenler için hazırladım. İsterseniz şöyle hızlıca bir gözatın ve ne hakkında olduğunu görün, ilerde kafanıza sorular takıldığında tekrar dönüp bakarsınız.

Parametre, Argüman, ByVal, ByRef

Argüman vs Parametre

Bazı prosedürlerimiz hiçbir parametre olmadan, çağrıldıklarında doğrudan çalışırlar. Bazıları ise parametre alırlar. Parametreler prosedür isminden sonra gelen parantezlerin içinde yer alırlar. Ör:

Function KareAl(rakam As Integer)
   KareAl=rakam*rakam
End Function

Şimdi bu yukardaki örnekte 'rakam', KareAl fonksiyonunun bir parametresidir.

Peki argüman nedir? Argüman da işte bu KareAl fonksiyonunu çağırırken parantez içine yazdığım ifadelere denir.

Yani aslında aynı şeyden yani 'rakam'dan bahsediyoruz. İşte bu rakam ifadesi, çağrılan prosedür için parametre, çağıran prosedür için ise argüman adını alır.

Sub test()
   a=Application.Inputbox("Bir sayı girin", Type:=1)
   sonuc=KareAl(a)
   MsgBox "Girdiğiniz sayınını karesi şudur:" & sonuc
End Sub

Bu örnekte çağıran prosedür test() yordamıdır(Sub), ve bu yordam 'a' değişkenini KareAl fonksiyonuna argüman olarak gönderiyor. Farkettiyseniz, argüman ve parametrenin aynen yazılması gerekmiyor. a argümanı, KareAl içine girdiğinde rakam parametresi olarak algılanıyor.

Gördüğünüz gibi aynı şeyin farklı yönlerden görünüşüne benziyor bu iki ifade, ve birçok yerde birbiri yerine bile kullanıldığını görebilirsiniz.

Çağırma şekilleri

Sub ve Function prosedürlerin çağrılmalarında küçük farklar bulunmaktadır. Hemen bakalım:

Bir Sub prosedürü çağırmanın da kendi içinde iki yolu var. Call terimini kullanarak veya kullanmayarak.

  • Call ifadesini yazdıysanız parantez kullanmak zorundasınız
  • Call yazmadıysanız parantez olmadan yazarsınız. (Ben şahsen Call yazmadan Sub çağırmanızı önermem, zira bu ifade ile o satırda başka bir prosedürün çağrıldığı hemen belli olmaktadır.)

Function çağırmanın da iki yolu var

  • Eğer dönen değeri bir değişkende depolayacaksanız parantez zorunludur
  • Dönen değerle ilgilenmiyorsanız parantez gerekmez

Yukarda KareAl fonksiyonumuza ek olarak aşağıda bir Sub prosedürümüz ve bir fonksiyonumuz daha var.

Sub Bilgiler(isim as String, yas as Integer, hayattamı as Boolean)
    MsgBox "Kişi " & yas & " yaşında,ismi " & isim & " ve kendisi " & IIf(True, "hayatta", "hayatta değil")
End Sub

Function CokSelamSoyle(rakam As Integer)
'bu aslında bir Sub olsa daha iyi olurdu, ancak örnek olması adına function kullandım
   CokSelamSoyle=rakam*100
   MsgBox CokSelamSoyle & " kez selam olsun"
End Function

Bunları aşağıdaki şekillerde çağırabiliriz;

Call Bilgiler("Volkan", 37, True) 'Sub çağırıken call varsa parantez zorunlu
Bilgiler "Volkan", 37, True 'Sub çağırırken call yoksa parantez zorunlu değil
a=KareAl(10) 'Function çağırırken bir değişkende depolayacağımız için parantez var
CokSelamSoyle 10  'dönen değerle ilgilenmiyoruz, parantez zorunlu değil

Fonksiyonlardan dönen değerle ilgilenmeye ve ilgilenmemeye çok güzel bir örnek MsgBox fonksiyonudur aslında. Hemen örneğe bakalım

'Kullancıya mesaj vermek istiyorum, kullancıdan bir cevap toplamıycam
Msgbox "İşlem tamam" 'parantez yok

'Kullancıdan bilgi toplayacağım
cevap=MsgBox("Devam edeyim mi?", vbYesNo)
If cevap=vbYes then
   'kodlar buraya
End If

İsimli argümanlar(Named arguments)

Bir Sub veya Function prosedürü çağırdığınızda, bu prosedürün parametre tanımında görüldüğü sırasıyla argüman sağlamamız gerekir. Parametre pozisyonuna bakmadan argüman yazmak istiyorsak isimli argümanlar kullanabiliriz. İsimli argümanı yazmak için Argümanismi:=Değer syntaxını kullanırız.

Mesela aşağıdaki gibi 3 parametreli bir Sub prosedürümüz olsun.

Sub Bilgiler(isim as String, yas as Integer, hayattamı as Boolean)
    'kodlar buraya
End Sub

Bu prosedürü isimli veya isimsiz nasıl çağırabiliyoruz bir bakalım(Sadece Call''lu versiyonları yazalım, Call'suz da yapılabilirdi)

'Klasik yöntem
Call Bilgiler("Volkan", 37, True) 'sadece bu sırada

'İsimli argüman yöntemi, argüman sırası önemli değildir
Call Bilgiler(hayattamı:=True,yas:=37,isim:="Volkan")
Call Bilgiler(yas:=37,hayattamı:=True,isim:="Volkan")
Call Bilgiler(yas:=37,isim:="Volkan",hayattamı:=True)
'3 parametre için toplam 6 kombinasyon var, biz üçünü yazmış olduk

İsimli argümanlar, özellikle Opsiyonel argümanlar çok sayıda varsa oldukça kullanışlı olmaktadır. Şimdi önce Opsiyonel argümanlara sonra da ikisinin bir arada kullanılmasına bakalım.

Opsiyonel argümanlar

Bazen prosedürünüze öyle parametreler koymak istersiniz ki, bunlar seçime bağlı olsun, kullanıcı isterse girsin isterse girmesin. Bunlara ayrıca varsayılan değer de girebilirsiniz. Yani özellikle kullanıcıların büyük çoğunluğunun aynı değeri gireceğini umduğunuz değişkenleri opsiyonel ayarlayıp değer olarak da bu en olası değeri girebilirsiniz.

Opsiyonel parametresi olan bir prosedürü çağırdığımızda bu parametreyi kullanmazsak, ve varsayılan değer varsa bu değer dikkate alınır, varsayılan değer yoksa ilgili datatipinin varsayılan değeri kullanırlır, mesela String için "", Integer için 0 gibi.

Opsiyonel parametrelere argüman sağlanıp sağlanmadığını IsMissing fonksiyonu ile kontrol edeblirsiniz. Ancak bu fonksiyon sadece Variant tipteki değişkenler için kullanılıyor. String, Integer gibi data tiplerindeki bir opsiyonel parametreye argüman sağlanmış mı diye bakmak için bunların default değerlerini kontrol ederiz. String için If x="" , integer için If x=0 gibi. IsMissignle ilgili MSDN örneği aşağıdaki gibidir:

Sub OptionalArgs(strState As String, Optional varRegion As Variant, _ 
Optional varCountry As Variant = "USA") 
 If IsMissing(varRegion) And IsMissing(varCountry) Then 
 Debug.Print strState 
 ElseIf IsMissing(varCountry) Then 
 Debug.Print strState, varRegion 
 ElseIf IsMissing(varRegion) Then 
 Debug.Print strState, varCountry 
 Else 
 Debug.Print strState, varRegion, varCountry 
 End If 
End Sub 

Opsiyonel parametreler, parametre listesinde [] içinde görünürler. Atlamayı düşündüğünüz opsiyonel parametreleri virgül(,) koyarak atlayabilrsiniz.

Aşağıda VBA'ın MsgBox fonksiyonunu görüyoruz. 1 adet zorunlu parametresi var, o da gösterilecek mesajı barındıran Prompt parametresi, diğerler hep opsiyoneldir.

DİKKAT:Opsiyonel parametreler, parametre sıralamasında en sonda yer almalıdır, ondan sonra opsiyonel olmayan bir parametre kullanırsanız kodunuzu çalıştırmadan önce hata alırsınız.

Opsiyonel ve İsimli argümanlar birarada

Şimdi diyelimki çok sayıda opsiyonel değişkeni olan bir fonksiyonu çağıracaksınız ama sadece zorunlu olanı bir de aradaki parametrelerden birini girmek istiyorsunuz. Nasıl yapardınız?

MsgBox'ı alalım yine; Sizin için sadece mesaj ve başlık göstermek yeterli diyelim. Aşağıdaki yöntemleri uygulayabilirsiiz.


MsgBox "İşlem tamam", vbOKOnly, "Rapor Sonucu" 'Named argüman kullanmıyoruz. Sıra önemli. Mecburen ilgili sırada tek tek yazarız 
MsgBox "İşlem tamam", , "Rapor Sonucu" 'Named argüman kullanmıyoruz. Sıra önemli. Gereksizleri virgülle atlarız 
MsgBox Title:="Rapor Sonucu", Prompt:="İşlem tamam" 'Named argüman: Sıra farketmez 
MsgBox "İşlem tamam", Title:="Rapor Sonucu" 'Prompt parametresi zaten sırasında olduğu için isimli argüman kullanmadı

Sizi bilmem ama bana göre en kullanışlısı ve basiti son seçenektir, özellikle 8-10 tane parametresi olan ve sadece ilk parametresiyle son parametresini kullanmak istiyorsam, araya 6 tane virgül koymakla uğraşmak istemem, onun yerine ilk parametresi için isim kullanmayıp sonrasında sadece son parametre için isim kullanırım, bu kadar basit. Mesela Application.InputBox fonksiyonunu çok kullanacaksınız, bunda aşağıdaki gibi 2 argüman girmek yeterlidir.

Cevap = Application.InputBox("Bir sayı girin", Type:=1)

ByVal vs ByRef

Bu konu benim uzun zaman kafamı karıştıran bir konuydu, araştırma yaptığımda karşıma çıkan örnekler hep kafamda canlandırmakta zorlandığım, i ve j'lerin kullanıldığı soyut örneklerdi. Tanımlar ise MSDN çevirisi veya varyasyonu olmaktan öteye gitmiyordu. O yüzden ben hem örnekleri verirken hem de tanımı yaparken biraz daha açıklayıcı olmaya çalışacağım, umarım bunda başarılı olabilirim.

Şimdi, eğer çağırdığımız prosedüre argüman olarak gönderdiğimiz değişkenleri bu prosedürde değişikliğe uğratmayı planlıyorsak amma ve lakin bunlar geri döndüğünde hala aynı değeri korumasını istiyorsak ByVal ile göndeririz, geldiğinde değeri değişerek gelsin istiyorsak ByRef ile göndeririz. Biliyorum şuan çok karışık geldi bu ifade, o yüzden hemen aşağıdaki örneği inceleyelim sonra gelip tekrar bu tanıma bakarsınız.

Sub çağrılan(ByVal dondugunde_degismeyen_rakam As Integer, ByRef dondugunde_degisen_rakam As Integer)
  dondugunde_degismeyen_rakam = 100
  dondugunde_degisen_rakam = 500
  'çeşitli kodlar
End Sub

Sub çağıran()
  Dim a As Integer, b As Integer 'özellikle b'yi integer olarak deklare etmemiz lazım, yoksa b'yi Variant algılar ve Type Mismatch hatası verir
  a = 1
  b = 2
  Call çağrılan(a, b)
  Debug.Print a, b '1 500 yazar
End Sub

Gördüğünüz gibi, a değeri döndüğünde hala 1 değerini korudu, b değeri ise değişerek geldi. Peki neden böyle birşey isteyelim ki? Üstelik bu örnek ilk paragrafta eleştirdiğim i ve j'li örneklere benzemedi mi, evet benzedi :) Tabiki bu örnek giriş niteliğindeydi, şimdi örneği biraz daha mantıklı hale getirelim.

Diyelim ki bi fonksiyona yaş değişkenini göndereceğim, gönderdiğim yerde bir hesaplamada kullanacağım(garip bir hesaplama olacak ama rakamın değişmesini istediğim için böyle döngüsel bir işlem olacak), sonra bu hesap sonucuyla da kişinin hakettiği maaşı hesaplayacağımm, ama geri geldiğinde ona hala yaş olarak ihtiyacım varsa işte o zaman ByVal ile gönderirim. Bi de doğumyeri bilgisini göndereceğim. Gittiği prosedürde, doğumyerine göre kişiyi iki bölgeden birine tayin eden bir hesaplamada kullanacağım, ve döndüğünde bu yerleşim yeri bilgisi ile gelmesini istiyorum, iyi de peki orjinal doğumyeri bilgisine de ihtiyacım varsa ne olacak, o zaman onu çağrıyı yapan prosedürde geçici bir değişkene atarız. Hadi örneğe bakıp daha iyi anlayalım.

Function maashesapla(ByVal y As Integer, ByRef d As String)

   If d = "İstanbul" Then
     d = "Doğu"
   Else
     d = "Batı"
   End If

   'maaş hesaplama katsayısını elde yöntemi
   For i = y To 1 Step -1
     y = y + i
   Next i

   maashesapla = y * 100

End Function


'çağıran prosedürümüz
Sub byvalref_ornek()
   Dim dogumyeri As String
   yas = 20 'bunu inputboxla sorup daha parametrik de yapabilirdik, ancak örnek basit olsun istedim
   dogumyeri = "Ankara" 'bunu da inputboxla sorabilirdik, siz böyle yapın isterseniz
   orjinaldogum = dogumyeri ' az sonra değişeceği için şimdiden başka bir dğeişkene atıyorum

   MsgBox yas & " yaşında ve " & orjinaldogum & " doğumlu aday " & maashesapla(yas, dogumyeri) & " lira maaşla " & dogumyeri & " bölgesine tayin edilmiştir"
End Sub

Prosedürü çalıştırınca aşağıdaki gibi bir mesajla karşılaşırız.

Burada ne oldu şimdi ona bi bakalım. Yas argümanı y parametresi olarak maashesapla fonksiyonuna girdi ve bir for-next döngüsünde 230'a kadar yükseldi, yani y'nin son değeri 20 değil 230dur. Sonra da maaş hesaplandı(23000), ama ByVal olarak gönderdiğimiz için geri dönerken yine 20 olarak geldi, 230 değil.

Dogumyeri argümanı da d parametersi olarak fonksiyona girdi ve if kontrolü ile yine kendisine bölge ataması yapıldı ve değeri Batı oldu, ana prosedüre de bu değerle döndü. Ama biz başta dedik ki, Ankara değerine de ihtiyacımız olacak, o yüzden ana prosedürde orjinaldogum diye bi değişken tanımladık ve fonksiyona göndermeden önce dogumyerini bu değişkene atadık, fonksiyona girdikten sonra atasaydık hiçbir anlamı olmazdı zira çoktan değişmiş olarak gelecekti.

Peki, diyelim ki yas parametresini ByVal değil de ByRef tanımlasaydık ne olurdu. İşte şu olurdu :)

İstenmeyen bir sonuç. Doğum yerinde olduğu gibi bunda da geçici bir değişken tanımlayıp 20 değerini koruyabilirdik ama sizce gerek var mı, elimizde ByVal diye bir seçenek varken, ne diye fazladan bir değişken yaratalım ve belleği dolduralım ki, aynı zamanda kodumuzu uzatalım ki?

Son olarak ByVal ve ByRef konsunda akılda tutulacak bazı hususlar var, onlara da bakalım:

Akılda tutulacaklar

  • Parametreleri varsayılan gönderme şekli ByRef'tir. Yani ByVal veya ByRef olarak belirtilmemiş bir parametrenin değeri ByRef olarak yorumlanır(Birçok yüksek seviyeli dilde, Vb.Net dahil, varsayılan gönderi şekli ByValue'dur. Eğer ilerde VBA kodlarınızı VB.NET veya C# diline taşıyıp bir VSTO uygulaması yapma gibi planınız olursa sorun çıkmaması adına ByRef ifadelerini mutlaka yazın, "Nasıl olsa default değer ByRefmiş, yazmaya gerek yok" diyip boş bırakırsanız kodlarınızı Vb.Net'e kopyaladığınızda onlar otomatik olarak ByVal olarak algılanacaktır, ki bu da sıkıntılara neden olabilir, çünkü siz onların ByRef olarak işleyeceğini düşünmüştünüz).
  • Çağrıda bulunduğumuz prosedürde ByRef olarak gönderilen argüman mutlaka Dim ile aynı veritipinde tanımlanmalıdır, yoksa hata alırsınız. Buna Variant tipler de dahildir. O yüzden Dim ile tanımlanmamış bir değeri string tipteki parametreye atamaya çalışırsanız hata alırsınız. Aşağıdaki örnekte olduğu gibi:
  • 'Çağrılan prosedür
    Sub rutinkod(ByVal rapor As String)
    ....
    End Sub
    
    'Çağıran kod
    Sub kredi ()
    Rpr="kredi" 'Rpr varianttir
    
    Rutinkod(Rpr) 'type mismatch hatasi
    
    End Sub
    
  • Gönderim şekliniz %90-95 oranlarında ByVal olacaktır, herkesin söylediği oran yaklaşık bu civardadır. İhtiyaca göre değişmekle birlikte, eğer gittiği yerde değişme riski yoksa boş da bırakablirsiniz yani ByRef kalabilir. Belki ilk maddede gözünüzü çok korkutmuş olabilirim ama pratikte bu kadar korkuya gerek yok, zira çoğu durumda gönderim şeklinin ByVal veya ByRef olması sizin için bişey farketmeyecektir.
  • Parametre, ByVal olarak tanımlandığında bir nevi ilgili değişkenin kopya alma işi yapıldığı için ByRef'e göre daha yavaş çalışır. Gerçi çok büyük döngülerden oluşmadığı sürece bu farkı hissetmezsiniz. Ancak büyük döngüleriniz varsa ve kodunuz uzun çalışıyorsa, gereksiz ByVal var mı diye kontrol etmelisiniz.
  • Parametre olarak Obje kullanılıyorsa bunlar mecburen ByRef tanımlanır. Çünkü Objeler referans tipli değişkenlerdir. (Referans tipli-Değer tipli arasındaki fark, çok ileri seviyeler olduğu için burada detaya girmeyeceğim)
  • ÖNEMLİ:Farkettiniz mi bilmiyorum ama normalde bir değer döndüren prosedürlere Fonksiyon diyoruz, ancak bir Sub prosedürde ByRef yöntemiyle ilettiğimiz değerden başka değerler de döndürebiliyoruz. Üstelik normal bir fonksiyon sadece tek değer döndürüken ByRef ile gönderi yaparak istediğimiz kadar değer döndürebiliriz. Yukardaki örnekte hem maaş tutarı hem de atama bölgesi olmak üzere 2 değer döndürmüş olduk.
  • ÖNEMLİ:ByRef'li parametresi olan bir prosedüre ByVal'miş gibi bir değer gönderebilme teknikleri vardır. Normalda bir prosedürü %99 oranınıda ByRef olarak ihtiyaç duyuyorsunuz diyelim, çünkü dönen değerin değişerek gelmesini istiyorsunuz, ama ender de olsa dönen değerin değişmesini istemediğiniz anlar olabilir. Bunun için kalkıp da mevcut prosedürün bi kopyasını alıp ByRefleri ByVal yapmanın anlamı yok, zira bunun için bir iki teknik var.
    • Çağrılan prosedür Sub ise, Call olmadan ama argümanı () içine koyarak. (Bu teknikte "isimli argüman" kullanılmaz).
    • İkinci yöntem, argüman olarak değişken değil doğrudan değerin kendisini vermek. Yukarıdaki örnek için Çağrılan(10) demek gibi.
    • Sub Çağrılan(ByRef rakam as Integer)
      'Kodlar burada
      End Sub
      
      '1.yöntem
      Sub Çağıran()
         sayı=10
         Çağrılan (sayı)
      End Sub
      
      '2.yöntem
      Sub Çağıran()
         Çağrılan (10)
      End Sub
      
    • Gönderilen argüman bir classa aitse(Class konusu çok ileri seviyedir, eğer başlangıç düzeyindeyseniz bunu atalyabilirsiniz). MSDN örneği şöyle:
    • Class Customer
          Public MyValue
      End Class
      
      Sub TestMethod (ByRef MyParam)
          MyParam = 5
      End Sub
      
      Dim cust
      Set cust = New Customer
      cust.MyValue = 123
      
      TestMethod cust.MyValue
      ' cust.MyValue is still 123.

Gerçek dünya örneği

Bu örnek, Fonksiyon ve Collection konularının iyice anlaşılmasını gerektirdiği için bu konuları öğrendikten sonra tekrar bakmanızı tavsiye ederim.

Şimdi diyelim ki, belirli sayda rapor adını kullanan belli sayıda prosedürünüz var. Bunların hepsinde tek tek bu rapor isimlerini kullanan kodları yazmak anlamsız. O yüzden sonuç olarak bunların adını döndüren bir fonksiyon yazmaya karar verdiniz. Raporları bi Collection'a atayacak ve prosedürlerinizden de bu fonskiyonu çağıracaksınız. Çağıracağınız fonksiyonda çalışan bir If bloğu var, kullanıcya sorulan sorunun cevabına göre de şifre değişkenine değer atanıyor. Tüm prosedürlerimizde bu IF bloğunu yazmak da anlamsız olacağı için bu kodu fonksiyon içinde tutmya karar vermişsiniz, ki mantıklı olan da budur. O yüzden çağırdığınız bu fonksiyondan collection ile birlikte aynı zamanda bu şifreyi de döndürmeniz gerekiyor. Yani çoklu değer döndüren bir collectiona ek olarak ikinci bir değer daha döndürüyorsunuz.

Örneğimiz basit olsun diye 2 çağırıcı prosedür, fonksiyon içinde de 4 rapor yazdık. Kodlarımız şöyle:

'1.prosedür
Sub pro1()
Dim myCol As New Collection
Dim myŞifre As String

myŞifre = ""
Set myCol = col(myŞifre)
'diğer işler

MsgBox myŞifre 'fonksiyondan değişerek gelir

End Sub

'2.prosedür
Sub pro2()
Dim myCol As New Collection
Dim şfr As String

şfr = ""
Set myCol = col(şfr)
'Diğer işler

MsgBox şfr 'fonksiyondan değişerek gelir

End Sub

'Fonksiyonumuz
Function col(ByRef şifre As String) As Collection
Dim c As New Collection

c.Add "rapor1"
c.Add "rapor2"
c.Add "rapor3"
c.Add "rapor4"

a = InputBox("DB türü girin," & vbCrLf & _
"Oracle için 1" & vbCrLf & _
"DB2 için 2" & vbCrLf & _
"SQL Server için 3")

Select Case a
    Case 1
        şifre = "volki1144"
    Case 2
        şifre = "volki1234"
    Case 3
        şifre = "volki7788"
    Case Else
        şifre = ""
End Select

Set col = c

End Function

Dikkat ettiyseniz geçici şifre değişkenini her iki prosedürde de "" şeklinde argüman olarak koyduk,aslında istedğimiz şekilde koyabilirdik, nasıl olsa fonksiyon içinde değişerek geleceğini biliyoruz.

Değişkenlerin Erişim Tipleri ve Yaşam Döngüleri

Yaşam Döngüsü

Yaşam döngüsü, değişkenlerin geçerli olduğu yeri ifade eder. Değişkenlerin tanımlandığı yere göre iki türü vardır.

Yerel Değişken(Prosedür seviyesi): Sub ve End Sub arasında tanımlanan değişkenlere yerel değişken denir. Mesela aşağıdaki örnekte yol1 değişkenini sadece bu yasam1 prosedüründe, yol2 değişkenini de sadece yasam2 prosedüründe kullanabiliriz.

Sub yasam1()
    Dim yol As String
    
    yol1 = "C:\makrolar"
End Sub	

Sub yasam2()
    Dim yol2 As String
    
    yol = "C:\hedefler"
End Sub			

Bu arada şöyle birşey var ki, Sub-End Sub arasında tanımlanmış bir değişken if blokları veya döngüler arasında tanımlanmış veya ilk kez orada kullanılmış olsa bile bunların dışında çıktığında da yaşamaya devam eder, yani yerel değişkenlerin yaşam ömrü tüm prosedürdür, sadece tanımlı ve kullanıldığı yer değil. Örneğin aşağıdaki kodda i değişkeni For-Next döngüsünden sonra da yaşamaya devam eder.

Sub scope()
For i = 1 To 10
    Debug.Print i
Next i

Debug.Print i * 5
End Sub	
Bu durum özellikle C#, C, Java gibi yüksek seviyeli dillerden gelenler için biraz şaşırtıcı olabilir, zira bunlarda yerel değişkenler sadece tanımlandıkları bloklarda geçerlidir.

Global Değişken(Modül seviyesi): Bir de global değişkenler vardır, bunlar genelde modülün en üstünde tanımlanır, tüm Sub'lardan önce. Diyelim ki birçok prosedürümde "şubeadet" diye bir değişken olacak, o zaman hepsinde tek tek tanımlamak yerine global tanımlarız, bi kere tanımlarız. Global değişkenin amacı da budur zaten.

Dim subeadet As Integer 'global tanımlandı
Sub yasam1()
    Dim yol As String
    
    yol1 = "C:\performans"
    'subeadet kullanılır
End Sub	

Sub yasam2()
    Dim yol2 As String
    
    yol2 = "C:\hedefler"
    'subeadet kullanılır
End Sub			

Şimdi bu örnekte subeadet değişkenini her iki prosedür içinde de kullanabiliriz.

(Global değişkenlerin) Erişim seviyesi

Şimdi bu yukardaki örnekte global değişkeni aynı modülün tepesinde Dim ifadesi ile tanımladık. Dim ifadesi yerine Private ifadesi de kullanılabilirdi, ki global değişken tanımlarken daha çok Private ifadesini kullanmakta fayda var(Hatalı bir durum olacağından değil, sadece kodlamacılar arasında bu daha çok tercih edilir). Dim'i daha çok yerel değişkenleri tanımlarken kullanalım.

İşte bu Private(Dim) ifadesi o değişkenin sadece o modül içindeki prosedürlerde kullanılabileceği anlamına gelir. Başka modüller bu değişkeni kullanamaz.

Başka modüllerin de bu değişkeni kullanabilmesini istersek o zaman onu Public ifadesi ile tanımlarız.

Şimdi şöyle bir örnek yapalım. Yeni iki yeni modül oluşturalım ve aşağıdaki kodları yazalım

'Modül1 içeriği		
Public publics As String
Dim dims As String
Private privates As String		

Sub erisim1()
	Debug.Print TypeName(publics) 'String yazar
	Debug.Print TypeName(dims)	'String yazar
	Debug.Print TypeName(privates) 'String yazar
End Sub


'Modül2 içeriği		
Sub erisim2()
	Debug.Print TypeName(publics) 'String yazar, çünkü public değişkeni kullandık
	Debug.Print TypeName(dims) 'Empty yazar, çünkü modül1deki Dimle tanımlanan değişkeni değil, bu modülde henüz tanımlanmamış olan yani Variant tipteki dims değişkeninin tip adını yazar, tanımlanmamışların TypeName değieri de Emptydir.
	Debug.Print TypeName(privates) 'bunda da bi yukardakiyle aynı nedenden Empty yazar
End Sub

NOT:Tabi bir de değişkenler konusunda gördüğümüz gibi Const ifadesi ile sabit tanımlama var. Sabitler de Private veya Public tanımlanabilmektedir.

Bir de Global keywordü(ifadesi) var ki artık pek kullanılmaz, onun yerine Public yeterlidir.(Detayı şu:Global sadece standart modüllerde kullanılabilirken, Public heryerde, o zaman ne gerek var Globale :) Sadece bilin ve gördüğünüzde bu da ne ki diye şaşırmayın diye dahil ettim. Bu sayfada "Global" kelimesini gördüğünüz her yerde, kelimenin seviye anlamını kullanılmıştır, keyword(ifade) anlamı değil.)

Peki bir Public değişkene başka bir Workbooktan erişilebilir mi? Evet. Ama ne benim böyle bir ihtiyacım oldu ne de bir başkasının böyle birşeye ihtiyaç duyduğunu gördüm. O yüzden bunun nasıl yapıldığını burada anlatmıycağım. Sadece yapılabildiğini bilin yeter :) İhtiyacınız olursa da bunla ilgili bi google araması yaparsınız.

Kullanım şekli nasıl olacak

"Tamam iyi diyorsun da, bunlar çok havada, nasıl kullanacağız" diye düşünüyor olabilirsiniz. İşte size kendimden örnekler ve tahmini bi senaryo:

  • Ana makro dosyamız Personal.xlsb olduğuna göre, onun ilk modülünün tepesine birkaç değişken ve sabit tanımlayın. Mesela Mağazalar zincirinde çalışıyorsanız mağaza adedi gibi. Neden bunu Global(Modülün tepesinde) ve Public tanımlayayım ki diye mi sordunuz? Global tanımlayalım çünkü birden çok prosedürde kullanacağız, hepsinde tek tek aynı şeyi tanımlamayalım. Public tanımlayalım çünkü birden çok modülde kullanacağız.
  • Yine sık sık kayıt yaptığınız bir klasör varsa(veya ilerde olacaksa) bunu Public Const olarak tanımlayabilirsiniz. (Mesela hergün çalışıp o günün tarihiyle belli bir klasöre dosya kaydediyorsunuzdur)
  • Benim Personal.xlsb dosyamdaki global değişken ve sabitlerim şöyle. Nerdeyse onlarca makromda bölge sayısını bir döngü içinde kullanıyorum (For i= 1 to bolgeadet gibi). Keza otomatiğe bağlanmış tüm raporlarımın kaydolduğu bir günlükklasör path'imi de tanımlamak çok akıllıca oldu, böylece her prosedürde tek tek bu path'i belirtmem gerekmiyor(NOT:An itibarıyle günlük çalışan 38 raporum bulunmakta, düşünsenize hepsine tek tek bu klasörün adresini yazdığımı, üstelik bu adres çok uzun görünüyor, yaklaşık 80 karakter, halbuki değişkenim 12 karakter)
    'ana modlümdeki global değişken ve sabitler
    Public Const gunlukyol As String = "Q:\Bireysel Bankacılık Satış Yönetimi\PERFORMANS TAKİP\GÜNLÜK ÇALIŞMALAR"
    Public Const bolgesayısı As Integer = 22
    Public subeadet As Integer 'şube açılılş ve kapanışı nedeniyle değişebilmektedir, o yüzden Const değil
    
    'diğer modüllerden örnek bir prosedür
    Sub tmvgelisim()
    If Weekday(Now, vbMonday) = 7 Then Exit Sub
    
    'eğer dosyayı önceki runlarda oluştuysa mail atmadan çık, yoksa çalıştır
    If Application.Run("PERSONAL.xlsb!FileFolderExists", gunlukyol + "\TMV Gelişim\TMV Gelişim - " & Date - 1 & " Sonuçları.xlsm") Then Exit Sub
    
    Workbooks.Open Filename:= _
    gunlukyol + "\TMV Gelişim\TMV Gelişim - Format.xlsm"
    
    'dosya save olduysa mailat yoks atma
    If Not Application.Run("PERSONAL.xlsb!FileFolderExists", gunlukyol + "\TMV Gelişim\TMV Gelişim - " & Date - 1 & " Sonuçları.xlsm") Then
    ActiveWorkbook.Close savechanges:=False
    Exit Sub
    Else
    ActiveWorkbook.Close
    Kill "C:\geçici\geçici2.xlsm"
    
    rapor = "TMV Gelişim"
    alici = "35516;37524"
    Call Mailat2(rapor, alici)
    
    End If
    
    
    End Sub		
  • Mağaza sayınız/şube adediniz v.s dönem dönem değişmiyorsa(açılış ve kapanışlar nedeniyle) bir tane magazaadet/subeadet sabiti tanımlayabilirsiniz. (Ör:Public Const magazaadet as Integer=540)
  • Mağaza sayınız değişkense Public magazaadet as Integer dersiniz ve her prosedürünüzde bu değişkeni kullanırsınz ve o anki açık mağaza sayısını da InputBox ile kullanıcıya sordurursunuz veya şubeler/mağazalar listeniz varsa oradan okutturursunuz.

Altın kural

Altın kural şu:Değişkenlerinizi olabildiğince yerel tanımlayın, global olacaksa Private olsun, en son seçenek Public olsun. Şimdi  şöyle düşünebilirsiniz. Madem ki Public diye birşey var ve heryerde geçerli neden Private'a ihtiyaç olsun ki, tüm değişkenlerimi Public tanımlarım.

Cevap biraz uzun ve karışık, ama özetleyecek olursak, hafıza problemleri, karışık kod düzeni, hataya açık olma durumu v.s gibi nedenler yüzünden böyle yapmalısınız diyebilirim. Detaylı bilgiye eğer İngilizceniz varsa buradan ulaşabilrsiniz.

Çatışma

Peki, aynı anda hem global değişkeniniz hem de yerel değişkeniniz aynı isimdeyse o zaman ne olacak. Hangisi hangisi nereden bileceksiniz?

Eğer, sadece birini kullanırsanız, VBA en düşük seviyedekini yani yerel seviyedekini kullanmış olur. Eğer ikisini birden kullanmanız gerekiyorsa, global değişkeni kullanmak için başına modül adını yazmanız gerekir. Ör:

Private subeadet As Long ' global değişken

Sub bolgerapor()
Dim subeadet As Long ' yerel değişken
subeadet = 50 ' yerel değişkeni kullanıyoruz, bölgenin sube sayısını verir
Module1.subeadet= 700 ' global kullanıyoruz, bankanın sube sayısını verir
End Sub 	

Bana kalırsa böyle bir kullanıma da çok ihtiyacınız olmayacaktır. Bunun yerine ilgili değişkenlere farklı isim vermeye çalışın. Yine de olur da ihtiyacınız olursa çözüm yukarıdaki gibidir.

Prosedürlerin erişim seviyesi ve çağrılması

Erişim seviyeleri

Prosedürlerin de değişkenler gibi erişim seviyesi bulunmaktadır ve varsayılan erişim şekli Public'tir. Varsayılandan kastım, önünde hiçbir erişim belirtecinin bulunmadığı tanımlama şeklidir.

Private olanlara sadece ilgili modülden erişilebilirken Public olanlara(Public denmemişse de Public sayılırlar) tüm modüllerden ulaşılabilir. Private tanımlanmış modüller, Excelin Makrolar dialog kutusunda listelenmezler.

Değişkenlerde olduğu gibi prosedürlere de dışardan yani başka bir workbooktan erişilebilir, ki bu durum değişkenlerde olduğu gibi çok nadir değil, oldukça olağandır. Bunu yapmak da oldukça kolaydır, aşağıda örnekleri göreceksiniz. Örneklere geçmeden önce bi noktaya daha değinmek istiyorum. Diyelim ki bir workbookta birden fazla modül ve prosedür var, prosedürlerden bi tanesini o workbooktaki tüm modüller kullanıyor olsun ancak buna diğer workbookların erişmesini istemeyebilirsiniz(gerekçeniz ne olur bilmiyorum ama teorik olarak bunu isteyebilirsiniz). Böyle bir durumda bu prosedürü farklı bir modüle alın ve en tepeye Option Private Module yazın. İşte bu kod, ilgili prosedürünüzü dışardan kullanıma kapatır, yani sadece kendi workbook içindeki modüller ulaşabilir.

Farklı modüllerdeki private prosedürleri çağırırken ise Application.Run metodunu kullanıyoruz. Bu metodu kullanırken, parametre de atayabiliriz, parameterleri virgül ile ayırırız.

İşte bu paremetre gönderme işi bize güzel bi fayda sağlıyor. Workbook ve Worksheets olaylarında detaylıca göreceğiz gerçi ama, bunların olayları(event) da Private Sub üretir ve Application.Run ile bu private sublara da ulaşabiliriz, üstelik parametre gönderme imkanına sahip olarak.

Private Sub Deneme()
   Application.Run "Sheet1.Worksheet_Change", Range("A1")
End Sub	

Farklı workbooklardaki prosedürlere de ulaşabiliriz, tabiki Public tanımlandılarsa. Bunlara da yine Application.Run metodu ile ulaşırız, bu sefer prosedür önüne Workbook adını yazar ve ! işareti ekleriz. Ulaşacağımız prosedür bir Sub değil de Function ise bunları parantez içine yazarız.

Bu anlattıklarımızın özet görüntüsü aşağıdaki tabloda bulunmaktadır. Aşağıdaki ifadelerin hepsinde Call opsiyoneldir, çıkartılabilir, ancak bir prosedüre çağrıda bulunduğunu hızlıca algılayabilmemiz adına kullanmanızı tavsiye ederim. Call çağrılarında Aplication.Run'dan farklı olarak parametreler parantez içine yazılır. Call ifadesi kullanmayacaksanız parametreyi prosedürden bir boşluk sonrasına da yazabilirsinz.

Amaç Yöntem
Aynı Modül içindeki bir prosedürü çağırmak
  • Call private_prosedür
  • Call public_prosedür
  • Call prosedür(parametre1, parameter2)
  • prosedür parametre1, parametre2(önermiyorum)
Aynı Workbook, başka modüldeki prosedür
  • Call public_prosedür
  • Call Modül2.public_prosedür(Modül1 içinde de public_preosedür isminde bir prosedür varsa karışmasın diye modül ismiyle betimleriz)
  • Application.Run "Modül2.private_prosedür"(Call ile olmaz)
Farklı workbooktaki prosedür
  • Application.Run "DiğerWB.xlsm!public_prosedür"
  • Application.Run "DiğerWB.xlsm!public_prosedür", parametre
  • Application.Run("DiğerWB.xlsm!public_function", parametre)

Aşağıda MSDN sitesinden alınan bir örnek var

Sub Main() 
 MultiBeep 56 'veya call MultiBeep(56) 
 Message 
End Sub 
 
Sub MultiBeep(numbeeps) 
 For counter = 1 To numbeeps 
     Beep 
 Next counter 
End Sub 
 
Sub Message() 
 MsgBox "Time to take a break!" 
End Su

Makrolarınızı gizlemek

Bazen kullanıcıların Makro diyalog kutusunu açıp da kazalara neden olabilecek makroları çalıştırmasını istemezsiniz. Bunu yapmanın birkaç yolu var. Gelin onlara bir bakalım.

  • İlgili makroları Private yapmak. Evet, Private prosedürler Makro penceresinde görünmezler. Ama diyelim ki makrolarınızın public olması gerekiyor, o zaman diğer yöntemlere devam edelim.
  • İlgili tehlikeli makroları ayrı bir modüle alıp en tepeye Option Private Module demek. (Yukarda söylemiştim, bu yöntemle aynı zamanda başka workbbokların da bu modüle erişimini engellemiş oluruz)
  • Prosedürünüze opsiyonel dummy bir parametre eklemek. Parametre alan prosedürler Makrolar dialog kutsunda görünmezler.
'1.modül içeriği
Sub publicmesajver()
    MsgBox "selam"
End Sub
Private Sub privatemesajver()
    MsgBox "selam"
End Sub
Sub dummylisub(Optional dummy As Byte)
    MsgBox "selam"
End Sub

'2.Modül içeriği
Option Private Module
Sub optionprivatelısub()
    MsgBox "selam"
End Sub

Şimdi gidelim Developer menüsüne ve Macros düğmesine tıklayalım. Evet, beklediğimiz gibi sadece publicmesajver prosedürü listeye geldi. privatemesajver gelmedi çünkü private(1.yöntem), dummylisub gelmedi çünkü parametresi var(3.yöntem) optionprivatelısub da gelmedi çünkü option private modül açık(2.yöntem)

YORUMLAR