Worksheet Olayları(Eventleri)
Bir workbook'un sayfalarındaki çeşitli olaylara tepki vermek adına devreye giren olaylar Worksheet olayları olarak adlandırılır. Bunları da yine Workbook olaylarını seçer gibi seçip içlerini doldurmaya başlayabilirsiniz. İlgili combobox seçildiğinde aşağıdaki gibi eventlerin bir kısmı görünür.
                Bunlardan en sık kullanacaklarımız:
- Change
 - SheetChange
 - BeforeDoubleClick
 - Activate/Deactivate
 - Calculate
 
Pivot tablolarla ilgili olanlar da önemli olup bunlara Pivot İşlemleri konusunda değineceğiz. Şimdi sırayla önemli eventlere bakalım.
Worksheet_Change Event
Kuşkusuz en önemli sayfa olayı sayfada bir hücrenin değişimiyle meydana gelen Change olayıdır. (Bu eventin adını AfterChange gibi düşünmeniz yerinde olur. Zira olay, hücre içi değiştikten sonra meydana gelir. Microsoft geliştiricileri olayın adını keşke böyle yapsalarmış. Ne de olsa After ve Before ile başlayan bir sürü event var.) Syntax'ı aşağıdaki gibidir.
                    
                        Private Sub Worksheet_Change(ByVal Target As Range)
                        End Sub
                            
                            
                Küçük Bir Örnek
Bu örnekte, her değişim oldukça sayfanın rengi değişsin. Bu örneği alıp istediğiniz bir dosyanın Sheet1 modülüne yapıştırın ve sonra gidip sayfada rasgele hücrelere bir şey girin. Her Enter'a basışınızda sayfa rengi değişecektir.
                    
                                Private Sub Worksheet_Change(ByVal Target As Range)
                                  x = WorksheetFunction.RandBetween(1, 1000000)
                                  ActiveSheet.Cells.Interior.Color = x
                                End Sub
                                    
                                    
                Tetikleyiciler ve Özel Hususlar
Change olayı kullanıcının manuel bir işlemi sonucunda tetiklenebileceği gibi bir makro kodu sonucunda da tetiklenebilir.
Bazı özel durumlar da vardır:
- Manuel hesaplama durumundan otomatik hesaplama durumuna geçildiğinde de hücrelerin içi değişir ama bu durum Change olayını tetiklemez. Yine de yeni duruma göre içerik kontrolü yapacaksanız bu sefer Calculate olayını kullanmanız gerekir.
 - Bir hücrenin içini silmek de değişiklik olduğu için Change olayı tetiklenir.
 - Merge butonu ile hücre birleştirmek tetiklemez.
 - Bir alanı sıralamak tetiklemez.
 - Goal Seek kullanarak bir hücrenin değişimi tetiklemez.
 
Target Parametresi
Target parametresi, belli bir hücrenin içeriğini değişip değişmediği öğrenmek amacıyla kullanılabileceği gibi ilgili hedefin tek bir hücre mi yoksa bir range mi olduğunu belirlemek için de kullanılabilir. Aslında Range nesnesinin tüm özelliklerini kontrol etmek için kullanılabilir.
                    
                                    If Target.Address="$A$1" Then 'bu bir adres kontrolüdür
                                    If Target.Cells.Count=1 Then 'bu da tek bir hücre mi yoksa bir range mi kontrolüdür
                                    Target'ın belirli bir aralıkta olup olmadığını öğrenmek için özel bir kullanım şekli vardır: If Not Intersect(Target, Range("..")) Is Nothing Then
                                        
                                        
                Aşağıdaki örnekte değişen hücrenin C3 veya C4'te olması beklenmektedir. Bununla ilgili daha detaylı örnek Çeşitli Örnekler bölümünde yapılacaktır.
                    
                    Private Sub Worksheet_Change(ByVal Target As Range)
                        If Not Intersect(Target, Range("C3:C4")) Is Nothing Then
                            'ana kod bloğu
                        Else
                            MsgBox "Yanlış yerden seçim yapıyorsunuz, sadece C3 ve C4 hücrelerini kullanınız"
                        End If
                    End Sub
                        
                        
                Aynı Hücredeki Değişimlerde Bir Önceki Değeri Elde Etme
