How do I change my macro into a Worksheet_Change event Excel VBA - vba

I have a macro that I call when the workbook closes. It checks the columns in two tables on separate worksheets and assigns row numbers based on what it finds.
Worksheet_Change handler is located on the sheet with Projects range. Database range is located on another worksheet in the same workbook.
Whenever I call the macro anywhere else, it either generates an error or causes an usual bug where excel is partially frozen (anyone know what the hell this is?!?!?!).
Anyway, my last resort before giving up is to change the macro into a worksheet change event and I was wondering if I could get some help creating this.
The original macro:
Sub FindRow()
'This module verifies row numbers in the database by matching them to the opportunities in the Projects
'worksheet. It then assigns row numbers in the Projects worksheet.
Application.ScreenUpdating = False
Dim LastRow As Long
LastRow = Application.ThisWorkbook.Sheets("Projects").Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Dim rng As Range
Dim foundRng As Range
For Each rng In Sheets("Projects").Range("B2:B" & LastRow)
Set foundRng = Sheets("Database").Range("C:C").Find(rng, LookIn:=xlValues, lookat:=xlWhole)
If Not foundRng Is Nothing Then
rng.Offset(0, -1) = foundRng.Row
End If
Next rng
Application.ScreenUpdating = True
End Sub
My proposed change:
Public Sub Worksheet_Change(ByVal Target As Range)
Dim Records As Range
Set Records = Range("Records")
If Not Application.Intersect(Records, Range(Target.Address)) Is Nothing Then
Application.ScreenUpdating = False
Dim LastRow As Long
LastRow = Application.ThisWorkbook.Sheets("Projects").Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Dim rng As Range
Dim foundRng As Range
For Each rng In Sheets("Projects").Range("B2:B" & LastRow)
Set foundRng = Sheets("Database").Range("C:C").Find(rng, LookIn:=xlValues, lookat:=xlWhole)
If Not foundRng Is Nothing Then
rng.Offset(0, -1) = foundRng.Row
End If
Next rng
Application.ScreenUpdating = True
End If
End Sub
However, I keep getting an error on the line that defines the variable LastRow.
I get an application-defined error even though everything is defined properly before.
Thanks in advance.

Worksheet_Change handles is located on the sheet with Projects range. Database range is located on another worksheet in the same workbook. – Remi 1 min ago
This means rng is also on the Projects sheet:
For Each rng In Sheets("Projects").Range("B2:B" & LastRow)
(BTW Me.Range("B2:B" & LastRow) would have been much less ambiguous here)
You're handling a Worksheet_Change event on the Projects sheet, which Excel fires whenever a cell value changes on the Projects sheet. Then inside that handler, you do this:
rng.Offset(0, -1) = foundRng.Row
With rng being a range on the Projects sheet, you're entering a recursive cycle of sheet changes, and that is likely what's crashing your code.
When you make worksheet changes while handling worksheet changes, you need to tell Excel "it's okay, I got this", by preventing it from re-firing the Worksheet.Change event every time:
Application.EnableEvents = False
'...code...
Application.EnableEvents = True
Moreover, when you toggle Application.ScreenUpdating = False, you're telling Excel "don't repaint yourself until I say so" - that can speed things up considerably, in a lot of cases, however it also means you need to toggle it back on manually if something bad happens.
You can avoid this by implementing an error handler - here's the principle:
Sub DoSomething()
On Error GoTo CleanFail
Application.EnableEvents = False
Application.ScreenUpdating = False
'...code...
CleanExit:
Application.EnableEvents = True
Application.ScreenUpdating = True
Exit Sub
CleanFail:
Debug.Print Err.Description
Stop
Resume CleanExit
Resume 'F8 takes you to the error-throwing statement
End Sub

You have not indicated what sheet is the activesheet that has the worksheet_change event.
Any way, here is a code to find the last row in column B sheet "Projects"
Dim LastRow As Long, sh As Worksheet
Set sh = Sheets("Projects")
LastRow = sh.Cells(sh.Rows.Count, "B").End(xlUp).Row
MsgBox LastRow & " is the last row in Column B Sheet Projects!"

Related

VBA Excel - Auto run macro to insert new blank row after cell above has value entered

