Excel VBA: Create Dynamic data validation formula - excel

I have a below formula which validates for IP address in Excel cell and works fine.
Dim cellAddress as Variant
cellAddress =Target.value 'Target is a Range
=AND(COUNT(FILTERXML("<t><s>"&SUBSTITUTE(A1,".","</s><s>")&"</s></t>","//s[.*1>-1][.*1<256]"))=4,LEN(A1)-LEN(SUBSTITUTE(A1,".",""))=3)
My problem is that I want to pass the cellAddress as a Dynamic value to my formula instead of 'A1'
Can somebody guide

You can create a Public Function that returns a Boolean, and pass in the cell reference as a Range to the function.
Here's an example of the code:
Option Explicit
' =AND(COUNT(FILTERXML("<t><s>"&SUBSTITUTE(A1,".","</s><s>")&"</s></t>","//s[.*1>-1][.*1<256]"))=4,LEN(A1)-LEN(SUBSTITUTE(A1,".",""))=3)
Public Function ValidateIPAddress(source As Range) As Boolean
' Get the first condition's value...
Dim xmltxt As String
xmltxt = "<t><s>" & Application.WorksheetFunction.Substitute(source.Value, ".", "</s><s>") & "</s></t>"
Dim sections As Variant
sections = Application.WorksheetFunction.FilterXML(xmltxt, "//s[.*1>-1][.*1<256]")
' Get the second condition's value...
Dim separators As Integer
separators = Len(Application.WorksheetFunction.Substitute(source.Value, ".", ""))
' Compare both conditions and return the result
ValidateIPAddress = Application.WorksheetFunction.Count(sections) = 4 And Len(source.Value) - separators = 3
End Function
And here's a snippet of the usage:
I tried to break out the formula you provided to make it legible without spending too much time on it. It can definitely be improved or you can even nest everything into one line if you wanted to, but I wouldn't recommend that because it'll be difficult to debug.
EDIT:
If you just wanted to replace the "A1" string you can concatenate the cellAddress variable with parts of the formula.
Here's a code snippet example:
Dim validationFormula As String
validationFormula = "=AND(COUNT(FILTERXML(""<t><s>""&SUBSTITUTE(" & _
Target.Value & ",""."",""</s><s>"")&""</s></t>"",""//s[.*1>-1][.*1<256]""))=4,LEN(" & _
Target.Value & ")-LEN(SUBSTITUTE(" & _
Target.Value & ",""."",""""))=3)"
Debug.Print validationFormula
Output of the formula when changing a cell's value to "A5":
Note: You have to replace all double quotes with 2 double quotes when handling quotes in strings.

Related

Concatenate values of more cells in a single variable in vba

I have an excel file with four columns: name, surname, address, area.
There are a lot of rows.
Is there a way to concatenate all the values of every single row in a variable, using vba?
I need a variable that should contain something like this:
(name1, surname1, address1, area1); (name2, surname2, address2, area2); (name3, surname3, address3, area3)...
If you have the following data in your worksheet
Then the following code will read the data into an array …
Option Explicit
Public Sub Example()
Dim RangeData() As Variant ' declare an array
RangeData = Range("A1:D5").Value2 ' read data into array
End Sub
… with the following structure:
Alternatively you can do something like
Public Sub Example()
Dim DataRange As Range
Set DataRange = Range("A2:D5")
Dim RetVal As String
Dim Row As Range
For Each Row In DataRange.Rows
RetVal = RetVal & "(" & Join(Application.Transpose(Application.Transpose(Row.Value2)), ",") & "); "
Next Row
Debug.Print RetVal
End Sub
To get this output:
(name1, surname1, address1, area1); (name2, surname2, address2, area2); (name3, surname3, address3, area3); (name4, surname4, address4, area4);
.. is there a way to write the result like a sort of list that shows all the values of the cells of the range?
Yes, there is. In addition to PEH's valid answers and disposing of Excel version MS365 you might also use
Dim s as String
s = Evaluate("ArrayToText(A2:D5, 1)") ' arg. value 1 representing strict format
resulting in the following output string:
{"name1","surname1","address1","area1";"name2","surname2","address2","area2";"name3","surname3","address3","area3";"name4","surname4","address4","area4"}
Syntax
ARRAYTOTEXT(array, [format])
The ARRAYTOTEXT function returns an array of text values from any specified range. It passes text values unchanged, and converts non-text values to text.
The format argument has two values, 0 (concise default format) and 1 (strict format to be used here to distinguish different rows, too):
Strict format, i.e. value 1 includes escape characters and row delimiters. Generates a string that can be parsed when entered into the formula bar. Encapsulates returned strings in quotes except for Booleans, Numbers and Errors.
Thank you for your answers, suggestions, ideas and hints. I am sorry if my question was not so clear, all the solutions you added were perfect and extremely elegant.
In the end I found a way - a dumber way in comparison to all the things you wrote - and I solved with a for statement.
I did like this:
totRow = ActiveSheet.UsedRange.Rows.Count
For i = 1 To totRow
name = Cells(i, 1)
surname = Cells(i, 2)
address = Cells(i, 3)
area = Cells(i, 4)
Example = Example & "(" & name & ", " & surname & ", " & address & ", " & area & "); "
Next i
Range("E1").Value = Example
It works (it does what I wanted to do), but I noticed a little limit: if the rows are a lot I can't keep the whole text in the variable.

Search Column headers Right to Left vba

I have a function to find a column header, but it searches from left to right. I need to find the right most column that matches, so I'd like to search right to left, but I can't figure out how. I tried using xlprevious/xlnext but they gave me the same answer.
Example:
Dim HeaderColFoundRng As Range
Set HeaderColFoundRng = Range("A1:H1").Find("FindStr", , xlValues, xlPart)
Using xlPrevious does seem to work for me:
Set HeaderColFoundRng = Range("A1:H1").Find(What:="FindStr", LookIn:=xlValues, _
LookAt:=xlPart, SearchDirection:=xlPrevious)
My guess as to why it didn't work for you is that you perhaps mixed up the argument positions. Using named arguments seems cleaner and easier to read here.
Alternative via worksheet function Filter()
If you dispose of the dynamic Excel function Filter (vs. MS/Excel 365) you can use the following steps:
a) construct a formula pattern returning column numbers (base formula e.g. =FILTER(COLUMN(A1:H1),LEFT(A1:H1,8)="forecast")),
b) evaluate the formula to get 1-based array elements with all found column numbers. - The function returns the most right occurrence (Right2Left) of the searched caption (within each string starting with the passed caption characters) which will be located via Ubound().
Further hints:
The search caption argument is Case insensitive. The Right to left direction can be changed via optional argument Right2Left = False.
Function HeaderCol(rng As Range, Caption As String, _
Optional Right2Left As Boolean = True) As Long
'a) construct formula
Dim addr As String
addr = rng.Address(False, False, external:=True)
Dim n As Long
n = Len(Caption)
Dim myFormula As String
myFormula = "=Filter(column(" & addr & "),Left(" & addr & "," & n & ")=""" & Caption & """)"
'b) get most right occurrence of evaluated formula (if Right2Left)
Dim chk
chk = Evaluate(myFormula)
If IsError(chk) Then Exit Function
HeaderCol = chk(IIf(Right2Left, UBound(chk), 1))
End Function
Possible Example call
Assuming a header row with changing column captions like "Forecast2020", "Forecast2021", "Sales2020", "Sales2021" you might want to get the last occurrence of Forecast data, i.e. the column number of "Forecast2021":
Sub testHeaderCol()
Dim col As Long
col = HeaderCol(Sheet1.Range("A1:H1"), "Forecast")
If col Then
Debug.Print "Found cell: " & Sheet1.Cells(1, col).Address(False, False, external:=True)
'do other stuff, e.g. set range object
'...
Else
Debug.Print "Nothing found!"
End If
End Sub

Using Excel Proper Function with exception | Excel

Essentially I have multiple strings within my Excel Spreadsheet that are structured the following way:
JOHN-MD-HOPKINS
REC-PW-RESIN
I would like to use the proper function but exclude the part of the string that is within the dashes (-).
The end result should look like the following:
John-MD-Hopkins
Rec-PW-Resin
Is there an excel formula that is capable of doing this?
You may need to create your own VBA function to do this, that checks if there are two hyphens in the data, and if so converts the first and last words to proper case without touching the middle word, otherwise just converts the string to proper case.
Paste the following into a module within Excel:
Function fProperCase(strData As String) As String
Dim aData() As String
aData() = Split(strData, "-")
If UBound(aData) - LBound(aData) = 2 Then ' has two hyphens in the original data
fProperCase = StrConv(aData(LBound(aData)), vbProperCase) & "-" & aData(LBound(aData) + 1) & "-" & StrConv(aData(UBound(aData)), vbProperCase)
Else ' just do a normal string conversion to proper case
fProperCase = StrConv(strData, vbProperCase)
End If
End Function
Then, in your worksheet, you can use this just as you would any built-in formula, so if "JOHN-MD-HOPKINS" is in cell A1, you would use this as a formula in another cell:
=fProperCase(A1)
Which would display John-MD-Hopkins as required.
EDITED CODE
As the requirement is to leave the second word, then this modified VBA function, which "walks" the array should work instead:
Function fProperCase2(strData As String) As String
Dim aData() As String
Dim lngLoop1 As Long
aData() = Split(strData, "-")
For lngLoop1 = LBound(aData) To UBound(aData)
If (lngLoop1 = LBound(aData) + 1) And (lngLoop1 <> UBound(aData)) Then
aData(lngLoop1) = aData(lngLoop1)
Else
aData(lngLoop1) = StrConv(aData(lngLoop1), vbProperCase)
End If
Next lngLoop1
fProperCase2 = Join(aData, "-")
End Function
It basically looks to see if the array element being dealt with is the second (lngLoop1=LBound(aData)+1) and also not the last (lngLoop1<>UBound(aData)).
Regards,

Variable String in Formula R1C1

I am trying to create a formula using R1C1 format that will change based on a string containing a variable. I have tried creating the string and inputting it in the formula, as well as creating the string in the formula and none seems to work. Below is my code:
Dim NewXX As Integer
Dim NewXX1 As Integer
Dim CE As Integer
Dim PrevCE As Integer
Dim CEText As String
CE = Cells(NewXX - 1, 1).Value + 1
CEText = "=" & CE
ActiveCell.FormulaR1C1 = _
"=SUMIFS(R[-209]C8:R[1]C8,R[-209]C1:R[1]C1,CEText,R[-209]C2:R[1]C2,""<>Summary"")*(1+R1C16)"
CEText is the variable that will change every time the macro is run. A few things I have tried:
CEText
""CEText"",
'"&CEText&"',
"CEText",
""="""&CE&",
""=""CE,
All of these trials either give me an 'Expected: end of statement' error, or the formula displayed in the cells matches the text (not value) found in the code.
Any help would be greatly appreciated! I am fairly new to VBA and am always up for learning a better way to do things!
Thanks!
CEText is not need when you are equating and since CE is a number, we only need to worry about removing from formula string and concatenating:
ActiveCell.FormulaR1C1 = _
"=SUMIFS(R[-209]C8:R[1]C8,R[-209]C1:R[1]C1," & CE & ",R[-209]C2:R[1]C2,""<>Summary"")*(1+R1C16)"
If you want CEText then we do the extra quotes in the formula:
ActiveCell.FormulaR1C1 = _
"=SUMIFS(R[-209]C8:R[1]C8,R[-209]C1:R[1]C1,""" & CEText & """,R[-209]C2:R[1]C2,""<>Summary"")*(1+R1C16)"
The main issue is that any vba variable must be outside the quotes and concatenated with &

Manipulating Ranges in Excel - Returning a Value (Error 2029)

I am a quite new to Excel VBA, and I come from a more... traditional programming background (Java, C). I am having issues with passing a Range Object as a parameter in my User-defined function (see below). The idea of my function is to take several parameters to complete a VLOOKUP of a filtered range.
I may have several syntax issues (I am unsure of my return type and my usage of VLOOKUP), and I would appreciate some guidance on this. See results, more information in my code:
Public Function GETVALUE(screen As String, strEvent As String, dataRange As Range, strDate As String) As String
'ASSUMPTION: dataRange has three columns; first column contains lookup values; Second
' column contains dates for filtering; Third column contains return values
Dim result As String
'remove irrelevant dates in dataRange; apply filter
'ASSUMPTION: This process should return a Range that is removes all Rows that does
'not have strDate in the second column
Dim newRange As Range
'RESULT: Returns #VALUE!. I know this is not the typical := syntax I see in many
'examples but this one apparently compiles, so I use it. I comment this line out
'and try to make the other lines below work with dummy parameters or fixed ranges
newRange = dataRange.AutoFilter(2, strDate)
'Now I try to use the newly filtered, "newRange" and use that in my VLOOKUP
'and return it.
result = [VLOOKUP("*" & screen & "/" & strEvent & "*", newRange, 3, False)]
'I can see an Error 2029 here on Result
GETVALUE = result
'RESULT: Returns #VALUE!
End Function
VLOOKUP ignores any filtering of your data. In other words VLOOKUP will also look in the hidden rows.
I would suggest two alternative approaches:
Copy the visible cells of the filtered range to a new sheet and perform the lookup there:
Set newRange = dataRange.AutoFilter(2, strDate).SpecialCells(xlCellTypeVisible)
set ws = worksheets.Add
ws.Range("A1").Resize(newRange.Rows.Count,newRange.Columns.Count).Value = newRange.Value
etc.
Note that this can not be done in a UDF, you would have to do it in a a Sub.
Store the values in dataRange in a variant array and loop to search for the required value:
Dim arr() as Variant
arr = dataRange.Value
For i = LBound(arr,1) to UBound(arr,1)
If (arr(i,2) = strDate) And (arr(i,1) LIKE "*" & screen & "/" & strEvent & "*"( Then
GETVALUE = arr(i,3)
Exit Function
End If
Next
This I think causes your problem:
result = [VLOOKUP("*" & screen & "/" & strEvent & "*", newRange, 3, False)]
Replace it with this instead:
result = Evaluate("VLOOKUP(*" & screen & "/" & strEvent _
& "*, " & newRange.Address & ", 3, False)")
[] which is shortcut for Evaluate doesn't work on variables.
If it is a direct VLOOKUP like below:
result = [VLOOKUP(D1,Sheet1!$A:$C,3,FALSE)]
it will work. But if you are working with variables as in your example, you have to explicitly state it.
And take note that Evaluate accepts Name argument in a form of string.
So you simply have to concatenate all your strings and then explicitly use Evaluate.
Edit1: Additional Inputs
This will not work as well: newRange = dataRange.AutoFilter(2, strDate).
To pass Objects to a Variable you need to use Set like this.
Set newrange = dataRange.AutoFilter(2, strDate)
On the other hand, AutoFilter method although returning a Range Object fails.
I'm not entirely sure if this can't really be done.
Moving forward, to make your code work, I guess you have to write it this way:
Edit2: Function procedures only returns values, not execute methods
Public Function GETVALUE(screen As String, strEvent As String, rng As Range)
GETVALUE = Evaluate("VLOOKUP(*" & screen & "/" & strEvent & "*, " _
& rng.Address & ", 3, False)")
End Function
To get what you want, use above function in a Sub Procedure.
Sub Test()
Dim dataRange As Range, strDate As String, myresult As String
Set dataRange = Sheet2.Range("A2:E65") 'Assuming Sheet2 as property name.
strDate = "WhateverDateString"
dataRange.AutoFilter 2, strDate
myresult = GETVALUE("String1", "String2", dataRange)
End Sub
Btw, for a faster and less complex way of doing this, try what Portland posted.
Basically you must write :
Getvalue = Application.VLookup( StringVar, RangeVar, ColumnNumberVar)
Vlookup needs your data to be previously ordered in alphabetical order, or it doesn't work.
Excel Developers's approach is a good one too, using a VBA Array.
I can also point the VBA functions FIND, and MATCH, wich will get you the row of searched data, and then you can pull what you need from the 3rd column of that row.
Wichever is faster depends of the size of your range.

Resources