Değişen hücrenin bir önceki değerini elde etmek istiyorsak Statik değişken kullanırız.
                    
                        Private Sub Worksheet_Change(ByVal Target As Range)
                        Static öncekiDeğer As String
                        Static öncekiAdres As String
                        If öncekiDeğer <> "" And öncekiAdres = Target.Address Then
                           MsgBox "Önceki:" & öncekiDeğer
                        End If
                        öncekiDeğer = Target.Value
                        öncekiAdres = Target.Address
                        MsgBox "yenisi:" & Target.Value
                        End Sub
                            
                            
                Bu örnekte statik değişkenlerimiz ilk başta boş olacaktır, zira henüz "öncesi" yoktur. İlk işlemden sonra önceki statik değişkenler dolmaya başlayacaktır. Akabinde, yeni hücre ile öncekinin aynı olup olmadığı kontrol edilir.
Worksheet_SelectionChange
Seçili hücre her değiştiğinde bu event oluşur.
                    
                        Private Sub Worksheet_SelectionChange(ByVal Target As Range)
                        End Sub
                            
                            
                Target, seçilen hücreyi gösterir.
Aşağıdaki örnekte, seçilen hücre pencerenin sol üst köşesindeki ilk hücre olacak şekilde ayarlanır.
                    
                        Private Sub Worksheet_SelectionChange(ByVal Target As Range)
                            With ActiveWindow
                                .ScrollRow = Target.Row
                                .ScrollColumn = Target.Column
                            End With 
                        End Sub
                            
                            
                Önceki Seçimi Elde Etme
Seçimden bir önceki hücreye de ihtiyacımız olacaksa Statik bir değişken kullanırız. İlk seçimde çalışmaz, sonrakilerde çalışır, çünkü ilk seçimde henüz "öncesi" yoktur.
                    
                        Private Sub Worksheet_SelectionChange(ByVal Target As Range)
                        Static öncekiRange As String
                        If öncekiRange <> "" Then
                          MsgBox "önceki:" & Range(öncekiRange).Address
                        End If
                        öncekiRange = Target.Address
                        MsgBox "yenisi:" & Target.Address
                        End Sub
                            
                            
                Daha farklı bir örnek ise, önceki hücre ile yeni hücre arasındaki alanı kırmızıya boyamak olabilir. "Ne işimize yarayacak" diye sormayın, bu haliyle bir işinize yaramaz, ama farklı bir konuda size fikir verebilir.
                    
                                    Private Sub Worksheet_SelectionChange(ByVal Target As Range)
                                    Static öncekiRange As Range
                                    If Not öncekiRange Is Nothing Then
                                        Range(öncekiRange, Target).Interior.Color = vbRed
                                    End If
                                    Set öncekiRange = Target
                                    End Sub
                                        
                                        
                Worksheet_BeforeDoubleClick
Bir hücreye çift tıklandığında bu olay olur ve Excel'in o anda nasıl davranmasını istiyorsak bu prosedüre bunları yazarız. Syntax'ı aşağıdaki gibidir.
                    
                    Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
                    End Sub
                        
                        
                Target'ı şimdiye kadar öğrenmiş olmalısınız; kullanım mantığı yine yukardakilerle aynı. Cancel parametresine ise True değerini atayarak eylemi iptal edebiliriz, yani Excel'e çift tıklama olmamış gibi davrandırtabiliriz.
En sık kullandığım caselerden birisi, toplanmış verileri tutan bir listede ilgili hücreye çift tıklama sonucunda o grubun alt detayını gösteren verilerin uygun miktarda satır açılarak araya eklenmesi; aynı hücreye tekrar çift tıklanması durumunda ise bu kayıtların animasyonlu bir şekilde silinip (sanki bu sitede bordo arkaplanlı başlıklara tıklandığında yavaşça katlanmasını sağlayan Jquery kodlarına benzer) listenin ilk hale gelmesidir. Böyle bir örnek kullanımı ADO içermesi sebebiyle bu sayfada vermeyip bunları veritabanı uygulamaları bölümünde ele alıyor olacağım. İlgili örneğe buradan ulaşabilirsiniz. Aynı örneği ilgili veriyi aynı sayfada gizlenmiş bir şekilde dururken unhide ederek de yapabilirsiniz. Ancak az önceki linkteki örnekteki liste dinamik bir yapıya sahip olduğu için hide etmek bir uygun bir çözüm olmamaktadır.
Başka bir örneği ise burada ele alabiliriz. Bunda da yine gruplu bir liste var. Bu listede bir hücreye çift tıklayınca bu hücreye ait alt veriler ayrı bir dosya olarak açılıyor olsun. Ör: En çok kredi düşüşü yaşayan şube listesinde şube koduna çift tıklayınca bize en çok düşüş yaşayan müşteriler dosyasını açıp bu şubeyi filtrelesin.
                    
                            Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
                            adres = "C:\...\"
                            If Not Intersect(Target, Range("B2:B20")) Is Nothing Then
                            sb = Target.Value
                            Workbooks.Open adres + "Kredisi en çok düşen müşteriler - " & Date - 1 & " Sonuçları.xlsm", ReadOnly:=True
                            ActiveSheet.ListObjects("Query_from_DWH").Range.AutoFilter Field:=2, Criteria:=sb
                            End If
                            End Sub
                                
                                
                Şahsen ben bu eylemi çok önemsiyorum. Bununla ADO'yu birleştirerek yüksek ücretli programlara alternatif programlar yazabilirsiniz. ADO kısmında diğer detayları bulabilirsiniz.
