Excel VBA - dropping and adding zeros in a specified character count - vba

I am attempting to build a vba code to build txt files that we use to test with. I am running into an issue. Some of my results will have .00 which I made dropped off using Str = Str & "00" & Left(CashRightJust(Range("h63"), 11), 9)
This basically is telling it to look at cell H63, right justified the amount but left justify the end by 9 to drop cents if it is "00".
My problem is we now need to test for it to have actual change like .25. Using this code alone adds a zero at the end of the change. I need to adjust this code to reflect if it is more then .00 do not edit or add zeros
I hope this makes sense. I am still fairly new at this and have gotten pretty far but there are still some moments I am lost. Thank you.
Spreadsheet created to build code to send to txt file
Function Detail_Rec1()
Dim strlencount As Integer
Dim strspacer As Integer
If Range("b63").Value <> "5" Then
Exit Function
End If
Str = Str & Range("b63").Value **Result: 5**
Str = Str & Range("c63").Value **Result: 400**
Str = Str & Range("d63").Value **Result: 1234567**
Str = Module1.SpaceAdd(Str, 1) **Result: 1 space**
Str = Str & Trim(Range("e63").Value)
strlencount = Len(Trim(Range("e63").Value))
strspacer = 30 - strlencount
Str = Module1.SpaceAdd(Str, strspacer) **Result: Company name with spacefill for 30 character; name is left justified**
Str = Str & Trim(Range("f63").Value)
strlencount = Len(Trim(Range("f63").Value))
strspacer = 11 - strlencount
Str = Module1.SpaceAdd(Str, strspacer) **Result: Company ID number; left justify; space filled total 11 characters**
Str = Str & Range("G63").Value Result: 116
Str = Str & CashRightJust(Range("h63"), 11) **Result: 1000; only 1000 no cents; dollars only; 11 character zero filled right justify**
Str = Str & CashRightJust(Range("i63"), 11)**Result: 1000; only 1000 no cents; dollars only; 11 character zero filled right justify**
Str = Str & CashRightJust(Range("j63"), 11)**Result: 1000; only 1000 no cents; dollars only; 11 character zero filled right justify**
Str = Str & Trim(Range("k63").Value)
strlencount = Len(Trim(Range("k63").Value))
strspacer = 4 - strlencount
Str = Module1.ZeroAdd(Str, strspacer) **Result: Rate of 4 characters entered; 4 character length**
Str = Module1.SpaceAdd(Str, 1) **Result: 1 space**
Str = Str & CashRightJust(Range("l63"), 11) **Result: 3348.75 needs to be 334875. 11 characters, right justified, no decimal.**
Str = Str & CashRightJust(Range("m63"), 11)
Str = Str & CashRightJust(Range("n63"), 11)
Str = Str & CashRightJust(Range("o63"), 11)
Str = Str & "00000" & Right(Range("p63").Value, 6)
strlencount = Len(Trim(Range("p63").Value))
strspacer = 6 - strlencount
Str = Module1.ZeroAdd(Str, strspacer)
[Excel image of line being coded][How text file should appear][2]2]
My end result for what I need is
23.45 - 2345
23.00 - 2300 unless the spec is saying dollars only then it needs to be 23
No rounding.
I hope this helps out more with the visuals
Added Info: My module that is used for the $ amts currently is following:
If Str = 0 Then
CashRightJust = ZeroAdd(Str2, c)
Exit Function
Else
If InStr(Str, ".") > 0 Then
Str2 = Right(Str, 2)
If InStr(Str2, ".") > 0 Then
strnew = Str & "0"
Else
strnew = Str
End If
Else
strnew = Str & "00"
End If
Excel snapshot of info being coded
54001234567 Bob's Tires 987654321 116000000010000000000005000000009503525 00000334875
This is how it is coming out:
54001234567 Bob's Tires 987654321 1160000010000000000005000000000950003525 000334875
I could not post the image of the txt file I don't have enough reputations; sorry; this is how it should appear

Can you try something like this? It assumes your entire string is in cell A1 and that this is the situation where dollars and cents are not required (I assume you can handle the situation where they are because it sounds like you more or less leave the string alone in that case).
lenOfStr = 11
newStr = ""
subStr = Right(Cells(1, 1), lenOfStr)
If Right(subStr, 2) = "00" Then 'we need to keep these
lenOfStr = 9
End If
foundLeftmost = False
For i = 1 To lenOfStr
If Mid(subStr, i, 1) <> "0" and Mid(subStr, i, 1) <> "." Then
newStr = newStr & Mid(subStr, i, 1) 'start collecting for the new string
foundLeftmost = True
End If
If foundLeftmost and Mid(subStr, i, 1) <> "." Then 'need to include zeros that may show up in the middle of the substring
newStr = newStr & Mid(subStr, i, 1)
End If
Next i
subStr = newStr
The end result is stored in subStr. Hopefully I understood your problem correctly. Let me know if I didn't.

