VBA to Add Measure to PowerPivot Pivot Table - vba

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

Related

Use VBA to select and deselect multiple slicer items (OLAP data)

I am working on a script which selects only the needed slicer items. I tried using .SlicerItems.Selected = True / False for selecting and deselecting but I am using an OLAP data source in which case .Selected is read-only. The slicer items are in the format of YYYYWW so 7th week of 2018 would be 201807.
I recorded a macro selecting some slicer items and this is what it gave me:
Sub Macro2()
ActiveWorkbook.SlicerCaches("Slicer_YYYYWW").VisibleSlicerItemsList = Array( _
"[Results].[YYYYWW].&[201726]", "[Results].[YYYYWW].&[201727]", _
"[Results].[YYYYWW].&[201728]", "[Results].[YYYYWW].&[201729]", _
"[Results].[YYYYWW].&[201730]", "[Results].[YYYYWW].&[201731]", _
"[Results].[YYYYWW].&[201732]", "[Results].[YYYYWW].&[201733]", _
"[Results].[YYYYWW].&[201734]", "[Results].[YYYYWW].&[201735]", _
"[Results].[YYYYWW].&[201736]", "[Results].[YYYYWW].&[201737]", _
"[Results].[YYYYWW].&[201738]", "[Results].[YYYYWW].&[201739]", _
"[Results].[YYYYWW].&[201740]", "[Results].[YYYYWW].&[201741]", _
"[Results].[YYYYWW].&[201742]", "[Results].[YYYYWW].&[201743]", _
"[Results].[YYYYWW].&[201744]", "[Results].[YYYYWW].&[201745]", _
"[Results].[YYYYWW].&[201746]", "[Results].[YYYYWW].&[201747]", _
"[Results].[YYYYWW].&[201748]", "[Results].[YYYYWW].&[201749]", _
"[Results].[YYYYWW].&[201750]", "[Results].[YYYYWW].&[201751]", _
"[Results].[YYYYWW].&[201801]", "[Results].[YYYYWW].&[201802]", _
"[Results].[YYYYWW].&[201803]")
End Sub
So I tried following this template and create an array like that. This is how far I have gotten:
Sub arrayTest()
Dim startDate As Long
Dim endDate As Long
Dim n As Long
Dim i As Long
Dim strN As String
Dim sl As SlicerItem
Dim strArr As Variant
Dim dur As Long
Dim result As String
endDate = Range("C17").Value ' endDate is the last SlicerItem to be selected
startDate = Range("G17").Value ' startDate is the first SlicerItem to be selected
dur = Range("C19").Value ' duration is the the number of SlicerItems to be selected
i = 0
ReDim strArr(dur) As Variant
With ActiveWorkbook.SlicerCaches("Slicer_YYYYWW")
' .ClearManualFilter
For n = startDate To endDate
strN = CStr(n) ' convert n to string
If n = 201753 Then ' this is needed for when the year changes
strN = CStr(201801)
n = 201801
End If
strArr(i) = """[Results].[YYYYWW].&[" & strN & "]""" ' write string into array
i = i + 1
' For Each sl In .SlicerCacheLevels(1).SlicerItems
' If sl.Name = strN Then
' sl.Selected = True
' Else
' sl.Selected = False ' this is read-only for OLAP data so it's not working
' End If
' Next
Next
MsgBox Join(strArr, ", ") ' the MsgBox returns the correct string to be applied to select the right slicer items
.VisibleSlicerItemsList = Join(strArr, ", ") ' Error 13: Type mismatch
End With
End Sub
Currently, the code gives Error 13: Type mismatch on .VisibleSlicerItemsList = Join(strArr, ", "), which is also commented. So I'm guessing that either dimensioning strArr as Variant is wrong, the data is not inserted correctly into strArr or it's just impossible to do it this way. In the case of the latest one, how should I do it?
The part commented out on lines 29-35 does not work as it gives the usual error of Application-defined or object-defined error (1004) on sl.Selected = False.
I had a similar issue to overcome. Which I resolved using the following code:
Sub show_SlicerItems()
Dim sc As SlicerCache
Dim sL As SlicerCacheLevel
Dim si As SlicerItem
Dim slicerItems_Array()
Dim i As Long
Application.ScreenUpdating = False
Set sc = ActiveWorkbook.SlicerCaches("Slicer_Name")
Set sL = sc.SlicerCacheLevels(1)
ActiveWorkbook.SlicerCaches("Slicer_Name").ClearManualFilter
i = 0
For Each si In sL.SlicerItems
ReDim Preserve slicerItems_Array(i)
If si.Value <> 0 Then
slicerItems_Array(i) = si.Name
i = i + 1
End If
Next
sc.VisibleSlicerItemsList = Array(slicerItems_Array)
Application.ScreenUpdating = True
End Sub
You need to feed .VisibleSlicerItemsList an array, not a string. Ditch the Join.
And your strArr assignment should be like this: strArr(i) = "[Results].[YYYYWW].&[" & strN & "]" i.e. you don't need to pad it out with extra "
Edit: Out of interest, I happen to be building a commercial add-in that is effectively a Pop-up Slicer, that allows you to filter an OLAP PivotTable to show all items between a range like you are attempting to do. It also lets you filter on wildcards, crazy combinations of AND and OR, and filter on lists stored in external ranges.
Here's a screenshot of it in action. Note there is a search bar up the top that lets you use < or > together to set lower and upper limits, which is what I've done in the current Search. And you can see the result: it has correctly identified the 14 items from the PivotField that fit the bill.
All I need to do to filter the PivotTable on these is click the "Filter on selected items" option, and it does just that:
But working out how to do this - particularly given the limitations of the PivotTable object model (especially where OLAP PivotTables are concerned) was a VERY long term project, with many, many hurdles to overcome to make it work seamlessly. I can't share the code I'm afraid, as this is a commercial offering that I aim to release shortly. But I just wanted to highlight that while this is certainly possible, you are going to be biting off quite a bit if you want it to not throw errors when items don't exist.
Forget my other answer...you can use the Labels Filter to do this easily, provided the field of interest is in the PivotTable as either a Rows or Columns field. Fire up the Macro Recorder, and do the following:
...and you'll see that the PivotTable gets filtered:
...and the resulting code is pretty simple:
ActiveSheet.PivotTables("PivotTable1").PivotFields("[Table1].[YYYYWW].[YYYYWW]" _
).PivotFilters.Add2 Type:=xlCaptionIsBetween, Value1:="201726", Value2:= _
"201803"
Use this:
Sub seleciona_lojas()
Dim strArr()
Dim x As Long
Dim i As Long
For x = 2 To 262
ReDim Preserve strArr(i)
strArr(i) = "[Lojas].[Location_Cd].&[" & Planilha5.Range("B" & x).Value & "]"
i = i + 1
Next x
ActiveWorkbook.SlicerCaches("SegmentaĆ§Ć£odeDados_Location_Cd1").VisibleSlicerItemsList = strArr
End Sub

