Select a list of items in a slicer - excel

I am trying to help a bookstore with its Xmas sales! I use a PivotTable connected to an OLAP Cube, with a lot of product references, with valuable information as sales of last week, inventory level etc.
I want to display only the data (sales and inventory levels) for the books on a current commercial actions (about 400 books) to check if inventory level is enough.
I have a slicer with ISBN numbers, with more than a million captions, and I want to manipulate that slice with VBA to display only the books I want.
List of the ISBN that I want to be displayed are in sheet "Catalogue EOY", column 3. I try to build an array with the right slicer names, to be used with the VisibleSlicerItemsList statement, but I get a message "Object required" on that line (last line). In my example, I have limited the list of books to the first 50 items.
Any idea how I can solve this?
Sub ShowProductList()
Dim ProductList(0 To 50) As Variant
Dim i
Dim Sc As SlicerCache
Dim sL As SlicerCacheLevel
Set Sc = ActiveWorkbook.SlicerCaches("Slicer_ISBN")
Set sL = Sc.SlicerCacheLevels(1)
For i = 2 To 52
ProductList(i - 2) = Chr(34) & "[DIM Artikel].[ISBN].&[" & _
Worksheets("Catalogue EOY").Cells(i, 3).Value & "]" & Chr(34)
Next i
sL.VisibleSlicerItemsList = ProductList
End Sub

Sub f()
Dim piv As PivotItem, pivf As PivotField, pivt As PivotTable, ProductList() As Variant, filterrng As Range, rng As Range
'the range where your background data is
Set filterrng = Worksheets("filter_criteria").Range("C2:C52") 'the range where your product list is
ReDim ProductList(filterrng.Cells.Count - 1)
For Each rng In filterrng
ProductList(i) = rng.Value2
i = i + 1
Next rng
Set pivt = Sheets("piv").PivotTables("PivotTable1") 'your pivottable, define it properly
Set pivf = pivt.PivotFields("ISBN") 'the pivot field
On Error Resume Next
For Each pvi In pivf.PivotItems
pvi.Visible = False
pvi.Visible = Application.Match(pvi.Name, ProductList, False) > -1 'if it's in the range, then make it visible, otherwise hide it
Next pvi
End Sub
Not the answer you want but the one you need.

You need to loop through each SlicerItem and test it against your list to choose to select it or not, here is how :
Sub ShowProductList()
With Application
.EnableEvents = False 'stop executing this code until we are done
.DisplayAlerts = False
.ScreenUpdating = False
'.Calculation = xlCalculationManual
End With
Dim ProductList(0 To 50) As Variant
Dim i As Long
Dim Sc As SlicerCache
Dim sI As SlicerItem
Dim sL As SlicerCacheLevel
Dim inLisT As Boolean
Set Sc = ActiveWorkbook.SlicerCaches("Slicer_ISBN")
Set sL = Sc.SlicerCacheLevels(1)
For i = 2 To 52
ProductList(i - 2) = Chr(34) & "[DIM Artikel].[ISBN].&[" & _
Worksheets("Catalogue EOY").Cells(i, 3).Value & "]" & Chr(34)
Next i
Sc.ClearManualFilter
For Each sI In Sc.SlicerItems
inLisT = False
For i = LBound(ProductList) To UBound(ProductList)
If sI.Name <> ProductList(i) Then
Else
inLisT = False
Exit For
End If
Next i
If inLisT Then
sI.Selected = True
Else
sI.Selected = False
End If
Next sI
With Application
.EnableEvents = True 'stop executing this code until we are done
.DisplayAlerts = True
.ScreenUpdating = True
'.Calculation = xlCalculationAutomatic
End With
End Sub

Related

Find values from a Word table in an Excel table

