Include code based on ToC existing? - vba

This is more of a general question on how to implement my code, than an issue with the code itself.
I have some code that scans through a Word Document, looks for some words, and puts them in an index. This document may or may not have a Table of Contents.
If there's a ToC, I want my macro to skip the text in that range, so I have a line like this:
Sub find_things()
Dim myDoc as Word.Document
Dim otherVariables here
[some preliminary code]
' the next line checks to see if the current range is in a ToC
If rngXE.InRange(myDoc.TablesOfContents(1).Range) = False Then
[code here will find my text, and do some things]
End If
End Sub
So, hopefully you can see that if the rngXE range is NOT in a table of contents, run some code.
However, since sometimes there's no ToC, I get an error raised at the If statement line. ("The requested member of the collection does not exist").
Question: What's the best way to handle this? Should I do On Error Resume Next before that? So if there's an error, it will continue on?
Or, is there some way to count the Tables of Contents, and if it's 0, then don't include the If statement lines?
Sub find_things()
Dim myDoc as Word.Document
Dim otherVariables here
[some preliminary code]
' the next line checks to see if the current range is in a ToC
If myDoc.TablesofContents.Count > 0 Then
If rngXE.InRange(myDoc.TablesOfContents(1).Range) = False Then
[code here will find my text, and do some things]
End If
End If
End Sub
...except if I use this, it's always going to skip my code when there's no TOC. I'd rather not use On Error Goto since I've had it drilled into my head that GoTo is generally a bad idea...but that may be the best option, no?
I hope this is clear at all - kinda hard to verbalize the issue but please let me know if I can clarify anything!
edit: The way I'm handling this now, is if I get that Error, I just Comment out the If rngXE.InRange(myDoc...) = False and corresponding End If lines, and it runs perfectly. I am looking for an automatic way to do this, rather than my personal intervention.

