1
votes

I have two sets of code. For some reason, in the first one I get an error, and in the second one I don't.

1:

Dim pi As PivotItem
Dim pf As PivotField

Set pf = Sheets("Afname per school").PivotTables("Draaitabel3").PivotFields("school")

For Each pi In pf.PivotItems
    If pi = "(leeg)" Then
        pi.Visible = False
    Else
        pi.Visible = True 'ERROR HERE
    End If
Next pi

2:

Dim pi As PivotItem
Dim pf As PivotField

Set pf = Sheets("Afname per school").PivotTables("Draaitabel3").PivotFields("naam locatie")

For Each pi In pf.PivotItems
    If InStr(pi, "BSO") Then
        pi.Visible = True
    Else
        pi.Visible = False
    End If
Next pi

I get the error: "unable to get the visible property of the pivotitem class"

I read I should solve this the following:

This is due to the Pivot table using the cached pivot items instead of the current one. Make sure the table does not retain any old items. To do so, right click on your pivot table, click on Data tab and set "Number of itesm to retain per field" to "None". The code to do so in VBA is:

Dim pt As PivotTable

pt.PivotCache.MissingItemsLimit = xlMissingItemsNone

I tried to add this line of code in two ways:

Dim pi As PivotItem
Dim pf As PivotField

Set pf = Sheets("Afname per school").PivotTables("Draaitabel3").PivotFields("school")

pt.PivotCache.MissingItemsLimit = xlMissingItemsNone  '1st TRY

For Each pi In pf.PivotItems
    pt.PivotCache.MissingItemsLimit = xlMissingItemsNone   '2nd TRY
    If pi = "(leeg)" Then
        pi.Visible = False
    Else
        pi.Visible = True
    End If
Next pi

This doesn't seem to solve my problem.

3
Started explanation in comments, but it is too long, starting an answer! ;) Just a quick question : Do you have multiples fields in your rows/columns fields?R3uK
Thanks for helping! Actually, I don't. There's one row field and one column field. Does it change much if I would like to add one?Grafit
BTW. Does it matter if my pivotfield is a report filter?Grafit
Apparently it doesn't, it seems to works the same way. But it might be possible that with the other filters, some PivotItems will not be available to display as they won't correspond to an existing row in you data set. Let me know if my answer helped you anywayR3uK

3 Answers

2
votes

1. Do you have multiples fields in your rows/columns fields?

Because the problem might be coming from this.

All the PivotItems in PivotField are not always displayed/displayable because they are in the second level, depending on the first level. To avoid code breaking because of the error, you'll have to use an Error Handler.

Only the PivotItems that are found with the corresponding PivotItem from the first level are displayable (IE you can't displayed a case that didn't happen in your data).

For instance you can't display the PivotItem "Car" at 2nd level

when the 1st level PivotItem is "Flying mode of transportation".


2. Refresh PivotCache

That being said, you could simply refresh the pivot cache (check that you have defined Pt as your PivotTable) right after using the setting MissingItemsLimit to be sure that you have the most recent data :

Set Pt = Sheets("Afname per school").PivotTables("Draaitabel3")
Set pf = Pt.PivotFields("school")
Pt.PivotCache.MissingItemsLimit = xlMissingItemsNone
Pt.PivotCache.Refresh

3. Code logic

Looking at your code afterwards, I'm a bit perplex, because what you are doing is hiding a specific PivotItem found by his name, but you also try to display every other PivotItem!

And I think that is the main problem here, I would suggest a routine with few arguments and error handling, something like this :

Sub Hide_PivotItem(PivotTable_Object As PivotTable, _
                    PivotField_Name As String, _
                    ByVal PivotItem_Name As String, _
                    ByVal UnHide_All As Boolean)

    Dim Pt As PivotTable, _
        Pi As PivotItem, _
        Pf As PivotField
    
    Set Pt = PivotTable_Object
    Set Pf = Pt.PivotFields(PivotField_Name)
    
    Pt.PivotCache.MissingItemsLimit = xlMissingItemsNone
    Pt.PivotCache.Refresh
    
    If UnHide_All Then
        On Error Resume Next
        For Each Pi In Pf.PivotItems
            Pi.Visible = True
        Next Pi
        On Error GoTo 0
    Else
        'Don't unhide other items
    End If

    For Each Pi In Pf.PivotItems
        If Pi.Name <> PivotItem_Name Then
        Else
            Pi.Visible = False
        End If
    Next Pi

End Sub
2
votes

Grafit: The two code snippets you have posted do very different things. The first one makes everything visible except for the value "(leeg)". The second one makes any item with "BSO" in it visible, and hides everything else. There are problems with both bits of code.

In regards to your first code snippet, if you want to show every item except for the one called "(leeg)" then there is no need to iterate though the PivotItems collection (which is really slow on large Pivots). Instead, simply do this:

pf.ClearAllFilters pf.PivotItems("leeg").visible = false

In regards to your second bit of code, then yes the error could be caused by the MissingItemsLimit issue, but it also will occur if the code tries to hide a PivotItem while no other PivotItems are currently visible during the loop. For instance, if you had the PivotTable filtered on say just one item such as "Aardvark", then because "Aardvark" does not have "BSO" in it, the code will attempt to hide it, and will then error out, because at least one PiovtItem must remain visible at all times.

So what you have to do is add a line before the loop that makes the last item in the PivotItems collection visible, so that you can pretty much guarantee that one item will remain visible right up to the end of the loop.