Related

InputBox not allowing certain ASCII control characters

Currently working with this string that has embedded ASCII control charters.
[)><RS>06<GS>17V0B100<GS>1PRID-001-A1<GS>S99999<RS><EOT>
Below filters out the record separators correctly
i = InputBox("Test") 'i = [)><RS>06<GS>17V0B100<GS>1PRID-001-A1<GS>S99999<RS><EOT>
i = Split(i, Chr(30))
'i(1) = 0617V0B1001PRID-001-A1S99999
But group separators do not. Why does the below not split?
i = InputBox("Test") 'i = [)><RS>06<GS>17V0B100<GS>1PRID-001-A1<GS>S99999<RS><EOT>
i = Split(i, Chr(29))
'i(0) = [)>0617V0B1001PRID-001-A1S99999
Ignoring the inputbox, this works fine:
Dim s As String, arr
s = "[)><RS>06<GS>17V0B100<GS>1PRID-001-A1<GS>S99999<RS><EOT>"
s = Replace(s, "<RS>", Chr(30))
s = Replace(s, "<GS>", Chr(29))
Debug.Print s '[)>0617V0B1001PRID-001-A1S99999<EOT>
Debug.Print Split(s, Chr(30))(1) '0617V0B1001PRID-001-A1S99999
Debug.Print Split(s, Chr(29))(0) '[)>06
Debug.Print Split(s, Chr(29))(1) '17V0B100
Thanks to Tim for leading me to the correct answer. Below worked for converting key events to text.
Private Sub Text0_KeyPress(KeyAscii As Integer)
Dim i As Integer
i = Me.Text0.SelStart
Select Case KeyAscii
Case 4
Me.Text0.Text = Me.Text0.Text + "<EOT>"
Me.Text0.SelStart = i + 5
Case 29
Me.Text0.Text = Me.Text0.Text + "<GS>"
Me.Text0.SelStart = i + 4
Case 30
Me.Text0.Text = Me.Text0.Text + "<RS>"
Me.Text0.SelStart = i + 4
End Select
End Sub

Is there a way to maintain the length of a user entered number, to prevent removal of extra 0's?