Worksheet_Activate/Deactivate
Belli bir sayfa (yeniden) aktif (veya inaktif) olduğunda çalışmasını istediğiniz kodları bu olayla tetiklenen olay prosedürleri içine yazabilirsiniz.
                    
                            Private Sub Worksheet_Activate()
                            End Sub
                                
                                
                Örneğin, ana menü sayfası gibi bir sayfanız var ve buna sadece diğer sayfalardaki Anamenü linki aracılığı ile ulaşmak istiyorsunuz, ve bu sayfalar açıken bu menü sayfası görünmesin istiyorsanız, işte bu menü sayfasından ayrılırken sayfanın gizlenmesini sağlayacak bir kod yazabilirsiniz.
                    
                                Private Sub Worksheet_Deactivate()
                                   Me.Visible = xlSheetHidden 
                                End Sub
                                'aşağıdaki kodu da diğer sayfalardaki Selection_Change eventine yazarsınız
                                If Target.Value = "Anamenü" Then
                                   Sheets("Anamenü").Visible = xlSheetVisible
                                   Sheets("Anamenü").Select
                                End If
                        
                        
                Worksheet_Calculate
Bu event, sayfadaki formüller yeniden hesaplandığında tetiklenir. Özetle o formülü etkileyen hücrelerden birinde değişiklik olursa tetiklenir. Mesela bir hücre grubunun altında SUBTOTAL formülü ile toplam/ortalama vs. alınmışsa ve hücre grubundaki filtrede bir değişiklik yapılırsa formülün içeriği de değişeceği için bu event tetiklenir.
Bu eventte hedef bir hücre (Target) bulunmaz, zira tüm hücreler yeniden hesaplanmıştır.
NOT: Sayfa için aynı zamanda Change eventi de varsa kod bloğu içine eventleri geçici olarak bastıran kodları eklemeyi unutmayın. (Bu konuyu hemen aşağıda inceleyeceğiz)
                    
                            Private Sub Worksheet_Calculate()
                            'çeşitli işlemler
                            End Sub
                                
                                
                Bu konuya ait güzel bir örneği şurada bulabilirsiniz.
Event Tetiklenmesini Bastırmak (Geçici Olarak Durdurmak)
Makronuzda, bir yerlerde ilgili eventi tekrar tetikleyecek bir kod varsa bu kod sonsuz döngüye girer ve Excel çökebilir (veya ayarlarınıza göre 100 civarı iterasyon sonucunda durabilir, bende 78. iterasyonda duruyor). Change eventi içinde bir hücrenin içeriği değiştirilmesi veya SelectionChange eventi içinde başka bir hücre seçilmesi gibi.
Mesela aşağıdaki örneği F8 ile deneyip görün, her F8 yapışınızda kod hiç durmadan bir aşağı inecektir.
                    
                        Private Sub Worksheet_SelectionChange(ByVal Target As Range)
                           Target.Offset(1, 0).Select
                        End Sub
                            
                            
                Aşağıdaki kodda ise sürekli olarak Change olayı kendisini tetikliyor.
                    
                                Private Sub Worksheet_Change(ByVal Target As Range)
                                   Target.Offset(1, 0).Value = Target.Row
                                End Sub
                                    
                                    
                İşte bu tür durumları önlemek için eventin başında Application.EnableEvents = False diyerek eventleri geçici olarak askıya alırız, sonra işlemleri yaptırır, sonra da Application.EnableEvents = True diyerek eventleri tekrar devreye sokarız. Tabi olur da kodumuzda bir hata oluşur da sona gelmeden durursa Eventler askıda kalabilir, bu yüzden bir hata yönetimi bloğu yazıp eventleri burada da tekrar aktive etmeliyiz.
                    
                                Private Sub Worksheet_Change(ByVal Target As Range)
                                On Error GoTo hata
                                    Application.EnableEvents = False
                                    'tetiklemeye neden olabilecek işlemler
                                    Application.EnableEvents = True
                                    Exit Sub
                                hata:
                                    Application.EnableEvents = True
                                End Sub
                                    
                                    
                Workbook'un Sheet Eventleri
