RIGHT function doesn't work when put within an IF statement - excel

I want to create an IF condition using the RIGHT function. It would look up the 4 last digits within a cell and compare it to another cell, then perform actions if it's a match.
Here's a simplified version of the code I toyed with. The action to be performed in this experience is just to display the counter in a cell.
Public vCounter
Sub Counter()
vCounter = 0
Sheets.Add.Name = "Test"
'The cells the RIGHT function will operate from (A1, A2 and A3)
Sheets("Test").Range("A1") = "123"
Sheets("Test").Range("A2") = "456"
Sheets("Test").Range("A3") = "789"
'The cells the result of the RIGHT function will be compared to (B1, B2 and B3)
Sheets("Test").Range("B1") = "23"
Sheets("Test").Range("B2") = "456"
Sheets("Test").Range("B3") = "89"
'This cell (G3) shows the result of a RIGHT function, considering the
'last two digits in A1, as an experience; it works.
Sheets("Test").Range("G3") = Right(Sheets("Test").Cells(1, 1), 2)
For i = 1 To 3
'The RIGHT function considers the two last digits of, successively,
'A1, A2 and A3, and those are compared to, respectively,
'B1, B2 and B3. For some reason, it doesn't work here.
If Right(Sheets("Test").Cells(i, 1), 2) = Sheets("Test").Cells(i, 2) Then
vCounter = vCounter + 1
End If
Next i
'This cell (E3) shows the counter, to test whether or not the If
'condition with the RIGHT function works. By changing the contents
'of the cells I compare between each other, I can check whether or
'not it counts correctly.
Sheets("Test").Range("E3") = vCounter
End Sub
Here's what I get:
The sheet that I get when I run this procedure
In conclusion, in this experience, the RIGHT function somehow doesn't work since the vCounter doesn't get to 2. It stays at 0, showing it doesn't count at all. I deduce from this result that the problem resides in the IF statement containing the RIGHT function. Maybe the For Loop has to do with it, but I doubt it.
Any thoughts?

Even though you're writing string values to your sheet, Excel will automatically assume them to be numeric values, so when you read them back you will be getting values of type Variant/Double.
If you pass one of those Doubles through Right() though, it will return a Variant\String, and it's that comparison between Variant\String and Variant\Double which seems to be failing.
Some test code:
Sub Tester()
Dim ws As Worksheet, v As Variant
Set ws = ThisWorkbook.Worksheets("Test")
ws.Range("A1").Value = "123"
ws.Range("B1").Value = "23"
'Comparing values...
Debug.Print Right(ws.Range("A1").Value, 2) = ws.Range("B1").Value '>> False (Variant\String vs Variant\Double)
Debug.Print Right(ws.Range("A1").Value, 2) = CStr(ws.Range("B1")) '>> True (Variant\String vs Variant\String)
End Sub

Related

Dynamic Lookup for multiple values in a cell (comma separated) and return the corresponding ID to a single cell (comma separated also)

