Excel VBA - Referencing Range using Cells - vba

I am trying to dynamically add validation (using DataValidation:List) to ranges in a worksheet. I recorded a macro that produced the following code:
With Worksheets("Clusters").Range("C2:C100").Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:="=Managers"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
This was working fine for the static range C2:C100, but the column may not be always be C. I have a variable cMANFCM that contains the column number. I tried to edit the code to use this:
With Worksheets("Clusters").Range(Cells(2,cMANFCM), Cells(100, cMANFCM)).Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:="=Managers"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
Why doesn't this work and how do I fix it?

Your code works on mine - I've added a line to delete all existing validation and it does create new validation without throwing an error:
Lots of the validation arguments can probably be ignored...and you've got a choice of how to reference the sheet/range when other sheets are active:
Option Explicit
Sub control()
'Call changeValidation(4)
'Call changeValidationPAlbert(5)
Call changeValidationTWilliams(6)
End Sub
Sub changeValidation(cMANFCM As Integer)
With Excel.ThisWorkbook.Worksheets("Clusters")
.Cells.Validation.Delete
.Range(.Cells(2, cMANFCM), .Cells(100, cMANFCM)).Validation.Add _
Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, _
Formula1:="=managers"
End With
End Sub
Sub changeValidationAlbert(cMANFCM As Integer)
With Excel.ThisWorkbook.Worksheets("Clusters")
.Cells.Validation.Delete
.Range("A2:A100").Offset(, cMANFCM - 1).Validation.Add _
Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, _
Formula1:="=managers"
End With
End Sub
Sub changeValidationTWilliams(cMANFCM As Integer)
With Excel.ThisWorkbook.Worksheets("Clusters")
.Cells.Validation.Delete
.Cells(2, cMANFCM).Resize(100, 1).Validation.Add _
Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, _
Formula1:="=managers"
End With
End Sub

Related

Dynamic Dropdrown VBA

I want to create an excel drop down in vba.
I am storing the values which I want in drop down in a named range, I want to use the name of the range in the code while creating drop down like below
Sheet1.Range("O3:O28").Name = "test"
With Range("rng_expensecategory").Offset(2, 0).Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:="=& test"
.IgnoreBlank = True
.InCellDropdown = True
.ShowInput = True
.ShowError = True
End With
But the line xlBetween, Formula1:="=& test" is giving me an error.
Is there a turnaround or some other way which i can use to refer to the name range in the above code

How to pass values from an array to a drop down list in a certain cell (not combo list) in excel VBA

I'm trying to pass values from an array to a drop down list in a specifc cell.
Say I have an array which contains the values 1,2,3 and I want cell A1 to contain a drop down list with these value, Is there any way I can do this?
I'm trying to achive this without having to first assign these value to different cells and use data validation. (And as stated in the title, I don't want to use combo boxes or user forms)
This should give you a way of doing it:
Dim myArray
myArray = Array("1", "2", "3")
Range("A" & 1).Select
With Selection.Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:=myArray(0) & "," & myArray(1) & "," & myArray(2)
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
Here's a couple of ways, same result,
Sub DataVal1()
With Range("A1").Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:="1,2,3"
End With
End Sub
Sub DataVal2()
Dim x As String
x = "1,2,3"
With Range("A1").Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:=x
End With
End Sub

Specify Range of cells for .validation

I would like to insert a data validation list that grabs data from another sheet. the column the list will be generated from is stored in another variable that is dynamic. My code so far is:
pRange = Sheets("Payer Output").Cells(24, 3).Value
With Sheets("Payer Output").Range("C23").Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, Formula1:=Sheets("Payers in Top 4").Cells(3, pRange)
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
This formula generates a list, and looks in the correct column, however the current code only specifies the list should look in a single cell, so when the list is generated, there is only one value. How do I specify a range of cells for the 'formula1:=' line? I've tried:
Formula1:=Sheets("Payers in Top 4").Range(Cells(3, pRange), Cells(10,pRange))
But this does not work. Thank you for your help!
Sometimes I will just name the range and put the named range in the list.
Sub AddDtaVal()
pRange = Sheets("Payer Output").Range("C24").Value
Sheets("Payers in Top 4").Range(Sheets("Payers in Top 4").Cells(3, pRange), Sheets("Payers in Top 4").Cells(10, pRange)).Name = "List"
With Sheets("Payer Output").Range("C23").Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:="=List"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
End Sub
You need to build the address formula in a string, Excel-style : 'Sheet Name'!A1:A10
Dim StrFormula As String
Dim pRange As Long
pRange = Sheets("Payer Output").Cells(24, 3).Value
With Sheets("Payers in Top 4")
StrFormula = "'" & .Name & "'!" & .Range(.Cells(3, pRange), .Cells(10, pRange)).Address
End With
With Sheets("Payer Output").Range("C23").Validation
.Delete
.Add Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, _
Formula1:=StrFormula
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
Try this
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, Formula1:="='Payers in Top 4'!" & Worksheets("Payers in Top 4").Cells(3, pRange).Resize(8).Address