Two fairly simple patterns you could use without resorting to either "On Error Goto" or "Goto" are:
If myDoc.TablesofContents.Count = 0 Then
call asubtodothework(rngXE) ' you may need to pass more parameters...
elseif not rngXE.InRange(myDoc.TablesOfContents(1).Range)
call asubtodothework(rngXE) ' ditto
end if
Dim bDoIt As Boolean
bDoIt = True
If myDoc.TablesofContents.Count > 0 Then
If rngXE.InRange(myDoc.TablesOfContents(1).Range) Then
bDoIt = False
End If
End If
If bDoIt Then
' do the work
End If
(As they stand, both make the same assumption as your existing code, i.e. that you only need to check for TableOfContents(1). My guess is that if you need to check for more ToCs, somewhere you will end up with some code rather like the second pattern anyway).
or even the following...
Dim bDoIt As Boolean
bDoIt = True
If myDoc.TablesofContents.Count > 0 Then
bDoIt = rngXE.InRange(myDoc.TablesOfContents(1).Range)
End If
If bDoIt Then
' do the work
End If
...or perhaps a single-liner would work (I haven't actually checked the logic, and I have a personal preference for using the full If...End If construct anyway)...
Dim bDoIt As Boolean
bDoIt = True
If myDoc.TablesofContents.Count > 0 Then bDoIt = rngXE.InRange(myDoc.TablesOfContents(1).Range)
If bDoIt Then
' do the work
End If

Related

Command button in a userform to delete multiple rows depending on selection in listbox

I am currently working on a userform which has a "Clear Process" command button. The idea is that my userform has a listbox which will list all of the current processes.
From here the user will select which process(es) he/she would like to clear from the worksheet (delete all rows relating to the process).
Embedded in the code I have used the word "Lisa" as a point of reference for the previous userform to know which cell the Process Name should be, using the offset function.
I would like to be able to use the word "Lisa" once the process to be deleted has been identified by the user. This would always be the row where "Lisa" is found and 19 rows below.
I have started some code but when trying to find "Lisa" depending on the selection made by the user I came across an issue.
Private Sub ClearButton_Click()
Dim findvalue As Range
Dim cDelete As VbMsgBoxResult
'hold in memory
Application.ScreenUpdating = False
'check for values
If Emp1.Value = "" Or Emp2.Value = "" Then
MsgBox "There are no processes to delete"
Exit Sub
End If
'confirm process should be deleted
cDelete = MsgBox("Are you sure you want to delete this process?", vbYesNo)
If cDelete = vbYes Then
'find the process to be deleted
'''''''set findvalue =
'''''''delete entire process
findvalue.EntireRow.Delete
End If
End Sub
Hopefully this is enough information, any help would be greatly appreciated :)
If you use Named ranges for your processes, which seems to be almost mandatory in your case, then you can do the following:
Sub DeleteNamedRange(rngName As String)
Range(rngName).ClearContents
ThisWorkbook.Names(rngName).Delete
End Sub
Invoke the Sub this way:
Call DeleteNamedRange("Lisa")
Something as small as this one would set a range to a found value and delete it:
Public Sub TestMe()
Dim findValue As Range
Set findValue = Selection.Find("Lisa")
findValue.EntireRow.Delete
End Sub
As a good practice, you may check whether the findValue is not nothing before deleting. Thus, you avoid an error:
If Not findValue Is Nothing Then
findValue.EntireRow.Delete
End If
And if you want to make the code even one step further, keep in mind that the default value of the argument After in Find() is the first cell. Thus, Find() always start looking from the second cell. To avoid this, and to start looking from the first cell, it is considered a good practice to pass the After:= argument explicitly:
Public Sub TestMe()
Dim findValue As Range
Dim selectedValue As Range
Set selectedValue = ActiveSheet.Range(Selection.Address)
With selectedValue
Set findValue = .Find("Lisa", after:=.Cells(.Cells.Count))
End With
If Not findValue Is Nothing Then
findValue.EntireRow.Delete
End If
End Sub
To make the code even more "interesting", one may decide to check whether a range is selected (a shape can be also selected). Thus, the TypeName(Selection) can be used with something like this:
If TypeName(Selection) <> "Range" Then
MsgBox "Range is not selected!"
Exit Sub
End If
Range.Find MSDN

VBA User form gives warning if duplicate is found

I think I need to try and make this question easier. So here goes;
I am creating a User form in Excel that will act as a data capture form.
In this form I have a Textbox called PolBX In this a is placed and at submission data in PolBX is copied into the "G" column using this code
Cells(emptyRow, 7).Value = PolBX.Value. This works great.
I discovered that there may be instances where the User may accidently use the same Unique Id number twice. so I am trying to find out how to code it that after the User has entered the Unique Id number it would check for that string (Consists of letters and numbers). if it finds the string already in the 7th column(G) it must say something like
"Policy number already Used, please try again"
I am thinking I will need to use the following subroutine
Private Sub PolBX_AfterUpdate()
End Sub
Can some please assist with creating this code...
Also can you please explain what you are doing as I started VBA about a week ago
You can add the following code to search for your policy number, and if nothing found then PolLookup = Nothing.
Option Explicit
Sub Test()
On Error GoTo ErrHandler
Dim ws As Worksheet, PolLookup As Range, LookupRng As Range
Set ws = ThisWorkbook.Worksheets(1)
'This is the range you want to search, it can be a long range
'or it can be a single cell.
Set LookupRng = ws.Range("A:A")
'Range.Find is looking for your value in the range you specified above
Set PolLookup = LookupRng.Find("YourLookupValue")
'PolLookup = Nothing if it didn't find a match, so we want to use
'If <NOT> Nothing, because this suggests .Find found your value
If Not PolLookup Is Nothing Then
Err.Raise vbObjectError + 0 'Whatever error you want to throw for finding a match
End If
'Exit before you reach the ErrHandler
Exit Sub
ErrHandler:
If Err.Number = vbObjectError + 0 Then
'Handle your error. Do you want to stop completely? Or have the
'User enter a new value?
End If
End Sub
Basically, after your user enters their value in your UserForm, just make a call to this Sub to do a quick lookup.
Playing around I discovered a Much easier way! I included a Button with he following code attached
Private Sub CommandButton8_Click()
Search = PolBX.Text
Set FoundCell = Worksheets("sheet1").Columns(7).Find(Search,LookIn:=xlValues, lookat:=xlWhole)
If FoundCell Is Nothing Then
MsgBox "No duplicates found"
Else
MsgBox "This policy has already been Assessed" & "Please assess a different case"
PolBX.Value = ""
End If

