VBA Refactoring, MsgBox everywhere - vba

I'm very reluctant to post this as I'm not looking for anyone to write code, rather to get me moving in the right direction here.
I have inherited a huge mass of VBA. I've rewritten an awful lot of it, but I've hit a wall here.
Basically the code checks through millions of lines of an external file, and checks character by character for certain values. However, the code displays a message box MsgBox for EVERY single value.
Here is an example:
If CharArray(1) = "S" & CharArray(2) = "SO" Then
MsgBox "Hello Stack Overflow"
Else: MsgBox "Hello somewhere else"
EndIf
And it goes on like this, thousands of times. I will refactor the endless If statements myself. Is there anything I can do to save this? I was going to just comment out manually any MsgBox that isn't displaying an error, but it's still potentially a mess.
Can I write to a cell with the data, and increment to the next row easily from my own Sub or Function to just do a replace of MsgBox with something else? That is to say write Sub ReplaceMsgBoxes() have it write to a current row, whatever the message box was going to write, and go on a line?
Questions:
Would this Sub need a global counter variable (for the Row to write to)?
Is there a commonly used method by programmers to get around this kind of mess?
If I write a Sub to replace this can I call the same way, i.e.
ReplaceMsgBox "Whatever the text for the MsgBox was to begin with"
Many thanks.

One easy way is to buffer the material into a string and then do a single MsgBoxSay we want to find all the dog in A1 thru A100
Sub FindDog()
Dim dog As String, msg As String
dog = "dog"
msg = ""
For i = 1 To 100
If Cells(i, 1).Value = dog Then
msg = msg & vbCrLf & Cells(i, 1).Address(0, 0)
End If
Next i
MsgBox msg
End Sub
If there were very many items, I would use a scrollable ListBox instead.

To write to a cell:
activecell = "what ever you want to say"
to advance to the next cell (below)
activecell.offset(1,0).select

Related

Count selected lines in MS Word

I wanted to count the lines of selected text in MS word document. I come up with code:
Sub Count_Lines()
ActiveDocument.ComputeStatistics (wdStatisticLines)
MsgBox ("The document contains " & NumLines & " Lines")
End sub
But it actually count the line of whole document.
Can anybody help, finding code which could count selected line only?
Just use Selection.Range object instead of ActiveDocument
Sub Count_Lines()
MsgBox ("The selection contains " & Selection.Range.ComputeStatistics(wdStatisticLines) & " Lines")
End Sub
Your code intends to show the value of NumLines in the message box. However, no value was assigned to that variable. In fact the variable itself was also not declared. The code below fills these two shortcomings and then works just fine. However, it will count all the lines in the document. If you just want the selected lines use the solution offered below.
Option Explicit
Sub Count_Lines()
Dim NumLines As Long
NumLines = ActiveDocument.ComputeStatistics(wdStatisticLines)
MsgBox ("The document contains " & NumLines & " Lines")
End Sub
NB. They say, Option Explicit "forces" you to declare variables which forces the reaction to resist. Compare the force with that of the traffic police who insist on your driving on the right side of the road. Using Option Explicit will not prolong your life but it will drastically reduce the amount of time you spend chasing stupid errors because you are too proud to let your computer point them out to you. Option Explicit was the tool that provided me with an immediate pointer to the source of the problem you are seeking help on here.

Button to add column?