The thing is not always the amount of values (IDs) will be the same within each cell (at least 1, max=several) that's why the fixed version of using concatenated vlookup+left/mid/right will not work for me due to that will solution will only work up to 3 values. The only fixed size is the size of the values to lookup (IDs - in green), 8 characters (letters+numbers).
I'm not sure but, is it possible to setup a loop within excel formulas/functions ?
Below is a table containing an example of the issue I'm trying to resolve and the expected values (tables are in different tab). Hope you can help.
Thanks.
example-tables
If you have windows Excel O365 with the TEXTJOIN and FILTERXML functions, you can use a formula:
=TEXTJOIN(",",TRUE,IFERROR(XLOOKUP(FILTERXML("<t><s>" & SUBSTITUTE(#[IDs],",","</s><s>") & "</s></t>","//s"),Table2[IDs],Table2[IDv2]),"""--"""))
Note that, in your data, there are two ID's in A4 that do not match any ID's in Table 2. Although that may be a typo, I left them as is to demonstrate the error handling.
Table1
Table2
Here is a UDF that will do what you describe. Paste the code into a standard code module (not one already existing in the workbook but one that you create and that would have a name like Module1 before you change it to what you like best. You can also rename the function to give it a more suitable name.
Function ID_v2(Cell As Range) As String
' 035
Dim Fun As String ' function return value
Dim Sp() As String ' array of CSVs of CellVal
Dim VLRng As Range ' the lookup range
Dim VL As Variant ' result of VLookup
Dim i As Integer ' loop counter
' this is a range similar to your sample A10:D19
Set VLRng = ThisWorkbook.Names("Table2").RefersToRange
Sp = Split(Cell.Cells(1).Value, ",")
If UBound(Sp) >= 0 Then
For i = 0 To UBound(Sp)
On Error Resume Next
VL = Application.VLookup(Trim(Sp(i)), VLRng, 3, False)
If Err Then VL = "[ERROR]"
Fun = Fun & VL & ","
Next i
ID_v2 = Left(Fun, Len(Fun) - 1) ' remove final comma
End If
End Function
Call the function with syntax like built-in functions. For example,
= ID_v2(A3)
This can be copied down like any other function. But remember to save the workbook as macro-enabled.
Try this:
Option Explicit
Sub Cell2List()
Dim wF As WorksheetFunction: Set wF = Application.WorksheetFunction 'To user Transpose
Dim i As Range
Dim j As Range
Dim s As String: s = "," 'The separator of the list
'Ask the user for the cell where are the list with the commas
'Just need to select the cell
Set i = Application.InputBox("Select just one cell where the values are", "01. Selecte the values", , , , , , 8)
'Ask the for the separator. If you are completely sure the comma will never change just delete this line
s = Application.InputBox("Tell me, what is the character separator, just one character! (optional)", "02. Separator (comma semicolon colon or any other char)", , , , , , 2)
If s = "" Then s = "," 'Verifying...........
'Ask the user where want to put the list
'You need to get ready the cells to receive the list.
'If there any data will be lost, the macro will overwrite anything in the cells
Set j = Application.InputBox("Select just one cell where the values will go as a list, just one cell!", "03. Selecte the cell", , , , , , 8)
Dim myArr: myArr = (Split(i.Value, s)) 'Split the list into a Array
Range(Cells(j.Row, j.Column), Cells(j.Row + UBound(myArr), j.Column)).Value = wF.Transpose(myArr)
'j.Row is the row of the cell the user selected to put the cell
'j.Column the same, but the column
'j.Row + UBound(myArr) = UBound(myArr) is the total count of elements in the list
' +j.Row
' _______________
' the last cell of the new list!
'wF.Transpose(myArr) = we need to "flip" the array... Don't worry, but Don't change it!
End Sub
You can put this macro with a button tin the ribbons, or use it as you can see in the gif
And this will be the result: (with a bigger list)
EDIT
You can use this UDF:
Function Cells2List(List As Range, Pos As Integer) As String
Cells2List = Split(List, ",")(Pos - 1)
End Function
Just need to define and index this way:
To tell the function, what index you want to see. You can use the function using ROW()-# to define an 1 at the beginning and when the formula send a #VALUE! delete the formulas. Where $A$1 is where the list are, and D7 is where the index are.

VBA code for generating QR codes, but where does the code begins?

For work, I have to generate some QR code with info in it.
Therefore, I checked on internet and found this "already made" QR code generator :
https://github.com/JonasHeidelberg/barcode-vba-macro-only
It's very nice, and works quite well.
I'm trying to integrate it into my VBA sheet to do data treatment before using the code to create the final QR code. (Nothing complex)
here is how it looks like :
The 4 cells "B4 to B6" get the entry data which are encoded or not, depending on the checkboxes, then the result it written in the column D.
Each cell content is stacked in a variable, and this variable is given to eat to the QR code generator :
Public Function GenerateQRCode()
Dim CurrentWS As String
UserDataRange = "B6:B9" 'The cells in which the data to be encoded are stored
InputDataRange = "D6:D9" 'the cells with the encoded (or not) values
InputCell = "A4" 'the cell where the text to be encoded in the QR code has to be put
'encode the text depending on hte value of the cell behind the chek boxes
For Each cell In Range(UserDataRange)
If cell.Offset(0, 1) = True Then
EncodedText = EncodeDecode.Base64EncodeString(cell.Value)
cell.Offset(0, 2).Value = EncodedText
ElseIf cell.Offset(0, 1) = False Then
cell.Offset(0, 2).Value = cell.Value
End If
Next
Range(InputCell).ClearContents
DataToEncode = ""
'puts the text in the input line with dashes between each value
For Each cell In Range(InputDataRange)
pouet = Range(InputDataRange).Address
If DataToEncode = "" Then
DataToEncode = cell.Value & Chr(10)
Else
If cell.Address = Mid(Range(InputDataRange).Address, InStr(1, Range(InputDataRange).Address, ":") + 1, _
Len(Range(InputDataRange).Address) - (InStr(1, Range(InputDataRange).Address, ":") - 1)) Then
DataToEncode = DataToEncode & cell.Value
Else
DataToEncode = DataToEncode & cell.Value & Chr(10)
End If
End If
Next
Range(InputCell).Value = DataToEncode
End Function
My concern is that "whatever cell I modify in the whole workbook, it lunches the QR code generator."
I wanted to give a condition at the beginning of the code like If cell A4 is modified, lunch the code, but I don't even achieve to understand what makes the code start and how the data are gathered...
My best guess is that this is the beginning of the code :
Public Function EncodeBarcode(ShIx As Integer, xAddr As String, _
code As String, pbctype%, Optional pgraficky%, _
Optional pparams%, Optional pzones%) As String
Dim s$, bctype%, graficky%, params%, zones%
Dim oo As Object
Call Init
If IsMissing(pzones) Then zones = 2 Else zones = pzones
If IsMissing(pparams) Then params = 0 Else params = pparams
If IsMissing(pgraficky) Then graficky = 1 Else graficky = pgraficky
If IsMissing(pbctype) Then bctype = 0 Else bctype = pbctype
But how is it started? O.o
I thought a line like Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, cancel As Boolean) followed by a control of the value of "target" was mandatory...
Here, it looks like magic to me :(
How does xAddr gets the address of the cell I clicked? Magic again...
I would like the execution of the code to happen only when I click the button I created. (it generate some infinite loops and excel shuts down :/ )
Or, if not possible, I would like it to be executed only when the data in the cell A4 are modified.
Thank's for your help :)
This is because the CELL function function is volatile it recalculates on every calculation (not only on calculation in cells it depends on). That means EncodeBarcode() calculates on every calculation too (because it is using the CELL() function in its parameters).
A Volatile Function is one that causes recalculation of the formula in the cell where it resides every time Excel recalculates.
This occurs regardless of whether the precedent data and formulas on which the formula depends have changed, or whether the formula also contains non-volatile functions.
If you remove the CELL() function and replace it with hard values
=EncodeBarcode(1;"B4";A4;51;1;0;2)
it doesn't re-calculate the barcode on every cell change. But it does only re-calculate the barcode if the cell it depends on changes (it this example cell A4).

Excel VBA: Dynamically Changing Function Based on Column

Heylo, I am trying to write an excel function that takes a user-selected range and performs different calculations based on the column the cell being populated lines up with. The screenshot below shows the setup of the columns.
I want to set AA5 to be "=myFunction($AA1:$AD4)", and then I want click-and-drag to use the autofill feature to populate AB5, AC5, and AD5 with the same "=myFunction($AA1:$AD4)" but this myFunction will do different things based on which cell is being populated during the autofill.
I know how to do this in a subroutine where the user would select the first open cell AA5, and is prompted for the range to use for calculations. I would do something along the lines of:
Sub CalcCells()
Dim myRange As Range
Set myRange = Application.InputBox("Select the cells you want to use...", Type:=8)
Dim numColumn As Long
For numColumn = 0 To myRange.Columns.Count - 1
Select Case numColumn
Case Is = 0
ActiveCell.Offset(0, numColumn).Formula = "=SUM(" + myRange.Columns(1) + ")"
Case Is = 1
ActiveCell.Offset(0, numColumn).Formula = "=SUMPRODUCT(" + myRange.Columns(1) + "," + myRange.Columns(2) + ")"
Case Is = 2
ActiveCell.Offset(0, numColumn).Formula = "=SUMPRODUCT(" + myRange.Columns(1) + "," + myRange.Columns(3) + ")/SUM(" + myRange.Columns(1) + ")"
Case Is = 3
ActievCell.Offset(0, numColumn).Formula = "=SUMSQ(" + myRange.Columns(4) + ")"
End Select
Next numColumn
End Sub
So basically I want to do exactly this, but I want it to be a function that when I click and drag and autofill AB5:AD5 it knows which column the cell lines up with and performs a calculation based on that, and uses it as an argument/parameter almost. It will not always be 4 rows either, so it needs to be capable of accommodating varying row counts, but the .Columns should work with that as long as the user selects only the same datatype.
Is this possible and how can I do it? Thank you for any help in advance. I've done a lot of searching and I don't know if I'm not searching the right way, but i cannot find anything that really helps.
What about something like this? Basically, you get the column of the cell you enter the formula into with Application.Caller.Column. Then inputRange.Column gives you the leftmost column of your input range. Based on the difference of the two, you know which worksheet function you want to use. If the difference is 0, your formula is entered in the 1st column, so you use Sum. If the difference is 1, you use Sumproduct, and so on.
Function SummarizeCells(inputRange As Range) As Double
Dim col As Long
col = Application.Caller.Column - inputRange.Column
Select Case col
Case 0
SummarizeCells = WorksheetFunction.Sum(inputRange.Columns(1))
Case 1
SummarizeCells = WorksheetFunction.SumProduct(inputRange.Columns(1), inputRange.Columns(2))
Case 2
SummarizeCells = WorksheetFunction.SumProduct(inputRange.Columns(1), inputRange.Columns(3)) / WorksheetFunction.Sum(inputRange.Columns(1))
Case 3
SummarizeCells = WorksheetFunction.SumSq(inputRange.Columns(4))
End Select
End Function
A sample view here:

Using If Statement in VBA to perform Copy and Paste

I need some help in writing a code in VBA. I thought I had a pretty good grasp of it, but apparently not.
I have two worksheets in a workbook, "Data" and "Results". "Data" has contents in E2 through E580, but this could change +/-. "Results" has contents in C10 that needs to be copied into D2 and down the column, but only if there is contents in "Data" (E2:E580). Here is what I have so far:
Worksheets("Data").If (Range("E2:E580") = " ", Copy.Worksheets("Results").Range("C10") AND Paste.Worksheets("Data").Range("D2:D580"), False)
Getting Compile Error:
Expected =
Thanks for your help in advance.
Sub copy_data()
Dim i As Integer
For i = 2 To Sheets("Data").Cells(Rows.Count, "E").End(xlUp).Row
If Sheets("Data").Cells(i, 5) <> "" Then
Sheets("Results").Cells(i, 4) = Sheets("Results").Range("C10")
Else
Sheets("Results").Cells(i, 4) = ""
End If
Next i
End Sub
Explanation: Loop through column E in "Data" down to the last entry and copy content from C10 in "Results" into column D if there is a value in the corresponding cell in "Data". If there is no value enter an empty string

Excel SpecialCells does not work when used inside spreadsheet

I wrote a VBA function in an attempt to get around using Array Formulas to check a range of cells against a condition and return some column offset.
It's basically a Sumif that, instead of returning a sum, returns a range of cells that would have been summed up.
The problem I'm encountering is that the code runs differently when being called inside a worksheet versus another function, specifically the .SpecialCells does not limit the range when needed.
For example, in the code I perform the comparison on only cells that have formulas or constants, and this works fine to limit the calculations performed when calling from another macro or the immediate window, but if I enter it as a formula in a sheet, it does not limit it at all (if I specified the entire column as a comparison, even if most of the cells in the column are blank it still goes through all 1048576 cells)
The important code is as follows:
For Each CheckCell In Check.SpecialCells(xlCellTypeConstants)
For Check as Range("A:A"), This ideally would run for, say 132 cells that have a constant in it, but instead runs the entire column.
Any ideas how to get this to work more sensibly? The rest of the code works very well for what I need, I just don't want to have it spending several seconds calculating the entire column for every cell this formula is used in.
Complete function:
Function RangeIf(returnColumn As Range, Check As Range, Condition As String) As Range
'Exit Function
Dim Operator As Integer, HasOperator As Boolean, TheColumn As String, CheckCell As Range, Passed As Boolean, ReturnRange As Range
HasOperator = True
Operator = 0
TheColumn = Mid(returnColumn.Cells(1, 1).Address, 2)
TheColumn = "$" & Mid(TheColumn, 1, InStr(1, TheColumn, "$"))
While HasOperator
Select Case Mid(Condition, 1, 1)
Case "<"
Operator = Operator Or 1
Condition = Mid(Condition, 2)
Case ">"
Operator = Operator Or 2
Condition = Mid(Condition, 2)
Case "="
Operator = Operator Or 4
Condition = Mid(Condition, 2)
Case Else
HasOperator = False
End Select
Wend
For Each CheckCell In Intersect(Check, Check.Parent.UsedRange).Cells
Passed = False
'UpdateStatusBar "Processing Cell: " & CheckCell.Address
Select Case Operator
Case 0, 4 'No op or Equals
If CheckCell.Value = Condition Then Passed = True
Case 1 ' Less than
If CheckCell.Value < Condition Then Passed = True
Case 2 ' Greater than
If CheckCell.Value > Condition Then Passed = True
Case 3 ' Not
If CheckCell.Value <> Condition Then Passed = True
Case 5 ' Less or Equal
If CheckCell.Value <= Condition Then Passed = True
Case 6 ' Greater or Equal
If CheckCell.Value >= Condition Then Passed = True
End Select
If Passed Then
If Not ReturnRange Is Nothing Then
Set ReturnRange = Union(ReturnRange, Range(TheColumn & CheckCell.Row))
Else
Set ReturnRange = Range(TheColumn & CheckCell.Row)
End If
End If
Next CheckCell
Set RangeIf = ReturnRange
End Function
SpecialCells does not work in UDFs. It's a limitation of Excel. Here's a list of things that don't work in UDFs.
http://www.decisionmodels.com/calcsecretsj.htm
You have to loop through the cells individually. Start my limiting Check to only the UsedRange.
For Each CheckCell in Intersect(Check, Check.Parent.UsedRange).Cells
That will keep it under a million, probably. You can reduce it further, but it will be specific to your situation.

Resources