Do nothing in vba

Is there an equivalent to python "pass" in VBA to simply do nothing in the code?
for example:
For Each ws In ThisWorkbook.Sheets
If ws.Name = "Navy Reqs" Then
ws.Select
nReqs = get_num_rows
Cells(1, 1).Select
If ActiveSheet.AutoFilterMode Then Cells.AutoFilter
Selection.AutoFilter
ElseIf ws.Name = "temp" Then
pass
Else
ws.Select
nShips = get_num_rows
End If
Next
I get an error here that pass is not defined. Thanks.
just remove pass and re run the code. VBA will be happy to accept that I believe
Don't include any statements:
Sub qwerty()
If 1 = 3 Then
Else
MsgBox "1 does not equal 3"
End If
End Sub
Just leave it blank. You can also use a Select statement, it's easier to read.
For Each ws In ThisWorkbook.Sheets
Select Case ws.Name
Case "Navy Reqs":
'...
Case "temp":
'do nothing
Case Else:
'...
End Select
Next
Write code that does what it says, and says what it does.
For Each ws In ThisWorkbook.Sheets
If ws.Name = "Navy Reqs" Then
ws.Select
nReqs = get_num_rows
Cells(1, 1).Select
If ActiveSheet.AutoFilterMode Then Cells.AutoFilter
Selection.AutoFilter
Else If ws.Name <> "temp" Then
ws.Select
nShips = get_num_rows
End If
Next
That's all you need. An instruction that means "here's some useless code" does not exist in VBA.
You want comments that say why, not what - a comment that says 'do nothing is the exact opposite of that. Don't write no-op code, it's pure noise.
Assuming Python's pass works like C#'s continue statement and skips to the next iteration, then the VBA equivalent is the one and only legitimate use of a GoTo jump:
For ...
If ... Then GoTo Skip
...
Skip:
Next
This code shows an IF test that keeps searching unless it gets a match.
Function EXCAT(Desc)
Dim txt() As String
' Split the string at the space characters.
txt() = Split(Desc)
For i = 0 To UBound(txt)
EXCAT = Application.VLookup(txt(i), Worksheets("Sheet1").Range("Dept"), 2, False)
If IsError(EXCAT) Then Else Exit Function
Next
' watch this space for composite word seach
EXCAT = "- - tba - -"
End Function
I coded in COBOL for many years and the equivalent 'do nothing' statement is NEXT SENTENCE.
In VBA, I find myself creating a dummy variable (sometimes a global) dim dummy as integer and then when I need that 'do nothing' action in an If..Then..Else I put in a line of code: dummy = 0.
This is actually a legit question. I want to run a debug routine that stops when a certain critical value is, say, 8, i.e., set break point at x = 8 and then step through line by line. So the following construct is useful:
Select Case x
Case 21
'do nothing
Case 8
'do nothing
Case 14
'do nothing
Case 9
'do nothing
End Select
Since you can't put a break point on a comment, an actual statement is needed.
You also can't put a break point on the Case statements because that gets executed every time.
Obviously anything here is OK e.g., x=x, but it would be nice to have something formal like pass.
But I've been using x=x.
Most languages have a "null" or "empty" statement like Python's pass. These statements have evolved because they are practically useful, and in some grammars, necessary. For example, as others have suggested, null statements can serve as side-effect free anchors for a debugger.
I use:
Debug.Assert True
Note that if your use case is to set a conditional breakpoint, you may find Stop more useful, as in:
If targetShape.Type = msoAutoShape Then
Stop
End

