I am trying to make a program that searches for specific content in cells for excel:
rows represent hours of the day from 00:00 to 23:00
columns represent a day of the month
The following code matches the input of the user to the content of the cells either by using the name. It can also add or skip a time interval for the event
However the following code always runs the instructions under Else even if the user inputs 2 numeric values for the 2 param fields. Some help would be appreciated:
pNume = paramNume
aux = ""
aux1 = paramHBegin - 1
aux2 = paramHEnd - 1
If IsNumeric(paramHBegin) And IsNumeric(paramHEnd) Then
For i = 1 To 31
For j = aux1 To aux2
If Cells(i + 1, j + 1) Like pNume & "*" Or Cells(i + 1, j + 1) Like "*" & pNume & "*" _
Or Cells(i + 1, j + 1) Like "*" & pNume Then
aux = aux + Cells(i + 1, j + 1) + " la ora " + CStr(i) + vbCrLf
End If
Next j
Next i
Else
For i = 1 To 31
For j = 1 To 24
If Cells(i + 1, j + 1) Like pNume & "*" Or Cells(i + 1, j + 1) Like "*" & pNume & "*" _
Or Cells(i + 1, j + 1) Like "*" & pNume Then
aux = aux + Cells(i + 1, j + 1) + " la ora " + CStr(i) + vbCrLf
End If
Next j
Next i
End If
displayInfo.Text = aux
This statement
If IsNumeric(paramHBegin) And IsNumeric(paramHEnd) Then
will only be true if both of the values you pass are numeric. But you wrote that "However the following code always runs the instructions under Else even if the user inputs 2 numeric values for the 2 param fields."
Assuming paramHBegin and paramHEnd are defined as strings, the only way I can see that happening is for one or both of the values having a character that isn't numeric. In Excel 2013 IsNumeric ignores carriage returns, tabs and spaces so they aren't the cause of the problem. If they are defined as objects, then you should specify the correct property of those objects.
Sorry I can't try it myself, but try something like this:
If IsNumeric(paramHBegin.Text) And IsNumeric(paramHEnd.Text) Then
or this:
If IsNumeric(val(paramHBegin.Text)) And IsNumeric(val(paramHEnd.Text)) Then
I think it's better to handle user inputs right at the input session itself rather then passing everything forward and then trying to handle exceptions
for instance, to force (to some extent) numeric inputs you could place in the UserForm code pane what follows:
Option Explicit
Private Sub paramHBegin_AfterUpdate()
TextBoxValidate Me.paramHBegin
End Sub
Private Sub paramHEnd_AfterUpdate()
TextBoxValidate Me.paramHEnd
End Sub
Private Sub paramNume_AfterUpdate()
TextBoxValidate Me.paramNume
End Sub
Sub TextBoxValidate(ctrl As MSForms.TextBox)
Dim number As Double
Me.CommandButton1.Enabled = False
With ctrl
If Not ValidateText(.text, number) Then
MsgBox "invalid input", vbCritical
.SetFocus
Else
.text = number
Me.CommandButton1.Enabled = True
End If
End With
End Sub
Function ValidateText(text As String, number As Double) As Boolean
On Error Resume Next
number = CDbl(WorksheetFunction.Substitute(text, " ", ""))
ValidateText = IsNumeric(number)
End Function
in essence:
add an AfterUpdate event handler for every relevant TextBox
that event handler would simply pass the validation dues to a specific sub (TextBoxValidate()) where you can put code to handle general TextBox validation environment
for instance I
disabled CommandButton1 button (the "OK" one in my test)
call a function (ValidateText()) for validating the Text property of a TextBox control which would return:
True and the validated number, if the Text property were actually a possible number
False otherwise
if validation result is True:
update the TextBox with the validated number
enable the "OK" Button
if validation result is False:
prompt a message and return the focus to the "invalid" TextBox
of course you can tune all those Subs and Functions to your actual needs like:
checking for specific text formats (with Like operator or - better - with RegEx object)
checking for specific types ( using Clng() or CInt() or CDate() instead of CDbl() in a sort of TryParse() fashion
changing validation rules according to each control type (TextBox rather than ListBox and so on) and property (Text Property rather than Value ...)
Finally
use "&" operator instead of "+" one to concatenate strings
If Cells(i + 1, j + 1) Like pNume & "*" Or Cells(i + 1, j + 1) Like "*" & pNume & "*" _
Or Cells(i + 1, j + 1) Like "*" & pNume Then
can be reduced to:
If Cells(i + 1, j + 1) Like "*" & CStr(pNume) & "*" Then
hope all this can help
Related
I have this Sub. It is activated when pressing a button on a user form and is used to count one entry up. I have the total amount of entries in this data base stored in A1. There is another button used to count one entry down, it works just fine. They both have checks so they don't load entries that don't exist. Somehow this one doesn't work.
Private Sub ButtonRight_Click()
MsgBox TextBoxID.Value
MsgBox Cells(1, 1).Value
MsgBox (TextBoxID.Value < Cells(1, 1).Value)
If TextBoxID.Value < Cells(1, 1).Value Then
LoadEntry (TextBoxID.Value + 1)
End If
End Sub
The LoadEntry Sub is used in other places as well and it works. I have this output stuff with MsgBox for debugging. It gives the outputs 1, 2, false. So (1 < 2) = false.
For comparison here is the other one which works:
Private Sub ButtonLeft_Click()
If TextBoxID.Value > 1 Then
LoadEntry (TextBoxID.Value - 1)
End If
End Sub
The problem is implicit conversions.
Strings are compared as text, so "10" is smaller than "2" because it sorts alphabetically as such.
Debug.Print "10" > "2" ' output: False
The value of a TextBox control is always a String; in order to treat it as a numeric value you must first convert it to a numeric value - but only if it's legal to do so (e.g. "ABC" has no equivalent numeric value).
Moreover, a cell's value is a Variant that may contain a number or another value that can (will) correctly but implicitly convert to a numeric value, but it could also be a Variant/Error (e.g. #N/A, or #VALUE! errors) that will throw a type mismatch error every time you try to compare it to anything (other than another Variant/Error value), so the cell's value should also be validated and explicitly converted before it's compared:
Dim rawValue As String
rawValue = TextBoxID.Value
If IsNumeric(rawValue) Then
Dim numValue As Double
numValue = CDbl(rawValue)
Dim cellValue As Variant
cellValue = ActiveSheet.Cells(1, 1).Value
If IsNumeric(cellValue) Then
If numValue < CDbl(cellValue) Then
LoadEntry numValue + 1
End If
End If
End If
Note that unqualified, Cells is implicitly referring to whatever the ActiveSheet happens to be - if that isn't the intent, consider qualifying that member call with an explicit Worksheet object, e.g. Sheet1.Cells(1, 1). If it is intentional, consider qualifying it with ActiveSheet so that the code says what it does, and does what it says.
Comparing values of different types in VBA is not a simple task, the result of the comparison depends on the types of variables, the possibility of conversion to a number, etc. Variant variables are compared differently than "non-Variant" variables. See https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/comparison-operators
According to the documentation, the Value property of the TextBox object has a base type Variant (see https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/value-property-microsoft-forms).
Therefore, the result of comparing Variant/String (TextBox.Value with String) and Variant/Double (Cell.Value with number) - TextBox.Value is always larger than Cell.Value:
Private Sub CommandButton1_Click()
TextBox1.Value = "123"
[A1].Value = 9999
Debug.Print "TextBox1.Value = " & TextBox1.Value & ", Type is " & TypeName(TextBox1.Value)
Debug.Print "[A1].Value = " & [A1].Value & ", Type is "; TypeName([A1].Value)
Debug.Print "TextBox1.Value > [A1].Value : (" & TextBox1.Value & " > " & [A1].Value & ") is " & (TextBox1.Value > [A1].Value)
Me.Hide
End Sub
'Output:
'TextBox1.Value = 123, Type is String
'[A1].Value = 9999, Type is Double
'TextBox1.Value > [A1].Value : (123 > 9999) is True
Therefore, it is advisable before comparing:
reduce the types of compared values to one;
to handle errors of type conversion
Simple way is to use Val() function https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/val-function
Private Sub ButtonRight_Click()
If Val(TextBoxID.Value) < Val(Cells(1, 1).Value) Then
LoadEntry (TextBoxID.Value + 1)
End If
End Sub
Also for this purpose I propose to create a function:
Function getNumDef(v As Variant, Optional defV As Long = -1) As Long
getNumDef = defV 'inintially getNumDef set as defaul value
On Error Resume Next
getNumDef = CLng(v) ' if error occurs, getNumDef value remains defV
End Function
It can be applied in the following way:
Private Sub ButtonRight_Click()
Dim TBV as Long, CV as Long
TBV = getNumDef(TextBoxID.Value) 'Type conversion and error handling
CV = getNumDef(Cells(1, 1).Value) 'Type conversion and error handling
If TBV < 0 Or CV < 0 Then
MsgBox "Some of the values are not numeric or less than 0" _
& vbCrLf & "Check the raw data", vbCritical + vbOKOnly, "Sub ButtonRight_Click()"
Else
If TBV < CV Then
'The parentheses in `LoadEntry (TextBoxID.Value + 1)` are syntax sugar,
' i.e. the argument `TextBoxID.Value + 1` in parentheses is passed as ByVal.
'If the argument without (), i.e. `LoadEntry TextBoxID.Value + 1`,
'it is passed as described in the Sub definition or the default ByRef
LoadEntry TextBoxID.Value + 1
End If
End If
End Sub
I have some ComboBoxes on my FORM. One of them have items as a result of SQL request from field PG (cbPG.RowSource = "SELECT DISTINCT W_report.PG FROM W_report WHERE ......) The size of the field is byte.
After reqest
User can select one of the variant or can list several comma-separated (2,4,5,7,11,13).
Correct value
The resulting ComboBox.value is used in a procedure similar to selecting pages for printing. Everything works correctly until changes are made to the event handler of cbPG. Then the values are automatically rounded (if one comma)
wrong value
or an error "The entered value is not appropriate for this field" occurs (if a few commas) and I have to copy cbPG from the backup because I can't find a property that changes format of cbPG.value to byte.
Here is part of program that use my ComboBox
Public Function MnogoListov(str As String) As String
Dim i, j As Integer
Dim res As String
Dim listArr() As String
res = ""
ReDim listArr(Len(str)) As String
For i = 1 To Len(str)
If Mid(str, i, 1) <> "," And Mid(str, i, 1) <> "." Then
listArr(j) = listArr(j) & Mid(str, i, 1)
Else
j = j + 1
End If
Next
For i = 0 To j
If i = 0 Then
res = listArr(i)
Else
res = res & " OR W_report.PG = " & listArr(i) End If
Next
MnogoListov = res
End Function
You can't do that. A combobox is for selecting one value from several.
So, either use a multi-select listbox or a simple textbox where you - similar to selecting pages for printing - parse the inputted values to obtain the sequence (list) of items (pages).
I am trying to delete part of the string. For example
mystring="site, site text, sales "
I want to remove 'site' from mystring. My required output is "site text, sales"
I use this line of code :
s1 = Replace(mystring, "site", "")
but i am getting "text, sales"
I am not sure how to do this and I'd really appreciate your help!
replace("site, site text, sales ","site, ","",1,1)
You can also send as a parameter the start position and then the number of times you want to replace... (the default is -1)
There are a lot of different options here :
Just by adding the coma in the search string to be replaced and use Trim to get rid of spaces :
s1 = Trim(Replace(mystring, "site,", ""))
Specify the number of time you want the string to be replaced (first "1" is the start, the second for the number of replacements)
s1 = Trim(Replace(mystring, "site,", "",1,1))
Or the hard/bad way, to decompose your string in two pieces after the first occurence and then recombine to get result...
TempStart = Left(mystring, InStr(1, mystring, "site") + Len(mystring) + 1)
TempEnd = Replace(mystring, TempStart, "")
TempStart = Replace(TempStart, "site", "")
mystring = CStr(TempStart & TempEnd)
You can also user VB's MID function like this:
Mystring=Mid(myString, 6)
output will be "site text, sales"
Just specify the number of characters you want to be removed in the number part.
In my case I wanted to remove the part of the strings that was between "[" and "]". And the following code worked great.
So With original string in column A (and solution in column B):
Sub remove_in_string()
Dim i, lrowA, remChar As Long
Dim mString As String
lrowA = ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To lrowA
mString = Cells(i, 1).Value
If InStr(mString, "[") > 0 Then
remChar = InStr(mString, "]") - InStr(mString, "[") + 1
Cells(i, 2).Value = Left(mString, Len(mString) - remChar)
ElseIf InStr(mString, "[") = 0 Then
Cells(i, 2).Value = Cells(i, 1).Value
End If
Next
End Sub
I need your help with this. I have a small code which sums up the value of 10 formfields. The format of this formfields is set with the 1000 separator. The problem that I'm running into is that VBA now returns a value like 1.000 as 1. In other words: 1+10+100+1.000=112. How can I adjust the code so that it ignores the 1000 separator while calculating those formfields? Any help is much appreciated!
ActiveDocument.FormFields("voattot").Result = Val(ActiveDocument.FormFields("betvoat1").Result) _
+ Val(ActiveDocument.FormFields("betvoat2").Result) + Val(ActiveDocument.FormFields("betvoat3").Result) _
+ Val(ActiveDocument.FormFields("betvoat4").Result) + Val(ActiveDocument.FormFields("betvoat5").Result) _
+ Val(ActiveDocument.FormFields("betvoat6").Result) + Val(ActiveDocument.FormFields("betvoat7").Result) _
+ Val(ActiveDocument.FormFields("betvoat8").Result) + Val(ActiveDocument.FormFields("betvoat9").Result) _
+ Val(ActiveDocument.FormFields("betvoat10").Result)
I'd Create a Seperate Function to parse the result string from the form field
Function GetFieldValue(FieldName as string)
Dim FF As FormField
Dim Result As String
Dim Value As Double
Set FF = ActiveDocument.Formfields(FieldName)'Find the Form Field
Result = FF.Result 'Get the Text from the Form Field
Result = Replace(Result, ",", "") 'Remove any commas
If Trim(Result) ="" Then
GetFieldValue= 0 'Return the numeric valu
ElseIf Isnumeric(Result) Then
Value = CDbl(Result) 'Convert to numeric value
GetFieldValue= Value 'Return the numeric value
Else
Debug.Print "Not IsNumeric(""" & Result & """)"
GetFieldValue= 0
End If
End Function
And then you can write your statement as
ActiveDocument.FormFields("voattot").Result = GetFieldValue("betvoat1") _
+ GetFieldValue("betvoat2") + GetFieldValue("betvoat3") _
+ GetFieldValue("betvoat4") + GetFieldValue("betvoat5") _
+ GetFieldValue("betvoat6") + GetFieldValue("betvoat7") _
+ GetFieldValue("betvoat8") + GetFieldValue("betvoat9") _
+ GetFieldValue("betvoat10")
This also means you can put error trapping into your Function to deal with empty or non numeric values or missing form fields.
Whether it's worth bothering with that is up to you
I have a highlighting algorithm that takes a string and adds highlighting codes around matches in it. The problem I am having is with words like "Find tæst" as the string to be searched and "taest" as the string to find. Since the length of the search string doesn't match the length of the match, I can't accurately find the end of the match. IndexOf in my case is showing me the match but since the combined æ is counted as one character, it is throwing off my detection of the end of the match. I don't think IndexOf will work for me here. Something that returns the index of the match and the length of the match would work. But I don't know what else to use.
' cycle through search words and replace them in the text
For intWord = LBound(m_arrSearchWords) To UBound(m_arrSearchWords)
If m_arrSearchWords(intWord).Length > 0 Then
' replace instances of the word with the word surrounded by bold codes
' find starting position
intPos = strText.IndexOf(m_arrSearchWords(intWord), System.StringComparison.CurrentCultureIgnoreCase)
Do While intPos <> -1
strText = strText.Substring(0, (intPos - 1) - 0 + 1) & cstrHighlightCodeOn & strText.Substring(intPos, m_arrSearchWords(intWord).Length) & cstrHighlightCodeOff & strText.Substring(intPos + m_arrSearchWords(intWord).Length)
intPos = strText.IndexOf(m_arrSearchWords(intWord), intPos + m_arrSearchWords(intWord).Length + cstrHighlightCodeOn.Length + cstrHighlightCodeOff.Length, System.StringComparison.CurrentCultureIgnoreCase)
Loop
End If
Next intWord
The Substring method is failing as the length is beyond the end of the string. I put a fix in for strings that end with the search term (not shown above). But longer strings will be highlighted incorrectly and I need to fix those.
While it would be nice of IndexOf to return the match length, it turns out you can just do the comparison yourself to figure it out. I just do a secondary comparison with a length to find the largest match. I start at the length of the searched for word, which should be the largest. And then work my way backwards to find the length. Once I've found the length I use that. If I don't find it, I work my way up in length. This works if the string I'm searching for is larger or if it is smaller. It means in the normal case at least one extra comparison and in the worst case an additional number based on the length of the search word. Maybe if I had the implementation for IndexOf, I could improve it. But at least this works.
' cycle through search words and replace them in the text
For intWord = LBound(m_arrSearchWords) To UBound(m_arrSearchWords)
If m_arrSearchWords(intWord).Length > 0 Then
' find starting position
intPos = strText.IndexOf(m_arrSearchWords(intWord), System.StringComparison.CurrentCultureIgnoreCase)
Do While intPos <> -1
intOrigLength = m_arrSearchWords(intWord).Length
' if there isn't enough of the text left to add the search word length to
If strText.Length < ((intPos + intOrigLength - 1) - 0 + 1) Then
' use shorter length
intOrigLength = ((strText.Length - 1) - intPos + 1)
End If
' find largest match
For intLength = intOrigLength To 1 Step -1
If m_arrSearchWords(intWord).Equals(strText.Substring(intPos, intLength), StringComparison.CurrentCultureIgnoreCase) Then
' if match found, highlight it
strText = strText.Substring(0, (intPos - 1) - 0 + 1) & cstrHighlightCodeOn & strText.Substring(intPos, intLength) & cstrHighlightCodeOff & strText.Substring(intPos + intLength)
' find next
intPos = strText.IndexOf(m_arrSearchWords(intWord), intPos + intLength + cstrHighlightCodeOn.Length + cstrHighlightCodeOff.Length, System.StringComparison.CurrentCultureIgnoreCase)
' exit search for largest match
Exit For
End If
Next
' if we didn't find it by searching smaller - search larger
If intLength = 0 Then
For intLength = intOrigLength + 1 To ((strText.Length - 1) - intPos + 1)
If m_arrSearchWords(intWord).Equals(strText.Substring(intPos, intLength), StringComparison.CurrentCultureIgnoreCase) Then
' if match found, highlight it
strText = strText.Substring(0, (intPos - 1) - 0 + 1) & cstrHighlightCodeOn & strText.Substring(intPos, intLength) & cstrHighlightCodeOff & strText.Substring(intPos + intLength)
' find next
intPos = strText.IndexOf(m_arrSearchWords(intWord), intPos + intLength + cstrHighlightCodeOn.Length + cstrHighlightCodeOff.Length, System.StringComparison.CurrentCultureIgnoreCase)
' exit search for largest match
Exit For
End If
Next
End If
Loop
End If
Next intWord
If I understand correctly, you are looking for a function that returns the "matched-string" - in other words, when you are looking for s1 inside s2, then you want to know exactly what part of s2 was matched (index of first and last character matched). This allows you to highlight the match, and doesn't modify the string (upper/lower case, ligature, etc).
I don't have VB.net, and unfortunately VBA doesn't have exactly the same search functionality as VB.net - so please understand that the following code correctly identifies the beginning and end of a match, but it's only tested with upper/lower case matching. I hope this helps you solve the problem.
Option Compare Text
Option Explicit
Function startEndIndex(bigString, smallString)
' function that returns start, end index
' of the match
' it keeps shortening the bigString until no match is found
' this is how it takes care of mismatches in number of characters
' because of a match between "similar" strings
Dim i1, i2
Dim shorterString
i2 = 0
' first see if there is a match at all:
i1 = InStr(1, bigString, smallString, vbTextCompare)
If i1 > 0 Then
' largest value that i2 can have is end of string:
i2 = Len(bigString)
' can make it shorter - but no shorter than twice the length of the search string
If i2 > i1 + 2 * Len(smallString) Then i2 = i1 + 2 * Len(smallString)
shorterString = Mid(bigString, i1, i2 - i1)
' keep making the string shorter until there is no match:
While InStr(1, shorterString, smallString, vbTextCompare) > 0
i2 = i2 - 1
shorterString = Mid(bigString, i1, i2 - i1)
Wend
End If
' return the values as an array:
startEndIndex = Array(i1, endOfString)
End Function
Sub test()
' a simple test routine to see that things work:
Dim a
Dim longString: longString = "This is a very long TaesT of a complicated string"
a = startEndIndex(longString, "very long taest")
If a(0) = 0 And a(1) = 0 Then
MsgBox "no match found"
Else
Dim highlightString As String
highlightString = Left(longString, a(0) - 1) & "*" & Mid(longString, a(0), a(1) - a(0) + 1) & _
"*" & Mid(longString, a(1) + 1)
MsgBox "start at " & a(0) & " and end at " & a(1) & vbCrLf & _
"string matched is '" & Mid(longString, a(0), a(1) - a(0) + 1) & "'" & vbCrLf & _
"with highlighting: " & highlightString
End If
End Sub