I want to iterate through the values of a column in a table in a word file, and check if those values are in a column in a table in an Excel file. I have the following code:
Private Sub CompararColumnas_Click()
Dim wrdTbl As Table
'Set your table
With ActiveDocument
If ActiveDocument.Tables.Count >= 1 Then
Set wrdTbl = .Tables(InputBox("Table # to copy? There are " & .Tables.Count & " tables to choose from."))
End If
End With
Dim AD_UsersPath As String
AD_UsersPath = "C:\Users\" & Environ("Username") & "\Desktop\Comparar Columnas VBA\Animales.xlsx"
Dim AD_USERS As Object
Set AD_USERS = CreateObject("Excel.Application")
AD_USERS.Visible = False
AD_USERS.Application.Workbooks.Open AD_UsersPath
LastRow = ThisDocument.Tables(1).Columns(1).Cells.Count
Dim I As Integer
For I = 1 To LastRow
wVal = ThisDocument.Tables(1).Cell(I, 1)
User = AD_USERS.Cells(AD_USERS.Range("A:A").Find(What:=wVal).Row, 1).Text
wrdTbl.Cell(I, 2).Range.Text = User
Next I
End Sub
This code iterates in wVal the values from the first column in a table from Word and after that it goes to Excel to find those values in the first column of the Excel table. If it finds them, it copies the values in the second column of the word table. However, it gives me an error 91. If instead of Find(What:=wVal) I put something like Find(What:="Word") it does not give me an error and puts the word "Word" in every cell of the second column of the word table. How can I solve this?
Cell values in Word have a two-character "end of cell" marker (Chr(13) + Chr(7)) which you need to remove:
Private Sub CompararColumnas_Click()
Dim wrdTbl As Table
'Set your table
With ActiveDocument
If ActiveDocument.Tables.Count > 1 Then
Set wrdTbl = .Tables(InputBox("Table # to copy? There are " & _
.Tables.Count & " tables to choose from."))
Else
Set wrdTbl = .Tables(1) 'default to the only table
End If
End With
Dim AD_UsersPath As String, wb As Object, ws As Object
AD_UsersPath = "C:\Users\" & Environ("Username") & _
"\Desktop\Comparar Columnas VBA\Animales.xlsx"
Dim AD_USERS As Object
Set AD_USERS = CreateObject("Excel.Application")
AD_USERS.Visible = False
Set wb = AD_USERS.Workbooks.Open(AD_UsersPath)
Set ws = wb.Worksheets(1)
Dim LastRow As Long, I As Long, User
LastRow = wrdTbl.Columns(1).Cells.Count
For I = 1 To LastRow
wVal = TwrdTbl.Cell(I, 1)
Left(wVal, Len(wVal)-2) 'strip off "end of cell" marker
User = ws.Cells(ws.Range("A:A").Find(What:=wVal).Row, 1).Text
wrdTbl.Cell(I, 2).Range.Text = User
Next I
wb.Close False
AD_USERS.Quit
End Sub

how to set PivotField.HiddenItemsList property's value if CubeField.Orientation = xlPageField