In excel 2013 I was hoping if someone can make a code for adding 1 blank column to a spreadsheet based on the user input after they click this button that is an activex control. The column will end based on how many rows there are in my table, meaning if there are 10 rows I should not see a column on line 11 of the spreadsheet.
I keep getting an error saying application defined or object error after I put in the column I want to add, I even tried both caps an lowercase and the same error comes up.
Private Sub CommandButton2_Click()
Dim x As String
x = InputBox("Enter a column that you want to add: ", "What column?")
If x = "" Then Exit Sub
ColumnNum = x
Columns(ColumnNum & ":" & ColumnNum).Insert shift:=xlShiftRight
Columns(ColumnNum - 1 & ":" & ColumnNum - 1).Copy Range("A1" & ColumnNum)
Columns(ColumnNum & ":" & ColumnNum).ClearContents
End Sub
This will get you started:
Dim x As Variant
Dim ColumnNum%
x = InputBox("Enter a column that you want to add: ", "What column?")
If x = "" Then Exit Sub
ColumnNum = x
ThisWorkbook.Sheets("Sheet1").Columns(ColumnNum).Insert shift:=xlRight
ThisWorkbook.Sheets("Sheet1").Columns(ColumnNum - 1).Copy
'THe line above doesnt make any sense whatsoever.
'Im not going to try and trouble shoot it but it seems like you dont understand how to
' properly scuplt things. Youll notice i changed how you strucutred the .copy part.
'THe part that doesnt make sense to me is the Range section.
ThisWorkbook.Sheets("Sheet1").Columns(ColumnNum).ClearContents
There were a few things wrong with your code.
You need to put Option Explicit at the top of your code. You didnt declare your variables.
you inpout box needs to be declared as variant
When i played around with this its clear you didnt fully reference what columns in what sheets need to be inserted. Notice my "ThisWorkbook....."
Your if statement is structured incorrectly as well.
I suggest you spend some time reading up on the basics some more :)

Excel VBA Self-Destruct Switch

I have had my first "Client-took-my-work-and-then-ghosted-me-without-paying" experience. For the future, I want to put in a killswitch, disguised as a regular macro, which makes the whole thing unusable. That way, even if they hire someone to crack the password and remove my "Your trial has expired..." check, a normal-looking macro (Something like "Fix_Sheet_Formatting") would be easily overlooked and run, destroying everything and saving the changes.
However, that leaves the VBA... We're talking a full purge here, so everything must go. I'll figure out how to do all of this on my own, I just don't want to waste time pursuing something that isn't possible:
Can VBA code delete itself from a workbook while running or does it have to be deleted from a macro on another workbook? And would deleting the code cause the macro to stop running, or can I have it delete everything except a nasty MsgBox after everything is done?
I'll leave the comments to debating whether or not this is a good idea (IMHO it probably isn't). As to the question itself, of course it's possible, and won't interrupt macro execution:
Public Sub Example()
Dim proj As Object
Set proj = ThisWorkbook.VBProject
Dim comp As Object
For Each comp In proj.VBComponents
With comp.CodeModule
.DeleteLines 1, .CountOfLines
If comp.Name = "ThisWorkbook" Then
.InsertLines 1, "Private Sub Workbook_Open()" & vbCrLf & _
vbTab & "MsgBox ""Where's my money, ##$%&!?""" & vbCrLf & _
"End Sub"
End If
End With
Next
ThisWorkbook.Save
End Sub
Obfuscation is an exercise for the reader.
Put a loop with due date as your payment data . Inside write code to get macro into infinity loop or to delete code.
If payment happens on time then just comment above piece of code or else just wait client come back to you on next day defined in loop.

VB, excel macro pause and resume working if possible