VLookUp not working - Property could not be assigned

My problem is, that when using the VlookUp I do get the Error:
The VLookup-Property of the WorksheetFunction-Object could not be assigned
' LookUp missing Data
Dim lookupRange As Range
Set lookupRange = Sheets("Kundenlisten HLK 2018").Range("A1:Y10354")
' Fill in Companyname
Dim tmp As String
tmp = Values(0)
tmp = TrueTrim(tmp)
testing.Cells(8, counter) = Application.WorksheetFunction.VLookup(tmp, lookupWS.Range("A2:Y10354"), 2, False)
Values = None
counter = counter + 1
lookupWS is the Name of the Worksheet
As you can see the table I am trying to lookup is filled with values from A to Y. The first column is the key I am trying to look up, but then the error from above occurs. The tmp variable is a String with a unique ID to look up the missing values, while the "2" is supposed to be the company name in the second column of the Range.
I looked up on the docs, but the types and everything are fine, I even checked while debugging.
testing.Cells(8, counter) can't be the source of the problem aswell, since I am using it before like this
testing.Cells(28, counter) = Left(mail.ReceivedTime, 10)
and it works
It's difficult to know what the problem is without any data, but here's something to help you in the right direction.
It's better to use Find and Offset than
WorksheetFunction.Vlookup in VBA
Something like this gives you exactly the same result, but you have much more control:
Sub Test()
Dim valueFound As Range
Set valueFound = lookupWS.Range("A2:A10354").Find(What:="Something", lookat:=xlWhole) 'xlWhole is equivalent to FALSE in VLOOKUP
If valueFound Is Nothing Then
MsgBox "Nothing found"
Else
MsgBox valueFound.Offset(0, 1) 'offsetting by 1 is equivalent to using 2 in a VLOOKUP
End If
End Sub