I'm creating a Diabetes management algorithm, and I'm trying to find a way for the user's entered time blocks to be maintained at 4 digits
I've been searching on google, but all I have been able to find is how to check the length of a variable, which I already know how to do.
Sub timeBlocks()
Dim file As String = "C:\Users\Connor\Documents\Visual Studio 2017\Projects\meterCodeMaybe\TIMEBLOCKS.txt"
Dim blockNum As Integer
Console.WriteLine("Please be sure to enter times as a 24 hour value, rather than 12 hour, otherwise the input will not be handled.")
Console.Write("Please enter the amount of time blocks you require for your day: ")
blockNum = Console.ReadLine()
Dim timeA(blockNum - 1) As Integer
Dim timeB(blockNum - 1) As Integer
Dim sensitivity(blockNum - 1) As Integer
Dim ratio(blockNum - 1) As Integer
For i = 0 To (blockNum - 1)
Console.WriteLine("Please enter the start time of your time block")
timeA(i) = Console.ReadLine()
Console.WriteLine("Please enter the end time of your time block")
timeB(i) = Console.ReadLine()
Console.WriteLine("Please enter the ratio for this time block (Enter the amount of carbs that go into 1 unit of insulin)")
ratio(i) = Console.ReadLine()
Console.WriteLine("Please enter the insulin sensitivity for this time block
(amount of blood glucose (mmol/L) that is reduced by 1 unit of insulin.)")
sensitivity(i) = Console.ReadLine()
FileOpen(1, file, OpenMode.Append)
PrintLine(1, Convert.ToString(timeA(i)) + "-" + Convert.ToString(timeB(i)) + " 1:" + Convert.ToString(ratio(i)) + " Insulin Sensitivity:" + Convert.ToString(sensitivity(i)) + " per mmol/L")
FileClose(1)
Next
End Sub
Basically, I want the user to be able to enter a 4 digit number for their time block, to match a 24 hr time, so if they enter 0000, it is displayed as this, however, it removes all previous 0's and sets it to just 0.
Perhaps pad the number with 4 leading 0's:
Right(String(digits, "0") & timeA(i), 4)
Or as an alternative, store the value as a string so that it can be printed out in its original form.
I have written a Function to get a 24 hours format time from user, I hope it would help:
Public Function Read24HFormatTime() As String
Dim str As String = String.Empty
While True
Dim c As Char = Console.ReadKey(True).KeyChar
If c = vbCr Then Exit While
If c = vbBack Then
If str <> "" Then
str = str.Substring(0, str.Length - 1)
Console.Write(vbBack & " " & vbBack)
End If
ElseIf str.Length < 5 Then
If Char.IsDigit(c) OrElse c = ":" Then
If str.Length = 0 Then
' allow 0, 1 or 2 only
If c = "0" OrElse c = "1" OrElse c = "2" Then
Console.Write(c)
str += c
End If
ElseIf str.Length = 1 Then
If str = "0" Then
'allow 1 to 9
If c <> ":" Then
If CInt(c.ToString) >= 1 AndAlso CInt(c.ToString) <= 9 Then
Console.Write(c)
str += c
End If
End If
ElseIf str = "1" Then
'allow 0 to 9
If c <> ":" Then
If CInt(c.ToString) >= 0 AndAlso CInt(c.ToString) <= 9 Then
Console.Write(c)
str += c
End If
End If
ElseIf str = "2" Then
'allow 0 to 4
If c <> ":" Then
If CInt(c.ToString) >= 0 AndAlso CInt(c.ToString) <= 4 Then
Console.Write(c)
str += c
End If
End If
End If
ElseIf str.Length = 2 Then
'allow ":" only
If c = ":" Then
Console.Write(c)
str += c
End If
ElseIf str.Length = 3 Then
If str = "24:" Then
'allow 0 only
If c = "0" Then
Console.Write(c)
str += c
End If
Else
'allow 0 to 5
If c <> ":" Then
If CInt(c.ToString) >= 0 AndAlso CInt(c.ToString) <= 5 Then
Console.Write(c)
str += c
End If
End If
End If
ElseIf str.Length = 4 Then
If str.Substring(0, 3) = "24:" Then
'allow 0 only
If c = "0" Then
Console.Write(c)
str += c
End If
Else
'allow 0 to 9
If c <> ":" Then
If CInt(c.ToString) >= 0 AndAlso CInt(c.ToString) <= 9 Then
Console.Write(c)
str += c
End If
End If
End If
End If
End If
End If
End While
Return str
End Function
The user can only enter time like 23:59 08:15 13:10 and he couldn't enter formats like 35:10 90:00 25:13 10:61
This is a sample code to show you how to use it:
Dim myTime = DateTime.Parse(Read24HFormatTime())
Dim name = "Emplyee"
Console.WriteLine($"{vbCrLf}Hello, {name}, at {myTime:t}")
Console.ReadKey(True)

How can I extract the 'logical_test' from an if statement in excel?

I'm putting together an excel spreadsheet for calculations, and I need to be able to show the formulas to go with the decisions, for the most part its pretty straight forward, but When I come to an 'if' formula in an excel cell, I don't want to show the value_if_true and value_if_false... Just the logical_test value.
Example:
Formula is: =if(and(5<=A1, A1<=10),"Pass", "Fail");
Result will be: "and(5<=A1, A1<=10)"
I need to be able to work with complex logical tests which may include nested if statements, so just splitting at the commas won't work reliably. Similarly the value_if_true and value_if_false statements could also contain if statements.
Any ideas?
If have clear understanding of what you asking for, then you can use something like this (shall be used only with IF() statement :
Function extrIf(ByVal ifstatement As Range) As String
Dim S$, sRev$, x%, k
S = Replace(Replace(ifstatement.Formula, "IF(", "\"), "),", ")|")
sRev = StrReverse(S)
If InStr(1, sRev, "|") > InStr(1, sRev, "\") Or InStr(1, sRev, "|") = 0 Then
x = InStr(1, StrReverse(Left(sRev, InStr(1, sRev, "\"))), ",") - 1
S = Mid(S, 1, Len(S) - InStr(1, sRev, "\") + x) & "|"
End If
sRev = ""
For Each k In Split(S, "|")
If k <> "" Then
If k Like "*\*" Then
sRev = sRev & ", " & Mid(k, InStr(1, k, "\") + 1, 999)
End If
End If
Next
extrIf = Mid(sRev, 3, 999)
End Function
example:
test:
Maybe this is not complete solution for you, but I think it might give you right direction.
If the cell formula starts with an If statement then you can return the logic test (starting after the first open parenthesis) by determining the position of the first comma where the sum of the previous open parenthesis - the sum previous closed = 0.
Formulas
Function ExtractIfTest(Target As Range) As String
Dim ch As String, s As String
Dim openP As Long
Dim x As Long
s = Target.formula
For x = 5 To Len(s)
ch = Mid(s, x, 1)
If Mid(s, x, 1) = "(" Then
openP = openP + 1
ElseIf Mid(s, x, 1) = ")" Then
openP = openP - 1
ElseIf Mid(s, x, 1) = "," And openP = 0 Then
ExtractIfTest = Mid(s, 5, x - 12)
End If
Next
End Function
Results
There might be instances where the is a comma without parenthesis A1,B1. If this happens simple escape them with parenthesis (A1,B1)
I've written an UDF that extract any of the parameters of the target formula. It's close to the one in Thomas answer, but more global and takes into account strings that can enclose commas or parenthesis.
Function ExtractFormulaParameter(Target As Range, Optional Position As Long = 1) As Variant
Dim inString As Boolean
Dim formula As String
Dim st As Long, sp As Long, i As Long, c As String
Dim parenthesis As Long, comma As Long
formula = Target.formula
st = 0: sp = 0
If Position <= 0 Then ExtractFormulaParameter = CVErr(xlErrValue): Exit Function
For i = 1 To Len(formula)
c = Mid$(formula, i, 1)
If inString Then
If c = """" Then
inString = False
End If
Else
Select Case c
Case """"
inString = True
Case "("
parenthesis = parenthesis + 1
If parenthesis = 1 And Position = 1 Then
st = i + 1
End If
Case ")"
parenthesis = parenthesis - 1
If parenthesis = 0 And sp = 0 Then sp = i: Exit For
Case ","
If parenthesis = 1 Then
comma = comma + 1
If Position = 1 And comma = 1 Then sp = i: Exit For
If Position > 1 And comma = Position - 1 Then st = i + 1
If Position > 1 And comma = Position Then sp = i: Exit For
End If
Case Else
End Select
End If
Next i
If st = 0 Or sp = 0 Then
ExtractFormulaParameter = CVErr(xlErrNA)
Else
ExtractFormulaParameter = Mid$(formula, st, sp - st)
End If
End Function
By default it returns the first parameter, but you can also return the second or the third, and it should work with any formula.
Thanks for the replies all. I thought about this more, and ended up coming up with a similar solution to those posted above - essentially string manipulation to extract the text where we expect to find the logical test.
Works well enough, and I'm sure I could use it to extract further logical tests from substrings too.

Calculate words value in vb.net

I have a textbox on a form where the user types some text. Each letter is assigned a different value like a = 1, b = 2, c = 3 and so forth. For example, if the user types "aa bb ccc" the output on a label should be like:
aa = 2
bb = 4
dd = 6
Total value is (12)
I was able to get the total value by looping through the textbox string, but how do I display the total for each word. This is what I have so far:
For letter_counter = 1 To word_length
letter = Mid(txtBox1.Text, letter_counter, 1)
If letter.ToUpper = "A" Then
letter_value = 1
End If
If letter.ToUpper = "B" Then
letter_value = 2
End If
If letter.ToUpper = "C" Then
letter_value = 3
End If
If letter.ToUpper = "D" Then
letter_value = 4
End If
If letter.ToUpper = "E" Then
letter_value = 5
End If
If letter.ToUpper = " " Then
letter_value = 0
End If
totalletter = totalletter + letter_value
Label1.Text = Label1.Text & letter_value & " "
txtBox2.Text = txtBox2.Text & letter_value & " "
Next letter_counter
This simple little routine should do the trick:
Private Sub CountLetters(Input As String)
Label1.Text = ""
Dim total As Integer = 0
Dim dicLetters As New Dictionary(Of Char, Integer)
dicLetters.Add("a"c, 1)
dicLetters.Add("b"c, 5)
dicLetters.Add("c"c, 7)
For Each word As String In Input.Split
Dim wordtotal As Integer = 0
For Each c As Char In word
wordtotal += dicLetters(Char.ToLower(c))
Next
total += wordtotal
'Display word totals here
Label1.Text += word.PadRight(12) + "=" + wordtotal.ToString.PadLeft(5) + vbNewLine
Next
'Display total here
Label1.Text += "Total".PadRight(12) + "=" + total.ToString.PadLeft(5)
End Sub
This should give you an idea:
Dim listOfWordValues As New List(Of Integer)
For letter_counter = 1 To word_length
letter = Mid(txtBox1.Text, letter_counter, 1)
If letter = " " Then
totalletter= totalletter + letter_value
listOfWordValues.Add(letter_value)
letter_value = 0
Else
letter_value += Asc(letter.ToUpper) - 64
End If
Next letter_counter
totalletter = totalletter + letter_value
If Not txtBox1.Text.EndsWith(" ") Then listOfWordValues.Add(letter_value)
txtBox2.Text = txtBox2.Text & string.Join(", ", listOFWordValues);
You can try something like this. Assuming txtBox1 is the string the user enters and " " (space) is the word delimiter:
Dim words As String() = txtBox1.Text.Split(New Char() {" "}, StringSplitOptions.RemoveEmptyEntries)
Dim totalValue As Integer = 0
Dim wordValue As Integer = 0
For Each word As String In words
wordValue = 0
For letter_counter = 1 To word.Length
Dim letter As String = Mid(txtBox1.Text, letter_counter, 1)
Select letter.ToUpper()
Case "A":
wordValue = wordValue + 1
Case "B":
wordValue = wordValue + 2
' And so on
End Select
Next
totalValue = toalValue + wordValue
Next
The above code first takes the entered text from the user and splits it on " " (space).
Next it sets two variables - one for the total value and one for the individual word values, and initializes them to 0.
The outer loop goes through each word in the array from the Split performed on the user entered text. At the start of this loop, it resets the wordValue counter to 0.
The inner loop goes through the current word, and totals up the values of the letter via a Select statement.
Once the inner loop exits, the total value for that word is added to the running totalValue, and the next word is evaluated.
At the end of these two loops you will have calculated the values for each word as well as the total for all the worlds.
The only thing not included in my sample is updating your label(s).
Try this ..
Dim s As String = TextBox1.Text
Dim c As String = "ABCDE"
Dim s0 As String
Dim totalletter As Integer
For x As Integer = 0 To s.Length - 1
s0 = s.Substring(x, 1).ToUpper
If c.Contains(s0) Then
totalletter += c.IndexOf(s0) + 1
End If
Next
MsgBox(totalletter)
I would solve this problem using a dictionary that maps each letter to a number.
Private Shared ReadOnly LetterValues As Dictionary(Of Char, Integer) = GetValues()
Private Shared Function GetValues() As IEnumerable(Of KeyValuePair(Of Char, Integer))
Dim values As New Dictionary(Of Char, Integer)
Dim value As Integer = 0
For Each letter As Char In "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
value += 1
values.Add(letter, value)
Next
Return values
End Function
Public Function CalculateValue(input As String) As Integer
Dim sum As Integer = 0
For Each letter As Char In input.ToUpperInvariant()
If LetterValues.ContainsKey(letter) Then
sum += LetterValues.Item(letter)
End If
Next
Return sum
End Function
Usage example:
Dim sum As Integer = 0
For Each segment As String In "aa bb ccc".Split()
Dim value = CalculateValue(segment)
Console.WriteLine("{0} = {1}", segment, value)
sum += value
Next
Console.WriteLine("Total value is {0}", sum)
' Output
' aa = 2
' bb = 4
' ccc = 9
' Total value is 15

Comparing Basic Strings Exponential Complexity

I may be asking a silly question but I am self teaching myself VBA and I am just stumped and I am not even sure what terms I can use to look up a solution.
I am writing a code that will compare three variables to three other variables then I want to display which variables have changed.
So if x = a but y <> b and z <> c then the output should be b/c
I have worked out a code that works fine
Dim Str As String
If X <> A Then
If Y <> B Then
If Z <> C Then
Str = "a/b/c"
Else
Str = "a/b"
End If
ElseIf Z <> C Then
Str = "a/c"
Else
Str = "a"
End If
ElseIf Y <> B Then
If Z <> C Then
Str = "b/c"
Else
Str = "b"
End If
Else
Str = "c"
End If
But as I increase the number of variables this becomes extremely complex very quickly.
If anyone can help direct me to a simpler method without the exponential complexity I would be very grateful.
Thank you all so much!
You need to test each variable pair independently from each other -- not link them together in one giant If construct tree.
Example:
str = "" 'Start with blank string. Append as required.
If x <> a Then str = str & "a/"
If y <> b Then str = str & "b/"
If z <> c Then str = str & "c/"
'Remove the extra / at the end
If Right(str, 1) = "/" Then str = Left(str, Len(str - 1))
You could put the 2 strings in 2 arrays, and then use a FOR...NEXT construct to loop both arrays. You can use UBound(arValues) to dynamically find out the number of items in the array.
Good luck