I cannot figure out the best way to do the following problem. Basically my macro (excel, VB) is checking several (100+) worksheets for correct values, if wrong value is found, I want it to stop, give me a warning, then I could have a look into the reason why the value is wrong, correct it manually and then I want to resume the macro, or remember the last value checked so if I return, it remembers where to continue (resume).
My current problem is that it finds the wrong value, then I can either make it stop so I check the problem, or it goes through all the sheets and then I have to remember which sheets had the wrong value.
What I thought of is make a list where the name of sheet is added every time a wrong value is found. The problem is that usually there is more than 1 wrong value in the same sheet if there is a wrong value at all and this added the same sheet name several times to the list. Another problem with that is that I cannot correct the values straight away.
I'm very inexperienced with programming and so would appreciate your idea on how to best approach this problem (I don't want to spend a long time on coding something which wouldn't be efficient for such a "simple" problem).
When the error is found (I'm assuming you've already been able to identify this), you can use the Application.InputBox function to prompt you for a new value.
For example, if rng is a Range variable that represents the cell being checked, and you have some logic to determine where the error happens, then you can just do:
rng.Value = Application.InputBox("Please update the value in " & rng.Address, "Error!", rng.Value)
The inputbox function effectively halts execution of the procedure, while waiting for input from the user.
If InputBox isn't robust enough, then you can create a custom UserForm to do the same sort of thing. But for modifying single range values, one at a time, the InputBox is probably the easiest to implement.
I believe you can handle this task by using one or two static local variables in your macro. A variable declared with "static" rather than "dim" will remember its value from the last time that procedure was run. This can hold where you left off so you can resume from there.
One thing that could be a problem with this solution would be if the macro gets recompiled. That would probably cause VBA to clear the value that the static variable was holding. Just doing a data edit in Excel should not cause a recompile, but you will want to watch for this case, just to make sure it doesn't come up. It almost certainly will if you edit any code between executions.
Create a public variable that stores the cell address of the last checked cell and use a conditional statement to see if it's "mid-macro" for want of a better phrase. here is a very crude example...
Public lastCellChecked As String
Sub Check_Someting()
Dim cell As Excel.Range
Dim WS As Excel.Worksheet
If Not lastCellChecked = vbNullString Then Set cell = Evaluate(lastCellChecked)
'// Rest of code...
'// Some loop here I'm assuming...
lastCellChecked = "'" & WS.Name & "'!" & cell.Address
If cell.Value > 10 Then Exit Sub '// Lets assume this is classed as an error
'// Rest of loop here...
lastCellChecked = vbNullString
End Sub
The best way to do this is to create a userform and as mentioned by prior users create a public variable. When the program finds an error store the cell and initiate the userform. Your code will stop on the userform. When you're done checking the problem have a button on the userform that you can click to continue checking. Your loop can be something like the below.
public y as integer
sub temp1 ()
rw1= range("a500000").end(xlup).row 'any method to create a range will do
if y = null then y=1
for x = y to rw1
cells(x,1).select
'check for the problem your looking for
if errorX=true then
userform1.show
y = activecell.row
exit sub
end if
next x
end sub
What about inserting a button (on the sheet or in a menubar) for stopping?
Insert the code below:
'This at the top of the module
Public mStop As Boolean
'This in the module
Sub MyBreak()
mStop = True
End Sub
'This is your macro
Sub YourMacro()
'This at the top of your code
mStop = False
'Your code
'...
'This code where you want to break
DoEvents '<<<< This makes possible the stop
If mStop Then
mCont = MsgBox("Do you want to continue?", vbYesNo)
If mCont = vbNo Then
Exit Sub
Else
mStop = False
End If
End If
'Your code
'...
End Sub
Now you need to create a button and link it to the macro called "MyBreak".

VBA: Invalid Next Control Variable Reference