Excel VBA using Combobox with Index Match

I have an Excel VBA UserForm Combobox for scanning asset tags to compare against a site baseline held in Sheet1. There can be upto 50,000+ assets. The named ranges are all correct.
I want the loop to fill the "Found" Asset attribute Textboxes for Type, Serial, MakeModel, Location & PrinterHost.
The code is below without the additional index match lookups for extra asset attributes as the process will be the same. Help appreciated as I'm not sure where I'm going wrong. Thanks in advance.
Private Sub ComboScanTag_Change()
Dim x As Integer
Dim AssetCount As Long
Dim BASELINE As Range
Dim AssetID As Range
Dim FoundType As Variant
Dim FoundSerial As Variant
Dim FoundMakeModel As Variant
Dim FoundLocation As Variant
Dim FoundPrinterHostName As Variant
If Me.ComboScanTag.Value = "" Then 'ScanTag has no value
MsgBox "Asset not Found - Re-Scan or enter New Asset details"
Me.ComboScanTag.SetFocus
End If
If Me.ComboScanTag.Value <> "" Then 'ScanTag has a value
Application.ScreenUpdating = False 'Turn off screen updating to speed app
For x = 1 To AssetCount 'Number of loop iterations from Baseline Assets Count D1 cell
FoundType = Application.Index("BASELINE", Application.Match(Me.ComboScanTag.Value, "AssetID", False), 3)
If Not IsError(FoundType) = False Then 'if error value in lookup return 0
Me.txtFoundType.Value = FoundType 'Fill textbox FoundType with lookup value from baseline
Else
On Error GoTo 0 'reset error handler
FoundSerial = Application.Index("BASELINE", Application.Match(Me.ComboScanTag.Value, "AssetID", False), 11)
If Not IsError(FoundSerial) = False Then
Me.txtFoundSerial.Value = FoundSerial
End If
Next x
End If
Application.ScreenUpdating = True
End Sub
AssetCount is not initialized. You need to initialize it before you use it like AssetCount = 10.
BASELINE and AssetID are not set as well.
If BASELINE and AssetID are named ranges, you cannot use it the way you do in Application.Index or Application.Match.
You need to pass it as object and not as string like this:
Set BASELINE = ThisWorkbook.Names("BASELINE").RefersToRange
Set AssetID = ThisWorkbook.Names("AssetID").RefersToRange
Then you can use it like this in Application.Index and Match:
With Application
FoundType = .Index(BASELINE, .Match(Me.ComboScanTag.Value, AssetID, False), 3)
End With

VBA in Excel: writing cell value to a column if it doesn't already exist there

Ok, so I have a table of names in one column and corresponding numbers in another. Most names appear more than once and each time with a different number. The table is likely to be added to in the future. I'm trying to write a VBA macro that will output each name once and the total sum of the numbers attached to them on a separate sheet. I haven't used VBA in like 8 months and I'm really rusty. Suggestions?
remember to add the reference microsoft scripting runtime, i believe. u would need to use dictionary here.
Sub test()
Dim var As Variant
Dim rng As Range
Set rng = Range("A1:A100")
Dim dico As Dictionary
Set dico = New Dictionary
For Each var In rng.Cells
if dico.Exists(var.value) Then
dico(var.value) = dico(var.value) + var.offset(0,1).value
else
dico.Add var.value, var.offset(0,1).value
end if
Next var
Set rng = Range("C1")
Dim i as double
i = 0
For Each var In dico.keys
rng.offset(i).value = var
rng.offset(i,1).value = dico(var)
Next var
End sub

