Find and highlight word in Outlook email - vba

How to find text (in this case -1.00 (some negative number) or +1.50 (some positive number) in the email body (compose part) and change font color of these words.

Even when you reduce your question to just the editing, I am confused. Do you really want to font the number but not the sign? The result looks very strange to me.
I have created a subroutine that does nothing but the editing. You can either put code around it to achieve your objective or you can pick ideas from my subroutine and incorporate them in your own routine.
The parameters of this subroutine are:
Prefix and Text
You could have Prefix = “+” and Text = “1.50” or Prefix = “” and Text = “+1.50”. With the first, only the number is formatted; with the second the sign is formatted as well. The length of these strings can be anything not just the 1 and 4 you request.
StartFormat and EndFormat
There are a variety of possible techniques for formatting bits of Html text but I cannot think of one that does not involve placing a start string before the text and an end string after the text. For my testing I created a html document with some black, some red and some blue text. I them placed “+1.50”s in each section. The values I passed for Start Format and EndFormat were "<span style=""color:#00FF00"">" and "</span>". The calls of EditHtml set all the “+1.50”s or all the “1.50”s green.
Html
This is the Html document to be amended. It is passed “By Reference” so the original string is updated.
I hope this helps.
Sub EditHtml(ByVal Prefix As String, ByVal Text As String, _
ByVal StartFormat As String, ByVal EndFormat As String, _
ByRef Html As String)
Dim Pos As Long
Pos = 1
' Loop for each occurrence of Prefix & Text.
Do While True
Pos = InStr(Pos, Html, Prefix & Text)
If Pos = 0 Then
Exit Do
End If
Pos = Pos + Len(Prefix) ' Step over prefix
' Insert string that starts format change
Html = Mid(Html, 1, Pos - 1) & StartFormat & Mid(Html, Pos)
Pos = Pos + Len(StartFormat) ' Step over string that starts format change
Pos = Pos + Len(Text) ' Step over text whose format is to be changed
' Insert string that ends format change
Html = Mid(Html, 1, Pos - 1) & EndFormat & Mid(Html, Pos)
Pos = Pos + Len(EndFormat) ' Step over string that ends format change
Loop
End Sub

Related

little boxes in front of text lines after putting contents of multiline textbox in Word document

I have a user form with multiline textbox. EnterKeyBehavior = True. I run the macro, the form opens, I type a few lines of text, pressing Enter after each line. The value from that textbox is put in a variable which is later put in the word document via Find/replace of placeholder text <>.
The text is put in the document and the first line in the multiline textbox entry looks OK, but every other line starts with a little empty box (some Ascii/unicode character I don't know). See pic.
I've tried to replace that little white box by doing a replace on vbCr, vbLf, vbCrLf, Char(10), Char(11), Char(13), from other posts I've found with those solutions, but none of them work for me. What's the fix?
This is what I have in the form right now and from where I'm trying to clean up the contents of that textbox before passing on the value to the variable that I then put in the word document.
Private Sub cmdCompInfoOK_Click()
txtCompanyInfo.Text = Replace(txtCompanyInfo.Text, Chr(11), "")
slkCompanyInfo = frmLtrAddress.txtCompanyInfo
Me.Hide
End Sub
The multiline field is called txtCompanyInfo.
slkCompanyInfo is the variable where I want to put the value from the textbox, and I'm declaring it as Public in the main module, which calls the userform.
Put the following code into a regular module, it will create a string with information about all characters in the input string.
Function DumpString(s As String, Optional specialCharsOnly As Boolean = False) As String
' Write all chars of String as ASCII-Value, concatenated as string
Dim i As Long
For i = 1 To Len(s)
Dim x As String
Dim a As Long, dumpMe As Boolean, c As String
a = AscW(Mid(s, i, 1))
dumpMe = Not specialCharsOnly Or a < 32 Or a > 128
If a = 13 Then
c = "<CR>"
ElseIf a = 10 Then
c = "<LF>"
ElseIf a = 9 Then
c = "<TAB>"
ElseIf a = 0 Then
c = "<NUL>"
Else
c = Mid(s, i, 1)
End If
x = a & "(" & c & ")"
If dumpMe Then
DumpString = DumpString & IIf(DumpString = "", "", ",") & x
End If
Next i
End Function
In your Event-Routine, put a statement like
Debug.Print DumpString(slkCompanyInfo)
If your input string is to long, you can set the 2nd parameter to True, in that case all regular characters are skipped.

VBA Find function incorrect highlighting

Can somebody help me fix this code, I have two textbox where when you paste the text on the 1st Textbox and click the search button, it highlighted the exact text on the 2nd textbox if it is present on the 2nd textbox. But when the string on 2nd textbox contains linefeed/newline, it added one character from the start of the text. Here is the code:
Private Sub FindText(ByVal start_at As Integer)
Dim pos As Integer
Dim target As String
target = Textbox1.Text
pos = InStr(start_at, Textbox2.Text, target)
If pos > 0 Then
' We found it.
TargetPosition = pos
Textbox2.SelStart = TargetPosition - 1
Textbox2.SelLength = Len(target)
Textbox2.SetFocus
Else
' We did not find it.
MsgBox "Not found."
Textbox2.SetFocus
End If
End Sub
' Search button
Private Sub cmdSearch_Click()
FindText 1
End Sub
I believe the problem is that Textbox is treating newline as a single character while Len() counts as CRLF as two. You should probably count the number of times CRLF appears in the text preceding the match target and adjust SelStart accordingly. Try this line instead:
Textbox2.SelStart = Len(Replace(Left(target, pos - 1), vbCrLf, "^")) - 1
'Textbox2.SelLength = Len(Replace(target, vbCrLf, "^"))
If target can include line breaks you may have a similar problem with SelLength which is why I've left the second, commented line. This works by substituting two-character line-break sequences into a single-character string. It's completely arbitrary what value to use for replacement since the result is discarded and only the length ultimately matters.

Sampling StringName.SubString(p,1) for paragraph formatting character

please try to specifically answer my question and not offer alternative approaches as I have a very specific problem that needs this ad-hoc solution. Thank you very much.
Automatically my code opens Word through VB.NET, opens the document, finds the table, goes to a cell, moves that cells.range.text into a String variable and in a For loop compares character at position p to a String.
I have tried Strings:
"^p", "^013", "U+00B6"
My code:
Dim nextString As String
'For each cell, extract the cell's text.
For p = 17 To word_Rng.Cells.Count
nextString = word_Rng.Cells(p).Range.Text
'For each text, search for structure.
For q = 0 To nextString.Length - 1
If (nextString.Substring(q, 1) = "U+00B6") Then
Exit For
End If
Next
Next
Is the structural data lost when assigning the cells text to a String variable. I have searched for formatting marks like this in VBA successfully in the past.
Assuming that your string contains the character, you can use ChrW to create the appropriate character from the hex value, and check for that:
If nextString.Substring(q, 1) = ChrW(&h00B6) Then
Exit For
End If
UPDATE
Here's a complete example:
Dim nextString = "This is a test " & ChrW(&H00B6) & " for that char"
Console.WriteLine(nextString)
For q = 0 To nextString.Length - 1
If nextString(q) = ChrW(&H00B6) Then
Console.WriteLine("Found it: {0}", q)
End If
Next
This outputs:
This is a test ¶ for that char
Found it: 15

A way to change font color besides *.SelectionColor, or a way to better make use of it?

So, I'm trying to create a function or two that takes html tags and colors them differently than the rest of the text (similar to how Visual Studio does it for key words like Dim). The only way I have found, is to use a rich text box and then do *.SelectionColor = Color.Blue, or something similar. Is there any other way to do this? I made it so whenever the textbox updates, it reads through it at changes all html tags to a different color. This works fine with a really short html file, but when they get to be larger, it takes too long, and the selection moves the cursor around.
So, is there any other way to do this, even if I have to use something other than a rich text box? If not, does anyone see a way to improve this?
Here are the two functions that run when the textbox updates. Tag is blue, attributes are red, stuff in quotes is green.
'//////////////////////////////////////////////////////////////////////////
'// findTag()
'// -finds a tag
'//////////////////////////////////////////////////////////////////////////
Private Function findTag()
Dim tag As String = ""
Dim i As Integer = 0
Dim startTag As Integer
While (i < txtCurrentFile.TextLength - 1)
If txtCurrentFile.Text(i) = "<" Then
startTag = i
While txtCurrentFile.Text(i) <> ">"
tag += txtCurrentFile.Text(i)
i += 1
End While
tag += ">"
colorCode(startTag, tag)
tag = ""
End If
i += 1
End While
Return Nothing
End Function
'//////////////////////////////////////////////////////////////////////////
'// colorCode()
'// -colors different tags accordingly
'//////////////////////////////////////////////////////////////////////////
Private Function colorCode(ByVal startIndex As Integer,
ByVal tag As String)
Dim i As Integer = 0
Dim isAttributes As Boolean = False
Do While (tag(i) <> " " And tag(i) <> ">")
txtCurrentFile.Select(startIndex + i, 1)
txtCurrentFile.SelectionColor = Color.Blue
i += 1
Loop
If i < tag.Length Then
Do Until (tag(i) = ">")
Do Until (tag(i) = Chr(34))
txtCurrentFile.Select(startIndex + i, 1)
txtCurrentFile.SelectionColor = Color.Red
i += 1
Loop
i += 1
Do Until (tag(i) = Chr(34))
txtCurrentFile.Select(startIndex + i, 1)
txtCurrentFile.SelectionColor = Color.Purple
i += 1
Loop
i += 1
Loop
txtCurrentFile.Select(startIndex + i, 1)
txtCurrentFile.SelectionColor = Color.Blue
End If
Return Nothing
End Function
a few suggestions:
Ditch the character scanner. Replace it with anything that is speedier (RegEx, HTML Agility Pack, ...)
If you really want to keep the character scanner, then limit the scan to the area around the modifications (say, 200 characters behind and in front of the cursor)
Remember where the cursor is before you start the color process and restore it when finished.
Implement a background colorizer that does a full file re-color on a separate thread (you'll have to clone the RTB and only apply the changes if the user hasn't made any changes while the colorizer was running).
... I don't know if this would work AT ALL!!!, but it could be cool if it did...
Maybe open the file in a webbrowser control and set your coloring rules in a css sheet??
Again, I don't know if this is a good idea or not, but it might do the trick very nicely since it's already HTML you're dealing with...

Insert line break in wrapped cell via code

Is it possible to insert line break in a wrapped cell through VBA code? (similar to doing Alt-Enter when entering data manually)
I have set the cell's wrap text property to True via VBA code, and I am inserting data into it also through VBA code.
Yes. The VBA equivalent of AltEnter is to use a linebreak character:
ActiveCell.Value = "I am a " & Chr(10) & "test"
Note that this automatically sets WrapText to True.
Proof:
Sub test()
Dim c As Range
Set c = ActiveCell
c.WrapText = False
MsgBox "Activcell WrapText is " & c.WrapText
c.Value = "I am a " & Chr(10) & "test"
MsgBox "Activcell WrapText is " & c.WrapText
End Sub
You could also use vbCrLf which corresponds to Chr(13) & Chr(10). As Andy mentions in the comment below, you might be better off using ControlChars.Lf instead though.
Yes there are two ways to add a line feed:
Use the existing constant from VBA (click here for a list of existing vba constants) vbLf in the string you want to add a line feed, as such:
Dim text As String
text = "Hello" & vbLf & "World!"
Worksheets(1).Cells(1, 1) = text
Use the Chr() function and pass the ASCII character 10 in order to add a line feed, as shown bellow:
Dim text As String
text = "Hello" & Chr(10) & "World!"
Worksheets(1).Cells(1, 1) = text
In both cases, you will have the same output in cell (1,1) or A1.
Have a look at these two threads for more information:
What is the difference between a "line feed" and a "carriage return"?
Differences Between vbLf, vbCrLf & vbCr Constants
I know this question is really old, but as I had the same needs, after searching SO and google, I found pieces of answers but nothing usable. So with those pieces and bites I made my solution that I share here.
What I needed
Knowing the column width in pixels
Be able to measure the length of a string in pixels in order to cut it at the dimension of the column
What I found
About the width in pixels of a column, I found this in Excel 2010 DocumentFormat :
To translate the value of width in the file into the column width value at runtime (expressed in terms of pixels), use this calculation:
=Truncate(((256 * {width} + Truncate(128/{Maximum Digit Width}))/256)*{Maximum Digit Width})
Even if it's Excel 2010 format, it's still working in Excel 2016. I'll be able to test it soon against Excel 365.
About the width of a string in pixels, I used the solution proposed by #TravelinGuy in this question, with small corrections for typo and an overflow. By the time I'm writing this the typo is already corrected in his answer, but there is still the overflow problem. Nevertheless I commented his answer so there is everything over there for you to make it works flawlessly.
What I've done
Code three recursive functions working this way :
Function 1 : Guess the approximate place where to cut the sentence so if fits in the column and then call Function 2 and 3 in order to determine the right place. Returns the original string with CR (Chr(10)) characters in appropriate places so each line fits in the column size,
Function 2 : From a guessed place, try to add some more words in the line while this fit in the column size,
Function 3 : The exact opposite of function 2, so it retrieves words to the sentence until it fits in the column size.
Here is the code
Sub SplitLineTest()
Dim TextRange As Range
Set TextRange = FeuilTest.Cells(2, 2)
'Take the text we want to wrap then past it in multi cells
Dim NewText As String
NewText = SetCRtoEOL(TextRange.Value2, TextRange.Font.Name, TextRange.Font.Size, xlWidthToPixs(TextRange.ColumnWidth) - 5) '-5 to take into account 2 white pixels left and right of the text + 1 pixel for the grid
'Copy each of the text lines in an individual cell
Dim ResultArr() As String
ResultArr() = Split(NewText, Chr(10))
TextRange.Offset(2, 0).Resize(UBound(ResultArr) + 1, 1).Value2 = WorksheetFunction.Transpose(ResultArr())
End Sub
Function xlWidthToPixs(ByVal xlWidth As Double) As Long
'Fonction to convert the size of an Excel column width expressed in Excel unit(Range.ColumnWidth) in pixels
'Parameters : - xlWidth : that is the width of the column Excel unit
'Return : - The size of the column in pixels
Dim pxFontWidthMax As Long
'Xl Col sizing is related to workbook default string configuration and depends of the size in pixel from char "0". We need to gather it
With ThisWorkbook.Styles("Normal").Font
pxFontWidthMax = pxGetStringW("0", .Name, .Size) 'Get the size in pixels of the '0' character
End With
'Now, we can make the calculation
xlWidthToPixs = WorksheetFunction.Floor_Precise(((256 * xlWidth + WorksheetFunction.Floor_Precise(128 / pxFontWidthMax)) / 256) * pxFontWidthMax) + 5
End Function
Function SetCRtoEOL(ByVal Original As String, ByVal FontName As String, ByVal FontSize As Variant, ByVal pxAvailW) As String
'Function aiming to make a text fit into a given number of pixels, by putting some CR char between words when needed.
'If some words are too longs to fit in the given width, they won't be cut and will get out of the limits given.
'The function works recursively. Each time it find an End Of Line, it call itself with the remaining text until.
'The recursive process ends whent the text fit in the given space without needing to be truncated anymore
'Parameters : - Original : The text to fit
' - FontName : Name of the font
' - FontSize : Size of the font
' - pxAvailW : Available width in pixels in wich we need to make the text fit
'Return : - The orignal text with CR in place of spaces where the text needs to be cut to fit the width
'If we got a null string, there is nothing to do so we return a null string
If Original = vbNullString Then Exit Function
Dim pxTextW As Long
'If the text fit in, may be it's the original or this is end of recursion. Nothing to do more than returne the text back
pxTextW = pxGetStringW(Original, FontName, FontSize)
If pxTextW < pxAvailW Then
SetCRtoEOL = Original
Exit Function
End If
'The text doesn't fit, we need to find where to cut it
Dim WrapPosition As Long
Dim EstWrapPosition As Long
EstWrapPosition = Len(Original) * pxAvailW / pxTextW 'Estimate the cut position in the string given to a proportion of characters
If pxGetStringW(Left(Original, EstWrapPosition), FontName, FontSize) < pxAvailW Then
'Text to estimated wrap position fits in, we try to see if we can fits some more words
WrapPosition = FindMaxPosition(Original, FontName, FontSize, pxAvailW, EstWrapPosition)
End If
'If WrapPosition = 0, we didn't get a proper place yet, we try to find the previous white space
If WrapPosition = 0 Then
WrapPosition = FindMaxPositionRev(Original, FontName, FontSize, pxAvailW, EstWrapPosition)
End If
'If WrapPosition is still 0, we are facing a too long word for the pxAvailable. We'll cut after this word what ever. (Means we must search for the first white space of the text)
If WrapPosition = 0 Then
WrapPosition = InStr(Original, " ")
End If
If WrapPosition = 0 Then
'Words too long to cut, but nothing more to cut, we return it as is
SetCRtoEOL = Original
Else
'We found a wrap position. We recurse to find the next EOL and construct our response by adding CR in place of the white space
SetCRtoEOL = Left(Original, WrapPosition - 1) & Chr(10) & SetCRtoEOL(Right(Original, Len(Original) - WrapPosition), FontName, FontSize, pxAvailW)
End If
End Function
Function FindMaxPosition(ByVal Text As String, ByVal FontName As String, ByVal FontSize As Variant, ByVal pxAvailW, ByVal WrapPosition As Long) As Long
'Function that finds the maximum number of words fitting in a given space by adding words until it get out of the maximum space
'The function is inteded to work on text with a "guessed" wrap position that fit in the space allowed
'The function is recursive. Each time it guesses a new position and the word still fits in the space, it calls itself with a further WrapPosition
'Parameters : - Text : The text to fit
' - FontName : Name of the font
' - FontSize : Size of the font
' - pxAvailW : Available width in pixels in wich we need to make the text fit
' - WrapPosition : The initial wrap position, positionned someware in the text (WrapPosition < len(Text)) but inside pxAvailW
'Return : - The position were the text must be wraped to put as much words as possible in pxAvailW, but without getting outside of it. If no position can be found, returns 0
Dim NewWrapPosition As Long
Static isNthCall As Boolean
'Find next Whitespace position
NewWrapPosition = InStr(WrapPosition, Text, " ")
If NewWrapPosition = 0 Then Exit Function 'We can't find a wrap position, we return 0
If pxGetStringW(Left(Text, NewWrapPosition - 1), FontName, FontSize) < pxAvailW Then '-1 not to take into account the last white space
'It still fits, we can try on more word
isNthCall = True
FindMaxPosition = FindMaxPosition(Text, FontName, FontSize, pxAvailW, NewWrapPosition + 1)
Else
'It doesnt fit. If it was the first call, we terminate with 0, else we terminate with previous WrapPosition
If isNthCall Then
'Not the first call, we have a position to return
isNthCall = False 'We reset the static to be ready for next call of the function
FindMaxPosition = WrapPosition - 1 'Wrap is at the first letter of the word due to the function call FindMax...(...., NewWrapPosition + 1). The real WrapPosition needs to be minored by 1
Else
'It's the first call, we return 0 | Strictly speaking we can remove this part as FindMaxPosition is already 0, but it make the algo easier to read
FindMaxPosition = 0
End If
End If
End Function
Function FindMaxPositionRev(ByVal Text As String, ByVal FontName As String, ByVal FontSize As Variant, ByVal pxAvailW, ByVal WrapPosition As Long) As Long
'Function working backward of FindMaxPosition. It finds the maximum number of words fitting in a given space by removing words until it fits the given space
'The function is inteded to work on text with a "guessed" wrap position that fit in the space allowed
'The function is recursive. Each time it guesses a new position and the word still doesn't fit in the space, it calls itself with a closer WrapPosition
'Parameters : - Text : The text to fit
' - FontName : Name of the font
' - FontSize : Size of the font
' - pxAvailW : Available width in pixels in wich we need to make the text fit
' - WrapPosition : The initial wrap position, positionned someware in the text (WrapPosition < len(Text)), but outside of pxAvailW
'Return : - The position were the text must be wraped to put as much words as possible in pxAvailW, but without getting outside of it. If no position can be found, returns 0
Dim NewWrapPosition As Long
NewWrapPosition = InStrRev(Text, " ", WrapPosition)
'If we didn't found white space, we are facing a "word" too long to fit pxAvailW, we leave and return 0
If NewWrapPosition = 0 Then Exit Function
If pxGetStringW(Left(Text, NewWrapPosition - 1), FontName, FontSize) >= pxAvailW Then '-1 not to take into account the last white space
'It still doesnt fits, we must try one less word
FindMaxPositionRev = FindMaxPositionRev(Text, FontName, FontSize, pxAvailW, NewWrapPosition - 1)
Else
'It fits, we return the position we found
FindMaxPositionRev = NewWrapPosition
End If
End Function
Known limitations
This code will work as long as the text in the cell has only one font and one font size. Here I assume that the font is not Bold nor Italic, but this can be easily handled by adding few parameters as the function measuring the string length in pixels is already able to do it.
I've made many test and I always got the same result than the autowrap function of Excel worksheet, but it may vary from one Excel version to an other. I assume it works on Excel 2010, and I tested it with success in 2013 and 2016. Fo others I don't know.
If you need to handle cases where fonts type and/or attributs vary inside a given cell, I assume it's possible to achieve it by testing the text in the cell character by character by using the range.caracters property. It should be really slower, but for now, even with texts to split in almost 200 lines, it takes less than one instant so maybe it's viable.
Just do Ctrl + Enter inside the text box