(Of course, if "BSO" does not appear in ANY of the PivotItems, then you'll still get the error when you go to process that last item).

Furthermore, whenever you iterate through the PivotITems collection you usually want to set PT.ManualUpdate to True, so that the PivotTable doesn't try to update the totals in the PivotTable after each and every item gets hidden/unhidden. Then at the end of the routine you set PT.ManualUpdate to False again, which then tells Excel "I'm done...you can update these PivotTable totals now." This usually makes a staggering difference in terms of speed of your routine. On large Pivots you will save A LOT of time.

I wrote an article that discusses this stuff in depth at http://dailydoseofexcel.com/archives/2013/11/14/filtering-pivots-based-on-external-ranges/ that I suggest you check out.

--edit-- Here's a routine to clear the PivotTable so that only one item is displayed:

Sub FilterPivot_PivotItem(pfOriginal As PivotField, _
                            Optional pi As PivotItem, _
                            Optional pfTemp As PivotField, _
                            Optional bDelete_wksTemp As Boolean = True, _
                            Optional bDelete_ptTemp As Boolean = False)

' If pfOriginal is a PageField, we'll simply turn .EnableMultipleItems to FALSE
' and select pi as a PageField

' Otherwise we'll
'   * create a temp copy of the PivotTable
'   * Make the field of interest a PageField
'   * Turn .EnableMultipleItems to FALSE and select pi as a PageField
'   * Add a Slicer to that PageField
'   * Connect that Slicer to pfOriginal, which will force it instantly to sync.
'     to pfTemp, meaning it shows just one item

' This is much faster than Iterating through a large PivotTable and setting all but
' one item to hidden, as outlined at http://dailydoseofexcel.com/archives/2013/11/14/filtering-pivots-based-on-external-ranges/



Const sRoutine = "FilterPivot_PivotItem"
Dim sc As SlicerCache
Dim bSlicerExists As Boolean
Dim ptOriginal As PivotTable
Dim ptTemp As PivotTable
Dim wksTemp As Worksheet
Dim bDisplayAlerts As Boolean
Dim lCalculation As Long
Dim bEnableEvents As Boolean
Dim bScreenUpdating As Boolean
Dim TimeTaken As Date


TimeTaken = Now()

Set ptOriginal = pfOriginal.Parent
With Application
    bScreenUpdating = .ScreenUpdating
    bEnableEvents = .EnableEvents
    lCalculation = .Calculation
    .ScreenUpdating = False
    .EnableEvents = False
    .Calculation = xlCalculationManual
End With


With pfOriginal
    If pi Is Nothing Then Set pi = .PivotItems(1)
    If .Orientation = xlPageField Then
        'Great: we're dealing with a field in the FILTERS pane, which let us
        ' select a singe item easily
        .EnableMultiplePageItems = False
        .CurrentPage = pi.Name
    Else
        ' For non PageFields we'll have to use a temp PivotTable and Slicer to quickly clear
        ' all but one PivotItem.

        'Check if pfOriginal already has a slicer connected
        ' If so, then we'll want to leave it in place when we're done
        bSlicerExists = Slicer_Exists(ptOriginal, pfOriginal)

        ' A temp PivotTable may aleady exist and have been passed in when the function was called
        ' Otherwise we'll need to create one.
        If pfTemp Is Nothing Then
            Set wksTemp = Sheets.Add
            Set ptTemp = ptOriginal.PivotCache.CreatePivotTable(TableDestination:=wksTemp.Range("A1"))
            Set pfTemp = ptTemp.PivotFields(.SourceName)
            'Set the SaveData state of this new PivotTable the same as the original PivotTable
            '(By default it is set to True, and is passed on to the original PivotTable when a Slicer is connected)
            If ptTemp.SaveData <> ptOriginal.SaveData Then ptTemp.SaveData = ptOriginal.SaveData
        Else
           Set ptTemp = pfTemp.Parent
            'Check if pfTemp already has a slicer conneced.
            If Not Slicer_Exists(ptTemp, pfTemp, sc) Then Set sc = ActiveWorkbook.SlicerCaches.Add(ptTemp, pfTemp)
        End If
        ptTemp.ManualUpdate = True

        With pfTemp
            .Orientation = xlPageField
            .EnableMultiplePageItems = False
            .CurrentPage = pi.Name
        End With
        ptTemp.ManualUpdate = False

        'Connect slicer on pfTemp to pfOriginal to pass through settings, then disconnect it
        sc.PivotTables.AddPivotTable pfOriginal.Parent
        If Not bSlicerExists Then
            sc.Delete
        Else
            sc.PivotTables.RemovePivotTable pfTemp.Parent
        End If
        If bDelete_wksTemp Then
            bDisplayAlerts = Application.DisplayAlerts
            Application.DisplayAlerts = False
            wksTemp.Delete
            Application.DisplayAlerts = bDisplayAlerts
        ElseIf bDelete_ptTemp Then ptTemp.TableRange2.ClearContents
        End If
    End If
End With

With Application
    .ScreenUpdating = bScreenUpdating
    .EnableEvents = bEnableEvents
    .Calculation = lCalculation
End With
TimeTaken = Now() - TimeTaken
Debug.Print Now() & vbTab & sRoutine & " took " & Format(TimeTaken, "HH:MM:SS") & " seconds."

End Sub
0
votes

You may need to set a true at very beginning, for instance

.PivotItems(1) = true

Then you can use a condition loop to set this item to which it should be.