I've created a small macro for inserting a hidden bookmark to a numbered paragraph
Sub blablabla()
Dim BkmrkName As String
ActiveDocument.Bookmarks.ShowHidden = True
Application.ScreenUpdating = False
heanum = InputBox("Enter Heading1 number", "List paragraph", "1")
Select Case Len(heanum)
Case 1
sPos1 = "00" & Left(heanum, 1)
Case 2
sPos1 = "0" & Left(heanum, 2)
Case 3
sPos1 = Left(heanum, 3)
End Select
ActiveDocument.ConvertNumbersToText
lisnum = Left(Selection, InStr(Selection, vbTab))
ActiveDocument.Undo
If IsNumeric(Left(lisnum, 1)) = True Then
lisnum = Left(lisnum, Len(lisnum) - 2)
Select Case Len(lisnum)
Case 3
sPos2 = "00" & Right(lisnum, 1)
Case 4
If Mid(lisnum, 2, 1) = Chr(46) Then
sPos2 = "0" & Right(lisnum, 2)
ElseIf Mid(lisnum, 3, 1) = Chr(46) Then
sPos2 = "00" & Right(lisnum, 1)
End If
Case 5
If Mid(lisnum, 2, 1) = Chr(46) Then
sPos2 = Right(lisnum, 3)
ElseIf Mid(lisnum, 3, 1) = Chr(46) Then
sPos2 = "0" & Right(lisnum, 2)
ElseIf Mid(lisnum, 4, 1) = Chr(46) Then
sPos2 = "00" & Right(lisnum, 1)
End If
Case 6
If Mid(lisnum, 3, 1) = Chr(46) Then
sPos2 = Right(lisnum, 3)
ElseIf Mid(lisnum, 4, 1) = Chr(46) Then
sPos2 = "0" & Right(lisnum, 2)
End If
Case 7
sPos2 = Right(lisnum, 3)
End Select
End If
ActiveDocument.Bookmarks.Add Name:=Chr(95) & sPos1 & Chr(95) & sPos2
Application.ScreenUpdating = True
End Sub
The user select a numbered paragraph and triggers the macro. Macro runs once per trigger and inserts a hidden bookmark with the name like _001_042 if the selection begins with "any_character".42. The first "001" is meant to depict a chapter number (i.e. "Chapter 1"), but could be any number and is determined by a user input through a message box. This macro works, though with each next numbered paragraph runs slower and slower. When I get to paragraph 1.100 it takes ~5 minutes (!!!) for macro to insert a single bookmark "_001_100".
Why does such a long latency happen? Is it possible to optimize the macro to run faster?
Many thanks in advance!
Avoid making useless changes to a document. That applies to manual editing, and it applies doubly to VBA code.
Your ConvertNumbersToText / Undo is as close to completely useless as it gets. Don't do such things. Word has an Undo buffer that you stress for no reason with this pointless edit. Despite being a wasteful no-op, you also destroy the user's ability to undo their own actions with this.
First off, you solve the problem of padding a string in the worst possible way, (multiple times!). Lets fix that.
Function PadLeft(ByVal value As String, length As Integer, Optional padding As String = " ")
PadLeft = String(Max(0, length - Len(value)), padding) & value
End Function
This function will pad any string to any given length. However, it depends on another utility function that will return the greater of two numbers.
Function Max(a As Long, b As Long) As Long
If b > a Then Max = b Else Max = a
End Function
Now, how about this code:
Sub SetParagraphBookmark()
Dim para As Range, _
paraNum As Long, headerNum As String, _
prefix As String, suffix As String
Set para = Selection.Paragraphs(1).Range
paraNum = para.ListFormat.ListValue
If paraNum Then
headerNum = InputBox("Enter Heading1 number", "List paragraph", "1")
If headerNum > "" Then ' otherwise the user clicked Cancel
prefix = PadLeft(headerNum, 3, "0")
suffix = PadLeft(paraNum, 3, "0")
ActiveDocument.Bookmarks.Add "_" & prefix & "_" & suffix, para
End If
Else
MsgBox "Please click on a valid list paragraph first.", vbInformation
End If
End Sub
This sets a bookmark that spans the entire paragraph the cursor is in, without moving the cursor or making any other changes to the document.
General notes:
Why would you ever write Chr(46) instead of "."?
Indent your code properly, this increases readability.
If you find yourself copy-pasting any section of code, you are already doing something wrong. If you need something complex done in two places, write a function.
Try breaking up the work you do into the smallest possible useful unit, like I did with PadLeft and Max. This allows re-using bits of your code elsewhere. You might want to place them into a separate Utilities module as well.
Set breakpoints in your code to see what's going on.
Word has a comprehensive object model. You can find out just about anything about the document by navigating around that object model without resorting to steamroller tactics like ConvertNumbersToText. Taking some time to pick the right property from the right object pays. There will be a lot documentation-reading involved, you'll just have to deal with that. Luckily the Microsoft documentation is superb.
It's most useful to enable the "Locals Window" and the "Immediate Window" in the VBA editor. It allows you to browse the objects you work with while you are in break mode, which helps to identify the properties you are looking for.
Last, but not least: Always, always, always have Option Explicit at the top of your modules. There is a setting in the VBA IDE's options for that ("Require variable declaration"). Enable it. Manually add that line to any module that does not have it. Fix the errors you get before you do anything else. (Disable the "Auto syntax check" feature while you are at it, this feature is counter-productive.)
This macro does it's job in only few seconds:
Sub AddBkmrkSmart()
Dim Author, Year As String
ActiveDocument.Bookmarks.ShowHidden = True
heanum = InputBox("Enter Heading1 number", "Heading1", "1")
Select Case Len(heanum)
Case 1
sPos1 = "H00" & Left(heanum, 1)
Case 2
sPos1 = "H0" & Left(heanum, 2)
Case 3
sPos1 = "H" & Left(heanum, 3)
End Select
'Debug.Print "sPos1: " sPos1
lisnum = Selection.Range.ListFormat.ListValue
'Debug.Print "Iteration 1 lisnum: " & lisnum
Select Case Len(lisnum)
Case 1
sPos2 = "L00" & Left(lisnum, 1)
Case 2
sPos2 = "L0" & Left(lisnum, 2)
Case 3
sPos2 = "L" & Left(lisnum, 3)
End Select
'Debug.Print "sPos2: " sPos2
ActiveDocument.Bookmarks.Add Name:= "_" & sPos1 & "_" & sPos2
End Sub
I suggest is as answer along with Tomalak's one.
Related
I am trying to change a case of particular character in a cell but i think i am missing very small thing in it.
Eg.
The cell has value 'A for Apple and GORILLA wears pajama'
so i am trying to change case of individual 'A' into lower case 'a'.
Now there are 3 scenarios:
A can be in Starting of cell.
A can be in Middle of cell.
A can be in End of cell.
for the middle one I have solution:
Activecell.value = Replace(Activecell.value," A "," a ",vbTextCompare)
but for other 2 when I'm trying to do change other A's also changing, for example:
Activecell.value = Replace(Activecell.value,"A ","a ",vbTextCompare)
this is giving answer 'a for Apple and GORILLa wears pajama'
wherein I'm trying to get 'a for Apple and GORILLA wears pajama'
my code is
Do Until ActiveCell.Offset(0, -cnt).Value = "" And ActiveCell.Offset(0, -cnt1).Value = ""
actc = ActiveCell.Value
If actc = "" Then
ActiveCell.Offset(1, 0).Select
Else
ActiveCell.Value = Replace(ActiveCell.Value, " m ", " m ", , , vbTextCompare)
ActiveCell.Value = Replace(ActiveCell.Value, " cm ", " cm ", , , vbTextCompare)
ActiveCell.Value = Replace(ActiveCell.Value, " dm ", " dm ", , , vbTextCompare)
ActiveCell.Value = Replace(ActiveCell.Value, " mm ", " mm ", , , vbTextCompare)
ActiveCell.Value = Replace(ActiveCell.Value, " mg ", " mg ", , , vbTextCompare)
End If
Loop
To replace only the "A " in the beginning check this in particular.
If Left$(ActiveCell.Value, 2) = "A " Then
ActiveCell.Value = "a" & Right$(ActiveCell.Value, Len(ActiveCell.Value) - 1))
End If
Accordingly for the " A" in the end (Even if I don't know about sentences ending with " A")
If Right$(ActiveCell.Value, 2) = " A" Then
ActiveCell.Value = Left$(ActiveCell.Value, Len(ActiveCell.Value) - 1)) & "a"
End If
The VBA Replace function will replace ALL instances of the substring in the target string.
To specify beginning and/or end of the string, you can test for the presence of the substring and, if it is present, use a different function (I chose to use the Replace Worksheet function) to replace only that character. Examine the following code snippet for an example.
With ActiveCell
If .Value Like "A*" Then .Value = WorksheetFunction.Replace(.Value, 1, 1, "a") 'Beginning only
If .Value Like "*A" Then .Value = WorksheetFunction.Replace(.Value, Len(.Value), 1, "a") 'End only
End With
If, on the other hand, you want the sentence to start (or end) with the word A, implying there is a space after (or before) the A, then change the Like pattern to reflect that "A *" or `* A"
If all of those substrings are, in fact, words, and you want to replace them all in each ActiveCell, you can either construct multiple loops along the format of what you are doing, or you could use Regular Expressions.
For example, the following will replace all instances of the capitals you have indicated with their lower case equivalents, whether they are at the beginning, middle or end.
The macro first matches all of the instances of the different words in sPat, and then cycles through the sentence to replace them. (Note that we must go from last to first in the loop, since the lengths of each word may not be the same).
Option Explicit
Sub doit()
ActiveCell = replaceCaps(ActiveCell)
End Sub
Function replaceCaps(ByRef S As String)
Const sPat As String = "\b(?:A|M|CM|DM|MM|MG)\b"
Dim RE As Object, MC As Object
Dim I As Long
Set RE = CreateObject("vbscript.regexp")
With RE
.Global = True
.ignorecase = False
.Pattern = sPat
If .test(S) = True Then
Set MC = .Execute(S)
For I = MC.Count - 1 To 0 Step -1
S = WorksheetFunction.Replace(S, MC(I).firstindex + 1, Len(MC(I)), LCase(MC(I)))
Next I
replaceCaps = S
Else
replaceCaps = S
End If
End With
End Function
I have created a massive routine to extract, clean and transform data (dynamically) that I run using a form in a spreadsheet which then outputs that info based on a load of variables. It looks at one of the worksheets (in that same workbook) to get all of the variable data and loops through it as needed.
This works perfectly when run using the workbook the form is in but I am trying to call it from another spreadsheet to create a looped schedule of tasks (which works fine as far as I'm aware)
Is it possible to call a routine in another workbook that contains a line to delete a row? At the moment it runs but blatantly skips over it.
Below is the code and I have thoroughly tested to ensure that all the workbook/sheet/row names are correct using msgbox etc and this code works when run from the workbook it is contained in.
Application.DisplayAlerts = False
ThisWorkbook.Worksheets("Exceptions").Rows(1).Delete
Application.DisplayAlerts = True
I've tried disabling alerts and making the worksheet visible for this section and neither made any difference.
The other strange thing is that it runs this part of the routine twice when it's only called once. When run through the form the routine is only called once and I have tested on the 'calling' workbook to make sure it is not calling this routine more than once (using a msgbox inside the loop) and it is not.
Is there a security function that disables this or something? I've tried many variations but cannot figure it out. I also cannot SELECT worksheets/cells manually this way.
Both Application.ScreenUpdating and Application.Visible are set to FALSE. When calling this routine I have disabled the form.
Thanks in advance!
EDIT
To answer some questions this is how I call the routine that is in the other workbook. I use variable pass-through values for most things and I've found the string dates generate properly but don't populate on the other end so I replicate this a second time in the called workbook.
I used a msgbox before and after 'Call_Macro_wb.Close False' to see how many times it calls and and it only shows once so I don't believe it's from this end.
One thing I did fail to mention is that this calls a looped routine in the 'called' workbook that calls another routine also in the 'called' workbook. That function has been working flawlessly for months from the form.
Apologies for not getting everything out as the rest of the code is quite large..
Schedule_Details = ThisWorkbook.Sheets("Schedule").UsedRange
For x = 1 To UBound(Schedule_Details)
Function_Name = Schedule_Details(x, 1)
Workbook_Name = Schedule_Details(x, 2)
Save_Location = Schedule_Details(x, 3)
Full_Path = Save_Location & "\" & Workbook_Name
If Schedule_Details(x, 4) = "1" Then
'Run [Special] reports here
'Weekly Figures
If Schedule_Details(x, 1) = "Weekly Figures" Then
If Application.WorksheetFunction.Weekday(Now(), 2) = 1 Or Application.WorksheetFunction.Weekday(Now(), 2) = 5 Then
strStartDate = Format(DateAdd("ww", -2, Date - (Weekday(Date, vbMonday) - 0)), "yyyy-mm-dd")
strEndDate = Format(DateAdd("ww", -2, Date - (Weekday(Date, vbMonday) - 6)), "yyyy-mm-dd")
Set Call_Macro_wb = Workbooks.Open(Full_Path)
Application.Run "'" & Workbook_Name & "'!Update_Weekly_Files(" & strStartDate & "," & strEndDate & ",,TRUE)"
Call_Macro_wb.Close False
MsgBox "Finished"
End If
End If
End If
Next
That then calls the below
Sub Update_Weekly_Files(ByVal strStartDate As String, ByVal strEndDate As
String, Optional ByVal Bangalore_Flag As Boolean, Optional ByVal
Auto_Day_Flag As Boolean)
Dim Cust_Name As String, Account_Details As Variant
Dim Clawback_Email As String, Email_Group As String, Save_Location As String,
Day_Answer As String, WB_Name As String, WB_Name_Suffix As String
Dim strAccount As String, Return_cons As String
Dim j As Long
Dim KPI As Single
Dim BR_Generic As Boolean, Report_Day As Boolean
Account_Details = ThisWorkbook.Sheets("Current Accounts").UsedRange
If Application.WorksheetFunction.Weekday(Now(), 2) = 1 Or
Application.WorksheetFunction.Weekday(Now(), 2) = 5 Then
Report_Day = True
Else: Report_Day = False
End If
If Auto_Day_Flag = True And Report_Day = True Then
strStartDate = Format(DateAdd("ww", -2, Date - (Weekday(Date, vbMonday) -
0)), "yyyy-mm-dd")
strEndDate = Format(DateAdd("ww", -2, Date - (Weekday(Date, vbMonday) - 6)),
"yyyy-mm-dd")
If Application.WorksheetFunction.Weekday(Now(), 2) = 1 Then
WB_Name_Suffix = " (INTERNAL).xlsb"
ElseIf Application.WorksheetFunction.Weekday(Now(), 2) = 5 Then
WB_Name_Suffix = " (INTERNAL WASH).xlsb"
End If
Else
Day_Answer = MsgBox("Click 'Yes' to update the RAW figures or 'No' to update
the FINAL figures", vbYesNo + vbQuestion, "Please choose an action")
If Day_Answer = vbYes Then
WB_Name_Suffix = " (INTERNAL).xlsb"
Else
WB_Name_Suffix = " (INTERNAL WASH).xlsb"
End If
End If
For j = 2 To UBound(Account_Details)
If Account_Details(j, 5) = "y" Then
DoEvents
Cust_Name = Account_Details(j, 1)
strAccount = Account_Details(j, 2)
Return_cons = Account_Details(j, 3)
Save_Location = Account_Details(j, 4)
Clawback_Email = Account_Details(j, 6)
KPI = Account_Details(j, 7)
If Left(Cust_Name, 3) = "StM" Then
Cust_Name = "StM"
ElseIf Left(Cust_Name, 3) = "UBT" Then
Cust_Name = "UBT"
ElseIf Left(Cust_Name, 3) = "HDM" Then
Cust_Name = "HDM"
ElseIf Left(Cust_Name, 3) = "Ine" Then
Cust_Name = "Inenco"
End If
WB_Name = "Performance Report - " & Cust_Name & WB_Name_Suffix
Call QueryException(Cust_Name, strStartDate, WB_Name, strEndDate,
strAccount, Return_cons, Save_Location, Clawback_Email, KPI)
If Application.WorksheetFunction.Weekday(Now(), 2) = 5 Then
If Account_Details(j, 9) = "y" Then
If IsEmpty(Cust_Name) = True Then
MsgBox "Didn't work homie"
Else
If Account_Details(j, 10) = "y" Then
BR_Generic = True
Else: BR_Generic = False
End If
Call Extract.Extract_Data(Cust_Name, strStartDate,
strEndDate, "W", BR_Generic, Clawback_Email, True)
End If
End If
End If
End If
Next
Application.Cursor = xlDefault
Application.Visible = True
MsgBox "~ Finish ~ " & Auto_Day_Flag
If Auto_Day_Flag = True Then
Exit Sub
End If
End Sub
ThisWorkbook.Worksheets(1).Row(1).Delete will delete row 1 in the first worksheet of the workbook that hosts the code. If you want to delete a row in another workbook you need to specify that workbook
Workbooks("AnotherWorkbook.xlsx").Sheets(1).row(1).delete
Note that if you have file extensions hidden you need to check that the file is actually called that and not .xlsm etc.
I am trying to do some renaming and I do not want the code to run if the last 3 characters = pdf. Should be simple, but when I run the macro the renaming works fine but it deletes every cell that ends with pdf.
SearchChar = "pdf"
For Each bCell In rng.Cells
Select Case Len(bCell)
Case 2
If Right(bCell, 3) <> SearchChar Then 'This must be wrong
val = SearchSite & Left(bCell, 1) & "00" & Mid(bCell, 2, 1) & "1.pdf"
End If
End Select
bCell.Value = val
Next
Have you tried changing your If statement to check if the right 3 characters are pdf, then "nothing" else your code? Would be similar to:
If Right(bCell,3)=SearchChar Then
'Nothing
Else
val = SearchSite & Left(r, 1) & "00" & Mid(r, 2, 1) & "1.pdf"
bCell.Value=val
End If
My guess is that where the last 3 characters are pdf, val = nothing, so what is printed out is bCell.Value= nothing, so it deletes it. I moved that inside the else section.
You may try like this to rename selection items not ending with "pdf"
Set rng = Selection
SearchChar = "pdf"
Select Case n
Case 2
For Each r In rng
If Right(r, 3) <> SearchChar Then 'This must be wrong
r = SearchSite & Left(r, 1) & "00" & Mid(r, 2, 1) & "1.pdf"
End If
Next
End Select
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
I have displayed YES if the Data of Column C>=0.05 and NO if it is not satisfied . Also , I have done same with Column D in the Excel. The program Code is Given.
Now , I want to Modify my program . I want to display all the Output in single display Box like in Qbasic Programming , C programming . Also, If My output is YES , Just I need to display YES and If it is NO , I need to Display corresponding values of A cell .
The Code is Given
Sub ArrayLoops1()
Dim arrCMarks()
Dim arrDMarks()
Dim i, j As Integer
a = 0
b = 0
'For Column C
arrCMarks = Range("C2:C1439").Value
For i = LBound(arrCMarks, 1) To UBound(arrCMarks, 1)
If arrCMarks(i, 1) >= 0.005 Then
a = a + 1
End If
Next i
If a = 0 Then
MsgBox (" YES ")
Else
MsgBox ("NO")
End If
' For Column D
arrDMarks = Range("D2:D1439").Value
For j = LBound(arrDMarks, 1) To UBound(arrDMarks, 1)
If arrDMarks(j, 1) >= 0.005 Then
b = b + 1
End If
Next j
If b = 0 Then
MsgBox ("YES")
Else
MsgBox ("NO")
End If
End Sub
To make sure I understood your problem correctly:
If at least one of the values in Column C is bigger than or equal 0.005, you want to display a MsgBox saying "Yes", if no value is bigger or equal one that says "No" and the same thing again for column D?
And now you want to combine that in a single MsgBox?
In that case, do the following:
Format your code to have a better overview
We can not stop iterating anymore because we need to find all values.
Construct a String out of multiple elements
Resulting Code:
Sub ArrayLoops1()
Dim i As Integer, j As Integer
Dim a As Boolean, b As Boolean
Dim MsgString As String
MsgString = "Column C: "
'For Column C
For i = 2 To 1439
If Range("C" & i).Value >= 0.005 Then
If a = False Then
a = True
MsgString = MsgString & "NO, values from coumn A are:" & vbCrLf
End If
'Add value from column A
MsgString = MsgString & Range("A" & i).Value & vbCrLf
'Can not stop iterating since we need to find all values
End If
Next i
If a = False Then
MsgString = MsgString & "YES" & vbCrLf
End If
MsgString = MsgString & "Column D: "
' For Column D
For j = 2 To 3
If Range("D" & i).Value >= 0.005 Then
If b = False Then
b = True
MsgString = MsgString & "NO, values from coumn A are:" & vbCrLf
End If
'Add value from column A
MsgString = MsgString & Range("A" & i).Value & vbCrLf
'Can not stop iterating since we need to find all values
End If
Next j
If b = False Then
MsgString = MsgString & "YES"
End If
'Display the message
MsgBox MsgString
End Sub
BTW, the line
Dim i, j As Integer
results in i beeing a Variant (not good) and only j beeing an Integer.
Better:
Dim i as Integer, j as Integer
Bye, vat
Edit: If result is No, the corresponding value from column A is displayed (see comments below)
Edit 2: The code now does not stop iterating since we need to find all violating values. All corresponding values from column A are now shown.
Okay this will output a one msgbox:
Sub ArrayLoops1()
Dim arrCMarks()
Dim arrDMarks()
Dim i As Integer, j As Integer
Dim otptStr As String
'For Column C
arrCMarks = Range("C2:C1439").Value
For i = LBound(arrCMarks, 1) To UBound(arrCMarks, 1)
If arrCMarks(i, 1) >= 0.005 Then
otptStr = otptStr & "C: " & arrCMarks(i, 1) & " A" & i + 1 & ": " & Range("A" & i + 1).text & vbCrLf
End If
Next i
' For Column D
arrDMarks = Range("D2:D1439").Value
For j = LBound(arrDMarks, 1) To UBound(arrDMarks, 1)
If arrDMarks(j, 1) >= 0.005 Then
otptStr = otptStr & "D: " & arrDMarks(j, 1) & " A" & j + 1 & ": " & Range("A" & j + 1).text & vbCrLf
End If
Next j
MsgBox otptStr, vbOKOnly, "Both Columns"
End Sub
It will output any that are greater than the .005.
Unless you (or your users) are totally obsessed with popup dialogs, I would suggest to use a filter instead.
Such a filter would hide the majority of rows, leaving only the ones where column C or D, or both, satisfy some condition. The reason for a row to remain visible, will be reported in a separate column.
Sub FilterCD()
' Use column Z to report violating columns.
' In this example: C = the value in column C > 0.005
' D = the value in column D < 0.006
' CD = both
Range("Z2").Formula = "=CONCATENATE(IF(C2>0.005,""C"",""""), IF(D2<0.006,""D"",""""))"
Range("Z2:Z6").FillDown
' Hide rows where column Z is empty.
AutoFilterMode = False
Cells.AutoFilter Field:=26, Criteria1:="<>"
End Sub
Notes:
Adding more column in addition to C and D, is straightforward; CONCATENATE accepts any number of arguments.
You need a header row above your data to make this work. In other words, your data should start from row 2. Considering your original code, I guess that is already the case with you.
You can use Ctrl+Shift+L to undo the filter afterwards.
On big Excels sheets, I expect this code to be a lot faster than anything using For loops. But I must admit I did not profile it.
Please let me know if there is anything in my answer that does not meet your demands.
I have the code of this macro which removes leading and trailing spaces in cells with text or numbers:
Sub LIMPIAR()
Dim i As Integer
For i = 2 To 20628
Cells(i, 7).Value = Trim(Cells(i, 6).Value)
Next
End Sub
However , there are cells which its content is " ". So I would like to convert that kind of cells to "". How Can I do that?
EDIT: I'm working with scraped data.
Maybe dealing with them like this can help:
If Len(Cells(i,6).Value) <= 2 Then Cells(i, 7).Value = "" End If
OR
If Cells(i,6).Value = " " Then Cells(i, 7).Value = "" End If
Not a very elagent solution, but I would make use of the split function and then reconcatenate the elements of the resulting array. Assuming your string is in cell A1,
mystring = ""
myarray = Split(Cells(1, 1), " ")
For i = LBound(myarray) To UBound(myarray)
If Trim(myarray(i)) <> "" Then
mystring = mystring & Trim(myarray(i)) & " "
End If
Next i
MsgBox Trim(mystring)
mystring should provide a string with just one space between words. You could put this code inside your loop.