Remove specific code from a module VBA using .DeleteLines

I'd like to use the .DeleteLinesfunction in VBA. As I'm not deleting all the lines in the module i need a targeted approach. I assume there is a function like Find("FooBar").LineNumber, however I can't find it here/with google:
https://msdn.microsoft.com/en-us/library/office/gg264546.aspx
Sub Deletings()
With Workbooks("ClassExperiment.xlsm").VBProject.VBComponents("Module2").CodeModule
.DeleteLines(HowDoIGetThisValue, 0)
End With
End Sub
Help appreciated.
If you're removing the entire procedure, you can find its location with the ProcStartLine property and the line count with ProcCountLines.
Dim module As CodeModule
Set module = Workbooks("ClassExperiment.xlsm").VBProject.VBComponents("Module2").CodeModule
Dim start As Long
Dim lines As Long
With module
start = .ProcStartLine("button_Click", vbext_pk_Proc)
lines = .ProcCountLines("button_Click", vbext_pk_Proc)
.DeleteLines start, lines
End With
Warning:
This should be obvious, but I'll throw it out there anyway. Do not use this (or any other method) to alter the module that the code is running from in Debug mode. This is a good way to break your workbook.
Sub test()
Dim vb As VBComponent
Dim i As Integer
Set vb = ThisWorkbook.VBProject.VBComponents("Module2")
For i =vb.CodeModule.CountOfLines to 1 step -1
If InStr(1, vb.CodeModule.Lines(i, 1), "' remove") <> 0 Then
vb.CodeModule.DeleteLines i, 1
End If
Next i
End Sub
I would of also suggested using a condition statement to allow execution of the code line, rather than deleting it, when is it put back? this could cause issues if you wish to automate that bit, as you'll need to know where it came from.

moving paragraph up/down (without copy / paste)

In word I am looking for a keyboard short cut which allows me to move the paragraph in which my cursor currently is one paragraph/line up or down.
I am new to VBA etc, but found this
Sub OutlineMoveUp()
Selection.Range.Relocate wdRelocateUp
End Sub
This comes pretty close to what I am looking for, but seems to move the paragraph up according to its position in the outline structure (what can become rather confusing). I just want to move it one paragraph/line up or down (also irrespective of its formatting).
(RStudio offers this nice feature where you can simply move selected text lines without copy-pasting; I am looking for the equivalent in word).
many thx.
The Relocate method is designed to work in Outline mode see here. Try the Move method instead:
Selection.Range.Move Unit:=wdParagraph, Count:=-1
You may need to adjust Count to get the effect you desire --- if -1 doesn't work, try -2, etc.
This would probably be cleaner using cut/paste but try this:
Sub Test_NewP()
Dim doc As Word.Document
Dim CurR As Word.Range
Dim NewP As Word.Paragraph
Dim IndexP As Long
Set doc = ActiveDocument
If doc.ActiveWindow.View = wdOutlineView Then
MsgBox "This program doesn't work in outline view --- please switch to another view", vbOKOnly, "Error"
Exit Sub
End If
Set CurR = Selection.Paragraphs(1).Range
IndexP = doc.Range(0, CurR.End).Paragraphs.Count
Set NewP = doc.Paragraphs.Add(doc.Paragraphs(IndexP - 1).Range)
NewP.Range.Text = CurR.Text
CurR.Delete
Set NewP = Nothing
Set CurR = Nothing
Set doc = Nothing
End Sub
This likely won't reliably manage formatting, but you could add code to fix that.
Hope that helps.