VBA change Case to look for LIKE string - vba

How can I change this code used to select page items in an XL pivotTable
Dim pvtSM1 As PivotTable
Dim pviSM1 As PivotItem
Dim pvfSM1 As PivotField
Set pvtSM1 = ActiveSheet.PivotTables("SM1")
' set Pivot field variable to "RESULT"
Set pvfSM1 = pvtSM1.PivotFields("RESULT")
' loop through all Pivot Items in "RESULT" Pivot Field
For Each pviSM1 In pvfSM1.PivotItems
Select Case pviSM1.Name
Case "4K2..00", "4K21.00", "4K22.00", "4K23.00", "4K41.00", "4K42.00", "4K43.00", "4KA1.00", "4KA2.00"
pviSM1.Visible = True
Case Else
pviSM1.Visible = False
End Select
Next pviSM1
End With
...into a LIKE "4K2*", "4K4*", "4KA*"
To save me adding all the exact codes

Dim pvtSM1 As PivotTable
Dim pviSM1 As PivotItem
Dim pvfSM1 As PivotField
I swear, I read them 5 times (okay, 3) before I figured out the [single-character] difference between them. And I've no idea whatsoever what SM1 might stand for. Suggestion:
Dim pvtTable As PivotTable
Dim pvtItem As PivotItem
Dim pvtField As PivotField
Use meaningful names that you can read out loud without sounding like an Ewok.
Better suggestion - declare variables closer to where you're using them, instead of in a wall of declarations at the top of your procedure; and then use Comintern's suggestion to get rid of the Select Case block altogether:
Dim pvtTable As PivotTable
Set pvtTable = MyPivotTableSheet.PivotTables("SM1") ' don't assume what the ActiveSheet is
Dim pvtField As PivotField
Set pvtField = pvtTable.PivotFields("RESULT")
Dim pvtItem As PivotItem
For Each pvtItem In pvtField.PivotItems
pvtItem.Visible = pvtItem.Name Like "4K[24A]*"
Next
And heck, naming is hard - don't name things per their type, name things per their purpose.
The code throws a runtime error if PivotTables("SM1") doesn't exist, or if PivotFields("RESULT") doesn't refer to anything. The best way to avoid this is to separate the concerns into small, specialized functions, that do one thing and that do it well:
Private Function FindPivotTable(ByVal sheet As Worksheet, ByVal name As String) As PivotTable
If sheet Is Nothing Then Err.Raise 5, "FindPivotTable", "'sheet' argument cannot be Nothing"
On Error Resume Next
Dim result As PivotTable
Set result = sheet.PivotTables(name)
On Error GoTo 0
Err.Clear
If result Is Nothing Then
Err.Raise 9, "FindPivotTable", "Could not locate pivot table '" & name & "' on worksheet '" & sheet.Name & "'."
Exit Function
End If
Set FindPivotTable = result
End Function
Private Function FindPivotField(ByVal pivot As PivotTable, ByVal name As String) As PivotField
If pivot Is Nothing Then Err.Raise 5, "FindPivotField", "'pivot' argument cannot be Nothing"
On Error Resume Next
Dim result As PivotField
Set result = pivot.PivotFields(name)
On Error GoTo 0
Err.Clear
If result Is Nothing Then
Err.Raise 9, "FindPivotField", "Could not locate pivot field '" & name & "' in pivot table '" & pivot.Name & "'."
Exit Function
End If
Set FindPivotField = result
End Function
Now your procedure can focus on its task, and you can reuse these specialized functions instead of either writing frail error-prone code, or copy-pasting the same fail-safe code over and over again:
On Error GoTo ErrHandler
Dim sourcePivot As PivotTable
Set sourcePivot = FindPivotTable(MyPivotTableSheet, "SM1")
If sourcePivot Is Nothing Then Exit Sub
Dim resultField As PivotField
Set resultField = FindPivotField(sourcePivot, "RESULT")
If resultField Is Nothing Then Exit Sub
Dim item As PivotItem
For Each item In resultField.PivotItems
item.Visible = item.Name Like "4K[24A]*"
Next
Exit Sub
ErrHandler:
MsgBox "Error in '" & Err.Source & "': " & Err.Description
...yet it still feels bloated, so I'd take that and parameterize it, so that it works off targetField - and since that's supposed to be "RESULT", I'll call the parameter resultField:
Private Sub SetItemVisibilityByPattern(ByVal resultField As PivotField, ByVal likePattern As String)
If resultField Is Nothing Then Exit Sub
Dim item As PivotItem
For Each item In resultField.PivotItems
item.Visible = item.Name Like likePattern
Next
End Sub
And now it's the caller's responsibility to figure out how resultField gets there, and you're left with a very, very simple procedure that does one single thing.
Like works, for basic pattern searches. When you start having need for more complicated patterns (e.g. match "4K2*" but also "685*"), consider using a regular expression pattern (here referencing the Microsoft VBScript Regular Expressions 5.5 library):
Private Sub SetItemVisibilityByPattern(ByVal resultField As PivotField, ByVal regexPattern As String)
If resultField Is Nothing Then Exit Sub
With New RegExp
.Pattern = regexPattern
Dim item As PivotItem
For Each item In resultField.PivotItems
item.Visible = .Execute(item.Name).Count > 0
Next
End With
End Sub
With a single regular expression pattern you can match anything you like, with as many possible alternatives as you need:
SetItemVisibilityByPattern(resultField, "(4K[24A]|685|923).*")