Basically, i'm trying to mimic a concatenate result using code i stripped apart and recycled for my purposes. But i'm having problems when the script attempts to process "Next T" idk, but i already indicated as a Dim - Integer, and that still didnt seem to do the trick.
Original source of code:
Concatenate multiple ranges using vba
I've been having a lot of problems with this one piece, cause it seems to be the only thing i've actually been trying to include in my script for a long time now. Had compile errors with closing the If, adjusting the Then, and even Exiting the loop.
I think the Next should be my final worries.
Btw, rnumbers is supposed to hold the place of a value/integer, but i'm not entirely sure if that was done correctly either.
rnumbers = Rows(ActiveCell.Range("A3").End(xlDown)) + 3
'or CellCount = ActiveCell.Range("A" & Rows.Count).End(xldown).Row
Do While Rows(ActiveCell.Range("A3").End(xlDown)) > 3
'For Q = 1 To 10 'This provides a column reference to concatenate - Outer For statement
For T = 3 To rnumbers 'This provides a rows reference to concatenate - Inner for statement
For Each Cell In Cells("A" & T) 'provides rows and column reference
If Cell.Value = "" Then
GoTo Line1 'this tells the macro to continue until a blank cell is reached
Exit For
End If
x = x & Cell.Value & Chr(10) 'This provides the concatenated cell value and comma separator
'Next ' this loops the range
Next T 'This is the inner loop which dynamically changes the number of rows to loop until a blank cell is reached
Line1:
On Error GoTo Terminate 'Terminates if there are less columns (max 10) to concatenate
ActiveCell.Value = Mid(x, 1, Len(x) - 1) 'This basically removes the last comma from the last concatenated cell e.g. you might get for a range 2,3,4, << this formula removes the last comma to
'give 2,3,4
ActiveCell.Offset(1, 0).Select 'Once the concatenated result is pasted into the cell this moves down to the next cell, e.g. from F1 to F2
x = "" 'The all important, clears x value after finishing concatenation for a range before moving on to another column and range
'Next Q 'After one range is done the second column loop kicks in to tell the macro to move to the next column and begin concatenation range again
'rnumbers = 0
'Next
Exit Do
'Resume
Terminate:'error handler
Trying again... when I took a closer look at your code I actually used a Bad Word.
You have been hanging with the wrong crowd, and are picking up some really bad code structure ideas. A GoTo followed by an Exit For? The latter statement can never be reached! And jumping out of a For loop is a dangerous (if not wrong) thing to do. And yes, you still needed a Next for the For Each statement (with a matching control argument - the Next T belonged with a different For loop, not the innermost one).
Anyway - I felt like the Cat In The Hat: "This mess is so big and so deep and so tall - we cannot pick it up, there is No Way At All!". So I decided to build you a new house instead.
I think the following does what you want to do, and quite elegantly. See if it makes sense, and if you can adapt it for your purpose. I need to go to sleep but will take a look in the morning to see if you figured it out from here.
Sub concAll()
Dim allRows As Range, target as range
Dim oneRow
Dim nc as Integer
Set allRows = Range("A3", "J10") ' pick the real range here - dynamically, probably
nc = allRows.Columns.Count ' need this number later to know where to put result
For Each oneRow In allRows.Rows ' loop over one row of the range at a time
Dim s As String
s = "" ' start with empty string
For Each c In oneRow.Cells ' loop over all the cells in the row
If Not IsEmpty(c) Then
s = s & "," & c.Text
Else
Exit For ' done with this row: found empty cell
End If
Next c ' keep looping over the cells...
Set target = oneRow.Cells(1).Offset(0, oneRow.Cells.Count) ' cell where we put result
target.Value = Mid(s, 2) ' put the concatenated value to the right of everything;
' skipping first comma (which came before first text)
Next oneRow ' repeat for all rows in source range
End Sub
I'm sorry, i shouldve explained what i was trying to produce than asking to fix something i wanted to do. My experience in vba has been self-taught, and i'm a little new to asking for help.
The script Floris produced seemed to have function but not as intended. Turns out what i wrote is a little outdated, and needs to be wiped and restarted. This was actually an old script i started a few months back that worked off of a web-query. But the website went thru some changes and now the script is all over the place.
the main issue i was having was a compile-error "Invalid Next Control Variable Reference" Which turns out to be caused by an open 'Do while' loop, that doesnt seem to have much of an exit point from the research i looked up. Was supposed to have used another 'If' command instead. At the same time, when attempting to solve that 'Do While' i added an extra 'Next' (cause i thought they were compatible), and it screwed with the script.
Hard to explain.. But the 'Do While' i used, i wanted it to combine the values only if the number of values were greater
rnumbers = Rows(ActiveCell.Range("A3").End(xlDown)) + 3
'or CellCount = ActiveCell.Range("A" & Rows.Count).End(xldown).Row
Do While Rows(ActiveCell.Range("A3").End(xlDown)) > 3
But instead it was supposed to be
Dim CellCount As Range
CellCount = ActiveCell.Range("A" & Rows.Count).End(xlDown).Row + 2
'cause its the active cell + two additional cells
If CellCount > 3
Which then opens up into the script Floris submitted. (But that failed too, because of what was stated above).
Thanks again, hope that it explains everything... Sorry if i wasted your time with that one Floris, really do appreciate the assistance. Just wish i had asked for the help sooner, would have saved me a lot of frustration that i'm dealing with now. >_>