Getting an Application-defined or Object-defined error in VBA using Validation

I am trying to get the address of a certain using the offset from the active cell. But it always throw an error "Object variable or With block variable not set". Can anyone point out my mistake and make a correction to it? Thanks in advance.
Here is the code:
*OTHER CODES HERE*
Dim offsetter As Range
offsetter = ActiveCell.Offset(0, -2).Address(False, False) //Error on this line
With Selection.Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, Formula1:="=INDIRECT(SUBSTITUTE("&offsetter&",' ','_'))"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
*OTHER CODES HERE*
I have updated the code and the mistake that I did was declaring the variable as range, whereas it should be string.
I am now getting a new error "application-defined or object-defined error"
*OTHER CODES HERE*
Dim offsetter As String
offsetter = ActiveCell.Offset(0, -2).Address(False, False)
With Selection.Validation
.Delete
//Error on this line [.add]
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, Formula1:="=INDIRECT(SUBSTITUTE("&offsetter&",' ','_'))"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
*OTHER CODES HERE*
Here is the final and working code:
Dim offsetter As String
offsetter = ActiveCell.Offset(0, -2).Address(False, False)
With Selection.Validation
.Delete
'Application-defined or Object-defined error
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, _
Formula1:="=INDIRECT(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(""" _
& offsetter & ""","" "",""_""),""-"",""_""),""/"",""_""),""("",""""),"")"",""""))"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
Thanks everyone!
A Validation-object has the following three methods:
Add
Delete
Modify
While Add and Modify work towards the Validation itself, Delete deletes the entire object. That's why afterwards you are not able to add a validition or to specify any other properties.
Here is the final and working code I did.
Dim offsetter As String
offsetter = ActiveCell.Offset(0, -2).Address(False, False)
With Selection.Validation
.Delete
'Application-defined or Object-defined error
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, _
Formula1:="=INDIRECT(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(""" _
& offsetter & ""","" "",""_""),""-"",""_""),""/"",""_""),""("",""""),"")"",""""))"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With

VBA controlling user focus during iteration

quick question that shouldn't really require any of my code. During my application, I use a 'for each' loop, that loops through a range of cells, and right now when the user runs it, the focus of the screen follows the selection as it jumps from cell to cell within that range. Is there any way to prevent the focus from following the path of the loop during the iteration, maybe have the user simply see something that says "processing" until it is complete?
Thanks in advance, I appreciate any and all help.
Code:
Dim iLoop As Integer
For iLoop = 5 To lastRow
Sheets("Planners").Activate
Range("J" & iLoop).Select
Range(Cells(iLoop, 9)).Select
With Selection.Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:="Yellow, Orange, Green, Red"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = "Invalid Entry"
.ErrorMessage = "Please choose from dropdown"
.ShowInput = True
.ShowError = True
End With
Next iLoop
Apparently you are using .Select in your code. There is a chance it's a right thing to do, but most of the time it isn't.
So stop using Select and ActiveCell and refer to cells using indices/references.
The above would be the correct solution.
The wrong solution would be to use Application.ScreenUpdating = False before the loop and Application.ScreenUpdating = True after the loop.
Edit:
Dim iLoop As long
dim w as worksheet
set w = Worksheets("Planners")
For iLoop = 5 To lastRow
With w.cells(iLoop, 9).Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:="Yellow, Orange, Green, Red"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = "Invalid Entry"
.ErrorMessage = "Please choose from dropdown"
.ShowInput = True
.ShowError = True
End With
Next
But given this code, you don't need a loop at all:
dim w as worksheet
set w = Worksheets("Planners")
With w.Range(w.cells(5, 9), w.cells(lastRow, 9)).Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:="Yellow, Orange, Green, Red"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = "Invalid Entry"
.ErrorMessage = "Please choose from dropdown"
.ShowInput = True
.ShowError = True
End With
All you need to do is add application.screenupdating = false at the start of you code (after the sub definition of course) and application.screenupdating = true at the end before 'end sub'.
It's good practice to add this to all your sub and functions as it will make them run faster as well.