Add side comments that point to specific words - vba

I have 2 word documents:
Document to review for wrong words. Sample sentence in document: Winnie the poop is cute.
Document with a matrix that contains wrong words to search for, and a suggestion.
Example: Term=Winnie the poop Suggestion=Correct spelling is Winnie the pooh.
At this point my code adds a comment, but it highlights the whole sentence (Winnie the poop is cute). How do I link the suggestion to the specific term that is wrong (Winnie the poop)?
Sub Search4WrongWords()
Dim MatrixCounter As Integer 'Counter to search for all terms in the Matrix
Dim DocToValidate As Word.Document 'Document to validate and search for wrong words
Dim MaxWordsInMatrix As Integer 'Total rows in Matrix
Const ColumnWithTerm = 2 'Matrix wrong terms Example: Winnie the Poop
Const ColumnWithSuggestion = 3 'Matrix suggested term. Example: Winnie The Pooh
MatrixCounter = 0
DocumentPath = "C:\Folder\File_to_validate.docx" 'File to validate for wrong words
MatrixPath = "C:\Folder\Matrix_with_suggestions.docx" 'Matrix with terms & suggestions
Set MatrixDoc = Documents.Open(MatrixPath) 'File path is provided by user
Set DocToValidate = Documents.Open(DocumentPath) 'File path is provided by user
MaxWordsInMatrix = MatrixDoc.Tables(1).Rows.Count 'Total rows in matrix
For MatrixCounter = 2 To MaxWordsInMatrix 'counter =2 to avoid reading matrix header row
With DocToValidate.range.Find
.Text = Trim(LCase(Left(MatrixDoc.Tables(1).Rows(MatrixCounter).Cells(ColumnWithTerm).range.Text, Len(MatrixDoc.Tables(1).Rows(MatrixCounter).Cells(ColumnWithTerm).range.Text) - 2)))
.Format = True
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.NoProofing = False
Do While .Execute(Forward:=True) = True
suggestion = MatrixDoc.Tables(1).Rows(MatrixCounter).Cells(ColumnWithSuggestion).range.Text
DocToValidate.Comments.Add DocToValidate.range, Text:=suggestion
Loop 'do while
End With 'DocToValidate
Next 'MatrixCounter
End Sub

When you execute a find the range, or selection, is redefined to the found match. This is useful if you are then going to further process the found range. In most circumstances it is possible to use the built-in range object of a document.
The exception to this is where you need to use the found range as an input parameter for another operation, as you do with adding a comment. In your code when you use DocToValidate.range as the anchor for the comment instead of referring to the found match it refers to the entire document.
You can overcome this by using an object variable for the range, as below.
Sub Search4WrongWords()
Dim MatrixCounter As Integer 'Counter to search for all terms in the Matrix
Dim DocToValidate As Word.Document 'Document to validate and search for wrong words
Dim MaxWordsInMatrix As Integer 'Total rows in Matrix
Const ColumnWithTerm = 2 'Matrix wrong terms Example: Winnie the Poop
Const ColumnWithSuggestion = 3 'Matrix suggested term. Example: Winnie The Pooh
MatrixCounter = 0
DocumentPath = "C:\Folder\File_to_validate.docx" 'File to validate for wrong words
MatrixPath = "C:\Folder\Matrix_with_suggestions.docx" 'Matrix with terms & suggestions
Set MatrixDoc = Documents.Open(MatrixPath) 'File path is provided by user
Set DocToValidate = Documents.Open(DocumentPath) 'File path is provided by user
MaxWordsInMatrix = MatrixDoc.Tables(1).Rows.Count 'Total rows in matrix
Dim findRange As Word.Range
Dim suggestion As String
For MatrixCounter = 2 To MaxWordsInMatrix 'counter =2 to avoid reading matrix header row
Set findRange = DocToValidate.Range 'necessary to ensure that the full document is being searched with each iteration
With findRange.Find
.Text = Trim(LCase(Left(MatrixDoc.Tables(1).Rows(MatrixCounter).Cells(ColumnWithTerm).Range.Text, Len(MatrixDoc.Tables(1).Rows(MatrixCounter).Cells(ColumnWithTerm).Range.Text) - 2)))
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.NoProofing = False
.Wrap = wdFindStop 'stops find at the end of the document
Do While .Execute(Forward:=True) = True
'findRange has now been redefined to the found match
suggestion = MatrixDoc.Tables(1).Rows(MatrixCounter).Cells(ColumnWithSuggestion).Range.Text
DocToValidate.Comments.Add findRange, Text:=suggestion
findRange.Collapse wdCollapseEnd 'necessary to avoid getting into endless loop
Loop 'do while
End With 'findRange.Find
Next 'MatrixCounter
End Sub