Workbook eventleri workbookla ilgili bir eylem gerçekleşince devreye giriyordu, Worksheet eventleri de sayfayla ilgili bir eylem gerçekleşince. Bir de ikisinin karışımı gibi olan ama aslında bir Workbook eventi olan event grubu var.
Bunların bir listesi aşağıdaki gibi olup, belli bir sayfada değil de herhangi bir sayfada bir eylem gerçekleştiğinde tetiklenirler.
                Mesela aşağıdaki kod ile hangi sayfa seçilirse onun adı bize MsgBox ile gösterilir.
                    
                        Private Sub Workbook_SheetActivate(ByVal Sh As Object)
                           MsgBox Sh.Name
                        End Sub
                            
                            
                Farklı Kullanıcılarda Eventlerin Tetiklendiğinden Emin Olmak
Giriş bölümündeki Güvenlik ayarları bölümünü okumadıysanız öncelikle orayı okumanızı öneririm. Orada belirtildiği gibi makro ayarları Disable All şeklindeyse sonuçta bir makro olan Event Prosedürleriniz de devreye girmez.
Örnek Senaryo
Hazırladığınız bir dosyanın anlamlı olabilmesi için eventlerin çalışması gerekmekte olsun. Ancak kullanıcının makro ayarları Disabled ise kullanıcı dosyadan istenen verimi alamayacaktır, üstelik sizin istemediğiniz şekilde yetkisi olmayan görüntülemeler bile yapabilecektir (Farklı şubenin rakamlarını görmek gibi).
Bunu engellemek için benim geliştirdiğim yöntem aşağıdaki gibidir (Daha iyi veya daha kötü yöntemler var olabilir, ben araştırdığımda hiçbir şeyle karşılaşmadığım için kendi çözümümü böyle geliştirmiştim).
Çalışmanın tam üstüne denk gelecek şekilde bir düğme koyarım ve bu düğme için bir kod yazarım. Eğer makrolar enable ise düğme kaybolur, makrolar disabled ise aşağıdaki gibi bir hata alır.
                Düğmeyi Silme veya Taşıma
Ayrıca düğmeyi silmesin veya başka bir yere taşımasın diye sayfaya protection da koymamız gerekiyor. Makro sırasında dosyayı gizlerken geçici olarak kaldırıyor, gizledikten sonra tekrar koyuyoruz, ki protection'ı başka amaçlar için de kullanabilelim. Buna ait bir örneği Çeşitli Örnekler bölümünde 2. örnekte bulabilirsiniz.
                Düğmenin Click Eventi
Düğmenin Click eventi ise şöyledir:
                    
                        Sub Button1_Click()
                           Sheets(1).Unprotect Password:="1234"
                           ActiveSheet.Shapes("Button 1").Visible = msoFalse 'düğmeyi gizler
                           Sheets(1).Protect Password:="1234"
                        End Sub
                            
                            
                Kısıtlar Uygulamak
Sayfanın Yazdırılmasını Engellemek
Diyelim ki kullanıcıların belli sayfaları basmasını istemiyorsunuz. Aşağıdaki kodu ilgili dosyanın Workbook_BeforePrint eventine yazmanız gerekir.
                    
                            Private Sub Workbook_BeforePrint(Cancel As Boolean) 
                                For Each s In ActiveWorkbook.SelectedSheets
                                    If s.Name = "Ham Data" Then
                                        MsgBox ("Bu sayfayı basamazsınız!!!")
                                        Cancel = True 
                                    End If
                                Next
                            End Sub
                                
                                
                Workbook içinde hiçbir sayfanın bastırılmasını istemiyorsanız bu sefer hiç sayfa kontrolü yapmadan doğrudan MsgBox ve Cancel=True satırları yeterli olacaktır.