If you really want select case with likes here's bulky example:
Sub test()
Dim str As String
str = InputBox("feed me a string")
Select Case str Like "4K[24A]*"
Case True
Call MsgBox("Da!")
'pviSM1.Visible = True
Case False
Call MsgBox("Net!")
'pviSM1.Visible = false
End Select
End Sub

Related

Nested dictionary object not found

I am attempting to take some table, a list of fields to group by, and a number field as input, to produce an output table that contains the group-by fields and medians for the number field.
I have this working for a single field to group by, but not multiple fields.
I am using nested dictionaries to eventually aggregate my grouped-field values and their medians.
When I try to access the inner dictionary I get a
'424' Run-time error: Object required
Once I put the inner dictionary in the outer dictionary I cannot access it like one would suspect.
Public Sub MedianByGroups(ByVal inputTable As String, ByVal strField As String, _
ByVal strGroup As String, ByVal outputTable As String, _
Optional ByVal strCriteria As String)
DoCmd.SetWarnings False
Dim db As DAO.Database: Set db = CurrentDb()
Dim rstDomain As DAO.Recordset
Dim strSQL As String
Dim varMedian As Long: varMedian = 0
Dim intRecords As Long
Dim splitgroup As Variant
splitgroup = Split(strGroup, ", ")
strSQL = "SELECT DISTINCT " & strGroup & " INTO TMP_GROUPINGTABLE FROM " & _
inputTable & ";"
DoCmd.RunSQL (strSQL)
Set rstDomain = db.OpenRecordset("TMP_GROUPINGTABLE")
Dim i As Integer: i = 0
Dim group_dict As New Dictionary
Dim tmp_dict As Dictionary
Set tmp_dict = New Dictionary
If Not (rstDomain.EOF And rstDomain.BOF) Then
rstDomain.MoveFirst
Do Until rstDomain.EOF = True
For Each Label In splitgroup
tmp_dict.Add Label, rstDomain.Fields(Label).Value
Next Label
group_dict.Add i, tmp_dct
Set tmp_dict = New Dictionary
i = i + 1
rstDomain.MoveNext
Loop
End If
rstDomain.Close
For Each key In group_dict.Keys
For Each addlKey In group_dict(key).Keys
Debug.Print addlKey, group_dict(key)(addlKey)
Next addlKey
Next key
DoCmd.SetWarnings True
end sub
If one had a table, called "MYTABLE", that looked like this:
GROUP1 GROUP2 VAL1
A C 400
B D 500
One would call this sub like:
call MedianByGroups("MYTABLE", "VAL1', "GROUP1, GROUP2", "OUTPUTTABLE")
This doesn't do the Median or any other parts yet, as I am hitting this stumbling point with the dictionaries.
Where it fails is the inner for loop on that nested for loop near the end.
For Each addlKey In group_dict(key).Keys
Edit: As ComputerVersteher pointed out, the issue was a spelling mistake I had made. The solution to avoid this as Andre mentioned is to add the option explicit to the module.
Not a complete answer -- I set up an example without the MS Access-specific parts and the logic in your code seems to run fine. As near as I can tell, there's no real difference in the logic below and your code above. Does this example run for you?
Option Explicit
Sub TestingNestedDictionaries()
Dim group_dict As New Dictionary
Dim tmp_dict As Dictionary
Set tmp_dict = New Dictionary
Dim labels As Variant
labels = Array("red", "orange", "yellow", "green", "blue", "indigo", "violet")
Dim i As Long
For i = 1 To 5
Dim label As Variant
For Each label In labels
tmp_dict.Add label, label & "-" & i
Next label
group_dict.Add i, tmp_dict
Set tmp_dict = New Dictionary
Next i
'--- pull out one color
' Const THIS_COLOR As String = "green"
' Dim group As Variant
' For Each group In group_dict
' Dim subDict As Dictionary
' Set subDict = group_dict(group)
' Debug.Print subDict(THIS_COLOR)
' Next group
Dim key As Variant
Dim addlKey As Variant
For Each key In group_dict.Keys
For Each addlKey In group_dict(key).Keys
Debug.Print addlKey, group_dict(key)(addlKey)
Next addlKey
Next key
End Sub

Passing Values in VBA

In the code I am posting, I am using a check box called "ACDS Test" and whenever it is checked it creates a sheet, then when it becomes unchecked it calls the upper function and deletes the sheet.
I am trying to add a message box that essentially works like a fail safe to ensure they want to delete the page. If they say they do not want to delete the page then I want the checkbox to stay checked.
For some reason I am getting this error message when I try to pass the value to make sure the checkbox stays checked and I cannot figure out why.
The error comes up on the line:
Sub ACDSTest_Click(CorrectValue As Integer)
And the specific error is: "Compile error: Procedure Declaration does not match description of event or procedure having the same name".
Any help is much appreciated! IF any more clarification is needed please feel free to ask!
Sub DeleteWorksheet(NameSheet As String)
Dim Ans As Long
Dim t As String
Dim CorrectValue As Integer
Dim i As Long, k As Long
k = Sheets.Count
Ans = MsgBox("Would you like to take this test off of the form?", vbYesNo)
Select Case Ans
Case vbYes
'Code reads through each page and finds one with corresponding name to string t
'Once it finds the correct page, it deletes it
For i = k To 1 Step -1
t = Sheets(i).Name
If t = NameSheet Then
Sheets(i).Delete
End If
Next i
CorrectValue = 0
Case vbNo
CorrectValue = 1
End Select
End Sub
Sub ACDSTest_Click(CorrectValue As Integer)
Dim NameSheet As String
Dim NameValue As String
NameSheet = "ACDS"
NameValue = "ACDS Test"
If ACDSTest.Value = True Then
CreateWorksheet (NameSheet), (NameValue)
Worksheets("Sheet1").Activate
Else
DeleteWorksheet (NameSheet)
If CorrectValue = 1 Then
ActiveSheet.Shapes("ACDS Test").ControlFormat.Value = 1
End If
End If
End Sub
The issue here is that the CorrectValue variable as you define it in DeleteWorksheet does not exist in the context of the
variable does not exist in context of the ACDSTest_Click subroutine. This is because variables defined within subroutines or functions are local to those functions. To correct this I would convert DeleteWorksheet to a function such as the below.
Further, the event that fires Private Sub ACDSTest_Click() cannot handle passing a value to that function, so changing it to Sub ACDSTest_Click(CorrectValue As Integer) causes an error.
Function DeleteWorksheet(ByVal SheetName As String) As Boolean
On Error GoTo SheetDNE
SheetName = Sheets(SheetName).Name 'Check if sheet exists w/o other objects
On Error GoTo 0
Select Case MsgBox("Would you like to take this test off of the form?", vbYesNo)
Case vbYes
Application.DisplayAlerts = False
Sheets(SheetName).Delete
Application.DisplayAlerts = True
DeleteWorksheet = True
Case Else: DeleteWorksheet = False
End Select
Exit Function 'Exit The Function w/o error
SheetDNE: 'Sheet Does Not Exist
MsgBox "The indicated sheet, " & SheetName & ", does not exist", vbOKOnly
End Function
And
Private Sub ACDSTest_Click()
Dim NameSheet As String
Dim NameValue As String
NameSheet = "ACDS"
NameValue = "ACDS Test"
If ACDSTest.Value = True Then
CreateWorksheet (NameSheet), (NameValue)
Worksheets("Sheet1").Activate
Else
If Not DeleteWorksheet(NameSheet) Then _
ActiveSheet.Shapes("ACDS Test").ControlFormat.Value = 1
End If
End Sub

Type mismatch error in VBA when adding data to textbox

I have a TextBox and a ListBox with a list of various cities being populated from an Excel file
Now each city has one of two options: either within territory or outside. I want that option to be shown in textBox
I tried something like this :
Private Sub CommandButton1_Click()
TextBox2.Value = Application.VLookup(Me.ListBox1.Text,Sheets("Sheet1").Range("B:C"), 2, False)
End Sub
But am getting error stating that :
Run Time Error 2147352571 (80020005) . Could not set Value property. Type mismatch.
My excel file is something like this :
Let say your data are stored in Sheet1. You want to bind these data to ListBox1 on UserForm. I'd suggest to use custom function to load data instead of binding data via using RowSource property. In this case i'd suggest to use Dictionary to avoid duplicates.
See:
Private Sub UserForm_Initialize()
Dim d As Dictionary
Dim aKey As Variant
Set d = GetDistinctCitiesAndTerritories
For Each aKey In d.Keys
With Me.ListBox1
.AddItem ""
.Column(0, .ListCount - 1) = aKey
.Column(1, .ListCount - 1) = d.Item(aKey)
End With
Next
End Sub
'needs reference to Microsoft Scripting Runtime!
Function GetDistinctCitiesAndTerritories() As Dictionary
Dim wsh As Worksheet
Dim dict As Dictionary
Dim i As Integer
Set wsh = ThisWorkbook.Worksheets("Sheet1")
Set dict = New Dictionary
i = 2
Do While wsh.Range("A" & i) <> ""
If Not dict.Exists(wsh.Range("B" & i)) Then dict.Add wsh.Range("B" & i), wsh.Range("C" & i)
i = i + 1
Loop
Set GetDistinctCitiesAndTerritories = dict
End Function
After that, when user clicks on ListBox, city and territory are displayed in corresponding textboxes.
Private Sub ListBox1_Click()
Me.TextBoxCity = Me.ListBox1.List(Me.ListBox1.ListIndex, 0)
Me.TextBoxTerritory = Me.ListBox1.List(Me.ListBox1.ListIndex, 1)
End Sub
Note: code was written straight from the head, so it can contains errors!
The problem is likely that you aren't checking to see to see if the call to Application.VLookup succeeded. Most values returned can be successfully cast to a String - with one important exception: If the VLookup returns an error, for example it doesn't find Me.ListBox1.Text - it can't cast the Variant returned directly.
This should demonstrate:
Private Sub ReturnsOfVLookup()
Dim works As Variant, doesnt As String
works = Application.VLookup("Something not found", _
Sheets("Sheet1").Range("B:C"), 2, False)
Debug.Print works
On Error Resume Next
doesnt = Application.VLookup("Something not found", _
Sheets("Sheet1").Range("B:C"), 2, False)
If Err.Number <> 0 Then
Debug.Print Err.Description
Else
Debug.Print doesnt 'We won't be going here... ;-)
End If
End Sub