I am trying to get a macro to auto run to:
insert a blank row in each section e.g. Architectural when the data validation row above (in column A) has a value entered into it.
I entered the code as a sub in the worksheet, when I click run in the developer tab in excel, it inserts a line once, but I would like it to run automatically (after the workbook is opened) every time something is entered into column A.
Sub BlankLine()
'Updateby20150203
Dim Rng As Range
Dim WorkRng As Range
xTitleId = "KutoolsforExcel"
Set WorkRng = Application.Selection
Set WorkRng = Application.InputBox("Range", xTitleId, WorkRng.Address, Type:=8)
Set WorkRng = WorkRng.Columns(1)
xLastRow = WorkRng.Rows.count
Application.ScreenUpdating = False
For xRowIndex = xLastRow To 1 Step -1
Set Rng = Range("B" & xRowIndex)
If Rng.Value = "" = False Then
Rng.Offset(1, 0).EntireRow.Insert Shift:=xlDown
End If
Next
Application.ScreenUpdating = True
End Sub
I think I can help you with your first question.
You can automatically start a macro when a cell changes with a Sub Worksheet_Change(ByVal Target As Range) Sub inside the worksheet.
Here is description: https://support.microsoft.com/en-us/help/213612/how-to-run-a-macro-when-certain-cells-change-in-excel
You can insert a new row with the following code:
Application.Selection.EntireRow.Insert shift:=xlDown
When you do just that, you will encounter that the new line will again trigger the event to start the macro, hence again inserting a new line. This leads to an infinity loop. To stop this from happening, we need to disable events for the time of the change.
Application.EnableEvents = False
Call new_line_below_selection_macro
Application.EnableEvents = True
Here is a question with a similar problem: How to end infinite "change" loop in VBA
I hope this helps.
Here is the code which should go into the sheet:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Set KeyCells = Range("A1:C10") 'Area this should apply
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
Application.EnableEvents = False
'either you put your code here
'Application.Selection.EntireRow.Insert shift:=xlDown
'or you call it from a module
Call Module1.BlankLine
Application.EnableEvents = True
End If
End Sub

PasteSpecial not pasting, but code does not error

At this point there are two problems, but the first one i want to deal with is that i cannot get the paste function to work. When I run through the code the specific cells are highlighted to copy (the cell border is b&w flashing) and the cells where they are to end up are now highlighted, but nothing pastes.
Sub OtherTask()
Dim DRng As Range
ActiveSheet.Range("g2:ah2").find(Date).Select
ActiveCell.Resize(5).Offset(5).Select
Selection.AutoFilter field:=1, Criteria1:="1", Operator:=xlFilterValues
Set DRng = ThisWorkbook.ActiveSheet.AutoFilter.Range.SpecialCells(xlCellTypeVisible)
DRng.Copy
ActiveSheet.Range("r12").PasteSpecial xlPasteAll
If ActiveSheet.AutoFilterMode = "True" Then
ActiveSheet.AutoFilterMode = "False"
End If
End Sub
I should bring up the second problem. When I execute this from the macro button it performs as per the description above, but when I am in the editor and I press the play button I get error 91 that the object is not set. Not sure why I would get the error with one form of execution and not the other?? Looking through similar perhaps I should be using value instead of copy? Thanks for any help.
I had to make some assumptions with your code because there are some things that are not that clear. The assumptions should be easy to see and to change according to your needs.
Sub OtherTask()
Dim ws as Worksheet
Dim DRng As Range
Set ws = Worksheets("mySheet")
With ws
Dim rFound as Range
Set rFound = .Range("g2:ah2").find(Date)
rFound.Resize(5).Offset(5).AutoFilter field:=1, Criteria1:="1", Operator:=xlFilterValues
'declare this range explicitly, whatever it is
Set DRng = .Range("A1:B5000").AutoFilter.Range.SpecialCells(xlCellTypeVisible)
DRng.Copy .range("R12") 'since you paste everything just do straight from copy method
If .AutoFilterMode = "True" Then .AutoFilterMode = "False"
End With
End Sub

Copy cells formulas VBA