Elegant way to highlight chart data series in Excel

I want to outline the chart data range source(s) in a table, in much the same way that the GUI will outline a range in blue if the chart data series is clicked. The user can choose various chart views and the range highlight colours for each data series need to match those displayed in the chart.
For the record, here are the methods I considered:
Parse the chart series values string and extract the data range
Do a lookup on a table that stores information on the ranges and the colours to be used
In the end I went with option 2 as is seemed easier to implement and to properly manage the colours I would probably have to store them for method 1 anyway, negating its benefits.
The highlight procedure is called from the Worksheet_Change event, a lookup is done on the chart name, the ranges and colours pulled from the table and then the cell formatting is carried out. The limitation of this method is that the range/colour data for each new chart view must be pre-calculated. This isn't much of a problem for my current implementation, but my be a limiting factor in future use where the charts might be more dynamic.
So although I've got a version of this working fine, I'm sure there must be a more elegant way of achieving this.
Any suggestions?
Edit:
OK, this seems to handle more cases better. The triggering code is the same, but here is new code for the module:
Function SeriesRange(s As Series) As Range
Dim sf As String, fa() As String
sf = s.Formula
sf = Replace(sf, "=SERIES(", "")
If sf = "" Then
Set SeriesRange = Nothing
Exit Function
End If
fa = Split(sf, ",")
Set SeriesRange = Range(fa(2))
End Function
Sub x(c As Chart)
Dim sc As Series
Dim sr As Range
If SeriesRange(c.SeriesCollection(1)) Is Nothing Then
Exit Sub
End If
Set sr = SeriesRange(c.SeriesCollection(1))
sr.CurrentRegion.Interior.ColorIndex = xlNone
For Each sc In c.SeriesCollection
If sc.Interior.Color > 1 Then
SeriesRange(sc).Interior.Color = sc.Interior.Color
ElseIf sc.Border.ColorIndex > 1 Then
SeriesRange(sc).Interior.Color = sc.Border.Color
ElseIf sc.MarkerBackgroundColorIndex > 1 And sc.MarkerBackgroundColorIndex < 57 Then
SeriesRange(sc).Interior.ColorIndex = sc.MarkerBackgroundColorIndex
ElseIf sc.MarkerForegroundColorIndex > 1 And sc.MarkerForegroundColorIndex < 57 Then
SeriesRange(sc).Interior.ColorIndex = sc.MarkerForegroundColorIndex
Else
MsgBox "Unable to determine chart color for data series " & sc.Name & " ." & vbCrLf _
& "It may help to assign a color rather than allowing AutoColor to assign one."
End If
Next sc
End Sub
/Edit
This is probably more barbaric than elegant, but I think it does what you want. It involves your first bullet point to get the range from the Series object, along with a sub to run through all the Series objects in the SeriesCollection for the chart. This is activated on Chart_DeActivate. Most of this code is jacked - see comments for sources.
In a module:
Function SeriesRange(s As Series) As Range
Dim sf As String, fa() As String
Dim i As Integer
Dim result As Range
sf = s.Formula
sf = Replace(sf, "=SERIES(", "")
fa = Split(sf, ",")
Set SeriesRange = Range(fa(2))
End Function
Sub x(c As Chart)
Dim sc As Series
Dim sr As Range
Set sr = SeriesRange(c.SeriesCollection(1))
sr.CurrentRegion.Interior.ColorIndex = xlNone
For Each sc In c.SeriesCollection
SeriesRange(sc).Interior.Color = sc.Interior.Color
Next sc
End Sub
In the ThisWorkbook object module:
' Jacked from C Pearson http://www.cpearson.com/excel/Events.aspx '
Public WithEvents CHT As Chart
Private Sub CHT_Deactivate()
x CHT
End Sub
Private Sub Workbook_Open()
Set CHT = Worksheets(1).ChartObjects(1).Chart
End Sub
Have you tried using Conditional Formatting?