PivotTable, Slicer ve Chart nesneleriyle çalışmak
Giriş
Pivot(Özet) tablolarla çalışmaya başlamadan önce Excel tarafında konuya hakim olmak önemlidir. Buraya kadar geldiğinize göre konuyu bildiğinizi varsayıyorum ancak bilgilerinizi tazlemek isterseniz sizi şöyle alayım.
Baştan belirtmekte fayda var; Pivot tablo(bundan sonra PT diye anılacak) konusu VBA tarafında biraz alengirli bir hal alır, zira Excel önyüzde görmediğimiz bir nesneyle çalışmamız gerekmektedir. Bunun adı PivotCachedir. Onun dışında aslında recorder ile üreteceğiniz birkaç kodu elden geçirmekle kolaylıkla öğrenebileceğiniz bir konudur. O yüzden ben bu sayfada gereksiz yere recorderla elde edebileceğiniz detayları koymamaya karar verdim. Onun yerine refresh işlemleri, birkaç önemli işlem, birkaç event ve sonrasında da slicerlarla olan ilişkisini anlatmak istiyorum. Tabiki otomasyon projelerinizde data üretimi sonrasında kodun bir PT da üretmesi gerekiyorsa PT yaratmayı bilmeniz lazım ama dediğim gibi bunu recorderla çok rahat görebilirsiniz, gereksiz fazlalıkları atmak size kalıyor.
Size tavsiyem öncelikle birkaç işlemi(pivot oluşturma, filtre koyma, filtre kaldırma, repeat all items yapma v.s) recorder ile yapın ve kodu inceleyin. Yine de "ben recorderla uğraşamam" diyorsanız MSDN'den detay bilgi edinebilirsiniz. İngilizceniz yoksa ve pivot detaylarını yine de yazmamı isterseniz bu sayfanın altına yorum bırakın, uygun bir zamanda bunları da eklerim, ama dediğim gibi gerçekten burada anlatacaklarım dışında nerdeyse herşeyi recorderla kolaylıkla elde edebilirsiniz ve bunlarla ilgili olarak dikkat çekecek bir husus bulunmamaktadır.
Veri Seti
Üzerinde çalışacağımız data seti, Excel konularındaki örnek data setidir, buradan indirebilirsiniz.
Mesela aşağıda, bu dosyanın "calculatedlar" sayfasındaki dataya uyguladığımız PT'ın recorderla elde edilmiş kodu bulunuyor. Tabi bu kodu şu an çalıştırsanız sizde çalışmaz zira sayfa isimleri değişmiş olacak. Onun yerine kendiniz yeni bir recorder çalıştırıp görün derim.
Sub Macro1()
'
' Macro1 Macro
'
'
Sheets.Add
ActiveWorkbook.PivotCaches.Create(SourceType:=xlDatabase, SourceData:= _
"calculatedlar!R1C1:R175C6", Version:=6).CreatePivotTable TableDestination _
:="Sheet1!R3C1", TableName:="PivotTable1", DefaultVersion:=6
Sheets("Sheet1").Select
Cells(3, 1).Select
With ActiveSheet.PivotTables("PivotTable1")
.ColumnGrand = True
.HasAutoFormat = True
.DisplayErrorString = False
.DisplayNullString = True
.EnableDrilldown = True
.ErrorString = ""
.MergeLabels = False
.NullString = ""
.PageFieldOrder = 2
.PageFieldWrapCount = 0
.PreserveFormatting = True
.RowGrand = True
.SaveData = True
.PrintTitles = False
.RepeatItemsOnEachPrintedPage = True
.TotalsAnnotation = False
.CompactRowIndent = 1
.InGridDropZones = False
.DisplayFieldCaptions = True
.DisplayMemberPropertyTooltips = False
.DisplayContextTooltips = True
.ShowDrillIndicators = True
.PrintDrillIndicators = False
.AllowMultipleFilters = False
.SortUsingCustomLists = True
.FieldListSortAscending = False
.ShowValuesRow = False
.CalculatedMembersInFilters = False
.RowAxisLayout xlCompactRow
End With
With ActiveSheet.PivotTables("PivotTable1").PivotCache
.RefreshOnFileOpen = False
.MissingItemsLimit = xlMissingItemsDefault
End With
ActiveSheet.PivotTables("PivotTable1").RepeatAllLabels xlRepeatLabels
With ActiveSheet.PivotTables("PivotTable1").PivotFields("KANAL")
.Orientation = xlRowField
.Position = 1
End With
With ActiveSheet.PivotTables("PivotTable1").PivotFields("URUN")
.Orientation = xlColumnField
.Position = 1
End With
ActiveSheet.PivotTables("PivotTable1").AddDataField ActiveSheet.PivotTables( _
"PivotTable1").PivotFields("TUTAR"), "Sum of TUTAR", xlSum
End Sub
Yukarıda da bahsettiğim gibi recorder kodunda gereksiz bir sürü kısım var, zira artık bildiğiniz üzere recorder bir nesneye ait tüm propertylere değer atıyor.
PivotCache nesnesi
Yukarıdaki koddan göreceğiniz üzere bir pivot table yaratılmadan önce PivotCache(Bundan sonra PC olarak anılacak) nesnesi yaratılıyor. PC, veri kaynağının bellekte özetlenmiş halidir . Her PT'ın arka planda dayandığı bir PC vardır. Neden böyle bir şeye ihtiyaç var diye sorabilirsiniz. PT'lar bence ve birçok kişi tarafından Excel'in en güçlü aracı olarak kabul edilir. Bunların gücü, birçok gruplama işini çok hızlı yapmasında yatıyor. İşte bu hız, bellekteki PC tarafından sağlanır, aksi halde her defasında veri kaynağına gitmek gerekseydi ki bu da hızın düşüşü anlamına gelecekti. İşte bu yüzden öncelikle PC yaratılır.
Excel 2007'den önce bir veri kaynağından üretilen her PT'ın ayrı bir PC'si olurdu, bu da bellek tüketimi için çok iyi olmazdı ve dosya boyutunu da gereksiz yere şişirirdi. Excel 2007 ile birlikte aynı kaynaktan üretilen PT'ların PC'si ortaklaştırıldı. Bununla beraber bazı durumlarda bunların PC'sini ayrıştırmak gerekebilir, bunu nasıl yapacağımızı aşağıda göreceğiz.
Rakamlarla örnek vermek gerekirse; 2007 öncesinde kaynak datanız 1 MB büyüklüğündeyse ve 3 PT yarattıysanız dosya boyutu 3 MB daha artıyordu. 2007 ve sonrasında sadece 1 MB artıyor. Bu 1 MB'dan da tasarruf etmek için "save source data" yapabilirsiniz.
Aşağıda benim üzerinde çalıştığım birkaç dosyanın boyutunu görebilirsiniz(Edit:Dosya adlarındaki baştaki "pivot"u görmezden gelin):
Daha detaylı analiz ettiğimizde;
- Boş bir dosya 9 KB
- Sadece kaynak datayı içerdiğinde 57 KB. Demekki datanın getirdiği net yük: 57-9=48 KB
- Bu dosyada tek bir PT yaratınca boyut 79'a çıkıyor. Demekki, cache + pivot sayfasındaki özet data=79-48=31 KB(Gerçi özet alanında hiç veri koymasam da boyut yine 79, demek özet veri ihmal edilebilir)
- 2. PT yaratınca boyut 79’dan 93’e çıkıyor. Bu da cache’den 14 KB. Bu da pivot alanı için 7 KB bir alan tanıyor demek.
- 3. PT yaratınca boyut 93'ten 107'ye çıkıyor. Yani son PT'ya 14 KB daha tanımış oluyorum. Bu da son PT'nın cache ile ilgili olarak 14 KB'lık bir alana sahip olduğu anlamına geliyor.
- Hepsi için toplamda 48+31+14+14=107
Yukarıda gördüğünüz üzere aslında data kaynağına gidip her seferinde veri almak yerine, bellekte bir PC tutarak, bunun üzerinden özetleme işlemlerini gerçekleştiriyor.
Sonuç olarak; Her PT için bir PC oluşturmak ve bunları eklemek gerekli. İşte bu yüzden kod içerisinde yukarıdaki gibi "PivotCaches.Create" ile PC yaratmak durumundayız.
Pivot Tabloların Oluşturulması
PT oluşturmak için yukarıda gördüğümüz kodu kullanabiliriz. Burada dikkat edilmesi gereken husus PivotCaches.Create metodu. Bunun dışında bazı propertiesler üzerinden de işlem yapmamız gerekebilir. Aşağıdaki gibi bir kod ile PT yaratılabilir.
Sub PivotTableOlustur()
Dim pc As PivotCache
Dim pt As PivotTable
Dim ws As Worksheet
Dim wsData As Worksheet
Set ws = Worksheets.Add
Set wsData = Worksheets("calculatedlar")
Set pc = ActiveWorkbook.PivotCaches.Create( _
SourceType:=xlDatabase, _
SourceData:=wsData.Range("A1:F175"))
Set pt = pc.CreatePivotTable( _
TableDestination:=ws.Range("A3"), _
TableName:="PivotTable1")
End Sub
Yukarıdaki kodda dikkat edilmesi gereken nokta "SourceData" alanıdır. Burada yine belirtilen veri aralığına dikkat etmek önemlidir. Bu aralık dışında PT yaratmaya çalışırsanız hata alırsınız. O yüzden aralık kontrolü yapmak iyi bir yöntem olacaktır. Bu noktada birkaç tane error handling metodu kullanılabilir. Bunları aşağıda görmeye çalışacağız.
Aralık Kontrolü
Pivot tabloyu oluştururken belirli bir aralığın kontrolünü sağlamak gerekir. Bunun için aşağıdaki yöntem kullanılabilir:
Sub PivotTableOlustur()
On Error GoTo Hata
Dim pc As PivotCache
Dim pt As PivotTable
Dim ws As Worksheet
Dim wsData As Worksheet
Dim aralik As Range
Set ws = Worksheets.Add
Set wsData = Worksheets("calculatedlar")
Set aralik = wsData.Range("A1:F175")
If Application.WorksheetFunction.CountA(aralik) = 0 Then
MsgBox "Veri kaynağı boş. Lütfen kontrol ediniz."
Exit Sub
End If
Set pc = ActiveWorkbook.PivotCaches.Create( _
SourceType:=xlDatabase, _
SourceData:=aralik)
Set pt = pc.CreatePivotTable( _
TableDestination:=ws.Range("A3"), _
TableName:="PivotTable1")
Exit Sub
Hata:
MsgBox "Hata oluştu: " & Err.Description
End Sub
Yukarıdaki kodda veri aralığı kontrol ediliyor. Application.WorksheetFunction.CountA fonksiyonu ile aralıkta veri olup olmadığı kontrol ediliyor. Eğer aralık boşsa kullanıcıya bir mesaj veriliyor ve kod çalışmayı durduruyor. Eğer bir hata oluşursa, On Error ifadesi ile hata mesajı gösteriliyor.
Pivot Tabloların Yeniden Oluşturulması
Eğer tabloyu yeniden oluşturmak gerekirse, PT üzerinde PivotTable.ClearTable metodu ile tablo temizlenebilir. Daha sonra yukarıdaki gibi yeniden oluşturulabilir.
Sub PivotTableTemizle()
Dim ws As Worksheet
Dim pt As PivotTable
Set ws = Worksheets("Sheet1")
Set pt = ws.PivotTables("PivotTable1")
pt.ClearTable
End Sub
Yukarıdaki kodda belirtilen tablo temizleniyor. Eğer tablo tamamen silinmek istenirse PivotTable.Delete metodu kullanılabilir. Bunu yaparken dikkat edilmesi gereken bir husus, tablo silindikten sonra o tabloya referans veren herhangi bir kod varsa, çalışmayacaktır.
PT'daki bir Field'da filtreli elemanları elde etme
Bir field'da seçili olan elemanları elde etmek VisibleItems özelliği ile oldukça kolaydır. Aşağıda, parametre olarak sayfa, PT adı ve field adını alan bir fonksiyonumuz ve bu fonksiyonu çağıran bir test prosedürümüz bulunuyor. Bu arada fonksiyonumuzun dönüş tipi PivotItems olup döngüsel olarak içinde dönerek elemanları yazdırıyoruz. (Dönüş tipini collection olarak da belirleyebilirdik, ancak bu sefer elemanları tek tek geçici bir Collection nesnesine atmak gerekirdi. Bu örneği Slicerdaki seçili elemanları getirme örneğinde yapacağız.)
Function Pivotun1FieldındakileriGetir(ws As Worksheet, ptName As String, ptfield As String) As PivotItems
'tek bir field için basit örnek
Dim pt As PivotTable
Dim pf As PivotField
Set pt = ws.PivotTables(ptName)
Set pf = pt.PivotFields(ptfield)
Set Pivotun1FieldındakileriGetir = pf.VisibleItems
End Function
Sub test_pivot_seçili_elemanlar()
'Örnek olarak, Tarihsl Datanın Pivot ve Slcr sayfasındayken çalıştırabilirsiniz. Pivotta bikaç bölge filtreli olsun
Dim kimlerseçili As PivotItems
Set kimlerseçili = Pivotun1FieldındakileriGetir(ActiveSheet, "ptBolgekodUrun", "Bölge Kodu")
For Each vi In kimlerseçili
Debug.Print vi.Name
Next vi
End Sub
PT'daki birden fazla fieldda seçili olan elemanları elde etme
Bu sefer, hangi fieldda hangi değerlere filtre konmuş, bunları elde edeceğiz. Tabi şimdi işimiz biraz daha zor, o yüzden farklı bir bakış açısıyla yaklaşmak lazım. Aslında bu paragrafın ilk cümlesi bize bir dictionary'nin gerekliliğini gösteriyor: Hangi fieldda ne seçili, yani bildiğimiz Key ve Value ikilisi. O yüzden fonksiyonumuzun dönüş tipini bu sefer Dictionary olarak tanımlayacağız.
Tekil field örneğinden farklı olarak bu sefer Field isimlerini bir collection'a atayıp gönderiyoruz. Fonksiyonumuzdan gelen bilgileri yazdırırken de "birleştir" isimli yardımcı bir fonksiyon ile her fielddaki itemları yan yana birleştirip öyle yazdırıyoruz.
Function PivotunÇokluFieldındakileriGetir(ws As Worksheet, ptName As String, ptfields As Collection) As Dictionary
Dim pt As PivotTable
Dim pf As PivotField
Dim dict As New Dictionary
Set pt = ws.PivotTables(ptName)
For Each p In ptfields
Set pf = pt.PivotFields(p)
dict.Add p, pf.VisibleItems
Next p
Set PivotunÇokluFieldındakileriGetir = dict
End Function
Sub test_pivot_seçili_elemanlar_çoklufield()
'Örnek olarak, Tarihsl Datanın Pivot ve Slcr sayfasındayken çalıştırabilirsiniz. Pivotta bikaç bölge ve ürün filtreli olsun
Dim mydict As Dictionary
Dim alanlar As New Collection
alanlar.Add "Bölge Kodu"
alanlar.Add "Ürün"
Set mydict = PivotunÇokluFieldındakileriGetir(ActiveSheet, "ptBolgekodUrun", alanlar)
For Each k In mydict.Keys
Debug.Print k, birleştir(mydict(k))
Next k
End Sub
Function birleştir(pis As PivotItems)
For Each pi In pis
geçici = geçici & "-" & pi
Next pi
birleştir = Mid(geçici, 2)
End Function
Aşağıdaki örnek üzerinden çalıştırdığımızda dönen sonuç da şöyle olmaktadır.
Bölge Kodu Başkent 1-Ege 2 Ürün ÜÜrün Ürün4-Ürün3-Ürün2
Bir fieldda filtre var mı kontrolü
Burada da AllItemsVisible şeklinde boolean tipli bir property ile sorgulamamızı yapabiliyoruz.
Function pivot_field_filtrelimi(ws As Worksheet, ptName As String, ptfield As String) As Boolean
pivot_field_filtrelimi = Not ws.PivotTables(ptName).PivotFields(ptfield).AllItemsVisible
End Function
Sub test_pivot_field_filtrelimi()
'data sayfasında çalıştırın
Debug.Print pivot_field_filtrelimi(ActiveSheet, "PivotTable1", "Bölge")
Debug.Print pivot_field_filtrelimi(ActiveSheet, "PivotTable1", "Şube Adı")
End Sub
Bir fieldda 1 adet filtre kriteri uygulamak
Tek bir field üzerinde 1 adet filtre uygulamanın en pratik yolu PivotFilters nesnesini kullanmaktır. Syntaxı aşağıdaki örnekten görüldüğü üzere oldukça pratiktir. Type argümanının alacağı değerlerin intellisense ile çıkmasını isterseniz PivotFilters nesnesini en başta Dim ile tanımlayıp öyle ilerleyebilirsiniz. Aşağıdaki örnekte spesifik bir tarih ile iki tarih arası değer uygulama örneği bulunmaktadır. Bunların alabileceği değerlerin bir listesini şu sayfadan görebileceğiniz gibi, makro recorder ile denemeler sırasında da elde edebilirsiniz.
Sub pivotta_filtrekoy()
'Tek fieldda tek değer filtreleme
Sheets("Tarihsl Datanın Pivot ve Slcr").Select
Application.EnableEvents = False 'ilgili sayfadaki eventler tetiklenmesin
ActiveSheet.PivotTables("ptAy").ClearAllFilters
ActiveSheet.PivotTables("ptAy").PivotFields("Ay").PivotFilters.Add2 _
Type:=xlSpecificDate, Value1:="31.01.2016"
ActiveSheet.PivotTables("ptAy").ClearAllFilters
ActiveSheet.PivotTables("ptAy").PivotFields("Ay").PivotFilters.Add2 _
Type:=xlDateBetween, Value1:="31.01.2016", Value2:="31.05.2016"
Application.EnableEvents = True
End Sub
Diğer PivotFilter örnekleri aşağıdaki gibi olabilir.
ActiveCell.PivotField.PivotFilters.Add FilterType := xlThisWeek
ActiveCell.PivotField.PivotFilters.Add FilterType := xlTopCount DataField := MyPivotField2 Value1 := 10
ActiveCell.PivotField.PivotFilters.Add FilterType := xlCaptionIsNotBetween Value1 := "A" Value2 := "G"
ActiveCell.PivotField.PivotFilters.Add FilterType := xlValueIsGreaterThanOrEqualTo DataField := MyPivotField2 Value1 := 10000
1 fieldda 1 filtre uygulamanın alternatifi
1 fielda sadece 1 adet kriter gireceksek PivotFilter gayet uygun bir yöntemdir. Örnek olarak sadece "A" değerini filtrelemek için aşağıdaki kodu uygulayabilirsiniz.
Sub pivotta_1_filtredene()
'Tek fieldda tek değer filtreleme
ActiveSheet.PivotTables("ptBolgekodUrun").PivotFields("Bölge Kodu").ClearAllFilters
ActiveSheet.PivotTables("ptBolgekodUrun").PivotFields("Bölge Kodu").CurrentPage = "A"
End Sub
Giriş
Slicerlarda da yine PT'larda olduğu gibi bir Slicercache(SC) ve bu cacheden beslenen Slicerın kendisi vardır. Burada da bi recorder çalıştırıp bakalım. Biz tabi PT'da olduğu gibi Slicer eklemeyi de genelde Excel ön yüzde manuel olarak yapmış olacağız. VBA ile daha ziyade Slicerların elemanlarına erişim, bunların seçimini değiştirme, refreshleme v.s gibi işlemler yapacağız ama yine de nasıl bir kod oluşuyor, ona bi bakalım.
Şimdi ilk olarak Data sayfasındaki Table'a(ListObject) Slicer uygulandığında nasıl bir kod oluştuğuna bakalım. Pivot üzerine uygulanan Slicer ile yine pivot üzerine uygulanan Timeline tipli Slicer kodları örnek dosya içinde bulunmaktadır.
Sub Macro2()'
' Macro2 Table Slicerı
'
'
ActiveWorkbook.SlicerCaches.Add2(ActiveSheet.ListObjects("Table7"), "Bölge"). _
Slicers.Add ActiveSheet, , "Bölge", "Bölge", 40.5, 414, 144, 198.75
ActiveWorkbook.SlicerCaches.Add2(ActiveSheet.ListObjects("Table7"), "Ürün Adı") _
.Slicers.Add ActiveSheet, , "Ürün Adı", "Ürün Adı", 78, 451.5, 144, 198.75
ActiveSheet.Shapes.Range(Array("Ürün Adı")).Select
With ActiveWorkbook.SlicerCaches("Slicer_Bölge")
.SlicerItems("Başkent 1").Selected = True
.SlicerItems("Başkent 2").Selected = False
.SlicerItems("Batı Karadeniz").Selected = False
.SlicerItems("Doğru Karadeniz").Selected = False
.SlicerItems("Ege 1").Selected = False
.SlicerItems("Ege 2").Selected = False
.SlicerItems("Güney Akdeniz").Selected = False
.SlicerItems("Güneydoğu Anadolu").Selected = False
.SlicerItems("İç Anadolu").Selected = False
.SlicerItems("İstanbul 1").Selected = False
.SlicerItems("İstanbul 2").Selected = False
.SlicerItems("İstanbul 3").Selected = False
.SlicerItems("Marmara").Selected = False
End With
ActiveWorkbook.SlicerCaches("Slicer_Bölge").ClearManualFilter
With ActiveWorkbook.SlicerCaches("Slicer_Bölge")
.SlicerItems("Başkent 2").Selected = True
.SlicerItems("Başkent 1").Selected = False
.SlicerItems("Batı Karadeniz").Selected = False
.SlicerItems("Doğru Karadeniz").Selected = False
.SlicerItems("Ege 1").Selected = False
.SlicerItems("Ege 2").Selected = False
.SlicerItems("Güney Akdeniz").Selected = False
.SlicerItems("Güneydoğu Anadolu").Selected = False
.SlicerItems("İç Anadolu").Selected = False
.SlicerItems("İstanbul 1").Selected = False
.SlicerItems("İstanbul 2").Selected = False
.SlicerItems("İstanbul 3").Selected = False
.SlicerItems("Marmara").Selected = False
End With
With ActiveWorkbook.SlicerCaches("Slicer_Ürün_Adı")
.SlicerItems("Ürün1").Selected = True
.SlicerItems("Ürün2").Selected = False
.SlicerItems("Ürün3").Selected = False
.SlicerItems("Ürün4").Selected = False
End With
With ActiveWorkbook.SlicerCaches("Slicer_Bölge")
.SlicerItems("Doğru Karadeniz").Selected = True
.SlicerItems("Başkent 1").Selected = False
.SlicerItems("Başkent 2").Selected = False
.SlicerItems("Batı Karadeniz").Selected = False
.SlicerItems("Ege 1").Selected = False
.SlicerItems("Ege 2").Selected = False
.SlicerItems("Güney Akdeniz").Selected = False
.SlicerItems("Güneydoğu Anadolu").Selected = False
.SlicerItems("İç Anadolu").Selected = False
.SlicerItems("İstanbul 1").Selected = False
.SlicerItems("İstanbul 2").Selected = False
.SlicerItems("İstanbul 3").Selected = False
.SlicerItems("Marmara").Selected = False
End With
With ActiveWorkbook.SlicerCaches("Slicer_Bölge")
.SlicerItems("Başkent 2").Selected = True
.SlicerItems("Doğru Karadeniz").Selected = True
.SlicerItems("Başkent 1").Selected = False
.SlicerItems("Batı Karadeniz").Selected = False
.SlicerItems("Ege 1").Selected = False
.SlicerItems("Ege 2").Selected = False
.SlicerItems("Güney Akdeniz").Selected = False
.SlicerItems("Güneydoğu Anadolu").Selected = False
.SlicerItems("İç Anadolu").Selected = False
.SlicerItems("İstanbul 1").Selected = False
.SlicerItems("İstanbul 2").Selected = False
.SlicerItems("İstanbul 3").Selected = False
.SlicerItems("Marmara").Selected = False
End With
ActiveSheet.Shapes.Range(Array("Bölge")).Select
ActiveWorkbook.SlicerCaches("Slicer_Bölge").Slicers("Bölge").Name = "BölgeSlc"
With ActiveWorkbook.SlicerCaches("Slicer_Bölge").Slicers("BölgeSlc")
.Caption = "Bölge"
.DisplayHeader = True
.SlicerCache.CrossFilterType = xlSlicerCrossFilterShowItemsWithDataAtTop
.SlicerCache.SortItems = xlSlicerSortAscending
.SlicerCache.SortUsingCustomLists = True
End With
End Sub
Görüldüğü üzere sadece bir iki adet işaretleme yapsam bile Slicerdaki diğer seçenekler için de otomatikman False değerini üretti. Manuel yazarken biz sadece istediklerimizi True yazarız, diğerleri mevcut değerleriyle kalır.
Mantıken, bir Slicerda belirli bir anda en az bir eleman seçili olmalıdır. Bu eleman seçiliyken başka bir elamanı da seçmek istersek, sadece onu seçen kodu yazmak yeterli. Ör: Slicerda A,B,C,D,E değerleri var olsun. Şuan sadece A seçiliyse, diğerlerinin Selected değeri False'tur. Biz ilaveten B'yi de seçmek istersek, B.Selected=True yazmak yeterli, ama sadece B seçili olsun istersek, A'ya da false atamak gerekir. Kendi bölge örneğimiz üzerinden gidecek olursak:
Sub ilave_secim()
With ActiveWorkbook.SlicerCaches("Slicer_Bölge")
'cachelere ya indeks ile ya da "formula name" ile ulaşılır, name ile değil. Name Slicer'ın adıdır, cache'nin değil.
.SlicerItems("Ege 1").Selected = True
End With
End Sub
Sub tek_secim()
With ActiveWorkbook.SlicerCaches("Slicer_Bölge")
.SlicerItems("Ege 1").Selected = True
.SlicerItems("Doğru Karadeniz").Selected = False
End With
End Sub
Bu arada farkettiyseniz SCs collection'ına bir indeks verince elde ettiğim tekil bir SC üzerinden doğrudan Sliceritemlara ulaşabiliyoruz, yani arada Slicerı seçmeye gerek yok. Zira bunlarda, PT'lardan farklı olarak bir SC ile bir Slicer eşleşiyor gibi bir durum var, zaten aksi garip olurdu.
Yukarıda recorder kodu olarak bir Table Slicer'ı vermiştik. Pivot Slicerı oluşturunca da pek farklı bi durum yok, sadece parametlerde ufak bir iki değişiklik var. Bundan başka Table'larda olmayıp sadece PT'larda olan Timeline da eklenebiliyor, ki bunlar da bir tür Slicer olarak kabul görüyor.
Çeşitli propertyler
Şimdi küçük bir kod üzerinde birkaç özelliğe bakalım.
NOT: OLAP yani PowerPivot gibi bir kaynaktan besleneme durumunda kullanılabilen özellikler(level, visibleitemlist v.s) bu bölümlerin kapsamı dışındadır.
Az önce Timelineların da bir tür Slicer olduğunu söylemiştik, bunların property olarak sadece SlicerCacheTypeları farklı oluyror; Microsoft dokümantasyonunda bu type değeri için sadece 1 ve 2 değerleri var; 1 Slicer, 2 Timeline oluyor ama ben aşağıdaki sorguyu çalıştırdığımda 0 değerini de görüyorum. Zannımca bu 2013te Slicer özelliği gelen Table'lardan üretilen SC'leri ifade ediyor. Microsoftun bunu hala(bu sayfanın hazırlandığı tarih olan Haziran 2019'da) dokümante etmemiş olması ilginç.
'Global değişkenler
Dim sl As Slicer
Dim slc As SlicerCache
Dim sli As SlicerItem
Dim ws As Worksheet
Sub tüm_slicerlarda_dolaşma()
For Each slc In ActiveWorkbook.SlicerCaches
For Each sl In slc.Slicers
Debug.Print slc.SourceName, slc.Index, slc.SlicerCacheType, sl.Name, sl.Parent.Name, sl.Caption, slc.Name
Next sl
Next slc
End Sub
Çeşitli İşlemler
Seçili olan tek bir elemanı elde etme
Bu örnekte aktif olarak seçili olan elemanı elde edeceğiz.
Sub seçili_eleman()
'focuslu ise çalışır, aksi halde hata verir
Debug.Print ActiveWorkbook.SlicerCaches("Slicer_Bölge_Kodu").Slicers(1).ActiveItem.Name
End Sub
Seçili olan birçok elemanı elde etme
Şimdi de birden çok eleman varsa onları yazdıralım. Tabi burada örnekleri basit tutmak adına elemanları sadece yazdırıyoruz, ihtiyaca göre elemanlar bir collectiona, formdaki listboxa v.s atanabilir.
Sub seçili_elemanlar()
For Each sI In ActiveWorkbook.SlicerCaches("Slicer_Bölge_Kodu").VisibleSlicerItems
Debug.Print sI.Name
Next sI
End Sub
Public Function SlicerdaSeçilileriGetir(SlicerName As String) As Collection
Dim geçici As New Collection
Set slc = ActiveWorkbook.SlicerCaches(SlicerName)
For Each sli In slc.SlicerItems
If sli.Selected Then
geçici.Add sli.Name
End If
Next
Set SlicerdaSeçilileriGetir = geçici
End Function
Sub test_SlicerdaSeçilileriGetir()
Dim seçililer As Collection
Set seçililer = SlicerdaSeçilileriGetir("Slicer_Bölge_Kodu")
For Each s In seçililer
Debug.Print s
Next s
End Sub
SlicerdaSeçilileriGetir("Slicer_Bölge_Kodu")(1)
SlicerItems dönüş tipli yönteme göre ise kodumuz daha kısa. Neden bu değil de Collection'lı kullanılıyor gerçekten bilmiyorum, Timer ile performans kontrolü de yaptım, ikisi de aynı sürede çalışıyor gibi görünüyor. Her neyse siz ikisini de bilin.
Public Function SlicerdaSeçilileriGetirSlicerItemslıYöntem(SlicerName As String) As SlicerItems
Set SlicerdaSeçilileriGetirSlicerItemslıYöntem = ActiveWorkbook.SlicerCaches(SlicerName).VisibleSlicerItems
End Function
Sub test_SlicerdaSeçilileriGetirSlicerItemslıYöntem()
Dim kimlerseçili As SlicerItems
Set kimlerseçili = SlicerdaSeçilileriGetirSlicerItemslıYöntem("Slicer_Bölge_Kodu")
For Each vi In kimlerseçili
Debug.Print vi.Name
Next vi
End Sub
Tek bir elemanı seçme
Bu yöntemde PT'larda yaptığımız gibi elemanlar üzerinde tek tek dolaşarak ilerleyeceğiz. Parametrelerden son parametreye dikkat, orada elemanların formatı farklı olabilir diye bunu da belirtme seçeceği veriyorum. Eğer bu parametre prosedüre geçirilmezse eleman aynen alınırken, geçirilme durumunda format fonksiyonu ile şekillendirilip öyle seçilir.
Sub slicerda_tek_eleman_seç(cacheName As String, eleman As Variant, Optional elemanFormat)
Dim item As SlicerItem
Application.EnableEvents = False 'ilgili sayfadaki eventler tetiklenmesin
ActiveWorkbook.SlicerCaches(cacheName).ClearManualFilter 'Önce tüm filtreleri kaldıralım ki, hiç elamanın seçili olmaadığı mantıksız bi durum oluşmasın
For Each item In ActiveWorkbook.SlicerCaches(cacheName).SlicerItems
'pivot örneğinde pivotitem'ı formatlıyorduk, burada filtre değerini
If item.Name <> IIf(IsMissing(elemanFormat), eleman, Format(eleman, elemanFormat)) Then
item.Selected = False
End If
Next item
Application.EnableEvents = True
End Sub
Sub test_slicerda_tek_eleman_seç()
slicerda_tek_eleman_seç "Slicer_Ürün", "Ürün3"
slicerda_tek_eleman_seç "Slicer_Ay", "29.02.2016", "Long Date"
End Sub
Çok eleman seçme
Bu örnekte ise birden çok elemanı seçiyoruz. Birden çok değerin varlığını sorgulamak durumunda olduğum için Dictionary kullanıyorum.
Sub slicerda_çok_eleman_seç(cacheName As String, elemanlar As Dictionary, Optional elemanFormat)
Dim item As SlicerItem
Application.EnableEvents = False
If Not IsMissing(elemanFormat) Then
For Each e In elemanlar.Keys
elemanlar.Key(e) = Format(e, elemanFormat)
Next e
End If
ActiveWorkbook.SlicerCaches(cacheName).ClearManualFilter 'Önce tüm filtreleri kaldıralım ki, hiç elamanın seçili olmağı mantıksız bi durum oluşmasın
For Each item In ActiveWorkbook.SlicerCaches(cacheName).SlicerItems
If Not elemanlar.Exists(item.Value) Then
item.Selected = False
End If
Next item
Application.EnableEvents = True
End Sub
Sub test_slicerda_çok_eleman_seç()
Dim dict As New Scripting.Dictionary
dict.Add "Ürün2", 1
dict.Add "Ürün3", 2
slicerda_çok_eleman_seç "Slicer_Ürün", dict
'olmayan veri de gönderelim
dict.RemoveAll
dict.Add "29.02.2016", 1
dict.Add "25.02.2016", 2
dict.Add "31.03.2016", 3
slicerda_çok_eleman_seç "Slicer_Ay", dict, "Long Date"
End Sub
PT eventlerini kullanarak Slicer değişimlerini yakalamak
Yukarıda PT'lar kısmında PT eventlerini görmüştük. Şimdi bu eventleri Slicerlarla birlikte nasıl kullanırız, ona bakacağız.
PT değişimiyle seçili elemanı elde etmek
İster PT üzerinden manuel, ister kod ile, ister Slicer'ın kendisi aracılığı ile PT üzerinde bir seçim yapmış olalım. Bu seçilen elemanı bir hücrede, bir grafik başlığında veya kod içinde her hangibir yerde değişken olarak kullanmak isteyebiliriz.
"Tarihsel Datanın Pivot ve Slicerı" sayfasındaki PT'lardan ptBolgekodUrun isimli olan PT'da bir değişklik olduğunda F1 hücresine ilgil PT'da seçilen elemanı yazdırıyoruz. Birden çok eleman seçilirse ilki yazılır. Bizim kodumuz, özellikle bir eleman seçiminin beklendiği durumlarda anlamlıdır. Zira muhtemelen bunu küçük bir veri bloğunun başlığı halinde kullanacağızdır, "Ege 1 Bölgesi Kredi Rakamları" gibi.
Private Sub Worksheet_PivotTableChangeSync(ByVal Target As PivotTable)
Debug.Print "changesync oldu"
If Target.Name = "ptBolgekodUrun" Then
[F1].Value = SlicerdaSeçilileriGetir("Slicer_Bölge_Kodu")(1) & " Bölgesi"
End If
End Sub
Dashboard Yönetimi
PT kısmında birden fazla PT aynı PC'yi kullanıyorsa bunlardan birinde yapılan seçimi diğerlerini de otomatikman etkiler demiştik.
Peki ya elimizde PC'leri farklı olan PT'lar varsa ve biz birinde yapılan seçim hepsinde geçerli olsun istiyorsak?
Mesela aşağıdaki farklı renkle gösterilen bloklardan oluşan bir dashboardda farklı kaynaklardan gelen PT'lar bulunuyor olsun. Bunların her biri için Slicer yapmaya gerek yok. Biz biri için bir Slicer yaparız, bunda seçilen elemanı bi değişkende depolarız(yukarıda gördüğümüz yöntemlerle) ve diğer PT'larda da bu elemanı seçtiririz(yine yukarıdaki yöntemlerle). İkinci kısmı yaptırabilmek için bir haber almamız, yani bir eventin tetiklenmesi lazım, acaba hangi event; tabiki Worksheet_PivotTableChangeSync eventi.
Biz örneğimizi basit tutmak adına sadece iki PT kullacağız. Soldakinin adı ptSol, sağdakinin adı ptSağ. Slicer'ımız soldakine aittir.
Buna göre kodumuz aşağıdaki gibidir.
Private Sub Worksheet_PivotTableChangeSync(ByVal Target As PivotTable)
If Target.Name = "ptLeft" Then
If Not pivot_field_filtrelimi(ActiveSheet, "ptLeft", "Bölge") Then
secilen = SlicerdaSeçilileriGetir("Slicer_Bölge2")(1)
Pivotun1Fieldında1ElemanFiltrele ActiveSheet, "ptRight", "Bölge Kodu", secilen
Else
ActiveSheet.PivotTables("ptRight").ClearAllFilters
End If
End If
End Sub
Dikkat ettiyseniz ilk olarak eventin soldaki PT için tetiklenip tetiklenmediğine bakıyoruz. Sağdaki için manuel bir filtre yapılırsa kod ilk If bloğundan sonra çıkar. Bir diğer kontrolümüz de değişklik sonucunda PT'da filtre var mı yok mu kontrolü, varsa esas kodlarımızı işletiyoruz, filtre yoksa sağdaki için de ClearAllFilters yapıyoruz.
Aşağıda seçim sonrası tabloların genel durumu görünüyor.
Table eventlerini kullanmak
Sadece PT eventi değil bir Table eventini kullanarak da Slicerlarla işlem yapabiliriz. Mesela aşağıdaki kod ile, Tarihsel Data sayfasında varsayalım ki bir veritabanı bağlantısı var ve bu tablo belli zamanlarda(dosya ilk açıldığında, sayfa aktive olduğunda v.s) refresh oluyor olsun. Refresh sonucunda mevcut tarihten daha büyük bi tarih gelirse, Slicer da buna göre güncellensin, yani slicerda en büyük tarih seçili olsun.
Private Sub Worksheet_TableUpdate(ByVal Target As TableObject)
Dim maxTarih As String
maxTarih = Format(WorksheetFunction.Max(Worksheets("Tarihsel Data").Columns(3)), "dd.mm.yyyy")
Application.EnableEvents = False
slicerda_tek_eleman_seç "Slicer_Ay", maxTarih, "Long Date"
Application.EnableEvents = True
End Sub
Kullanıcılara PT Slicerını Table Slicer'ı gibi kullandırtmak
Bazen olur ki, Table'a ait Slicer üzerinde bir değişklik yaptığınızda bunun bir eventi tetiklemesini istersiniz. Ancak Table'ların filtrelenmesine yönelik böyle bir event yoktur. Sadece TableUpdate eventi var, o da tablo güncellendiğinde devreye girer.
Böyle bir durumda Excel'i kandırma yoluna gidebiliriz. İzleyeceğimiz yollardan biri şudur:
- Bu tabledan bir PT yaratmak
- Bu PT üzerine bir Slicer koymak
- PT' bölgesini gizlemek veya aşağı bir bölgeye almak, yeter ki görünmesin
- Slicer seçildiğinde PT'ın ChangeSync eventinin tetiklenmesini sağlamak
- PT eventi tetiklenince seçilen değeri alıp bu değeri Table üzerinde filtre olarak uygulamak(Table üzerinde doğrudan veya ona ait Slicerda dolaylı. Slicer kullanacaksak bunu genelde gizlemeyi tercih ederiz, iki slicer karışmasın diye, ama ben bu sayfadakini gizlemeden bıraktım)
- ve diğer işlerimizi yaptırmak
Buna ait basit bir örneği aşağıda bulabilirsiniz. Kod Data sayfasına aittir.
Private Sub Worksheet_PivotTableChangeSync(ByVal Target As PivotTable)
'Exit Sub 'Event örneklerini yaptığımızda bu satırı commentleyin, event dışındaki örneklerde açık kalsın
Application.EnableEvents = False
If ActiveWorkbook.SlicerCaches("Slicer_Bölge1").VisibleSlicerItems.Count = ActiveWorkbook.SlicerCaches("Slicer_Bölge1").SlicerItems.Count Then 'pivotun slicerı
ActiveWorkbook.SlicerCaches("Slicer_Bölge").ClearManualFilter 'Tableınki, görünmezdir, veya aşağılarda görünmez olsa da olur
GoTo çıkış
Else 'sadece tek seçim yapıldığı varsayılmıştır, çoklu seçimlerde işe yaramaz
secilen = SlicerdaSeçilileriGetir("Slicer_Bölge1")(1)
ActiveWorkbook.SlicerCaches("Slicer_Bölge").ClearManualFilter 'önce clear
slicerda_tek_eleman_seç "Slicer_Bölge", secilen
End If
çıkış:
Application.EnableEvents = True
End Sub
İzleyeceğimzi bir başka yol da Worksheet_Calculate eventinden faydalanmaktır. Her iki alternatifi de göreceğimiz daha anlamlı bir örneği Chart nesnesinden sonraki Örnek Uygulama bölümünde bulabilirsiniz.
Dashboard yönetimi kısmında makroların gücünü döngüler ve koşullu yapıları kullanarak çok güzel görmüştük. Grafiklerde de benzer kolaylıklardan yararlanacağız. Tabiki PT'larda olduğu gibi grafik yaratmayla pek uğraşmayacağız. Otomasyon projelerinde kodunuzun bir grafik yaratması gerekse bile bunu recorder ile ürettiğiniz kodu elden geçirerek yapabilirsiniz.
Mesela yukarıda Slicerlar bölümünde 6 farklı renkli gösterilmiş dashboardu düşünün, orada o bloklar PT'lardan oluşuyordu, şimdi varsayalım ki bunlar birer grafik. Bu sefer her birininin tipini, boyutunu, pozisyonunu, lejandını, renkleri v.s tek seferde değiştirmek istiyoruz, tabiki bunu döngülerle yapacağız.
Veya grafiğe konu olan datasetinde seçilen bir değere göre gragiği dinamik olarak güncellemek gerekecek. Bu gibi işleri yine makro ile başaracağız.
Nesneler hiyerarşisi
Şimdi öncelikle elimizdeki nesneler bi bakalım
- Chart nesnesi: Bunlar bağımsız grafik sayfaları olabileceği gibi normal bir sayfa(worksheet) içindeki gömülü grafikler de olabilir. Ancak bunlardan sadece bağımsız sayfa olarak bulunanlar Charts koleksiyonunun bir üyesidir, gömülü olanlar değil.
- ChartObject nesnesi: Worksheet içinde bulunan grafiklerin(Worksheet Chartlarının) konteynerıdır. Esasen bir grafik nesnesi değil, onun kabuğudur. Propertylerine falan bakıldığında grafikten ziyade Shape nesnesinin özelliklerini görürsünüz. ChartObjects koleksiyonunun bir üyesidir. Aynı zamanda Shapes koleksiyonunun da bir üyesidir. Hatta macro recorder ile grafik oluşturduğunuzda Shapes üzerinden oluşturur.
Yani aslında 3 tür nesnemiz var diyebilir.
- Worksheetlerdeki gömülü Chart: Chart. Parent nesnesi ChartObjecttir.
- Bağımsız Chart: Bu da Chart'tır. Parent nesnesi Workbook'tur.
- Worksheet Chartı için kontyner: ChartObject. Buna ulaşmadan içindeki Charta ulaşılamaz. Parent nesnesi Worksheet'tir.
Örnek birkaç erişim şekli şöyle olabilir:
'Worksheet içindeki bir Chart'a zincirleme olarak
Sheets("Sheet1").ChartObjects("Chart 1").Chart.ChartTitle.Text = "Yeni başlık"
'ya da önce konteynerı seçince aktif olan ActiveChart ile
Sheets("Sheet1").ChartObjects("Chart 1").Activate
ActiveChart.ChartTitle.Text = "Yeni başlık"
'bağımsız chart
Sheets("Chart 1").ChartTitle.Text = "Yeni başlık" 'sayfa adından
ActiveWorkbook.Sheets("Chart1").ChartTitle.Text = "Yeni başlık" 'nesne adından
Bu bilgiler ışığında chartlarda dolaşma şu şekilde olur.
Sub chartlarda_dolaşma()
Dim chto As ChartObject
Dim cht As Chart
'sayfa olan chartlarda dolaşalım
For Each cht In ActiveWorkbook.Charts
Debug.Print cht.Name, TypeName(cht), TypeName(cht.Parent)
Next cht
'gömülü chartlarda dolaşalım
For Each ws In ActiveWorkbook.Worksheets
For Each chto In ws.ChartObjects
Debug.Print chto.Name, chto.Index, chto.Parent.Name 'Chart'ın adı biraz anlamsız, ChartObject'in adını kullanalım
Next chto
Next ws
End Sub
Yaratım ve değişiklik
Yaratım ve özelliklerdeki değişiklikler için hep recorder kullanmanızı tavsiye ediyorum arkadaşlar. O yüzden buna ait kodları buraya koymuyorum.
Diğer hususlar
Grafik datasının olduğu bölgeyi grafikle kaplamak
Özellikle dashboard sayfalarında grafik datasının ortalıkta görünmesini istemeyiz. Gizli satır/kolon veya sayfanın aşağılarında bir yerlere koymak bir çözüm olabileceği gibi, data alanı çok küçük/büyük değilse bu bölgeyi grafiğin kendisiyle kapatmak bir çözüm olabilir.
Aşağıdaki kod ile bunu yapabiliriz. Kodda bir yardımcı fonksiyon kullanıyorum. Bu fonksiyonda F8 ile ilerleyerek "dizi" değişkenini incelerseniz adres1 ve adres2de ne yaptığımı anlayacaksınız.
Dashboard sayfanızdaki tüm grafiklerde döngüsel olarak çalışacak şekilde bu kodu modifiye edebilirsiniz.
Sub RangeiGrafikleKapla() Dim cht As ChartObject Dim grafikAlan As Range Set cht = ActiveChart.Parent Set grafikAlan = GrafikRange(cht.Chart) cht.Left = grafikAlan.Left cht.Width = grafikAlan.Width cht.Top = grafikAlan.Top cht.Height = grafikAlan.Height End Sub 'Yardımcı fonksiyonumuz Function GrafikRange(cht As Chart) As Range dizi = cht.SeriesCollection(1).Formula adres1 = Split(Replace(Split(dizi, ",")(1), cht.Parent.Parent.Name & "!", ""), ":")(0) adres2 = Split(Replace(Split(dizi, ",")(2), cht.Parent.Parent.Name & "!", ""), ":")(1) Set GrafikRange = Range(adres1, adres2) End Function
Grafikleri silme ve gizleme
Silme ve gizleme işlemlerini Shapeler aracılığı ile yapabileceğimiz gibi ChartObject'in kendisiyle de yapabiliriz. Burada önemli olan Chart nesnesini değil ChartObject nesnesini silebildiğimizdir.
ActiveChart.Delete 'çalışmaz
ActiveSheet.ChartObjects(1).Delete 'bu 1 nolu chartı siler
ActiveSheet.ChartObjects.Delete 'bu sayfadaki tüm grafikleri siler
Chart sayfalarını ise normal sayfa siler gibi siliyoruz.