Is there a Way to test if a Chart in Excel has its series stacked or not

To my understanding Chars have the ChartType as XlChartType property that but that is a long list of Enumerated Values. Is there a way to test if chart is using stacked Series without listing them all?
I am trying to avoid following scenario:
Select ActiveChart.ChartType
Case xlAreaStacked
....
Case xlBarStacked
....
Case xlColumnStacked
....
... 1000 more Cases ....
End Select
Some sample code below to produce a dictionary object with the members of the requested Enum.
Code adapted from dlmille's answer here: http://www.experts-exchange.com/Software/Office_Productivity/Office_Suites/MS_Office/Excel/Q_27613392.html
Sub tester()
Dim dict
Set dict = GetEnumLookup("Excel", "XlChartType")
If Not dict Is Nothing Then
'get string from numeric value and see if it contains "stacked"
Debug.Print UCase( dict(XlChartType.xl3DAreaStacked) ) Like "*STACKED*"
Debug.Print UCase( dict(XlChartType.xl3DArea) ) Like "*STACKED*"
Else
MsgBox "Enum not recognised!"
End If
End Sub
'VB Project References required:
' Microsoft Visual Basic for Applications Extensibility
' TypeLib Information
Function GetEnumLookup(LibName As String, sEnumName As String) As Object
Dim rv As Object
Dim tl As TLI.TypeLibInfo
Dim mi As TLI.MemberInfo
Dim tiEnum As TLI.TypeInfo
Dim vbProj As VBProject, oVBProjRef As Reference
Set vbProj = ThisWorkbook.VBProject
For Each oVBProjRef In vbProj.References
'Debug.Print oVBProjRef.Name, oVBProjRef.FullPath
If oVBProjRef.Name = LibName Then
Set tl = New TypeLibInfo
tl.ContainingFile = oVBProjRef.FullPath
On Error Resume Next
Set tiEnum = tl.GetTypeInfo(sEnumName)
On Error GoTo 0
If Not tiEnum Is Nothing Then
Set rv = CreateObject("scripting.dictionary")
For Each mi In tiEnum.Members
rv.Add mi.Value, mi.Name
'or if you want to map the other direction...
'rv.Add mi.Name, mi.Value
Next mi
End If
Exit For
End If
Next oVBProjRef
Set GetEnumLookup = rv
End Function