I did a program in VBA to copy the formulas in each cell in a specific column, I have 30501 points and the program is really slow even to calculate 100 points, there is a better way to do so?
Sub Copyformulas()
Dim i As Integer
Dim cell As Range
Dim referenceRange As Range
Dim a As String
a = "$T$30510"
Set range1= ActiveSheet.Range("A1:A30510")
Set myrange = Range("T16:T30510")
i = 16
Do Until Cells(20, 30510)
With range1
For Each cell In myrange
If cell.HasFormula Then
Cells(i, 35).Value = cell.Address
Cells(i, 36).Value = "'" & CStr(cell.Formula)
i = i + 1
End If
Next
End With
Loop
End Sub
You can use SpecialCells to refine your range. You don't need to use ActiveSheet it is implied.
Set rSource = Range("A16:A30510").SpecialCells(xlCellTypeFormulas)
Sub Copyformulas()
Application.Calculation = xlManual
Application.ScreenUpdating = False
Application.EnableEvents = False
Dim c As Range
Dim rSource As Range
Set rSource = ActiveSheet.Range("A16:A30510").SpecialCells(xlCellTypeFormulas)
For Each c In rSource
c.Offset(0, 34) = c.Address
c.Offset(0, 35) = "'" & c.Formula
Next
Application.Calculation = xlAutomatic
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Try adding the following:
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.EnableEvents = False
... Your Code ...
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.EnableEvents = True
You may only need the first one, but they are all good practice in using. Also, where are you using the With ... End With statement? I don't see any use of it in the block.
It is good practice to use Option Explicit at the top of the module. And range1 and myrange are not declared.
Application.Calculation
When a worksheet is accessed or a range's precedents has changed, Excel will automatically recalculate the formulas on the worksheet. Since you are looping over 30,000 times, this causes Excel to recalculate each time through the loop and, thus, slows down performance.
Application.ScreenUpdating
This line stops Excel from screen flashes and other things that occur as the macro runs.
Application.EnableEvents
This line turns off events, such as Worksheet_Change, so that the event is not triggered. If it is not turned off then any time a change occurs on the worksheet the code in the change event will run. If you have a Worksheet_SelectionChange event then code will run every time you select a different cell. These events are written in the worksheet or workbook objects located in the project window of the VBE and there are many events to choose from. Here is a very simple illustration. Place the following in the Sheet1 object in the project window:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
MsgBox "Hi!"
End Sub
Now click around on the worksheet. You see it responds to each selection change. Now place the following in a regular module:
Sub TestEnableEvents()
Application.EnableEvents = False
ActiveCell.Offset(1, 0).Select
Application.EnableEvents = True
End Sub
When you run the above code the message box will not be triggered.

Inserting Comment and Color into a cell that satisfies If...Then Statement

So I am basically trying to insert a comment and color the cell that basically meets the criteria that I set in my code. I searched all over but cant seem to find a viable solution.
Here is the code that I have so far and I mentioned in the code below where I would like the color and comment to be. The way I have this macro set up is that it gets "Called" from the Worksheet. I used the Selection_Change function. So I have a range where in one column someone enters data and then whatever data is entered the following macro runs and checks to see if it is within limits.
If it is not within the limits that are set in the excel sheet ("M7" and "M19"), I would like a color to highlight that certain cell and a set comment in that cell. How would I go about this? I really appreciate the help. Thank you!
Also I found a code online and my problem is that when i use the
ActiveCell.AddComment ("Text")
I keep getting an error, and also after I enter my data point and I press enter, the comment goes into the next cell.
Here is the macro that gets called:
Option Explicit
Public Sub OutofControlRestofData()
Dim lRow As Long
Dim lstRow As Long
Dim data As Variant
Dim ul As Variant
Dim ll As Variant
Dim wb As Workbook
Dim ws As Worksheet
With Application
.ScreenUpdating = True
.EnableEvents = True
.DisplayAlerts = True
End With
Set ws = Sheets(2)
ws.Select
lstRow = WorksheetFunction.Max(1, ws.Cells(Rows.Count, "R").End(xlUp).Row)
For lRow = 1 To lstRow
data = Cells(lRow, "E").Value
ul = Range("M7")
ll = Range("M19")
If data > ul Or data < ll Then
If IsNumeric(data) = True And data Like "" = False Then
MsgBox ("There was an Out of Control Point at " & Cells(lRow, "C").Value)
'THIS IS WHERE I THINK THE COMMENTING AND COLOR CODE WOULD BE
End If
End If
Next lRow
End Sub
Also here is the code that Calls the Macro:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Target.Worksheet.Range("E39:E138")) Is Nothing Then
Run ("OutofControlRestofData")
End If
End Sub
A few things to note.
You should practice using tab to "nest" your If statements. Makes
it clearer to see.
You can go ahead and combine the two Subs. Just make sure you put the code in the Sheet's code page (not in a workbook module).
You don't need a loop if you already have a "Target" as that is the cell (Range) you want to check anyways.
You have defined your Change sub to only work if the data entry is between E39 and E138. Will this always be the case? Consider using the entire column E if you want more flexibility to grow your sheet and data.
Code:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws As Worksheet
Set ws = Sheets(2)
If Not Intersect(Target, ws.Range("E39:E138")) Is Nothing Then
Dim lRow As Long
Dim lstRow As Long
Dim data As Variant
Dim ul As Variant
Dim ll As Variant
Dim wb As Workbook
Dim ws As Worksheet
With Application
.ScreenUpdating = True
.EnableEvents = True
.DisplayAlerts = True
End With
data = Target.Value
ul = Range("M7").Value
ll = Range("M19").Value
If data > ul Or data < ll Then
If IsNumeric(data) = True And data Like "" = False Then
MsgBox ("There was an Out of Control Point at " & Target.Address)
Target.Interior.Color = RGB(255, 0, 0)
Target.AddComment ("This is an Out of Control Point")
End If
End If
End If
End Sub
Just to be on the safe side, I'd recommend changing your code here to include value:
data = Range("E" & lRow).Value
ul = Range("M7").Value
ll = Range("M19").Value
Then in the spot where you want to do the color/comment stuff:
Range("E" & lRow).Interior.Color = RGB(255, 0, 0)
Range("E" & lRow).AddComment("This is an Out of Control Point")