Related

VBA Simplify code by modifying For i Next i

I am in the process of simplifying a series of statements which are unnecessarily complex. I wish to simplify the below example using a For i procedure, but I am unsure how to increase the scope of my statement to affect the visibility of more objects on the sheet (this may be an easy solve that I am missing somehow, tunnel vision may be in effect today).
Example:
Sheet1 contains 135 chart objects, which are labeled in the following pattern:
A1Z
A2Z
A3Z
A4Z
A5Z
A6Z
A7Z
A8Z
A9Z
B1Z
B2Z
B3Z
B4Z
B5Z
B6Z
B7Z
B8Z
B9Z
And so on, through to the final object on the sheet, "O9Z".
Currently a CommandButton_Click event is assigned button on the sheet that calls these (ugly) procedures, which are written as follows:
If Sheet2.Range("D12").Value = "A1Z" Then
Sheets("Charts").ChartObjects("A1Z").Visible = True
Sheets("Charts").ChartObjects("A2Z").Visible = False
Sheets("Charts").ChartObjects("A3Z").Visible = False
Sheets("Charts").ChartObjects("A4Z").Visible = False
Sheets("Charts").ChartObjects("A5Z").Visible = False
Sheets("Charts").ChartObjects("A6Z").Visible = False
Sheets("Charts").ChartObjects("A7Z").Visible = False
Sheets("Charts").ChartObjects("A8Z").Visible = False
Sheets("Charts").ChartObjects("A9Z").Visible = False
Sheets("Charts").ChartObjects("B1Z").Visible = False
Sheets("Charts").ChartObjects("B2Z").Visible = False
Sheets("Charts").ChartObjects("B3Z").Visible = False
Sheets("Charts").ChartObjects("B4Z").Visible = False
Sheets("Charts").ChartObjects("B5Z").Visible = False
Sheets("Charts").ChartObjects("B6Z").Visible = False
Sheets("Charts").ChartObjects("B7Z").Visible = False
Sheets("Charts").ChartObjects("B8Z").Visible = False
Sheets("Charts").ChartObjects("B9Z").Visible = False
I am able to simplify this bloated procedure somewhat using a For i statement:
If Sheet2.Range("D12").Value = "A1Z" Then
Dim i As Integer
For i = 2 To 9
Sheets("Charts").ChartObjects("A" & i & "Z").Visible = False
Sheets("Charts").ChartObjects("A1Z").Visible = True
Next i
One problem with my procedure however is that it will only affect the visibility of objects A2Z through A9Z without affecting objects B1Z-O9Z.
I believe it may be possible to add a second variable in addition to i that loops through each letter in a range "A", "B", "C", "D" and so on to letter "O" and adjust the For i statement to account for it, so that every object on the worksheet that does not match the value in quotes in the If statement (in this example, "A1Z") is hidden.
I am unsure of which method to employ to account for that range of letters however.
Try looping through all the chart objects.
Dim cht As ChartObject
For Each cht In Sheets("Chart").ChartObjects
cht.Visible = cht.Name = "A1Z"
Next cht
If you want the chart that is visible to be dynamic then:
Dim cht As ChartObject
For Each cht In Sheets("Chart").ChartObjects
cht.Visible = cht.Name = Sheet2.Range("D12").Value
Next cht
You could try something like this using a for each loop:
Dim chartObj As ChartObject, strTest As String
strTest = Sheet2.Range("D12").Value
For Each chartObj In Sheets("Charts").ChartObjects
If chartObj.Name = strTest Then
chartObj.Visible = True
Else
chartObj.Visible = False
End If
Next chartObj
A for each loop enables you to iterate through each object in a collection, for example you could do it for each worksheet in sheets