The task is to automate OLAP pivot table data filtering. There are some items in pivot field named sPivotFieldName I need to exclude. The code below works pretty fine.
With Worksheets(sWorksheetName).PivotTables(sPivotTableName)
With .CubeFields(sCubeFieldName)
.Orientation = xlRowField
.IncludeNewItemsInFilter = True
End With
.PivotFields(sPivotFieldName).HiddenItemsList = vSomeItemsToExclude
End With
But the problem appears when I'm trying to change cube field ".Orientation" property's value to xlPageField. Run-time error 1004 fires each time. Here's an example:
With Worksheets(sWorksheetName).PivotTables(sPivotTableName)
With .CubeFields(sCubeFieldName)
.Orientation = xlPageField
.IncludeNewItemsInFilter = True
End With
.PivotFields(sPivotFieldName).HiddenItemsList = vSomeItemsToExclude
End With
The reason seems to be that items of the fields placed in pagefield aren's visible as they are when placed for example in the rowfield (one can see them as row captions). Or maybe there's something else. What am I missing?
This functionality obviously isn't available for PageFields. Seems to me a workaround is to use the .VisibleITemsList approach instead, but make sure it doesn't include the items you want to exclude.
To do this, you need to dump all the unfiltered items to a variant, loop the variant looking for the term you want to hide, and if you find it, just replace that element for some other element that you don't want to hide. (This saves you having to create a new array without that item in it).
The tricky thing is to get a list of all unfiltered items: .VisibleItemsList won't give it to you if the PivotTable doesn't have some kind of filter applied. So we need to get sneaky by making a copy of the PivotTable, making the PageField of interest a RowField, removing all other fields, and then hoovering up the complete list of items, so we know what should be visible after we remove the ones that should be hidden.
Here's a function that handles filtering no matter whether you're dealing with a RowField or a PageField and no matter whether you want to use the .VisibleItemsList to set the filter, or the .HiddenItemsList
In your particular case, you would call it like so:
FilterOLAP SomePivotField, vSomeItemsToExclude, False
Function FilterOLAP(pf As PivotField, vList As Variant, Optional bVisible As Boolean = True)
Dim vAll As Variant
Dim dic As Object
Dim sItem As String
Dim i As Long
Dim wsTemp As Worksheet
Dim ptTemp As PivotTable
Dim pfTemp As PivotField
Dim sPrefix As String
Set dic = CreateObject("Scripting.Dictionary")
With pf
If .Orientation = xlPageField Then
pf.CubeField.EnableMultiplePageItems = True
If Not pf.CubeField.EnableMultiplePageItems Then pf.CubeField.EnableMultiplePageItems = True
End If
If bVisible Then
If .CubeField.IncludeNewItemsInFilter Then .CubeField.IncludeNewItemsInFilter = False
.VisibleItemsList = vList
Else
If .Orientation = xlPageField Then
' Can't use pf.HiddenItemsList on PageFields
' We'll need to manipulate a copy of the PT to get a complete list of visible fields
Set wsTemp = ActiveWorkbook.Worksheets.Add
pf.Parent.TableRange2.Copy wsTemp.Range("A1")
Set ptTemp = wsTemp.Range("A1").PivotTable
With ptTemp
.ColumnGrand = False
.RowGrand = False
.ManualUpdate = True
For Each pfTemp In .VisibleFields
With pfTemp
If .Name <> pf.Name And .Name <> "Values" And .CubeField.Orientation <> xlDataField Then .CubeField.Orientation = xlHidden
End With
Next pfTemp
.ManualUpdate = False
End With
sPrefix = Left(pf.Name, InStrRev(pf.Name, ".")) & "&["
Set pfTemp = ptTemp.PivotFields(pf.Name)
pfTemp.CubeField.Orientation = xlRowField
pfTemp.ClearAllFilters
vAll = Application.Transpose(pfTemp.DataRange)
For i = 1 To UBound(vAll)
vAll(i) = sPrefix & vAll(i) & "]"
dic.Add vAll(i), i
Next i
'Find an item that we know is visible
For i = 1 To UBound(vList)
If Not dic.exists(vList(i)) Then
sItem = vList(i)
Exit For
End If
Next i
'Change any items that should be hidden to sItem
For i = 1 To UBound(vList)
If dic.exists(vList(i)) Then
vAll(dic.Item(vList(i))) = sItem
End If
Next i
.VisibleItemsList = vAll
Application.DisplayAlerts = False
wsTemp.Delete
Application.DisplayAlerts = True
Else
If Not .CubeField.IncludeNewItemsInFilter Then .CubeField.IncludeNewItemsInFilter = True
.HiddenItemsList = vList
End If
End If
End With
End Function
Someone please, show me example how it works((
Dim pt As PivotTable
Dim pf As PivotField
Set pt = ActiveSheet.PivotTables("Сводная таблица2")
Set pf = pt.PivotFields("[груп бай].[Название клиента].[Название клиента]")
wList = "[груп бай].[Название клиента].&[ООО ""Сеть автоматизированных пунктов выдачи""]"
FilterOLAP(pf, wList, FAlse)
debuging here
> If .Name <> pf.Name And .Name <> "Values" And .CubeField.Orientation
> <> xlDataField Then .CubeField.Orientation = xlHidden

Excel macro infinite loop keeps asking for user's input and can't "step into" to debug

I am creating a few macros to do the following in Excel 2010:
1. Upon creating a new worksheet ask for what the user wants to name his/her worksheet and sets the new worksheet to the name provided; calls Sort_Active_Book and Rebuild_TOC in order
2. Sort_Active_Book: Asks the user if he/she wants to sort the workbook's worksheets in ascending/descending order and proceeds to do so.
3. Rebuild_TOC: Deletes the Table of Contents page and rebuilds it based on all the worksheets in the workbook minus the TOC itself.
My problem is Excel keeps asking me to input the name of the new worksheet to be created and does not progress any further in the code. I notice it manages to create the named worksheet and asks me if I would like to sort ascending or descending but then proceeds to ask me again the name of the new worksheet. Could anyone please point out how to fix this and provide a code fix (if possible) please?
What I have already
This code portion is from ThisWorkbook, this is what prompts the user for the name of the worksheet upon creation.
Private Sub Workbook_NewSheet(ByVal Sh As Object)
Dim sName As String
Dim bValidName As Boolean
Dim i As Long
bValidName = False
Do While bValidName = False
sName = InputBox("Please name this new worksheet:", "New Sheet Name", Sh.Name)
If Len(sName) > 0 Then
For i = 1 To 7
sName = Replace(sName, Mid(":\/?*[]", i, 1), " ")
Next i
sName = Trim(Left(WorksheetFunction.Trim(sName), 31))
If Not Evaluate("ISREF('" & sName & "'!A1)") Then bValidName = True
End If
Loop
Sh.Name = sName
Call Sort_Active_Book
Call Rebuild_TOC
End Sub
These two macros are in "Module 1":
Sub Sort_Active_Book()
Dim TotalSheets As Integer
Dim p As Integer
Dim iAnswer As VbMsgBoxResult
' Move the TOC to the begining of the document.
Sheets("TOC").Move Before:=Sheets(1)
' Prompt the user as to which direction they wish to
' sort the worksheets.
iAnswer = MsgBox("Sort Sheets in Ascending Order?" & Chr(10) & "Clicking No will sort in Descending Order", vbYesNoCancel + vbQuestion + vbDefaultButton1, "Sort Worksheets")
For TotalSheets = 1 To Sheets.Count
For p = 2 To Sheets.Count - 1
' If the answer is Yes, then sort in ascending order.
If iAnswer = vbYes Then
If UCase$(Sheets(p).Name) = "TOC" Then
Sheets(p).Move Before:=Sheets(1)
ElseIf UCase$(Sheets(p).Name) > UCase$(Sheets(p + 1).Name) Then
Sheets(p).Move After:=Sheets(p + 1)
End If
' If the answer is No, then sort in descending order.
ElseIf iAnswer = vbNo Then
If UCase$(Sheets(p).Name) = "TOC" Then
Sheets(p).Move Before:=Sheets(1)
ElseIf UCase$(Sheets(p).Name) < UCase$(Sheets(p + 1).Name) Then
Sheets(p).Move After:=Sheets(p + 1)
End If
End If
Next p
Next TotalSheets
End Sub
and
Sub Rebuild_TOC()
Dim wbBook As Workbook
Dim wsActive As Worksheet
Dim wsSheet As Worksheet
Dim lnRow As Long
Dim lnPages As Long
Dim lnCount As Long
Set wbBook = ActiveWorkbook
With Application
.DisplayAlerts = False
.ScreenUpdating = False
End With
' If the TOC sheet already exist delete it and add a new worksheet.
On Error Resume Next
With wbBook
.Worksheets(“TOC”).Delete
.Worksheets.Add Before:=.Worksheets(1)
End With
On Error GoTo 0
Set wsActive = wbBook.ActiveSheet
With wsActive
.Name = “TOC”
With .Range(“A1:B1”)
.Value = VBA.Array(“Table of Contents”, “Sheet # – # of Pages”)
.Font.Bold = True
End With
End With
lnRow = 2
lnCount = 1
' Iterate through the worksheets in the workbook and create sheetnames, add hyperlink
' and count & write the running number of pages to be printed for each sheet on the TOC.
For Each wsSheet In wbBook.Worksheets
If wsSheet.Name <> wsActive.Name Then
wsSheet.Activate
With wsActive
.Hyperlinks.Add .Cells(lnRow, 1), “”, _
SubAddress:=”‘” & wsSheet.Name & “‘!A1”, _
TextToDisplay:=wsSheet.Name
lnPages = wsSheet.PageSetup.Pages().Count
.Cells(lnRow, 2).Value = “‘” & lnCount & “-” & lnPages
End With
lnRow = lnRow + 1
lnCount = lnCount + 1
End If
Next wsSheet
wsActive.Activate
wsActive.Columns(“A:B”).EntireColumn.AutoFit
With Application
.DisplayAlerts = True
.ScreenUpdating = True
End With
End Sub
You are creating a new sheet with sub Rebuild_TOC. Causing the newsheet macro to run again.
You will need to avoid running the newsheet macro by adding a enableevents = false and true surrounding your code when creating a new sheet for your TOC. The rest of your code appears to be working as you want it to.
Application.EnableEvents = False
With wbBook
.Worksheets("TOC").Delete
.Worksheets.Add Before:=.Worksheets(1)
End With
Application.EnableEvents = True
Why would you want to delete the TOC worksheet, why not just update it?

Disabling other slicers in excel when slicer is pressed

I have three slicers operating on a pivot table and pivot chart in excel. However, the filters placed from the two other slicers has to be cleared, when one of the slicers is pressed, assuring that only one slicer is operating at the same time. I think this has to be solved using VBA, listening for a click then executing code, other than that I have no idea as I have never worked with Excel or VBA before.
Anyone got any suggestions on how I would do this?
Working out what Slicer got clicked is very tricky indeed, because the only application event that gets raised by clicking on a slicer is the PivotTable_Update event. This event tells us which PivotTable the slicer is connected to, but not which field in that PivotTable got filtered. So if you have multiple silcers connected to a PivotTable, you can't tell which one was just clicked on.
I came up with a very convoluted workaround that I posted at http://dailydoseofexcel.com/archives/2014/07/10/what-caused-that-pivottableupdate-episode-iv/ that will get you part the way there: it will tell you which field in a PivotTable just got updated, and then you just need to iterate through all the slicers connected to that PivotTable and clear them if they don't have the same sourcename.
I'll see if I can code something up in due course, but I'm pretty busy at present so I can't promise a fast resolution.
Note that you can assign a macro directly to a slicer that gets triggered when a user clicks on it, and from that you can determine which slicer it is. But unfortunately that macro interferes with the slicer itself: a user can no longer actually operate the slicer to actually change anything.
---UPDATE---
Here's some code that does what you want. There's a lot of different modules here, because the routine code calls quite a few other generic routines I use. And at it's heart is a routine that works out which particular field of a PivotTable gets updated, and that doesn't care if multilpe fields are filtered.
You call it with this event handler, which goes in the ThisWorkbook module for the book in the Visual Basic Editor:
Private Sub Workbook_SheetPivotTableUpdate(ByVal Sh As Object, ByVal Target As PivotTable)
Slicers_OneSlicerOnly Target
End Sub
And that calls these other functions in turn. You don't have to amend anything, and this works on any PivotTables or Slicers you add to this workbook.
Function Slicers_OneSlicerOnly(target As PivotTable)
Dim sField As String
Dim slr As Slicer
Dim sSlicer As String
Dim bEnableEvents As Boolean
Dim bScreenUpdating As Boolean
Dim bManualupdate As Boolean
Dim lCalculation As Long
Dim bRecordLayout As Boolean
Dim sLayout_New As String
Dim sLayout_Old As String
Dim lng As Long
With Application
bEnableEvents = .EnableEvents
bScreenUpdating = .ScreenUpdating
lCalculation = .Calculation
.EnableEvents = False
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
bManualupdate = target.ManualUpdate
target.ManualUpdate = True
sField = Pivots_FieldChange(target)
If sField <> "" Then
For Each slr In target.Slicers
sSlicer = slr.SlicerCache.SourceName
If sSlicer <> sField Then
If Not target.PivotFields(sSlicer).AllItemsVisible Then
target.PivotFields(sSlicer).ClearAllFilters
bRecordLayout = True
End If
End If
Next slr
End If
target.ManualUpdate = bManualupdate
If bRecordLayout Then
PivotChange_RecordLayout target, sLayout_New
With target
lng = InStr(.Summary, "[Layout]")
sLayout_Old = Mid(.Summary, lng + Len("[Layout]"), InStr(.Summary, "[/Layout]") - Len("[Layout]") - lng)
.Summary = Replace(.Summary, "[Layout]" & sLayout_Old & "[/Layout]", "[Layout]" & sLayout_New & "[/Layout]")
End With
End If
With Application
.EnableEvents = bEnableEvents
.ScreenUpdating = bScreenUpdating
.Calculation = lCalculation
End With
End Function
Public Function Pivots_FieldChange(target As PivotTable) As String
' Description: Works out what caused a PivotTableUpdate event, and if caused by someone changing a filter returns the
' name of the PivotField that was filtered.
' Programmer: Jeff Weir
' Contact: weir.jeff#gmail.com or jeff.weir#HeavyDutyDecisions.co.nz
' Inputs: PivotTable
' Outputs: String
' Name/Version: Date: Ini: Modification:
' PivotChange_20140712 20140712 JSW Initial programming as per http://dailydoseofexcel.com/archives/2014/07/10/what-caused-that-pivottableupdate-episode-iv/
' PivotChange_20140723 20140423 JSW Restructured code as per http://dailydoseofexcel.com/archives/2014/07/23/broken-arrow/
' PivotChange_20140802 20140802 JSW Added: If sLastUndoStackItem = "Filter" Or sLastUndoStackItem = "Slicer Operation" Then
' so that Filter routines only get called in response to filtering
' Pivots_FieldChange 20151016 JSW Changed the way info is saved in .summary
Dim sLastUndoStackItem As String
Dim sField As String
Dim sPossibles As String
Dim sLayout_New As String
Dim sLayout_Old As String
On Error Resume Next 'in case the undo stack has been wiped or doesn't exist
sLastUndoStackItem = Application.CommandBars(14).FindControl(ID:=128).List(1) 'Standard Commandbar, undo stack
On Error GoTo 0
If sLastUndoStackItem = "Filter" Or sLastUndoStackItem = "Slicer Operation" Then
sField = PivotChange_CompareLayout(target, sLayout_New, sLayout_Old)
If sField = "" Then sField = PivotChange_EliminationCheck(target, sPossibles)
If sField = "" Then sField = PivotChange_UndoCheck(target, sPossibles)
If sLayout_Old = "" Then
target.Summary = "[Layout]" & sLayout_New & "[/Layout]"
Else
target.Summary = Replace(target.Summary, "[Layout]" & sLayout_Old & "[/Layout]", "[Layout]" & sLayout_New & "[/Layout]")
End If
End If
Pivots_FieldChange = sField
Debug.Print Now() & vbTab & "Pivots_FieldChange:" & vbTab & sField
End Function
Function PivotChange_RecordLayout(pt As PivotTable, ByRef sLayout_New As String) As Boolean
Dim pf As PivotField
For Each pf In pt.PivotFields
With pf
Select Case .Orientation
Case xlRowField, xlColumnField
sLayout_New = sLayout_New & .Name & "|" & .VisibleItems.Count & "|" & .VisibleItems(1).Name & "||"
Case xlPageField
'pf.VisibleItems.Count doesn't work on PageFields
'So for PageFields we’ll record what that PageField’s filter currently displays.
'#DEV# Maybe it's quick to iterate through the .VisibleItems collection (if there is one) and count?
sLayout_New = sLayout_New & .Name & "|" & .LabelRange.Offset(, 1).Value & "|" & .EnableMultiplePageItems & "||"
End Select
End With
Next pf
End Function
Function PivotChange_CompareLayout(pt As PivotTable, ByRef sLayout_New As String, ByRef sLayout_Old As String) As String
Dim i As Long
Dim lng As Long
Dim vLayout_Old As Variant
Dim vLayout_New As Variant
PivotChange_RecordLayout pt, sLayout_New
With pt
lng = InStr(.Summary, "[Layout]")
If lng > 0 Then
sLayout_Old = Mid(.Summary, lng + Len("[Layout]"), InStr(.Summary, "[/Layout]") - Len("[Layout]") - lng)
If sLayout_Old <> sLayout_New Then
vLayout_Old = Split(sLayout_Old, "||")
vLayout_New = Split(sLayout_New, "||")
For i = 0 To UBound(vLayout_Old)
If vLayout_Old(i) <> vLayout_New(i) Then
PivotChange_CompareLayout = Split(vLayout_Old(i), "|")(0)
Exit For
End If
Next i
End If
Else:
'Layout has not yet been recorded.
'Note that we only update .Summary at the end of the main function,
' so we don't wipe the UNDO stack before the PivotChange_UndoCheck routine
End If
End With
End Function
Function PivotChange_EliminationCheck(pt As PivotTable, ByRef sPossibles As String) As String
'Check all the visible fields to see if *just one of them alone* has
' neither .AllItemsVisible = True nor .EnableMultiplePageItems = false.
' If that's the case, then by process of elimination, this field
' must be the one that triggered the change, as changes to any of the
' others would have been identified in the code earlier.
Dim pf As PivotField
Dim lngFields As Long
lngFields = 0
On Error Resume Next ' Need this to handle DataFields and 'Values' field
For Each pf In pt.PivotFields
With pf
If .Orientation > 0 Then 'It's not hidden or a DataField
If .EnableMultiplePageItems And Not .AllItemsVisible Then
If Err.Number = 0 Then
'It *might* be this field
lngFields = lngFields + 1
sPossibles = sPossibles & .Name & ";"
Else: Err.Clear
End If
End If
End If
End With
Next
On Error GoTo 0
If lngFields = 1 Then PivotChange_EliminationCheck = Left(sPossibles, Len(sPossibles) - 1)
End Function
Function PivotChange_UndoCheck(pt As PivotTable, sPossibles) As String
Dim i As Long
Dim dicFields As Object 'This holds a list of all visible pivotfields
Dim dicVisible As Object 'This contains a list of all visible PivotItems for a pf
Dim varKey As Variant
Dim pf As PivotField
Dim pi As PivotItem
Dim bidentified As Boolean
Dim lngVisibleItems As Long
Application.EnableEvents = False
'Create master dictionary
Set dicFields = CreateObject("Scripting.Dictionary")
'Cycle through all pivotfields, excluding totals
For i = 0 To UBound(Split(sPossibles, ";")) - 1
'Create dicVisible: a dictionary for each visible PivotField that contain visible PivotItems
Set dicVisible = CreateObject("Scripting.Dictionary")
Set pf = pt.PivotFields(Split(sPossibles, ";")(i))
With pf
If .Orientation <> xlPageField Then
For Each pi In .VisibleItems
With pi
dicVisible.Add .Name, .Name
End With
Next pi
Else:
'Unfortunately the .visibleitems collection isn't available for PageFields
' e.g. SomePageField.VisibleItems.Count always returns 1
' So we'll have to iterate through the pagefield and test the .visible status
' so we can then record just the visible items (which is quite slow)
For Each pi In .PivotItems
With pi
If .Visible Then
dicVisible.Add .Name, .Name
End If
End With
Next pi
End If 'If .Orientation = xlPageField Then
'Write dicVisible to the dicFields master dictionary
dicFields.Add .Name, dicVisible
End With
Next i
Application.Undo
For Each varKey In dicFields.keys
Set pf = pt.PivotFields(varKey)
Set dicVisible = dicFields.Item(varKey)
'Test whether any of the items that were previously hidden are now visible
If pf.Orientation <> xlPageField Then
For Each pi In pf.VisibleItems
With pi
If Not dicVisible.exists(.Name) Then
PivotChange_UndoCheck = pf.Name
bidentified = True
Exit For
End If
End With
Next
Else 'pf.Orientation = xlPageField
lngVisibleItems = dicVisible.Count
i = 0
For Each pi In pf.PivotItems
With pi
If .Visible Then
If Not dicVisible.exists(.Name) Then
PivotChange_UndoCheck = pf.Name
bidentified = True
Exit For
Else: i = i + 1 'this is explained below.
End If
End If
End With
Next
' For non-PageFields, we know that the number of .VisibleItems hasn't changed.
' But we *don't* know that about Pagefields, and an increase in the amount of
' .VisibleItems won't be picked up by our Dictionary approach.
' So we'll check if the overall number of visible items changed
If Not bidentified And i > lngVisibleItems Then
PivotChange_UndoCheck = pf.Name
Exit For
End If
End If
If bidentified Then Exit For
Next
'Resore the original settings
With Application
.CommandBars(14).FindControl(ID:=129).Execute 'Standard Commandbar, Redo command
.EnableEvents = True
End With
End Function
End Sub

Excel VLOOKUP using String to look for data in a File. Goes too slow

This is the best I can get. Incase anyone searches and needs the best answer for this type of data pull. I had to break it down into sections; these work computers just can't handle this type of load. Max data pull is around 800 lines and takes around a minute to pull all the formula(s) and data. Thanks to the people below with thier help.
Sub Update()
Dim ScreenUpdateState As Boolean
Dim StatusBarShow As Boolean
Dim CalcState As Long
Dim EventState As Boolean
Dim ws As Worksheet
Dim location_string As String
Dim count As Integer
'Save the current state of Excel settings
ScreenUpdateState = Application.ScreenUpdating
StatusBarShow = Application.DisplayStatusBar
CalcState = Application.Calculation
EventState = Application.EnableEvents
'Change Excel to faster procedure settings
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
ActiveSheet.DisplayPageBreaks = False
Set ws = ThisWorkbook.Sheets("%")
location_string = Sheets("Driver(s)").Cells(5, "G").Text
For count = 7 To 139
Cells(count, "F").Formula = "=IFERROR((VLOOKUP($C" & count & ",'S:\xxxx\xxxxx\xxxxxx\xxxxx\xxxxxxxxxx\[xxxxxxxxxxxxxxxxxxxxxxxxxx.xlsx]" + location_string + "'!$A:$K,11,FALSE)),"" - "")"
Next count
'Restore Excel settings to original state
Application.ScreenUpdating = ScreenUpdateState
Application.DisplayStatusBar = StatusBarShow
Application.Calculation = CalcState
Application.EnableEvents = EventState
MsgBox ("Update Complete")
End Sub
Good luck!
Ross
Orignal thread:
Ok I have this now and it works. It however is to slow to be used as
this one code only runs maybe 1/16th of the required calculations and
takes a few minutes to complete. Anyone know a way to speed up the
process?
Sub Test()
Dim ws As Worksheet
Dim location_string As String
Dim count As Integer
Set ws = ThisWorkbook.Sheets("%")
location_string = ws.Cells(2, "E").Text
count = 7
While count < 138
Cells(count, "F").Formula = "=IFERROR((VLOOKUP($C" & count & ",
'S:\xxxx\xxxx\xxxx\xxxx\xxxxx\[xxxxxx.xlsx]" + location_string + "'!$A:$K,11,FALSE)),"" - "")"
count = count + 1
Wend
MsgBox ("Done")
End Sub
Below is the original post:
I have a list of values on another sheet that will create part of the
string I need:
=CONCATENATE ((INDEX('Driver(s)'!$B$1:$B$48,'Driver(s)'!$G$3,1)),"Epic")
this will set a cell to = 'O614Epic
now trying to add a Vlookup to pull from:
S:\xxxxxxxxxxxxxxx\xxxxxxxxx\xx\xx\xx\[Random File Name.xlsx]0614Epic'!$A:$K
Based on the drop down box, the ####Epic file will change to the
correct value as a string at the moment but can not get Vlookup to
pull from the correct workbook. I also need this to open non-opened
workbooks. Too much data to import into the Excel workbook itself.
Thanks.
Ross
If there's no getting around using VLOOKUP, skip to the Bonus Info. Instead of having VLOOKUP formulas recalculating with every change and slowing down your spreadsheet, you can use VBA to find and put the value in the cell instead of a formula. I did my best to tailor it to what you provided. Please let me know if you have questions on any parts.
Function WorksheetExists(ByVal WorksheetName As String) As Boolean
Dim Sht As Worksheet
For Each Sht In ActiveWorkbook.Worksheets
If Application.Proper(Sht.Name) = Application.Proper(WorksheetName) Then
WorksheetExists = True
Exit Function
End If
Next Sht
End Function
Sub RossQuestion()
Dim wbdata As Workbook
Dim ws As Worksheet
Dim Cell As Range
Dim location_string As String
Dim strcheck As String
Dim count As Integer
Set ws = ThisWorkbook.Sheets("%")
location_string = ws.Cells(2, "E").Text
count = 7
While count < 138
Set wbdata = Workbooks.Open("S:\xxxx\xxxx\xxxx\xxxx\xxxxx\xxxxxx.xlsx", , True)
If WorksheetExists(location_string) Then
Set Cell = wbdata.Sheets(location_string).Columns("A").Find(ws.Range("$C$" & count).Value, _
wbdata.Sheets(location_string).Range("A1"), xlFormulas, xlWhole, xlByRows, xlNext, False)
strcheck = Cell.Offset(0, 10).Value
If Len(Trim(strcheck)) <> 0 Then
ws.Cells(count, "F").Value = Cell.Offset(0, 10).Value
Else
ws.Cells(count, "F").Value = " - "
End If
Else
ws.Cells(count, "F").Value = " - "
End If
count = count + 1
wbdata.Close False
Wend
MsgBox "Done"
End Sub
Bonus Info:
If you're not wrapping your code in something like this, consider using this for all future VBA. The 1st tip in this link details these actions.
Dim ScreenUpdateState As Boolean
Dim StatusBarShow As Boolean
Dim CalcState As Long
Dim EventState As Boolean
'Save the current state of Excel settings
ScreenUpdateState = Application.ScreenUpdating
StatusBarShow = Application.DisplayStatusBar
CalcState = Application.Calculation
EventState = Application.EnableEvents
'Change Excel to faster procedure settings
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
ActiveSheet.DisplayPageBreaks = False
'<<<YOUR CODE HERE>>>
'Restore Excel settings to original state
Application.ScreenUpdating = ScreenUpdateState
Application.DisplayStatusBar = StatusBarShow
Application.Calculation = CalcState
Application.EnableEvents = EventState
Original Answer:
While you can reference data from other workbooks (even non-opened), your path in VLOOKUP's table_array argument has to be completely typed in.
So while VLOOKUP accepts...
=VLOOKUP('Driver(s)'!$G$3, 'S:\xxxxx\FileName.xlsx'!$A:$K, 3, FALSE)
it won't accept any calculations or concatenations in the table_array such as...
=VLOOKUP('Driver(s)'!$G$3, 'S:\xxxxx\ & O614Epic & .xlsx'!$A:$K, 3, FALSE)
=VLOOKUP('Driver(s)'!$G$3, 'S:\xxxxx\ & INDIRECT(B1) & Epic.xlsx'!$A:$K, 3, FALSE)
Anything other than the complete path string is considered too volatile. Same goes for MATCH INDEX. Unfortunately VLOOKUP isn't as dynamic as you'd like and ####Epic needs to be typed as O614Epic by you and not coming from another cell.
There's always VBA. Everything's possible with VBA.
Remove the loop and try this:
Sub Test()
Dim ws As Worksheet
Dim location_string As String
Dim myformula As String
With Application
.ScreenUpdating = False
.DisplayAlerts = False
End With
Set ws = ThisWorkbook.Sheets("%")
location_string = ws.Cells(2, "E").Value '~~> I'd suggest you use Value
myformula = "=IFERROR((VLOOKUP($C7,'S:\xxxx\xxxx\xxxx\xxxx\xxxxx\[xxxxxx.xlsx]" & _
location_string & "'!$A:$K,11,FALSE)),"" - "")"
Range("F7:F138").Formula = myformula
Msgbox "Done"
With Application
.ScreenUpdating = True
.DisplayAlerts = True
End With
End Sub
This takes 5 sec in my machine but it will differ specially if the target file is in a network server. HTH.

Resources