Excel Macro for creating new worksheets

I am trying to loop through some columns in a row and create new worksheets with the name of the value of the current column/row that I am in.
Sub test()
Range("R5").Select
Do Until IsEmpty(ActiveCell)
Sheets.Add.Name = ActiveCell.Value
ActiveCell.Offset(0, 1).Select
Loop
End Sub
This code creates the first one correctly starting at R5 but then it appears that the macro switches to that worksheet and doesn't complete the task.
The Sheets.Add automatically moves your selection to the newly created sheet (just like if you insert a new sheet by hand). In consequence the Offset is based on cell A1 of the new sheet which now has become your selection - you select an empty cell (as the sheet is empty) and the loop terminates.
Sub test()
Dim MyNames As Range, MyNewSheet As Range
Set MyNames = Range("R5").CurrentRegion ' load contigeous range into variable
For Each MyNewSheet In MyNames.Cells ' loop through cell children of range variable
Sheets.Add.Name = MyNewSheet.Value
Next MyNewSheet
MyNames.Worksheet.Select ' move selection to original sheet
End Sub
This will work better .... you assign the list of names to an object variable of type Range and work this off in a For Each loop. After you finish you put your Selection back to where you came from.
Sheets.Add will automatically make your new sheet the active sheet. Your best bet is to declare variables to your objects (this is always best practice) and reference them. See like I've done below:
Sub test()
Dim wks As Worksheet
Set wks = Sheets("sheet1")
With wks
Dim rng As Range
Set rng = .Range("R5")
Do Until IsEmpty(rng)
Sheets.Add.Name = rng.Value
Set rng = rng.Offset(0, 1)
Loop
End With
End Sub
Error handling should always be used when naming sheets from a list to handle
invalid characters in sheet names
sheet names that are too long
duplicate sheet names
Pls change Sheets("Title") to match the sheet name (or position) of your title sheet
The code below uses a variant array rather than a range for the sheet name for performance reasons, although turning off ScreenUpdating is likely to make the biggest difference to the user
Sub SheetAdd()
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim strError As String
Dim vArr()
Dim lngCnt As Long
Dim lngCalc As Long
Set ws1 = Sheets("Title")
vArr = ws1.Range(ws1.[r5], ws1.[r5].End(xltoRight))
If UBound(vArr) = Rows.Count - 5 Then
MsgBox "sheet range for titles appears to be empty"
Exit Sub
End If
With Application
.ScreenUpdating = False
.EnableEvents = False
lngCalc = .Calculation
End With
For lngCnt = 1 To UBound(vArr)
Set ws2 = Sheets.Add
On Error Resume Next
ws2.Name = vArr(lngCnt, 1)
If Err.Number <> 0 Then strError = strError & vArr(lngCnt, 1) & vbNewLine
On Error GoTo 0
Next lngCnt
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = lngCalc
End With
If Len(strError) > 0 Then MsgBox strError, vbCritical, "These potential sheet names were invalid"
End Sub
This is probably the simplest. No error-handling, just a one-time code to create sheets
Sub test()
Workbooks("Book1").Sheets("Sheet1").Range("A1").Activate
Do Until IsEmpty(ActiveCell)
Sheets.Add.Name = ActiveCell.Value
Workbooks("Book1").Sheets("Sheet1").Select
ActiveCell.Offset(0, 1).Select
Loop
End Sub