Alright, I am trying to create a user defined function that will accept one character input, and then return a String. Although I couldn't get the Char data type to work for VBA (not sure why?) I was able to get it working fine if I inputted the single character value as a String.
Currently, when the UDF is running within Excel, it will work as long as quotes are placed around the String input, as shown:
=tradeClass("o")
that will work and return the correct result. But what I really would like to do is be able to write the Excel function in this manner:
=tradeClass(o)
I wrote out code that will concatenate "" onto the ends of my user input:
Public Function tradeClass(class As String)
Dim result As String
Dim before As String
Dim after As String
before = """"
after = """"
class = before & class & after 'my attempt to concatenate " on either side of the class input
Select Case LCase(class)
Case "s"
result = "Sale"
Case "r"
result = "Redemption"
Case "i"
result = "Exchange In"
Case "o"
result = "Exchange Out"
Case "x"
result = "Ignore"
Case "k"
result = "Settle"
Case "m"
result = "Transfer"
Case "w"
result = "ML PR3 Redemption (No longer in use)"
Case Else
result = "Invalid Entry"
End Select
tradeClass = result
End Function
It returns a #VALUE! error in Excel, so I am starting to think that I will always have to enter quotation marks with my character. Is this the case?
I can't imagine what compelling reason you have to want to do this. If you were truly determined, there is a hack/workaround/kludge that will allow this to work.
I strongly advise you not to use this, but you asked a question to which I have an answer. So here goes:
When you pass an unquoted string of character(s) to a function in Excel, it assumes that it is a reference to another cell. This is obvious in a function call such as =tradeClass(A1). That function will pass the value of the cell A1 to the function tradeClass.
Excel also supports naming cells to make formulas easier to follow. For example, you could assign the name "StartYear" to cell A1. So instead of using =DATE(A1, 1, 1) you could use =DATE(StartYear, 1, 1) which will be easier to maintain.
Now, in your case, we can abuse this power and name a bunch of cells s, r, i, o, x, k, m, and w. In the cell named s we would enter the letter "s" as the cell's value and so on. So then when you call =tradeClass(o), Excel will look up the cell named o and get its value (which we set to "o") and pass the value "o" of the cell named o to your tradeClass function.
This is a dreadful abuse of the cell-naming capabilities of Excel and violates all sorts of good programming practice. But ask, and you shall receive.
DISCLAIMER: Please do not actually use this.
One final note: When, someday, someone comes along and changes the value of the cell named o to the letter "s", please refer to the above disclaimer.
Unfortunately, if you want this function to work on user input as specified, you're going to need to pass in with quotes. Try out a built-in Excel function, for example:
=LEN(String)
vs.
=LEN("String")
The second one gives you 6, while the first one gives you an error.
However, your code would work nicely if the trade classes you're after are in their own column, like this:
Public Function tradeClass(Str As Variant)
Str = CStr(Str) 'force the variant to a string
Select Case LCase(Str)
Case "s"
result = "Sale"
Case "r"
result = "Redemption"
Case "i"
result = "Exchange In"
Case "o"
result = "Exchange Out"
Case "x"
result = "Ignore"
Case "k"
result = "Settle"
Case "m"
result = "Transfer"
Case "w"
result = "ML PR3 Redemption (No longer in use)"
Case Else
result = "Invalid Entry"
End Select
tradeClass = result
End Function
I know it’s an old question but got the same error and found the reason and something like this worked for me: you shall use Public Function tradeclass(class) and then class = class.address. Hope this helps!
Related
I am using:
For t = 1 To ActiveProject.Tasks.Count
With Tasks(t)
' DurSen is a Custom Field Name. It returns an application field Number (such as 188743770 for Number4)
Projectfield = FieldNameToFieldConstant("DurSen")
' the SpearmanRankCorrelation function occasionally produces a result like -6.06060606060606E-03.
.SetField (Projectfield), SpearmanRankCorrelation(ProjectDuration, TmpVec, NN)
End With
Next t
This is a number expressed as an exponent. Due to the presence of the "E" in the number, it fails the Setfield function. The .Setfield places the calculated value into the field referenced by ProjectField for a specific task, (t) but fails when the calculated value contains an "E".
Any ideas on avoiding this problem?
The SetField method expects the Value parameter to be a string. So the solution is to format the number as a string. (e.g. Format(-6.06060606060606E-03, "0.0#####") => -0.006061)
However, Microsoft Project will round the value to the nearest hundredth, so you will lose the precision.
Better to store numbers that require more than two decimal places in a text field and convert them back to numbers as needed in code.
I had a simpler answer hat I figured out yesterday. Instead of trying to deal with it as a number, I simply converted to a string, used the InStr method to see if an "E" was present and if it is, assign a value of 0.001 as the result, as any E value will be smaller than .01.
If InStr(SpearmanRankCorrelation(ProjectDuration, TmpVec, NN), "E") > 0 Then
.SetField (Dursen), 0.001
PD = 0.001
outputstr = ("575 - ID= " & tasks(t).ID & " SpearmanRankCorrelation= " & SpearmanRankCorrelation(ProjectDuration, TmpVec, NN) & " Replaced by 0.001")
Call Txt_Append(myfile, outputstr)
Else
.SetField (Dursen), SpearmanRankCorrelation(ProjectDuration, TmpVec, NN)
End If
Thank you, Rachel, for your answer. I am trying not to use Project Custom fields. The .Setfield above assigns the Result to the field which is identified by the value of Dursen ( which is an internal field reference).
Rachel, I tried your approach but whenever I encountered a result that was expressed with an "E" in the Result, the conversion failed. using >Setfield will only round to the nearest hundreth if the entire string is a numeric value (when converted). The >Setfield method does not work when there is an "E" in the expression.
I have problem of extracting two-character code from the string format like:
"VA198-VA200-VA197"
I just want to get the string:
"VA-VA-VA"
Also the data I have are not just in one format, some data is like:
"DL123-DL245"
or
"DL123-VA345-HU12-OZ123"
Does anyone know how to do it fast in excel? Thanks.
With data in A1, in B1 enter the array formula:
=TEXTJOIN("",TRUE,IF(ISERR(MID(A1,ROW(INDIRECT("1:100")),1)+0),MID(A1,ROW(INDIRECT("1:100")),1),""))
NOTE:
The formula strips out all numeric characters, leaving only the alphas and the dash.
Array formulas must be entered with Ctrl + Shift + Enter rather than just the Enter key. If this is done correctly, the formula will appear with curly braces around it in the Formula Bar.
There are a couple of ways you can approach this depending on how many possible segments their are in your string. If we assume your flight number is in A1:
First Segment: =LEFT(A1,2)
Second Segment: =MID(A1,FIND("-",A1)+1,2)
Third Segment: =MID(A1,FIND("-",A1,FIND("-",A1)+1)+1,2)
You could then concatenate the three expressions together and add a fourth with some conditionals. The problem is that based on your information you can have anywhere from 1 to 4 (at least) names which means you'll need a conditional:
Second Segment: =IF(ISERR(FIND("-",A1)),"",MID(A1,FIND("-",A1)+1,2))
Adding in the separators we end up with something like this for up to four segements:
=CONCATENATE(LEFT(A1,2),IF(ISERR(FIND("-",A1)),"",CONCATENATE("-",MID(A1,FIND("-",A1)+1,2))),IF(ISERR(FIND("-",A1,FIND("-",A1)+1)),"",CONCATENATE("-",MID(A1,FIND("-",A1,FIND("-",A1)+1)+1,2))),IF(ISERR(FIND("-",A1,FIND("-",A1,FIND("-",A1)+1)+1)),"",CONCATENATE("-",MID(A1,FIND("-",A1,FIND("-",A1,FIND("-",A1)+1)+1)+1,2))))
This will give you everything in one field.
Here is a VBA type answer.Assuming all strings are structured in the same way. Meaning Two letters followed by numbers and separated with "-". If one such string is A1, and you want to write the result to B1:
Sub BreakStrings()
d = Split(Range("A1"), "-")
For i = LBound(d) To UBound(d)
d(i) = Left(d(i), 2)
Next i
strg = Join(d, "-")
Range("B1") = strg
End Sub
User-defined function (UDF):
Function GetVal(cell)
With CreateObject("VBScript.RegExp")
.Global = True: .Pattern = "(\w{2})(.+?)(?=-|$)"
GetVal = .Replace(cell, "$1")
End With
End Function
In some of my column's cells there appear #VALUE! words and formulas inside are as follows example:
=IF(VALUE(RIGHT(CELL("nome.arquivo";A1);LEN(CELL("nome.arquivo";A1))-SEARCH("]";CELL("nome.arquivo";A1))))=1;1;1+INDIRECT(ADDRESS(329;COLUMN();;;VALUE(RIGHT(CELL("nome.arquivo";A1);LEN(CELL("nome.arquivo";A1))-SEARCH("]";CELL("nome.arquivo";A1))))-1)))
Mentioned column is CT. Now when i am trying to loop through the cells when it comes to first occurence of #VALUE! i get an error:
Run Time Error 13, Type mismatch on this line:
L = 9
Do While Cells(L, "CT").Value <> "" '<========= HERE AN ERROR
L = L + 8
Loop
How to fix that?
Cells doesn't take any String parameter, the type mismatch error you're getting has nothing to do with #VALUE / the data being processed - because the code never gets to evaluate the data, since Cells wants two Integer parameters: dang this bites me everytime - apparently you can use string parameters in the Cells function. Ugh.
So the problem is with comparing the value to "" - a string. #VALUE! isn't a valid string function so you need to account for it:
Dim theCell As Range
Set theCell = Cells(L, "CT")
If Not IsError(theCell.Value) Then
Do While Not IsEmpty(theCell.Value)
'...
L = L + 8
Loop
End If
You might also want to correctly qualify that function call:
Do While Not IsEmpty(ActiveSheet.Cells(L, "CT").Value)
That way it's explicit that you're looking at a cell in the active worksheet; an unqualified call is implicitly doing that, and anything implicit is potentially confusing and bug-prone.
Make sure the top of the module says Option Explicit and that L is properly declared:
Dim L As Long
"L" being a meaningless identifier, you should consider naming it after what you're using it for:
Dim currentRow As Long
currentRow = 9
Do While Not IsEmpty(ActiveSheet.Cells(currentRow, "CT"))
That way your code becomes much easier to read, follow and understand.
I have putted several checkboxes in a frame and I need to make an select case statement among these checkboxes. Is there a way to select the checkboxes which are within the frame through a select case statement?
I have tried it below ( my frame is named diet_frame ) but I get a type mismatch error.
Private Sub add_button_Click()
Dim target_range As Range: Set target_range = Range("A2:G29")
Dim period As String
target_range.End(xlDown).Offset(1, 0).Select
With Selection: i = 1
Selection.Value = name_input
Selection.Offset(0, i) = period_input.Value
Select Case diet_frame <<<--- name of my frame, trying to get to the checkboxes placed inside it.
Case meat_input.Value = True
Selection.Offset(0, i + 1) = carnivore
Case vegetation_input.Value = True
Selection.Offset(0, i + 1) = herbivore
Case vegetation_input = True And meat_input = True
Selection.Offset(0, i + 1) = omnivore
End Select
Selection.Offset(0, i + 6) = group_input.Value
End With
Frames are usually used to group option (radio) buttons, to make them mutually exclusive. I don't think they provide the functionality you expect. You need to be explicit about a few things: Where are name_input, period_input, group_input coming from? If those are cells I expect to see some "RangeRefersTo". Do you have a single set of checkboxes or many sets aligned with these many rows of the Selection object? Programmatically generated or manually (and left as-is for the life of workbook)? You might have meant a "ForEach ... in Selection" rather than a "With"? The With is not going to iterate through cells for you. By the way, you aren't incrementing your "i" column-number so why not just hard-code the 1, 2, and 7? If you have lots of static checkboxes use the tag property to store the row number associated with them. That is a way to tie them to specific cells if you don't have bound-cells for them. If you have lots of boxes and bound-cells then you can't re-use the 3 names for them without going to some extra trouble. You want to poll a bunch of checkboxes?
(quite possibly pseudocode)
Dim ctrl as control
For Each ctrl in controls
If ctrl.typeof = "checkbox" then
'maybe further specificity based on If ctrl.name = "somename" ...
' more code...
cells(ctrl.tag,7).value2 = whatever
End if
As far as your "Select", your code will run as-is if you just put Select Case True (weird but it works) but for Omnivores you're setting the value 3 times. Actuall you don't even need VBA. You can do all this with cell formulas. Put 1 for true in bound cell for meat, 2 for veggie. Put a sum of them in an IF or CHOOSE formula in the target cell (where "...vore" will appear). So 1 is Carnie, 2 is Veggie, 3 is Omni. On a Gnu/Linux box or I'd be more explicit.
The help says that
the select case statement requires a numeric or string expression - ie an expression which result can be evaluated as a number or a string via the play of automatic conversions. So strictly on the form, and unless the Frame object default property actually returned a numeric or string expression, you're going to get a type mismatch error there with your code
the result of the expression from the select case statement is going to be matched against the result of the expressions for the different Case clauses - so these expressions need to be type compatible as well
Try and use the True keyword as your expression for the select case statement - booleans are automatically convertible to both numbers and strings -, and the values of your checkboxes for each Case clauses. This would also require for you to move the case where both your checkboxes are True to the first place in your Case hierarchy - reason for this is that when expressions match for more than one Case clause, only the statements following the first match are executed
In my Excel spreadsheet I have two columns.
A contains strings with the values 'Yes', 'No' or 'Maybe'.
B contains strings with a year in.
I need a function to determine the number of occurrences of a year in column B, where the equivalent value in column A is 'Yes'.
I currently have the following code:
Function CountIfYearAndValue(Rng As Range, YNM As String, Year As String) As Integer
Dim count As Integer
count = 0
For Each c In Rng.Cells
If (StrComp(Abs(c.Value), Year, vbTextCompare) = 0) And (StrComp(Cells(c.Row, A), YMN, vbTextCompare) = 0) Then count = count + 1
Next
CountIfYearAndValue = count
End Function
The idea of this code is that we iterate through every cell in the range given (a range on column B) and check if the year is equal to the Year parameter. And if the equivalent cell on column A is equal to the YNM parameter we increment the count variable.
For some reason this code does not work when I use the following parameter:
=CountIfYearAndValue('Years'!B1:B7,"Yes","Year 7")
It just does the #VALUE error and refuses to display any outcome.
Any help would be much appreciated.
Edit: All of the values in both cells are on of an unformatted datatype ('General') and no cells are blank.
It sounds like you are reinventing the wheel... There already is a built in function (advantage: being much faster than a UDF) that does exactly what you are after. It is called COUNTIFS()
All YESes for Year 7 in rows 1 to 10.
=COUNTIFS(B1:B10, "Year 7",A1:A10, "Yes")
I just had a quick look at your code and I think there are possibly a few reasons why your original code is not working as expected.
YNM is a valid column name therefore it should not be used as a variable name. You should avoid naming your variables like that - give it a more meaningful name
YNM != YMN as you had it in your code (see function definition and then the misspelled version in the StrComp() function)
Year is a valid VBA built in function, therefore once again you should avoid using it as a variable name as you're exposing yourself to a naming collision.
Add Option Explicit at the top of your module. This requires you to Dimension all you variables. It's always recommended for many many reasons.
rng variable is of Range type therefore you do not need to explicitly add the .Cells property to it. Even though it may help in some cases - at a bit more advanced level you may face some runtime type compatibility issues. ( runtime may convert your rng Range variable to a 2D array etc )
Added an explicit conversion in the second StrComp() function around the c.Offset(0, -1) as you don't want the runtime to (rare but still possible) convert your Yes to a Boolean data type. Explicit conversion to a String just gives you that extra protection ;p (lol)
therefore, something like this returns the correct value
Function CountIfYearAndValue(rng As Range, choice As String, myYear As String) As Long
Dim count As Long
count = 0
Dim c As Range
For Each c In rng
If (StrComp(c, myYear, vbTextCompare) = 0) And (StrComp(CStr(c.Offset(0, -1)), choice, vbTextCompare) = 0) Then
count = count + 1
End If
Next c
CountIfYearAndValue = count
End Function
Right, I hope this helps you understand bits and pieces :) any questions please leave a comment