Log in multiple users

I'm trying to create a log in with multiple users. I have a welcome sheet with cells specifically for the username and password and a sheet with the username and password combinations.
I'm getting an error at If wk.Range("B3").Value = ws.Range(i, "A").Value Then
it says "object defined" error.
I thought it made sense to just try to check if the string in the cell matched a username/password in the users sheet through a loop.
I'm not sure if I'm going about it right. And then depending on if you log in as an operator or another user it affects which sheets you see.
Public CurrentUser As String, CurrentRole As String, LoginUserName As String, LoginPassword As String
Public LoginStatus As Boolean
Sub Login()
'Worksheets("Users").Activate
Dim numberOfUsers, i As Integer
Dim ws, wk As Worksheet
Set ws = ThisWorkbook.Worksheets("Users")
Set wk = ThisWorkbook.Worksheets("Welcome")
numberOfUsers = ws.Range("Users").Rows.Count
LoginStatus = False
For i = 1 To numberOfUsers
If wk.Range("B3").Value = ws.Range(i, "A").Value Then
If wk.Range("B4").Value = ws.Range(i, "B").Value Then
CurrentUser = wk.Range("B3").Value
LoginStatus = True
Else
LoginStatus = False
MsgBox ("Wrong Login Data")
End If
Else
LoginStatus = False
MsgBox ("Wrong Login Data")
Next i`
Select Case CurrentUser
Case "Operator"
Worksheets("Received_Calls").Visible = True
Worksheets("Welcome").Visible = False
Worksheets("Users").Visible = False
Worksheets("Reported_actions").Visible = False
Worksheets("Parameters").Visible = False
Worksheets("Distances").Visible = False
Worksheets("NewCalls").Visible = False
Worksheets("NewActions").Visible = False
Case Else
Worksheets("Received_Calls").Visible = False
Worksheets("Welcome").Visible = False
Worksheets("Users").Visible = False
Worksheets("Reported_actions").Visible = True
Worksheets("Parameters").Visible = False
Worksheets("Distances").Visible = False
Worksheets("NewCalls").Visible = False
Worksheets("NewActions").Visible = False
'need to filter
End Select
End Sub
I cant comment yet (less than 50 rep), so I'll put this in an answer, but I'd like to address a commenter above:
sous2817 - both wk and ws are dimensioned as worksheets, so the comparison is correct.
Now to the Answer:
As Dirk states, the workbook.Range() function takes two arguments, but they should both be cell addresses in string format, and if both are supplied, the range returned will include all cells between them. You have:
ws.Range(i, "A").Value
which is telling excel to get cell i and cell "A" and get all cells in a square between them. This won't work because there is no such cell as "A" (and you would refer to column A as "A:A"), and the variable i will evaluate to a number (again there is no such address as 1,2,3, etc, only "1:1" etc)
What you need to change this to is, as Dirk says:
ws.Range("A" & i).value
The ampersand (&) acts as a concatenator, and will create a string for each iteration of the loop, evaluating into A1, A2, A3 etc.
You will need to do the same for the Range function that references column B as well.
Having said all that, a better solution would the answer eluded to by sous2817 in their second comment in that you could do this:
Dim userCell as Range
Set userCell = ws.Range("Users").Resize(,1).Find(wk.Range("B3").Value)
If userCell is nothing then
'Username is invalid code goes here
Elseif wk.Range("B4").Value = userCell.offset(,1).value then
'Password is valid
Else
'Password is invalid
End If
As findwindow stated, you can expand upon this by first checking if the username supplied matches Application.UserName to see if it is the current windows user.
Hope this helps!

Using Word VBA, Apply Various Heading Styles Based on Number Patterns

I am fairly new to VBA. I tag text with heading styles in large documents from a variety of authors. Is it possible identify a number pattern on a line of bolded text, and apply the appropriate style to that entire line (there is usually a hard return at the end of the line).
For example, often our documents are numbered as shown below, and we tag the text accordingly.
1.0 text here (apply Heading 1)
1.2 text here (apply Heading 2)
1.2.1 text here (apply Heading 3)
1.2.1.1 text here (apply Heading 4)
2.0 text here (apply Heading 1)
2.2 text here (apply Heading 2)
….and so on
I have done a lot of research, but I am not sure if this is possible. We do not use any type of auto numbering.
Yes, it's possible. Try this code:
Sub ApplyHeadings()
Dim rg1 As Range
Dim rg2 As Range
Dim pos As Long
Dim i As Long
Dim dots As Long
Set rg1 = ActiveDocument.Range
With rg1.Find
.MatchWildcards = True
.Text = "[0-9.]{2,}[!^13]#[^13]"
.Wrap = wdFindStop
While .Execute
Set rg2 = rg1.Duplicate
dots = 0
' isolate the numbering
pos = InStr(rg2.Text, " ")
If pos > 0 Then rg2.End = rg2.Start + pos - 1
For i = 1 To Len(rg2.Text)
' count the dots in the number
If Mid(rg2.Text, i, 1) = "." Then dots = dots + 1
Next i
' apply correct heading level
Select Case dots
Case 1
If Mid(rg2.Text, 3, 1) = "0" Then
rg1.Style = ActiveDocument.Styles("Heading 1")
Else
rg1.Style = ActiveDocument.Styles("Heading 2")
End If
Case 2, 3 ' maybe more...
rg1.Style = ActiveDocument.Styles("Heading " & CStr(dots + 1))
Case Else
' do nothing
End Select
' prepare for next find
rg1.Collapse wdCollapseEnd
Wend
End With
End Sub

Word Search&Replace all Uppercase result

i want to replace some texts in the header area of my word file.
the actual replacement works, but it replaces all in Upper-case, no matter how the replacement text really looks like.
that's the code I use:
og = ""
vn = ""
nn = ""
tl = ""
fx = ""
getUserdatadata()
Dim tags As String() = {"XCDATEX", "XORGANIX", "XNAMEX", "XTELEX", "XFAXX"}
Dim name As String = vn + " " + nn
Dim replaces As String() = {Today.ToShortDateString, og, name, tl, fx}
worddoc.Application.ActiveWindow.ActivePane.View.Type = WdViewType.wdPrintView
worddoc.ActiveWindow.ActivePane.View.SeekView = WdSeekView.wdSeekFirstPageHeader
For i As Integer = 0 To 4
With worddoc.Application.Selection.Find
.Text = tags(i)
.Replacement.Text = replaces(i)
.Forward = True
.Wrap = WdFindWrap.wdFindContinue
.Execute(Replace:=WdReplace.wdReplaceOne)
End With
Next
for date, organisation, phone and fax there is no problem (organisation is fully in upper-case anyway).
But for the name, it always transforms the whole text to uppercase.
How to fix this?
found it:
I needed to add
.MatchCase = true
to the with statement

Define letter as number, include as part of string for loop

If SecretWordLength = 5 Then
Label3.Visible = True
Label4.Visible = True
Label5.Visible = True
Label6.Visible = True
Label7.Visible = True
End If
This is the current code for making the dash underneath a letter visible for my hangman game based on the length of the secret word. How can I change this into a FOR loop so that I don't have to repeat this code for every label individually?
I was thinking of using a FOR loop in this way:
For i = 3 To 7
Labeli.Visible = True
Next
But it does not work as it recognizes the i as the letter itself, not the number I want it to represent. Help please?