I have a pivot table that contains a number of Expand/Collapse buttons related to Customer, Year, and Quarters all of which have vba behind them to .Showdetails as appropriate. These individual sets of code work. However, I'm trying to make my code more efficient, and manageable, and thanks to another user, I'm getting closer.
Here's the code for each button that works:
Sub Expand_Quarter()
Range("R13").Select
ActiveSheet.PivotTables("PivotTable1").PivotFields("Quarter").ShowDetail = IIf(Selection.ShowDetail, False, True)
End Sub
And Here is the code that is throwing an error. I've commented where the error occurs:
Sub ExpColl()
Dim pt As PivotTable, pf As PivotField, b As String
b = Application.Caller
With ActiveSheet
Set pt = .PivotTables("PivotTable1")
Select Case b
Case "btnExpCollCustProd": Set pf = pt.PivotFields(.Range("Q15").PivotField.Name)
Case "btnExpCollYear": Set pf = pt.PivotFields(.Range("R12").PivotField.Name)
Case "btnExpCollQtr": Set pf = pt.PivotFields(.Range("R13").PivotField.Name)
End Select
'--- Run Time Error 1004 Application-defined or Object Defined Error"
pf.ShowDetail = IIf(pf.ShowDetail, False, True)
End With
End Sub
The variable pf does return exactly what I expect, so I'm a little perplexed. All help greatly appreciated.
I worked it out!!! The following code will work exactly how I want it to:
Sub ExpColl()
Dim pt As PivotTable, pf As PivotField, b As String, s As Shape, bev As Long, r As Range
b = Application.Caller
Set s = ActiveSheet.Shapes(b)
If b Like "btnExpColl*" Then
With ActiveSheet
Set pt = .PivotTables("PivotTable1")
Select Case b
Case "btnExpCollCustProd":
Set r = .Range("Q15")
Set pf = pt.PivotFields(r.PivotField.Name)
Case "btnExpCollYear":
Set r = .Range("R12")
Set pf = pt.PivotFields(r.PivotField.Name)
Case "btnExpCollQtr":
Set r = .Range("R13")
Set pf = pt.PivotFields(r.PivotField.Name)
End Select
pf.ShowDetail = IIf(r.ShowDetail, False, True)
s.ThreeD.BevelTopType = IIf(s.ThreeD.BevelTopType = 3, 7, 3)
End With
End If
End Sub
I hope this helps someone else out there :)
I assume this problem might be related to the fact that each value of the field might have different value of ShowDetail property, so Excel does not even try to return it even if all the values were identical.
One way to avoid hardcoding specific ranges (which depends on the physical layout of the pivot table) is to use the following code:
pf.ShowDetail = Not pf.DataRange.Cells(1).ShowDetail
Related
I'm trying to filter a pivot field to values held within an array using some nested for each loops. The pivot field I'm trying to filter contains accounting periods - the ones I want to keep visible are held in a range of cells on separate sheet - Date Control Sheet. The code I have is as below:
Sub updateMSFPivotCharts()
Dim PivotYear As String, PvtTable As PivotTable, PivotPeriodsRange As Range, DateLastRow As String, PivotPeriods As Variant, ReportPeriodPF As PivotField, YearPF As PivotField, Pi As PivotItem
Dim LboundReportPeriodPF As String, PivotPeriodItem As Variant
PivotYear = Worksheets("Date Control Sheet").Range("A2").Value
DateLastRow = Worksheets("Date Control Sheet").Range("B2").End(xlDown).Row
Set PivotPeriodsRange = Worksheets("Date Control Sheet").Range("B2:B" & DateLastRow)
PivotPeriods = PivotPeriodsRange.Value
'Update Topline Performance Pivot
Call AdjustPivotDataRange("MSF Topline Performance", "1", "A9", "BI", strLastRow)
Set PvtTable = Worksheets("MSF Topline Performance").PivotTables("PivotTable1")
Set YearPF = Worksheets("MSF Topline Performance").PivotTables("PivotTable1").PivotFields("Year")
Set ReportPeriodPF = Worksheets("MSF Topline Performance").PivotTables("PivotTable1").PivotFields("Report Period")
'Filter Pivot to Date Ranges expressed on Date Control Sheet
With PvtTable
'Update Pivot Table Field Filtering
YearPF.ClearAllFilters
ReportPeriodPF.ClearAllFilters
YearPF.CurrentPage = PivotYear
End With
With ReportPeriodPF
For Each PivotPeriodItem In PivotPeriods
For Each Pi In ReportPeriodPF.PivotItems
If Pi = PivotPeriodItem Then GoTo Skipstep Else Pi.Visible = False
Next Pi
Skipstep:
Pi.Visible = True
Next PivotPeriodItem
End With
The problem I have, is that when I match Pi to PivotPeriodItem and exit out of the loop, Pi starts back at the beginning with PivotPeriodItem moving onto the next Item, hence when Pi is equal to the previous PivotPeriodItem, it then hides it again, when I want it to show.
Does anyone have any suggestion on how I can re-write the code to get by this problem? Many thanks for any help provided in advance.
Andrew
Instead of
With ReportPeriodPF
For Each PivotPeriodItem In PivotPeriods
For Each Pi In ReportPeriodPF.PivotItems
If Pi = PivotPeriodItem Then GoTo Skipstep Else Pi.Visible = False
Next Pi
Skipstep:
Pi.Visible = True
Next PivotPeriodItem
End With
Replace it with
With ReportPeriodPF
For Each Pi In ReportPeriodPF.PivotItems
If WorksheetFunction.CountIf(PivotPeriodsRange, Pi) > 0 Then
Pi.Visible = True
Else
Pi.Visible = False
End If
Next Pi
End With
One loop, just using CountIf to test against a range instead of individual items in the range.
I have a pivot table structure that looks like (i.e. there are three entries in the "ROWS" box in the pivottable UI)
Category
SubCategory
Sub Sub Category
I know that I can get all the Categories, subcategories, and sub-sub categories by doing (in VBA) PT.PivotFields(3).PivotItems(), PT.PivotFields(2).PivotItems() and PT.PivotFields(1).PivotItems() respectively, where PT is my pivottable.
How can I find out which subcategories are in each category, and same for sub sub categories in categories?
I tried using PT.PivotFields(3).PivotItems()(1).ChildItems() but I get an error <Unable to get the ChildItems property of the PivotItem class> and same for trying ParentItem.
Any idea how I can do this?
An example of what I am looking for. Take the pivot table below, and enumerate (in some way) that:
a has subcategories d,e; b has subcategories e,f; c has sub categories d,e,f; and it would be the same if there were multiple levels on the columns position.
Requirement:
To build a table showing all the Items combinations for all RowFields and ColumnsFields of a given PivotTable.
Solution:
This can be achieved by setting some of the properties and methods of the PivotTable and the Row, Column and Data Fields as follows:
Set these PivotTable properties:
RowGrand, ColumnGrand, MergeLabels, RowAxisLayout
Set these properties for the ColumnFields:
Orientation
Set these properties for the RowFields:
RepeatLabels, Subtotals
Set these properties for the DataFields:
Orientation
Procedure:
Sub PivotTable_Hierarchy_Rows_And_Columns(pt As PivotTable, _
aPtHierarchy As Variant, blClearFilters As Boolean, blIncludeCols As Boolean)
This procedure adjusts all the above-mentioned properties in order to display the PivotTable in a “table like” format generating an array with the PivotTable’s Hierarchy. It also provides the options to clear or not the PivotTable filters and to include or not the ColumnFields in the hierarchy.
Parameters:
Pt: Target PivotTable
aPtHierarchy: Array output containing the hierarchy of the target PivotTable.
blClearFilters: Boolean. Determines whether to clear or not the all PivotTable filters.
blIncludeCols: Boolean. Used to include or not the ColumnFields in the output hierarchy.
Syntax:
Call PivotTable_Hierarchy_Rows_And_Columns(pt, aPtHierarchy, blClearFilters, blIncludeCols)
VBA:
Sub PivotTable_Hierarchy_Rows_And_Columns(pt As PivotTable, _
aPtHierarchy As Variant, blClearFilters As Boolean, blIncludeCols As Boolean)
Dim pf As PivotField
Rem PivotTable Properties & Methods
With pt
.RowGrand = False
.ColumnGrand = False
.MergeLabels = False
.RowAxisLayout xlTabularRow
If blClearFilters Then .ClearAllFilters
End With
Rem ColumnFields Properties
For Each pf In pt.ColumnFields
If blIncludeCols Then
pf.Orientation = xlRowField
Else
pf.Orientation = xlHidden
End If: Next
Rem RowFields Properties
For Each pf In pt.RowFields
With pf
On Error Resume Next
.RepeatLabels = True
.Subtotals = Array(False, False, False, False, _
False, False, False, False, False, False, False, False)
On Error GoTo 0
End With: Next
Rem DataFields Properties
For Each pf In pt.DataFields
pf.Orientation = xlHidden
Next
Rem Set Hierarchy Array
aPtHierarchy = pt.RowRange.Value2
End Sub
Example:
Assuming we need to obtain the Hierarchy of the PivotTable in fig. 1.
Note that the PivotTable has some filters applied.
The procedure PivotTable_Hierarchy_Rows_And_Columns can be called as follows depending on the required outcome:
Sub PivotTable_Hierarchy_Rows_And_Columns_TEST()
Dim pt As PivotTable, aPtHierarchy As Variant
'Set PivotTable - Change worksheet and pivottable name as required
Set pt = ThisWorkbook.Worksheets("Summary").PivotTables("PtTst")
'1. To obtain the Hierarchy for Rows and Columns, clearing all the filters applied to the PivotTable try this:
Call PivotTable_Hierarchy_Rows_And_Columns(pt, aPtHierarchy, True, True) 'See results in Fig. R1 (Table & Array)
‘2. To obtain the Hierarchy for Rows only, clearing all the filters applied to the PivotTable try this:
Call PivotTable_Hierarchy_Rows_And_Columns(pt, aPtHierarchy, True, False) 'See results in Fig. R2 (Table & Array)
'3. To obtain the Hierarchy for Rows and Columns with the filters currently applied to the PivotTable try this:
Call PivotTable_Hierarchy_Rows_And_Columns(pt, aPtHierarchy, False, True) 'See results in Fig. R3 (Table & Array)
'4. To obtain the Hierarchy for Rows only with the filters currently applied to the PivotTable try this:
Call PivotTable_Hierarchy_Rows_And_Columns(pt, aPtHierarchy, False, False) 'See results in Fig. R4 (Table & Array)
End Sub
Fig. 1
Fig. R1
Fig. R2
Fig. R3
Fig. R4
For additional information on the resources used see the following pages:
PivotTable Object (Excel)
PivotTable.RowAxisLayout Method (Excel)
PivotField Object (Excel)
I don't know how many categories and sub-categories you have, but you can put everything to a multiple array (matrix) and then manage like you want.
Try this:
Sub GetCategories_SubCat()
Dim PTable As PivotTable
Dim matrix() As Variant
Dim matrix_s() As Variant
Dim msg$
Set PTable = ActiveSheet.PivotTables("PT")
ReDim matrix(1 To PTable.RowFields.Count)
For i = 1 To PTable.RowFields.Count
matrix(i) = PTable.RowFields(i)
For j = 1 To PTable.RowFields(i).PivotItems.Count
ReDim matrix_s(1 To PTable.RowFields.Count, 1 To PTable.RowFields(i).PivotItems.Count)
matrix_s(i, j) = PTable.RowFields(i).PivotItems(j)
Next j
Next i
End Sub
I think you can use GetPivotData function to try to obtain value for each set of pivot fields. If no error is thrown, you know that certain category has certain sub and sub-sub category. See my rough code, for table with three levels:
Sub ExaminePT()
Dim pt As PivotTable
Dim pf As PivotField
Dim Lev1 As PivotField
Dim Lev2 As PivotField
Dim lev3 As PivotField
Dim pi1 As PivotItem
Dim pi2 As PivotItem
Dim pi3 As PivotItem
Dim checkVal As Double
Set pt = ActiveSheet.PivotTables(1)
For Each pf In pt.PivotFields
If pf.Orientation = xlRowField Then
Select Case pf.Position
Case Is = 1
Set Lev1 = pf
Case Is = 2
Set Lev2 = pf
Case Is = 3
Set lev3 = pf
End Select
End If
Next pf
For Each pi1 In Lev1.PivotItems
For Each pi2 In Lev2.PivotItems
For Each pi3 In lev3.PivotItems
On Error Resume Next
checkVal = pt.GetPivotData("Incremental Benefits 2017", Lev1.Name, pi1.Name, _
Lev2.Name, pi2.Name, lev3.Name, pi3.Name)
If Err.Number = 0 Then Debug.Print pi1 & "| " & pi2 & "| " & pi3
On Error GoTo 0
Next pi3
Next pi2
Next pi1
End Sub
In the first loop I assign category, sub- and subsubcategory to variables, and then do what I had suggested above. The results are written in the Immediate window, this is sketch of the solution.
Probably, with little effort some could turn it into more universal procedure to examine pivot tables for any number of nested levels.
UPDATE: I think I found the answer but I have not been able to see if there is a way to do this in Excel 2013.
https://msdn.microsoft.com/en-us/library/office/mt574976.aspx
That link has documentation on ModelMeasures.Add Method but there are no real great examples I can find out there right now. If anyone has a good example that works in Excel 2013 to add a measure to a model using VBA, please share as the answer.
Best Example I could find, but not able to accomplish in Excel 2013:
https://social.msdn.microsoft.com/Forums/en-US/c7d5f69d-b8e3-4823-bbde-61253b64b80e/vba-powerpivot-object-model-adding-measures-with-modelmeasuresadd?forum=isvvba
ORIGINAL POST:
I am trying to automate the adding of calculated fields to a powerpivot pivot table using VBA. I am not experienced in VBA.
When I manually add a Calculated Field using the below formula I am able to see the Calculated Field added. What is wrong with this VBA code?
Here is my code:
Sub Macro5()
Dim PvtTbl As PivotTable
Set PvtTbl = Worksheets("Sheet4").PivotTables("PivotTable6")
'Table1 is part of the PowerPivot data model and I have created a pivot table from Table1
PvtTbl.CalculatedFields.Add "column", "=IF(HASONEVALUE(Table1[TEXT1]), VALUES(Table1[TEXT1]), BLANK())"
'Selecting the pivot table and adding the new calculated field
Range("D7").Select
ActiveSheet.PivotTables("PivotTable6").AddDataField ActiveSheet.PivotTables( _
"PivotTable6").CubeFields("[Measures].[column]")
End Sub
The Error I get:
Run-time error '1004': Application-defined or object-defined error
Hacked this together to load from an Excel worksheet (will need to change ranges etc)
Will overwrite existing measure's formula so can iterate through and not have to deal with error messages (apart from last with the error handler).
Best part is that can load measure out of sequence so that a measure that depends on another measure can be loaded.
Sub AddMeasures()
Dim Mdl As Model
Dim tbl As ModelTable
Set Mdl = ActiveWorkbook.Model
Set tbl = Mdl.ModelTables(1)
Dim rng As Range
Set rng = Worksheets("Sheet2").Range("A2:A75")
Dim measure_name As String
Dim measure_formula As String
Dim cell As Range
Dim item As Integer
For Each cell In rng
measure_name = cell.Value
measure_formula = cell.Offset(0, 1).Value
item = GetItemNumber(measure_name)
If item > 0 Then
Mdl.ModelMeasures.item(item).formula = measure_formula 'replace the existing formula
Else
On Error GoTo errhandler
If cell.Offset(0, 2).Value = 1 Then
Mdl.ModelMeasures.Add measure_name, tbl, measure_formula, Mdl.ModelFormatWholeNumber(1)
Else
Mdl.ModelMeasures.Add measure_name, tbl, measure_formula, Mdl.ModelFormatPercentageNumber(False, 1)
End If
End If
Next cell
errhandler:
Debug.Print cell.Address, "Now we have a real problem"
End Sub
Function GetItemNumber(measure_name As String) As Integer
Dim cnt As Integer
Dim Mdl As Model
Dim tbl As ModelTable
Set Mdl = ActiveWorkbook.Model
Set tbl = Mdl.ModelTables(1)
For cnt = 1 To Mdl.ModelMeasures.Count
If Mdl.ModelMeasures.item(cnt).Name = measure_name Then
Debug.Print "Have a duplicate measure name"
Exit For
End If
Next cnt
If cnt > 0 And cnt <= Mdl.ModelMeasures.Count Then
GetItemNumber = cnt
Else
GetItemNumber = 0
End If
End Function
I am trying to create a module to share functionality across a number of worksheets within a single workbook. All works fine apart from some filter code.
The code below works fine if I place it in a single worksheet
ActiveSheet.PivotTables("PivotTable1").PivotFields("MyField"). _
ClearValueFilters
ActiveSheet.PivotTables("PivotTable1").PivotFields("MyField").PivotFilters.Add Type:=xlValueIsBetween, DataField:=ActiveSheet.PivotTables _
("PivotTable1").PivotFields("MyValue"), Value1:=bottom, Value2:=Top
However, I want to use this code in my Module and send it the following: Pivot Table Name, Row PivotField, Value Pivot fieldtop "between" valuebottom "between" value
Something like:
The sheet call
Call Mod_Filter("PivotTable1", "MyRowFieldName", "MyValueFieldName", 0, intvalue)
The Module code
Public Sub Mod_Filter(ByRef PT_Name As String, ByRef StaticField As String, ByRef Filter_field As String, bottom As Double, Top As Double)
ActiveSheet.PivotTables(PT_Name).PivotFields(StaticField).ClearValueFilters
ActiveSheet.PivotTables(PT_Name).PivotFields(StaticField).PivotFilters.Add Type:=xlValueIsBetween, DataField:=ActiveSheet.PivotTables _
(PT_Name).PivotFields(Filter_field), Value1:=bottom, Value2:=Top
End Sub
However, I cannot get this to work. Any suggestions??
So I'm using the "St" Row to filter the YTD% based on a value selected from a dropdown combo box
Requirement:
To filter a Pivot RowField based on a Pivot DataField
This procedure uses the PivotField.SourceName to validate & set the DataField to apply the Filter Type and Values.
Sub Ptb_Filter_Between(WshTrg As Worksheet, _
sFldTrg As String, sFldDta As String, dFromVal As Double, dToValue As Double)
Dim pTbl As PivotTable, pFldDta As PivotField
Rem Application Settings - OFF
Application.ScreenUpdating = False
Application.EnableEvents = False
Rem Set PivotTable
'Using Index 1 as OP confirmed there is only one PivotTable per Sheet
'If this condition change then PivotTable Index should be provided as an argument
Set pTbl = ActiveSheet.PivotTables(1)
With pTbl
Rem Set DataField - User provides the Data Field
For Each pFldDta In .DataFields
If pFldDta.SourceName = sFldDta Then Exit For
Next
If pFldDta Is Nothing Then GoTo ExitTkn
Rem Filter PivotField
.ClearAllFilters 'Use this line to clear all PivotTable Filters
On Error GoTo ExitTkn
With .PivotFields(sFldTrg)
.ClearAllFilters 'This line clears all PivotField Filters
.PivotFilters.Add2 Type:=xlValueIsBetween, _
DataField:=pFldDta, Value1:=dFromVal, Value2:=dToValue
End With: End With
ExitTkn:
Rem Application Settings - ON
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
These are some samples of how to call the procedure:
sFldTrg = "Type"
sFldDta = "Amount"
dFromVal = 25000
dToValue = 30000
Call Ptb_Filter_Between(Worksheet Object, sFldTrg, sFldDta, dFromVal, dToValue)
sFldTrg = "Type"
sFldDta = "YTD%"
dFromVal = 0.3
dToValue = 0.35
Call Ptb_Filter_Between(Worksheet Object, sFldTrg, sFldDta, dFromVal, dToValue)
sFldTrg = "Row"
sFldDta = "YTD%"
dFromVal = 0.1 'To set from value as 10%
dToValue = 0.2 'To set to value as 20%
Call Ptb_Filter_Between(Worksheet Object, sFldTrg, sFldDta, dFromVal, dToValue)
Running the code below and what I'm hoping to see as a result is to have all columns with 0s on row 27 hidden - but depending on the frequency of the data, the range for those columns to be hidden is different. Basically anything that is in a sheet that starts with Daily/monthly/weekly will have to have columns hidden, all the rest of the sheets should be left alone.
It worked on a simple workbook using an if statement (sheets starting with X vs. all other), but when I added the case statement it broke...
The line marked down with bold is the one where I get an error:
Run-time error '1004'
Application-defined or object-defined error
I'm new to this, so please feel free to suggest a good vba tutorials website/book.
Sub Hide_Zero_Columns()
Dim WS As Worksheet
Dim Col_to_hide As Range
Dim Range_to_hide As Range
Dim X As Integer
For Each WS In ThisWorkbook.Sheets
Worksheets(WS.Name).Activate
With WS
Select Case Data_Frequency_Sheets
Case Left(WS.Name, 5) = "Daily"
Set Range_to_hide = Range("BDV$27:CWH$27")
Case Left(WS.Name, 5) = "Month"
Set Range_to_hide = Range("AY$27:CO$27")
Case Left(WS.Name, 5) = "Weekl"
Set Range_to_hide = Range("HF$27:NN$27")
Case Else
Set Range_to_hide = Range("A1:B1")
End Select
Select Case Data_Condition
Case Left(WS.Name, 5) = "Daily"
X = 1
Case Left(WS.Name, 5) = "Month"
X = 30
Case Left(WS.Name, 5) = "Weekl"
X = 7
Case Else
X = 999
End Select
If X <> 999 Then
For Each Col_to_hide In ActiveSheet.Range(Range_to_hide) '<-- Error here
If UCase(Col_to_hide) = 0 Then
Col_to_hide.EntireColumn.Hidden = True
Else: Col_to_hide.EntireColumn.Hidden = False
End If
Next Col_to_hide
End If
End With
Next
ActiveWorkbook.Worksheets("Registrations").Activate
End Sub
Since you have already defined a Range, you the problem is you are trying to evaluate: Sheet.Range(Range) which throws the error you are getting.
Since it appears you are wanting to iterate across the columns, all you need to do is change the line to this:
' Iterate across the columns in the defined range.
For Each Col_to_hide In Range_to_hide.Columns
' Each "Col_to_hide" will represent all cells within the column.
MsgBox Col_to_hide.Address
The error you're getting is because you're passing a Range object as the argument to Activesheet.Range() and it doesn't accept that because of the Range object's default value. It would be like doing this in the Immediate Window
?Range(Range("A1")).Address
You'll get the same error. Whereas with
?Range("A1").Address
You don't. You could do this too
?Range(Range("A1").Address).Address
So the thing is that when you don't specify a property for an object, like Range("A1") instead of Range("A1").Address, then the default property is used. The Range object is a bit strange, but in this case its default property is the Value property. So Activesheet.Range(Range_to_hide) is the same as Activesheet.Range(Range_to_hide.Value). And if Range_to_hide is a multi-cell range, then it's Value property returns an array, which you definitely can't pass into a Range's argument.
That's the explanation, the short answer is simply to use Range_to_hide, like
For Each Col_to_hide in Range_to_hide.Columns
Why the .Columns. Remember when I said that the Range object was a little strange. Well, unlike most objects, it has two default properties based on the context you're using it in. Earlier, the default property was Value, but in the context of a For..Each, the default value is Cells. If you don't specify .Columns in your For..Each, then it will loop through every cell in the Range. In fact, I always specify how the range is aggregated, even if it's the default Cells.