Excel VBA Run Time Error '424' object required

I am totally new in VBA and coding in general, am trying to get data from cells from the same workbook (get framework path ...) and then to start application (QTP) and run tests.
I am getting this error when trying to get values entered in excel cells:
Run Time Error '424' object required
I believe I am missing some basic rules but I appreciate your help. Please see below the part of code in question:
Option Explicit
Private Sub RunTest_Click()
Dim envFrmwrkPath As Range
Dim ApplicationName As Range
Dim TestIterationName As Range
'Dim wb As Workbook
'Dim Batch1 As Worksheets
Dim objEnvVarXML, objfso, app As Object
Dim i, Msgarea
Set envFrmwrkPath = ActiveSheet.Range("D6").Value ' error displayed here
Set ApplicationName = ActiveSheet.Range("D4").Value
Set TestIterationName = ActiveSheet.Range("D8").Value
The first code line, Option Explicit means (in simple terms) that all of your variables have to be explicitly declared by Dim statements. They can be any type, including object, integer, string, or even a variant.
This line: Dim envFrmwrkPath As Range is declaring the variable envFrmwrkPath of type Range. This means that you can only set it to a range.
This line: Set envFrmwrkPath = ActiveSheet.Range("D6").Value is attempting to set the Range type variable to a specific Value that is in cell D6. This could be a integer or a string for example (depends on what you have in that cell) but it's not a range.
I'm assuming you want the value stored in a variable. Try something like this:
Dim MyVariableName As Integer
MyVariableName = ActiveSheet.Range("D6").Value
This assumes you have a number (like 5) in cell D6. Now your variable will have the value.
For simplicity sake of learning, you can remove or comment out the Option Explicit line and VBA will try to determine the type of variables at run time.
Try this to get through this part of your code
Dim envFrmwrkPath As String
Dim ApplicationName As String
Dim TestIterationName As String
Simply remove the .value from your code.
Set envFrmwrkPath = ActiveSheet.Range("D6").Value
instead of this, use:
Set envFrmwrkPath = ActiveSheet.Range("D6")
You have two options,
-If you want the value:
Dim MyValue as Variant ' or string/date/long/...
MyValue = ThisWorkbook.Sheets(1).Range("A1").Value
-if you want the cell object:
Dim oCell as Range ' or object (but then you'll miss out on intellisense), and both can also contain more than one cell.
Set oCell = ThisWorkbook.Sheets(1).Range("A1")
Private Sub CommandButton1_Click()
Workbooks("Textfile_Receiving").Sheets("menu").Range("g1").Value = PROV.Text
Workbooks("Textfile_Receiving").Sheets("menu").Range("g2").Value = MUN.Text
Workbooks("Textfile_Receiving").Sheets("menu").Range("g3").Value = CAT.Text
Workbooks("Textfile_Receiving").Sheets("menu").Range("g4").Value = Label5.Caption
Me.Hide
Run "filename"
End Sub
Private Sub MUN_Change()
Dim r As Integer
r = 2
While Range("m" & CStr(r)).Value <> ""
If Range("m" & CStr(r)).Value = MUN.Text Then
Label5.Caption = Range("n" & CStr(r)).Value
End If
r = r + 1
Wend
End Sub
Private Sub PROV_Change()
If PROV.Text = "LAGUNA" Then
MUN.Text = ""
MUN.RowSource = "Menu!M26:M56"
ElseIf PROV.Text = "CAVITE" Then
MUN.Text = ""
MUN.RowSource = "Menu!M2:M25"
ElseIf PROV.Text = "QUEZON" Then
MUN.Text = ""
MUN.RowSource = "Menu!M57:M97"
End If
End Sub