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.

post-thumb

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.

post-thumb

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.

post-thumb
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.

post-thumb
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.