The macro that follows works alright if I put it in sheet1. However I would like to put this macro in sheet2. Unfortunately it doesn't make the job from sheet2, only from Sheet1. Can you please help me to make it run from Sheet2?
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Dim sRes As Variant
On Error GoTo haveError
Set rng = Application.Intersect(Sheet1.Range("I15:I18"), Target)
If Not rng Is Nothing Then
If rng.Cells.count = 1 Then
sRes = Application.VLookup(rng.Value, _
Sheet2.Range("A56:B58"), 2, True)
'turn off events before updating the worksheet
Application.EnableEvents = False
rng.Offset(0, 1).Value = IIf(IsError(sRes), "???", sRes)
Select Case rng.Offset(0, 1).Value
Case "Low Risk": rng.Offset(0, 2).Value = Date + 180
Case "Medium Risk": rng.Offset(0, 2).Value = Date + 150
Case "High Risk": rng.Offset(0, 2).Value = Date + 120
End Select
Application.EnableEvents = True
End If '<< edit added missing line here
End If
Exit Sub
haveError:
Application.EnableEvents = True '<< ensures events are reset
End Sub
The answer to this question it is simpler than I thought:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Dim rng1 As Range
Dim sRes As Variant
Dim sRes1 As Variant
On Error GoTo haveError
Set rng = Application.Intersect(Sheet1.Range("I15:I18"), Target)
Set rng1 = Application.Intersect(Sheet1.Range("I20:I23"), Target)
If Not rng Is Nothing Then
If rng.Cells.count = 1 Then
sRes = Application.VLookup(rng.Value, _
Sheet2.Range("A56:B58"), 2, True) 'Waterlow
'turn off events before updating the worksheet
Application.EnableEvents = False
rng.Offset(0, 1).Value = IIf(IsError(sRes), "???", sRes)
Select Case rng.Offset(0, 1).Value
Case "Low Risk": rng.Offset(0, 2).Value = Date + 180
Case "Medium Risk": rng.Offset(0, 2).Value = Date + 150
Case "High Risk": rng.Offset(0, 2).Value = Date + 120
End Select
Application.EnableEvents = True
End If '<< edit added missing line here
ElseIf Not rng1 Is Nothing Then
If rng1.Cells.count = 1 Then
sRes1 = Application.VLookup(rng1.Value, _
Sheet3.Range("A28:B30"), 2, True) 'MUST
'turn off events before updating the worksheet
Application.EnableEvents = False
rng1.Offset(0, 1).Value = IIf(IsError(sRes1), "???", sRes1)
Select Case rng1.Offset(0, 1).Value
Case "Low Risk": rng1.Offset(0, 2).Value = Date + 180
Case "Medium Risk": rng1.Offset(0, 2).Value = Date + 150
Case "High Risk": rng1.Offset(0, 2).Value = Date + 120
End Select
Application.EnableEvents = True
End If '<< edit added missing line here
End If
Exit Sub
haveError:
Application.EnableEvents = True '<< ensures events are reset
End Sub
It is only a matter to combine two procedures. A simple Elseif make it run (work :)) the macro.
Thanks to all of you for trying to help me.
Related
So currently I am trying to come up with a if statement. Basically if A3 has any text value I want it to equal awesome. I want to loop this command with the last column in mind.
Sub Criteria
If Range("A2") = "Feedback" And Range("A3") = "**" Then
Range("A1") = "Awesome"
Else
Range("A1") = ""
End If
End sub
(This is the code I came up with can someone help me make it cleaner/faster)
Sub Status()
lastrow = Rows(Rows.Count).End(xlUp).Row
For i = 2 To lastrow
If Cells(i, 1) = "Onsite" And Not IsEmpty(Cells(i, 2)) Then
Cells(i, 3) = "Feedback"
Else
If Cells(i, 1) = "Phone" And Not IsEmpty(Cells(i, 2)) Then
Cells(i, 3) = "Feedback"
Else
If Cells(i, 1) = "Phone" And IsEmpty(Cells(i, 2)) Then
Cells(i, 3) = "Pending Next Step"
Else
If Cells(i, 1) = "Onsite" And IsEmpty(Cells(i, 2)) Then
Cells(i, 3) = "Pending Decision"
End If
End If
End If
End If
Next i
End Sub
Try using Option Explicit also set your worksheet so your not running the code on wrong sheet or to avoid a error
Option Explicit
Public Sub Status()
Dim Sht As Worksheet
Dim rng As Range
Set Sht = ThisWorkbook.Sheets("Sheet1")
For Each rng In Sht.Range("A2", Sht.Range("A9999").End(xlUp))
Debug.Print rng.Address ' print on immed win
DoEvents ' For Debuging
If rng.Value = "Onsite" And rng.Offset(0, 1).Value > 0 Then
rng.Offset(0, 2).Value = "Feedback"
ElseIf rng.Value = "Onsite" And rng.Offset(0, 1).Value = "" Then
rng.Offset(0, 2).Value = "Pending Decision"
End If
If rng.Value = "Phone" And rng.Offset(0, 1).Value > 0 Then
rng.Offset(0, 2).Value = "Feedback"
ElseIf rng.Value = "Phone" And rng.Offset(0, 1).Value = "" Then
rng.Offset(0, 2).Value = "Pending Next Step"
End If
Next
Set Sht = Nothing
Set rng = Nothing
End Sub
Range.Offset Property (Excel)
Syntax: expression.Offset(RowOffset, ColumnOffset)
Returns a Range object that represents a range that?s offset from the specified range.
I am trying to run a macro that will fill in the date and time when something has been updated, but I need to have it happen twice in the same row.
I have it set up to fill in column B+C when initials are entered in column A, but I would like to to run when someone enters another value in column N
Private Sub Worksheet_Change(ByVal Target As Range)
Dim A As Range, B As Range, Inte As Range, r As Range
Set A = Range("A:A")
Set Inte = Intersect(A, Target)
If Inte Is Nothing Then Exit Sub
Application.EnableEvents = False
For Each r In Inte
If r.Value > 0 Then
r.Offset(0, 1).Value = Date
r.Offset(0, 1).NumberFormat = "mm-dd-yy"
r.Offset(0, 2).Value = Time
r.Offset(0, 2).NumberFormat = "hh:mm AM/PM"
Else
r.Offset(0, 1).Value = ""
r.Offset(0, 2).Value = ""
End If
Next r
End Sub
You just need to test what the Address/location of the Target range is:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Select Case Target.Column
Case 1 ' "A"
Dim A As Range, B As Range, Inte As Range, r As Range
Set A = Range("A:A")
Set Inte = Intersect(A, Target)
If Inte Is Nothing Then Exit Sub
For Each r In Inte
If r.Value > 0 Then
r.Offset(0, 1).Value = Date
r.Offset(0, 1).NumberFormat = "mm-dd-yy"
r.Offset(0, 2).Value = Time
r.Offset(0, 2).NumberFormat = "hh:mm AM/PM"
Else
r.Offset(0, 1).Value = ""
r.Offset(0, 2).Value = ""
End If
Next r
Case 14 ' "N"
' Do something else
End Select
Application.EnableEvents = True ' <-- Don't forget to turn this back on!
End Sub
Further, since such an approach can get unwieldy, it's often a good idea to break down your methods. (Smaller methods are almost always better.)
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Select Case Target.Column
Case 1, ' "A"
Call AddDatesAfterInitialsEntered(Target)
Case 14 ' "N"
' Do something else
End Select
Application.EnableEvents = True ' <-- Don't forget to turn this back on!
End Sub
Private Sub AddDatesAfterInitialsEntered(Target As Range)
Dim A As Range, B As Range, Inte As Range, r As Range
Set A = Range("A:A")
Set Inte = Intersect(A, Target)
If Inte Is Nothing Then Exit Sub
For Each r In Inte
If r.Value > 0 Then
r.Offset(0, 1).Value = Date
r.Offset(0, 1).NumberFormat = "mm-dd-yy"
r.Offset(0, 2).Value = Time
r.Offset(0, 2).NumberFormat = "hh:mm AM/PM"
Else
r.Offset(0, 1).Value = ""
r.Offset(0, 2).Value = ""
End If
Next r
End Sub
I'm very new to Excel VBA (started about a day ago!) but I'm slowly struggling through. I've created a formula that copies a selection of three cells to another part of the sheet if column D contains the value "(2)", then assigns the value "0" to some more cells in the same row.
The trouble is, I've used a mixture of recording and typing my macro so the end result is pretty messy. Currently the macro takes a while to complete (it moves everything around and then a little hourglass appears for a good 15 seconds or so). I'm assuming this is in part due to my use of "Select" (I'm aware this is a bad thing!) but I'm just trying to work out what I can strip from the formula to make it more efficient while retaining the same outcome.
Sub MoveNames()
Dim SrchRng As Range, cel As Range
Set SrchRng = Range("D:D")
For Each cel In SrchRng
If InStr(1, cel.Value, "(2)") > 0 Then
cel.Offset(0, 1).Range("A1:C1").Select
Selection.Copy
ActiveCell.Offset(-1, 40).Range("A1").Select
ActiveSheet.Paste
Application.CutCopyMode = False
ActiveCell.Offset(0, -4) = "0"
ActiveCell.Offset(0, -5) = "0"
ActiveCell.Offset(0, -6) = "0"
ActiveCell.Offset(0, -7) = "0"
ActiveCell.Offset(0, -10) = "0"
ActiveCell.Offset(0, -12) = "0"
End If
Next cel
End Sub
Any help would be much appreciated.
If I understand what you're trying to do, this should do the same thing without having to use any objects or any copy/paste methods:
Sub MM_MoveNames()
For i = 2 To Cells(Rows.count, 4).End(xlUp).Row
If InStr(Cells(i, 4).value, "(2)") Then
Cells(i - 1, 44).Resize(1, 3).value = Cells(i, 5).Resize(1, 3).value
Cells(i, 37).Resize(1, 4).value = 0
Cells(i, 34).value = 0
Cells(i, 32).value = 0
End If
Next
End Sub
More importantly though - if your code is working, and you just want advice for improvements then you should post your code on Code Review, not on Stack Overflow.
try this
Sub MoveNames()
Dim SrchRng As Range
lastrow = Range("D" & Rows.Count).End(xlUp).Row
Set SrchRng = Range("D1:D" & lastrow)
For Each cel In SrchRng
If InStr(1, cel.Value, "(2)") > 0 Then
With cel.Offset(0, 1).Range("A1:C1")
.Copy cel.Offset(-1, 40).Range("A1")
End With
With cel.Offset(-1, 40)
.Offset(0, -4) = "0"
.Offset(0, -5) = "0"
.Offset(0, -6) = "0"
.Offset(0, -7) = "0"
.Offset(0, -10) = "0"
.Offset(0, -12) = "0"
End With
End If
Next cel
End Sub
Give this a shot, you can definitely clean it up more by combining the multiple offsets and ranges.
Sub test()
Dim rngIndex As Range
For Each rngIndex In Range("D:D")
If InStr(1, rngIndex.Value, "(2)") > 0 Then
rngIndex.Offset(0, 1).Range("A1:C1").Copy _
rngIndex.Offset(0, 1).Range("A1:C1").Offset(-1, 40).Range("A1")
With rngIndex.Offset(0, 1).Range("A1:C1")
Range(.Offset(0, -4), .Offset(0, -7)).Value = 0
.Offset(0, -10) = "0"
.Offset(0, -12) = "0"
End With
End If
Next rngIndex
End Sub
Instead of going throug each cell in column D, you can go through just the used range, like this:
Set SrchRng = Range("D1:D" & ActiveSheet.UsedRange.Rows.Count)
Which should speed it up quite a bit.
You can use Select, I found that easier when I was learning VBA myself. In time you will learn to avoid it.
To speed up macro execution when using Select, you can add Application.ScreenUpdating = False at the beginning and Application.ScreenUpdating = True at the end of your procedure.
Disabling automatic calculations is also beneficial, you can do it by adding Application.Calculation = xlManual and Application.Calculation = xlManual at the beginning and end respectively.
Hope that helps. if you have more questions, just ask.
My turn - instead of looking at each cell, just jump to the ones containing (2).
Sub MoveNames()
Dim SrchRng As Range, cel As Range
Dim rFound As Range
Dim sFirstAddress As String
Set SrchRng = ThisWorkbook.Worksheets("Sheet1").Range("D:D")
Set rFound = SrchRng.Find("(2)", LookIn:=xlValues, LookAt:=xlPart, SearchDirection:=xlNext)
If Not rFound Is Nothing Then
sFirstAddress = rFound.Address
Do
rFound.Offset(, 1).Resize(, 3).Copy Destination:=rFound.Offset(-1, 41)
rFound.Offset(-1, 34).Resize(, 4) = 0
rFound.Offset(-1, 29) = 0
rFound.Offset(-1, 31) = 0
Set rFound = SrchRng.FindNext(rFound)
Loop While Not rFound Is Nothing And rFound.Address <> sFirstAddress
End If
End Sub
I am creating an Excel spreadsheet. I have 2 separate functions that I need to combine but I am not sure how to smash them together. I know I can only have 1 change event. The first function will unprotect the sheet (column c is locked), auto populate column C when data is entered in to column A or erase C when A is erased and re-protect when complete. The second will return the cell focus to the next row, column A, when data is entered into A and B. Separately they work as needed.
Private Sub Worksheet_Change(ByVal Target As Range)
On Error Resume Next
Unprotect Password:="my password"
If Target.Column = 1 Then
Dim A As Range, B As Range, Inte As Range, r As Range
Set A = Range("A:A")
Set Inte = Intersect(A, Target)
If Target.Offset(0, 1 - Target.Column).Value = "" Then
Target.Offset(0, 3 - Target.Column).Clear
Exit Sub
End If
Application.EnableEvents = False
For Each r In Inte
r.Offset(0, 2).Value = Date & " " & Time
r.Offset(0, 2).NumberFormat = "m/d/yyyy h:mm am/pm"
Next r
Application.EnableEvents = True
End If
Protect Password:="my password"
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo Whoa
Application.EnableEvents = False
If Not Target.Cells.CountLarge > 1 Then
If Not Intersect(Target, Columns(1)) Is Nothing Then
Target.Offset(, 1).Select
ElseIf Not Intersect(Target, Columns(2)) Is Nothing Then
Target.Offset(1, -1).Select
End If
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
How about this, seems to do what you want, as I understand the question.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngIntersect As Range
Dim rngCell As Range
On Error GoTo TidyUp
Application.EnableEvents = False
If Target.Column = 1 Then
Set rngIntersect = Intersect(Range("A:A"), Target)
For Each rngCell In rngIntersect
If rngCell.Value = "" Then
rngCell.Offset(0, 2).Value = ""
Else
rngCell.Offset(0, 2).Value = Date & " " & Time
rngCell.Offset(0, 2).NumberFormat = "m/d/yyyy h:mm am/pm"
End If
Next rngCell
End If
If Target.Column < 3 And Target.Value <> "" Then ' lose the 'And Target.Value <> ""' as desired
Cells(Target.Row + Target.Rows.Count, 1).Select
End If
TidyUp:
Set rngIntersect = Nothing
Set rngCell = Nothing
Application.EnableEvents = True
End Sub
I'd also suggest using UserInterfaceOnly in your worksheet.Protect, then you don't have to unprotect the sheet for VBA to act on the sheet.
Implement it in two Sub-Procedures on a modul, then just call both of them in the Event-Procedure.
I have the following code to insert a new row based on the cell value. How can I modify this to also copy the value in column A for example) to the newly inserted row?
Private Sub Worksheet_Change(ByVal Target As Range)
Set Rng = Range("B11:B50")
If Target.Count = 1 Then
If Target.Value = "Annual" Then
If Not Intersect(Target, Rng) Is Nothing Then
Application.EnableEvents = False
Target(1).Offset(1, 0).EntireRow.Insert Shift:=xlDown
Application.EnableEvents = True
End If
End If
End If
End Sub
Cells(Target.Row + 1, 1).Value = Cells(Target.Row, 1).Value
To copy both A & B
Cells(Target.Row + 1, 1).Value = Cells(Target.Row, 1).Value
Cells(Target.Row + 1, 2).Value = Cells(Target.Row, 2).Value