Gördüğünüz gibi bu işlemi bir worksheet eventi ile değil workbook eventi ile yapıyoruz.
Sayfada Cut/Copy Engellemek
Bu işlemin tüm dosya bazında yapılmasıyla ilgili örnek şurada olup, sayfa bazında yapmak için Worksheet_Activate ve Worksheet_Deactivate olaylarında kullanılması yeterlidir.
Mevduat Fiyatlama Hesap Makinası (Animasyonlu)
Bu örnekte, 4 parametreden oluşan bir denklemin herhangi 3'ü bilinirken diğer 4.sünün tespit edilmesine yönelik bir kod yazacağız. Klasik Excel yöntemiyle yapmak istediğinizde 4 ayrı çalışma yapmanız gerekirken VBA ile tek bir format ile tüm senaryoları ele alabileceğiz.
Bunun için aşağıdaki gibi bir form hazırladım. Dosyanın kendisine buradan ulaşabilirsiniz.
Çalışmaya Ait Kodlar
Sheet1 Modülü:
                    
                                Private Sub Worksheet_Change(ByVal Target As Range)
                                On Error GoTo çıkış
                                    Application.EnableEvents = False
                                    If Target = [stopaj] And IsEmpty(Target) Then
                                        Target.Value = "0,15 (TL, 6 aya kadar)"
                                    End If
                                    If Not Intersect(Target, [alan]) Is Nothing Then
                                        ActiveSheet.Unprotect 1234
                                        Call temizlik([alan])
                                        If [alan].Cells.SpecialCells(xlCellTypeBlanks).Count = 1 Then
                                            [alan].SpecialCells(xlCellTypeBlanks).Select
                                            Select Case ActiveCell
                                                Case [anapara]
                                                    ActiveCell.Formula = "=365*NetGetiri/(Vade*Faiz*(1-value(left(stopaj,4))))"
                                                Case [Faiz]
                                                    ActiveCell.Formula = "=365*NetGetiri/(Vade*Anapara*(1-value(left(stopaj,4))))"
                                                Case [Vade]
                                                    ActiveCell.Formula = "=365*NetGetiri/(Anapara*Faiz*(1-value(left(stopaj,4))))"
                                                Case [NetGetiri]
                                                    ActiveCell.Formula = "=Anapara*Faiz*Vade*(1-value(left(stopaj,4)))/365"
                                                Case Else
                                                    MsgBox "Böyle bir seçenek bulunmamaktadır"
                                            End Select
                                            ActiveCell.Font.Color = vbRed
                                            [uyarı].Value = ""
                                            Call Fontsizedeğiş(24, 20)
                                            Call alancopypaste
                                        End If
                                    End If
                                    Application.EnableEvents = True
                                    ActiveSheet.Protect 1234
                                    Exit Sub
                                çıkış:
                                If Err.Description = "No cells were found." Then
                                    [uyarı].Select
                                    ActiveCell.Value = "Lütfen hangi alanın yeniden hesaplanmasını istiyorsanız onu silin."
                                    Call Fontsizedeğiş(14, 10)
                                End If
                                Application.EnableEvents = True
                                ActiveSheet.Protect 1234
                                End Sub
                                    
                                    
                Standart Modül İçeriği
Bunda sleep metodu kullanıldığı için aşağıdaki özel kod en başa eklenmiştir.
                    
                        #If VBA7 Then
                        Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr) '64 Bit Sistemler için
                        #Else
                        Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) '32 Bit Sistemler için
                        #End If
                        Sub beklet(sure As Integer)
                            Sleep sure
                        End Sub
                            
                            
                Temizlik Butonunun Kodu
                    
                        Sub Button1_Click()
                          Range("alan").ClearContents
                          ActiveSheet.Unprotect 1234
                          [uyarı].Value = ""
                          ActiveSheet.Protect 1234
                        End Sub
                            
                            
                Bu hesap makinesinin kullanımı şöyledir:
Kullanıcı diyelim ki, bilinen olarak anapara, faiz ve vadeyi girip müşterinin net kazancını hesaplamak istiyor olsun. Bu üçünü yazınca net kazanç bilgisi otomatik hesaplanır. Bu hesaplamanın sonucu da bir döngü ile font hacminin önce büyüyüp sonra da küçülmesiyle animasyonlu bir şekilde gösterilir. Kullanıcı diyelim ki sonradan kazanç bilgisini de manuel değiştirdi, o zaman tüm alanlar dolu olacağı için kodumuz neye göre hesaplama yapacağını bilmez ve kullancıya "Lütfen hangi alanın yeniden hesaplanmasını istiyorsanız onu silin" mesajını yine animasyonlu bi şekilde gösterir. Çalışma mantığı ise şöyledir: Sayfada belli name'ler tanımlanmış durumda. Makronun tetiklenmesi için "alan" isimli namede bir hücrenin değişmesi beklenmekte. Tabi değişklikler sonucunda başka tetkilenme olmasın diye eventler geçici olarak baskılanmakta. Değişlik sonucunda alan isimli name'de boş hücre sayısının 1 olup olmadığına bakılmaktadır([alan].Cells.SpecialCells(xlCellTypeBlanks).Count = 1 kodu ile). Böylece bu boş olana uygun formül yazılmakta ve sonuç copy-paste yapılmaktadır. Alan isimli namede 2 hücre doluyken 3.sünün doldurulması durumunda da, 4 hücre doluyken birinin silinmesi durumunda kontrol sonucu 1 dönecek ve esas işi yapan kod bloğu çalışmış olacaktır.