I have a list of names that I need to filter a pivot table on. I use a Case Select to make the pivot items that are equal to the names visible and all other not visible. Can I make a one time array or list of these 15 names and call the list/array name within the code instead of using the 15 names in multiple locations? I have to fun a pivot table and sort on these names many times.
This is what I am trying to avoid. It works as is, but I'm trying to save myself headaches in the future with modifications.
Set table = Worksheets("Sheet2").PivotTables("PivotTable2")
With table.PivotFields("Assigned to")
For Each PvI In .PivotItems
Select Case PvI.Name
Case "Antone", "Brad", "Cavan", "Chris", "Daneisha", "Edward", "James", "Jonathan", "Joesph", "Karen", "Shaun", "Steve", "Timothy", "Tracey"
PvI.Visible = True
Case Else
PvI.Visible = False
End Select
Next
End With
Try this REVERSE Select Case
Sub Sample()
Dim sNames As String
sNames = "/Antone/Brad/Cavan/Chris/Daneisha/Edward/James/Jonathan/Joesph/Karen/Shaun/Steve/Timothy/Tracey/"
Set Table = Worksheets("Sheet2").PivotTables("PivotTable2")
With Table.PivotFields("Assigned to")
For Each PvI In .PivotItems
Select Case 0 '<~~ Reverse Select case
Case InStr(1, sNames, "/" & PvI.Name & "/", vbTextCompare)
PvI.Visible = False '<~~ This also reverses
Case Else
PvI.Visible = True
End Select
Next
End With
End Sub
Here is a simple way to see how Reverse Select Case works :)
Sub Sample()
Dim sNames As String, NameToCheck As String
sNames = "/Antone/Brad/Cavan/Chris/Daneisha/Edward/James/Jonathan/Joesph/Karen/Shaun/Steve/Timothy/Tracey/"
NameToCheck = "Antoneeeee"
'NameToCheck = "Daneisha"
Select Case 0
Case InStr(1, sNames, "/" & NameToCheck & "/", vbTextCompare)
MsgBox "Not found"
Case Else
MsgBox " Found"
End Select
End Sub
Sure and it eliminates the Select Case. I tested the code below minus the pivot table aspect. This stores your names in an array. You can keep the array as a global variable or as a range in the workbook (for the latter change the nameArr = ... line). The code (UBound(Filter(nameArr, PvI.Name)) > -1) will check if PvI.Name exists in nameArr by filtering the array for your desired name and checking if the resulting array has anything in it. Then the true/false value returned by the "array contains check" is assigned to PvI.Visible.
Dim nameArr As Variant
nameArr = Array("Antone", "Brad", "Cavan", "Chris", "Daneisha", "Edward", "James", "Jonathan", "Joesph", "Karen", "Shaun", "Steve", "Timothy", "Tracey")
Set table = Worksheets("Sheet2").PivotTables("PivotTable2")
With table.PivotFields("Assigned to")
For Each PvI In .PivotItems
PvI.Visible = (UBound(Filter(nameArr, PvI.Name)) > -1)
Next
End With
Elaborating on my comment, I recommend a table-driven approach.
The first thing to do is put the names of interest in a worksheet, and give that range a name. I called the range "MyNames" in this example.
The second thing to do is add this function to your project:
Function NameIsInList(LookFor As String) As Boolean
On Error GoTo Err_NameIsInList
NameIsInList = WorksheetFunction.Match(LookFor, Range("MyNames"), 0) > 0
On Error GoTo 0
Exit Function
Err_NameIsInList:
' the name was not found, or a bad parameter was supplied
NameIsInList = False
On Error GoTo 0
End Function
Finally, modify your procedure to use the function:
Set table = Worksheets("Sheet2").PivotTables("PivotTable2")
With table.PivotFields("Assigned to")
For Each PvI In .PivotItems
PvI.Visible = NameIsInList(PvI.Name)
Next
End With
The advantages I was shooting for here are:
Table-driven: The list of values can be stored in a worksheet where it's easily accessible and modified. (If you don't want users to see the LOV you can always hide the worksheet.)
Reusable: Let the function do the work of checking the name list. You suggest this is something that needs to be done many times; the function can be called from multiple places.
Related
I have built the foundation and most of the functionality for a user-form that is supposed to allow a user to pick a category of data and move the cell references of that data between two different options. I have three separate sales categories to select from as the main data in the tables where they are stored. Additionally, for reference, I have a fourth and final category that moves all of the previous three categories at once. So essentially, I have four category selections in a combo box to move the data from a cell for "not-shipping" and "shipping" data for sales. I am using the formula functions for the box to move the data between locations. However, for some reason, I have not been able to get any iteration of my nested statements to work.
If the data is also present in the other shipping status (not shipping change to shipping and vice versa), it deletes the cell reference in the formula with a find and replace statement using strings. I have four option buttons as well that set parameters for time period and the likelihood of shipping. I also have exception handling if statements that flag the cell if the data is already present in one of them so that it doesn't double-count my data. It should send a warning message saying the data is already there.
I have tried changing the basic outline of the nested statements from if/elseif/end if to a SELECT Case case is = "sales category" end select layout. My biggest issue is getting the nested statements to trigger when the prescribed event takes place (either moves the cell reference to the new cell or flags it as already being in that location). Theoretically, it should function exactly like the other three.
I have truncated the code to show a basic overview of the data since the actual code is very long and can be cumbersome to read through.
`
````
Private Sub OKButton_Click()
Dim ws As Worksheet
Dim wb As ThisWorkbook
Dim strfilename As String
strfilename = "C:\Users\user.name\~.xlsm"
Set wb = Workbooks.Open(strfilename)
Set ws = Worksheets(1) 'I want the sub to select the first worksheet in my workbook.
If Quarter.Value = True And ShippingLikely.Value = True Then
Select Case Trim(AddinSales.Value)
Case Is = "Sales Category 1"
If ws.Range("B19").Formula Like "%E12%" Then
oldstr = "+E12"
newstr = ""
Replace ws.Range("B19").Formula, oldstr, newstr
ElseIf ws.Range("Shipping Cell").Formula Like "%E12%" Then
MsgBox "Number is already included in shipping.", vbExclamation
Else
Formula = "+E12"
ws.Range("Shipping Cell").Formula = ws.Range("B20").Formula + Formula
End If
Case Is = "Sales Category 2"
If ws.Range("B19").Formula Like "%E13%" Then
oldstr = "+E13"
newstr = ""
Replace ws.Range("B19").Formula, oldstr, newstr
ElseIf ws.Range("B20").Formula Like "%E13%" Then
MsgBox "Number is already included in shipping.", vbExclamation
Else
Formula = "+E13"
ws.Range("B20").Formula = ws.Range("B20").Formula + Formula
End If
End Select
````
`
ElseIf Quarter.Value = True And NotLikely.Value = True Then
Select Case Trim(AddinSales.Value)
Case Is = "Sales Category 1"
If ws.Range("B20").Formula Like "%E12%" Then
oldstr = "+E12"
newstr = ""
Replace ws.Range("B20").Formula, oldstr, newstr
ElseIf ws.Range("B20").Formula Like "%E12" Then
MsgBox "Number is already included in shipping.", vbExclamation
Call resetform
Else
Formula = "+E12"
ws.Range("B19").Formula = ws.Range("B19").Formula + Formula
End If
Case Is = "Sales Category 2"
If ws.Range("B20").Formula Like "%E13%" Then
oldstr = "+E13"
newstr = ""
Replace ws.Range("B20").Formula, oldstr, newstr
ElseIf ws.Range("B20").Formula Like "%E13%" Then
MsgBox "Number is already included in shipping.", vbExclamation
Call resetform
Else
Formula = "+E13"
ws.Range("B19").Formula = ws.Range("B19").Formula + Formula
End If
Case Is = "All Sales Categories"
If ws.Range("B20").Formula Like "%E12+E13+%" Then
E12 = "+E12"
E13 = "+E13"
E14 = "+E14"
newstr = ""
Replace ws.Range("B20").Formula, E12, newstr
Replace ws.Range("B20").Formula, E13, newstr
Replace ws.Range("B20").Formula, E14, newstr
ElseIf ws.Range("B20").Formula Like "%E12+E%" Then
MsgBox "Number is already included in shipping.", vbExclamation
Call resetform
Else
Formula = "+E12+E13+E14"
ws.Range("B19").Formula = ws.Range("B19").Formula + Formula
End If
End Select
End If
````
````
`
I have the exact same sort of layout for a month period table as well. Note: by table, I do not mean that it the table is one of excel's especially formatted tables with built-in filtering. Mine are just formatted to look like a generic table.
Here is the code for the combo box that is initialized when the user-form is opened up. I do not have them hard-coded in a spreadsheet so if that is a problem, I would love some guidance on making those changes.
`
```
`
Private Sub UserForm_Initialize()
Dim ws As Worksheet
Dim wb As ThisWorkbook
Dim strfilename As String
Dim ctrlType1 As String
strfilename = "C:\Users\user-name\~.xlsm"
Set wb = Workbooks.Open(strfilename)
Set ws = Worksheets(1)
ws.Activate
With AddinSales
.AddItem "Select an Option"
.AddItem "Sales Category 1"
.AddItem "Sales Category 2"
.AddItem "Sales Category 3"
.AddItem "All Sales Categories"
If AddinSales.Value = Null Then
AddinSales.Value = "Select an Option"
End If
.DropButtonStyle = fmDropButtonStylePlain
End With
End Sub`
```
`
I use the following code to allow users to write a value into Cell A1.
Sub TestUsername()
If Environ("Username") = "firstname1.lastname1" Or Environ("Username") = "firstname2.lastname2" _
Or Environ("Username") = "firstname3.lastname3" Or Environ("Username") = "firstname4.lastname4" Then
Sheet1.Range("A1").Value = 1
Else
Sheet1.Range("A2").Value = 2
End If
End Sub
As you can see I list each user who is allowed to enter a value into Cell A1 with an OR-condition in my VBA code. All this works fine.
Now, I was wondering if there is an easier way to do this. Something like this:
Sub TestUsername()
If List of or-conditions: {"firstname1.lastname1", "firstname2.lastname2", _
"firstname3.lastname3", "firstname4.lastname4"} = True Then
Sheet1.Range("A1").Value = 1
Else
Sheet1.Range("A2").Value = 2
End If
End Sub
I just know in PHP you can compress multiple conditions like here. Therefore, I thought this might also be possible for VBA programming.
Maybe something like this
Sub TestUsername()
Select Case Environ("Username")
Case "firstname1.lastname1", "firstname2.lastname2", "firstname3.lastname3"
Sheet1.Range("A1").Value = 1
Case Else
Sheet1.Range("A2").Value = 2
End Select
End Sub
I suppose, if you had an atrocious amount of conditions, you could stick them in an array and then simply replace your conditional statement
If Environ("Username") = "firstname1.lastname1" Or Environ("Username") = "firstname2.lastname2" _
Or Environ("Username") = "firstname3.lastname3" Or Environ("Username") = "firstname4.lastname4" Then
with this
If IsInArray(Environ("Username"), arr) Then
This does require that you dimension an array with the conditions first and use this function, however:
Public Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
Dim i
For i = LBound(arr) To UBound(arr)
If arr(i) = stringToBeFound Then
IsInArray = True
Exit Function
End If
Next i
IsInArray = False
End Function
This way, your code becomes much more readable and easy to maintain.
Since you're working in a cell, you might want to define the allowed usernames within the spreadsheet.
Here's how the spreadsheet table might look:
And here's the code you might use:
Sub TestUsername()
Dim username As String
Dim userInTable As Integer
Dim allowedUserRange As Excel.Range
username = Environ("username")
Set allowedUserRange = Excel.Range("tUsers")
userInTable = Excel.WorksheetFunction.CountIf(allowedUserRange, username)
If userInTable Then
Sheet1.Range("A1").Value = 1
Else
Sheet1.Range("A1").Value = 2
End If
End Sub
The Select Case provides a great solution to testing multiple conditions at the same time. I am using this to alert the user when they have not furnished all the required inputs. I am monitoring inputs from a number of Drop Down Boxes as well as some direct cell inputs.
Select Case True
Case Range("Customer_DD_Control_Cell") > 0 _
And Range("Dealer_DD_Control_Cell") > 0 _
And Range("Rep_DD_Control_Cell") > 0 _
And Range("Product_DD_Control_Cell") > 0 _
And Len(Range("Customer_State_Input")) > 0 _
And Len(Range("Contract_Date_Input")) > 0
Case Else
MsgBox "You have not completed the required inputs"
End Select
I have a dynamically defined named range in my excel ss that grabs data out of a table based on a start date and an end date like this
=OFFSET(Time!$A$1,IFERROR(MATCH(Date_Range_Start,AllDates,0)-1,MATCH(Date_Range_Start,AllDates)),1,MATCH(Date_Range_End,AllDates)-IFERROR(MATCH(Date_Range_Start,AllDates,0)-1,MATCH(Date_Range_Start,AllDates)),4)
But if the date range has no data in the table, the range doesn't exists (or something, idk). How can I write code in VBA to test if this range exists or not?
I have tried something like
If Not Range("DateRangeData") Is Nothing Then
but I get "Runtime error 1004, method 'Range' of object '_Global' failed."
Here is a function I knocked up to return whether a named range exists. It might help you out.
Function RangeExists(R As String) As Boolean
Dim Test As Range
On Error Resume Next
Set Test = ActiveSheet.Range(R)
RangeExists = Err.Number = 0
End Function
You can replicate the match in your VBA to count before using the range how many rows you would have, or you can use error handling:
On Error Resume Next
Debug.Print range("DateRangeData").Rows.Count
If Err = 1004 Then
MsgBox "Range Empty"
Exit Sub
Else
MsgBox "Range full"
End If
Err.Clear
On Error GoTo 0
This is another approach. It has the advantage to take the container and the name you want to test. That means you can test either Sheets Names or Workbook Names for example.
Like this:
If NamedRangeExists(ActiveSheet.Names, "Date") Then
...
Else
...
End If
or
If NamedRangeExists(ActiveWorkbook.Names, "Date") Then
...
Else
...
End If
Public Function NamedRangeExists(ByRef Container As Object, item As String) As Boolean
Dim obj As Object
Dim value As Variant
On Error GoTo NamedRangeExistsError:
value = Container(item)
If Not InStr(1, CStr(value), "#REF!") > 0 Then
NamedRangeExists = True
End If
Exit Function
Exit Function
NamedRangeExistsError:
NamedRangeExists = False
End Function
Depending on the application you're doing, it's good to consider using a Dictionary. They're especially useful when you wanna check whether something exists.
Take this example:
Dim dictNames as Scripting.Dictionary
Sub CheckRangeWithDictionary()
Dim nm As Name
'Initially, check whether names dictionary has already been created
If Not dictNames Is Nothing Then
'if so, dictNames is set to nothing
Set dictNames = Nothing
End If
'Set to new dictionary and set compare mode to text
Set dictNames = New Scripting.Dictionary
dictNames.CompareMode = TextCompare
'For each Named Range
For Each nm In ThisWorkbook.Names
'Check if it refers to an existing cell (bad references point to "#REF!" errors)
If Not (Strings.Right(nm.RefersTo, 5) = "#REF!") Then
'Only in that case, create a Dictionary entry
'The key will be the name of the range and the item will be the address, worksheet included
dictNames(nm.Name) = nm.RefersTo
End If
Next
'You now have a dictionary of valid named ranges that can be checked
End Sub
Within your main procedure, all you need to do is do an existence check before using the range
Sub CopyRange_MyRange()
CheckRangeWithDictionary
If dictNames.exists("MyRange") then
Sheets(1).Range("MyRange").Copy
end if
End Sub
While loading the dictionary may look a little longer, it's extremely fast to process and search. It also becomes much simpler to check whether any named range referring to a valid address exists, without using error handlers in this simple application.
Please note that when using names at sheet level rather than workbook level, it is necessary to use more elaborate keys to guarantee uniqueness. From the way the dictionary was created, if a key is repeated, the item value is overwritten. That can be avoided by using the same Exists method as a check in the key creation statement. If you need a good reference on how to use dictionaries, use this one.
Good luck!
This is an old post, but none of the rated answers has a dynamic solution to test if a name exists in a workbook or worksheet. This function below will accomplish that:
Function pg_Any_Name(thename As String) As Boolean
Dim n As Name, t As String
For Each n In ThisWorkbook.Names
t = Mid(n.Name, InStr(1, n.Name, "!", vbTextCompare) + 1, 999)
If UCase(thename) = UCase(t) Then
pg_Any_Name = True
Exit Function
End If
Next n
End Function
Worth noting that this would not have worked for this specific question because OP had a dynamic defined range. This question would have been more accurately titled Test if Name is a Valid Range because the name always existed as a formula, the issue was if it was a valid RANGE. To address this question with a solution that checks both workbook and sheets... this function would work:
Function PG_Range_Name(thename As String) As Boolean
Dim n As Name, t As String
For Each n In ThisWorkbook.Names
t = Mid(n.Name, InStr(1, n.Name, "!", vbTextCompare) + 1, 999)
If UCase(thename) = UCase(t) Then
On Error Resume Next
PG_Range_Name = n.RefersToRange.Columns.Count > 0
Exit Function
End If
Next n
End Function
I am trying to build a macro that cycles through a column of cells and replaces a two letter country code in that cell with the name of that country. However I get an object not found error when I try to run the macro.
Sub ChangeCountryText()
'
' ChangeCountryText Macro
' Changes country codes
'
For counter = 2 To 20
Set curCell = ActiveSheet.Cells(counter, 1)
Select Case curCell.Text
Case "JP"
curCell.Text = "Japan"
Case "FR"
curCell.Text = "France"
Case "IT"
curCell.Text = "Italy"
Case "US"
curCell.Text = "United States"
Case "NL"
curCell.Text = "Netherlands"
Case "CH"
curCell.Text = "Switzerland"
Case "CA"
curCell.Text = "Canada"
Case "CN"
curCell.Text = "China"
Case "IN"
curCell.Text = "India"
Case "SG"
curCell.Text = "Singapore"
End Select
Next counter
End Sub
The Text property is read-only - you can't set it. Assign to the Value property and it should work (e.g. curCell.Value = "Japan")
I'm sure you have a great reason for using a macro for this, but you may want to look into the LOOKUP or VLOOKUP worksheet functions as a way to do something like this without writing a macro.
You should be able to enter the debugger by clicking to the left of your macro text in the editor and placing a red dot on the line
For counter = 2 To 20
Then you can step through your macro until you get to the error.
Alternatively you can add error handling to your macro
On Error Goto Failed
at the top and before the end sub add
Failed:
'handle error here
"Object not found" is likely from the curCell.Text call (curCell is null, nothing, or invalid, so calling .Text on it is failing) or the ActiveSheet.Cells call (not sure if this can happen)
I am creating the validation dynamically and have hit a 256 character limit. My validation looks something like this:
Level 1, Level 2, Level 3, Level 4.....
Is there any way to get around the character limit other then pointing at a range?
The validation is already being produced in VBA. Increasing the limit is the easiest way to avoid any impact on how the sheet currently works.
I'm pretty sure there is no way around the 256 character limit, Joel Spolsky explains why here: http://www.joelonsoftware.com/printerFriendly/articles/fog0000000319.html.
You could however use VBA to get close to replicating the functionality of the built in validation by coding the Worksheet_Change event. Here's a mock up to give you the idea. You will probably want to refactor it to cache the ValidValues, handle changes to ranges of cells, etc...
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ValidationRange As Excel.Range
Dim ValidValues(1 To 100) As String
Dim Index As Integer
Dim Valid As Boolean
Dim Msg As String
Dim WhatToDo As VbMsgBoxResult
'Initialise ValidationRange
Set ValidationRange = Sheet1.Range("A:A")
' Check if change is in a cell we need to validate
If Not Intersect(Target, ValidationRange) Is Nothing Then
' Populate ValidValues array
For Index = 1 To 100
ValidValues(Index) = "Level " & Index
Next
' do the validation, permit blank values
If IsEmpty(Target) Then
Valid = True
Else
Valid = False
For Index = 1 To 100
If Target.Value = ValidValues(Index) Then
' found match to valid value
Valid = True
Exit For
End If
Next
End If
If Not Valid Then
Target.Select
' tell user value isn't valid
Msg = _
"The value you entered is not valid" & vbCrLf & vbCrLf & _
"A user has restricted values that can be entered into this cell."
WhatToDo = MsgBox(Msg, vbRetryCancel + vbCritical, "Microsoft Excel")
Target.Value = ""
If WhatToDo = vbRetry Then
Application.SendKeys "{F2}"
End If
End If
End If
End Sub