When data is entered (user has hit "return") into any cell in Column B, I want to insert four rows directly below the row that just had data entered into it.
I want the program to run automatically after the user has hit return on the cell. I've been having three sticking points:
Finding a way for the program to run without the user having to hit a button. I've spent a fair amount of time searching for example code to use and have found several resources, but the two examples I've used haven't seemed to work. PDCA is the sheet name, Add_Row is the macro I've written to add rows below the user-inputted data.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, [B3:B14]) Is Nothing Then Sheets("PDCA Tracking").Add_Row
End Sub
Actually running the Add_Row program. I get an Error 1004 Application Defined or User Defined Error. My second question is, when the user hits return, the active cell wouldn't be the one s/he just entered data in then, would it? How would I mitigate that? It would be the last row of the spreadsheet, could I find the last row and then just add rows below that?:
Sub Add_Row'Insert row below active cell
ActiveCell.Offset(1).EntireRow.Insert
Cells(ActiveCell.Offset(1), 3).Value = "Zulu"
ActiveCell.Offset(2).EntireRow.Insert
Cells(ActiveCell.Offset(2), 3).Value = "Yankee"
ActiveCell.Offset(3).EntireRow.Insert
Cells(ActiveCell.Offset(3), 3).Value = "X-Ray"
ActiveCell.Offset(4).EntireRow.Insert
Cells(ActiveCell.Offset(4), 3).Value = "Whiskey"
'Call Merge_Cells
End Sub
After I enter the data, I want to take the cells below the row where the user just added the data and merge them. (ie if the user input "banana" and I added four rows below "banana", I want JUST the four new cells under banana to merge with the cell containing "banana". I know there's a .Merge command but again, not sure of the syntax to use.
Any and all help is very appreciated!
UPDATE: I figured out how to add data below the last filled in row, I believe.
Dim lastRow As Long
lastRow = Range("A" & Rows.Count).End(xlUp).Row
Cells(lastRow + 1, 3).Value = "Zulu"
Cells(lastRow + 2, 3).Value = "Yankee"
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo haveError
If Target.Cells.Count = 1 And _
Not Application.Intersect(Target, [B3:B14]) Is Nothing Then
Application.EnableEvents = False
With Target
.Offset(1, 0).Resize(4, 1).Insert Shift:=xlDown
.Resize(5, 1).Merge
.VerticalAlignment = xlTop
End With
Application.EnableEvents = True
End If
Exit Sub
haveError:
Application.EnableEvents = True
End Sub
Related
I am working on a spreadsheet that utilizes user input to fill out a table. This data is entered into a table with numerous columns required. I have a command button that creates a new row and inserts all the data validation necessary for a new line item (below is an example.)
My problem revolves on a specific a column (blue circle) where other cells rely on based on a "yes" or "no" string. If the column has a "yes", the adjacent cells do something. If the column has a "no", the adjacent cells do something different.
As this table grows in rows, my column range I am focused on will change dynamically. I want the VBA code to run if the worksheet experiences a change event in that dynamic range utilizing the "Worksheet_Change(ByVal Target As Range) sub.
Right now, I am defining that dynamic range in my commandbutton_click sub because everytime i click the button, a new row is added thus I need the spreadsheet to be aware that my range has changed (see my code below).
Sub CommandButton1_Click()
Application.EnableEvents = False
Dim LastRowEntry As Long
Dim DeviceNo As Integer
'Dim RTUTable As Range
'Determine the last entry row & Copy
LastRowEntry = Sheet1.Cells(Rows.Count, 1).End(xlUp).Row
DeviceNo = Sheet1.Cells(Rows.Count, 1).End(xlUp)
Sheet1.Cells(Rows.Count, 1).End(xlUp).EntireRow.Copy
'Once the last row is determined, go to the next row to paste
Sheet1.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormats
Sheet1.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValidation
'Incase the above cell has Conditional Formatting, we set the color back to "white"
Sheet1.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).EntireRow.Interior.ColorIndex = 0
Application.CutCopyMode = False
ActiveCell.Value = DeviceNo + 1
With RTUTable
RTUTable = Sheet1.Range(("G7"), Sheet1.Cells(LastRowEntry + 1, "G"))
End With
Application.EnableEvents = True
End Sub
Then, in a different sub I enter code that will "check" to see if that dynamic range has had a change in value with a "yes" or "no" answer. This is where I enter my defined dynamic range (see my code below).
Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
'This code checks to see if the Device is polled by RTU
If Not Intersect(Target, Range(RTUTable)) Is Nothing Then
If ActiveCell.Value = "NO" Then
"DO SOMETHING"
Else
"DO SOMETHING"
End If
End If
Application.EnableEvents = True
End Sub
Can someone help me out on the error message "Type mismatch" I am receiving? Am I entering the dynamic range correctly into the intersect code?
Thank you
****Update - More information for Clarification*****
I have added some information in the array I am evaluating. Below is a snapshot of my spreadsheet.
When I go into the Debug mode, I am viewing my "Locals table" and see I am successfully capturing my information in an array.
I need to run the intersect command with this array
This has been resolved. Below is the code I used to determine the dynamic range and then the code evaluates that range for a change.
Dim dyString As String
Dim dyRange As Range
Dim LastRowEntry_1 As Integer
Dim i As Integer
LastRowEntry_1 = Sheet1.Cells(Rows.Count, 1).End(xlUp).Offset(-2, 0).row + 1
dyString = "G8:G" & LastRowEntry_1
Set dyRange = Range(dyString)
If Not Intersect(Target, dyRange) Is Nothing Then
For i = 1 To 6
If ActiveCell.Value = "NO" Then
Target.Offset(0, i).Interior.ColorIndex = 23
Target.Offset(0, i).Font.Color = vbWhite
Target.Offset(0, i).Value = "N/A"
Target.Offset(0, i).Locked = True
ElseIf ActiveCell.Value = "YES" And ActiveCell.Offset(0, i).Value = "N/A" Then
Target.Offset(0, i).Value = ""
Target.Offset(0, i).Interior.ColorIndex = 0
Target.Offset(0, i).Font.Color = vbBlack
Target.Offset(0, i).Locked = False
To summarize:
No matter what, my dynamic range will always begin at cell "G8"
After that, the range will change dynamically. Either grows or shortens depending on
the info. So my "LastRowEntry_1" determines where that last cell entry is. (Note: I
had to offset it because of some footer information on my spreadsheet)
I created a string where I could combine the range of the fixed cell to the dynamic
cell.
Then I set that string as a range.
Then based if the target range was intersected or not, I run a "For" loop to perform
my conditional formatting.
I'm attempting to create a worksheet macro that will populate specific cells with default values in the same row when a value is entered in the first column of the row and also copy an entered value from the same row into other cells in that row. For example, when the user enters some value in 2A, cells 2C and 2D automatically populate with the numbers 10 and 20 respectively. Then, when the user enters a value in 2S, that same value is automatically copied back to cells 2I and 2J.
Thanks for the additional info Ralph. Based off of what I've found through researching similar questions on stackoverflow and general internet searches, I put together the following:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim A As Range, S As Range, InteA As Range, InteS As Range, r As Range
Set A = Range("A:A")
Set S = Range("S:S")
Set InteA = Intersect(A, Target)
Set InteS = Intersect(S, Target)
Application.EnableEvents = False
If Not InteA Is Nothing Then
For Each r In InteA
r.Offset(0, 2).Value = "10"
r.Offset(0, 3).Value = "20"
Next r
ElseIf Not InteS Is Nothing Then
For Each r In InteS
r.Offset(0, -9).Value = Target
r.Offset(0, -10).Value = Target
r.Offset(0, -11).Value = Target
Next r
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
To get a macro to run, an event of some kind has to occur. Its tempting to try to run a macro whenever ANY change is made to the worksheet, but imagine how often that's going to trigger? All the time. Then you have to worry if 10 & 20 will start flying into those cells when you don't want them to and write some conditional code to skip the process if you aren't typing in column A...
So here's a different option you might prefer. Enter formulas in columns C and D that will result in 10 & 20 if data exists in A.
=IF(A2<>"",10,"") or =IF(ISNUMBER(A2),10,0) ...whatever you like.
Then select your header row and data row, convert to an real "Excel table" on the Insert menu. (Insert...Table) This will extend your formulas to new rows as you type into column A.
Macro averted?
I'm new to this forum but have been building up my coding experience in the last couple of months due to the VBA requirements of my current role. Today's problem has seen me trawling through many sites (and my Excel VBA for Dummies book), but I haven't quite nailed it.
I am trying to make an audit tracker file in Excel for our company Risk Register. The idea is that once the risk register is established, any changes will create an audit trail (on a separate tab) which shows both the old and the new record.
I have written the code using the Change Event handler. I want my macro to fire every time there is a change and do the following:
1. Make a reference of the old cell value (what the user has just overwritten)
2. Jump to the 'Audit trail' tab and paste two copies of the full risk record - each risk record is a row of data that occupies 17 columns
3. In the first copy of these 17 columns, work out which column was edited and replace this cell with the old cell value (captured in step 1)
4. Insert a time stamp
5. Have conditional formatting highlight the record that has changed [this function is not required in the code as I've set it up within the spreadsheet itself]
6. Jump back to cell where the user just made their edit (on the 'Risk Register' tab)
I have managed steps 1, 2 and 4-7 but I am having problems getting the code to input the "old cell value" into the right spot in the 'Audit Tracker' tab. I can get it there if I manually define the cell range for it to paste into, but I can't seem to make it dynamic so that it will automatically recognize what field the user is changing and ensure the same field is amended in the audit trail.
Would really appreciate any insights as to why the "PasteRange.Value = Worksheets("Risk Register").Range("oldValuePaste")" line isn't working
My code is as follows:
Dim oldValue As Variant
Dim LastRow As Long
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Application.ScreenUpdating = False
If Not Intersect(Target, Range("b13:r13")) Is Nothing Then
oldValue = Target.Value
End If
Application.ScreenUpdating = True
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Application.ScreenUpdating = False
If Not Intersect(Target, Range("b13:r14")) Is Nothing Then
If Target.Value <> oldValue Then
'MsgBox "You just changed " & Target.Address
Cells(65, 5).Value = oldValue 'this cell is a named range called: OldValuePaste
Cells(66, 5).Value = Target.row 'this cell is a named range called: OldValueRowNumber
Cells(67, 5).Value = Target.Column 'this cell is a named range called: OldValueColumnNumber
Range(Cells(Target.row, 2), Cells(Target.row, 18)).Copy
'Cells(70, 2).PasteSpecial xlPasteValues
Call Paste_on_AuditSheet
Sheets("Risk Register").Activate
Target.Select
Application.CutCopyMode = False
End If
End If
Application.ScreenUpdating = True
End Sub
_____________________________________________________________________________________________________
Sub Paste_on_AuditSheet()
Application.ScreenUpdating = False
Dim LastRow As Long
Dim ColNum As Long
Dim PasteRange As Range
ColNum = OldValueColumnNumber
Sheets("Audit trail").Select
'MsgBox "Activated " & ActiveSheet.Name
'Find the last used row in a Column: column B in this example
With ActiveSheet
LastRow = .Cells(.Rows.Count, "B").End(xlUp).row
End With
Set PasteRange = Cells(LastRow, ColNum)
'The following two lines bring in the new data and paste into old record and new record sections:
Cells(LastRow + 1, 2).PasteSpecial xlPasteValues
Cells(LastRow + 1, 20).PasteSpecial xlPasteValues
'Then this line goes back over the piece just pasted in and changes one cell in "old record" section to what it was prior to the edit:
'PasteRange.Value = Worksheets("Risk Register").Range("oldValuePaste")
'Above line of code is not working, but can get it to do the right thing using this code (although it's not dynamic):
Range("E3").Value = Worksheets("Risk Register").Range("oldValuePaste")
'Add a time stamp:
Cells(LastRow + 1, 1) = Now
Application.ScreenUpdating = True
End Sub
One last point - despite my repeated use of Application.ScreenUpdating commands, I still get some screen flashing - any ideas why?
Thanks in advance for the help!
In reviewing your code, I saw a few things that I didn't think would work as you supposed they would, and also recognized that your code could be made much simpler and just be called from the Worksheet_Change event.
So the refactored code below and let me know if you have issues:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("b13:r14")) Is Nothing Then
'get oldValue
Dim sNewVal As String, sOldVal As String
sNewValue = Target.Value 'store current or "new" value since this is what is stored after the cell change takes place
With Application
.EnableEvents = False 'turns off event firing so the code will not go into endless loop
.Undo 'undo the change (to store old value in next line)
End With
sOldValue = Target.Value 'store old value
Target.Value = sNewValue 'reset new value
Dim lCol As Long
lCol = Target.Column 'which column of data was changed
'assumes columns A-Q are 17 columns
Me.Range(Me.Cells(Target.Row, 1), Me.Cells(Target.Row, 17)).Copy
With Sheets("Audit Trail")
Dim lRow As Long
lRow = .Range("B" & .Rows.Count).End(xlUp).Offset(1).Row
.Range("B" & lRow).PasteSpecial xlPasteValues
.Range("B" & lRow + 1).PasteSpecial xlPasteValues
.Range("A" & lRow).Value = Now
.Cells(lRow, lCol + 1).Value = sOldValue 'store old value in first pasted line ... add 1 since starting from column B
End With
End If
Application.EnableEvents = True
End Sub
I am quite new in excel vba and I would really appreciate if you can assist me.
The thing is that I have cell which updates each minute because it is linked with a function to Blomberg. The thing is that I want that each time cell updates excel copies it and pastes to another, new cell that i can observe the intra day changes.
I have come up with some codes but I can copy and paste only to one, similar cell.It looks like following:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
If Not Intersect(Target, Range("E4")) Is Nothing Then
Range("E4").Copy
Range("E4").PasteSpecial xlPasteValues
End If
End Sub
Any help would be highly appreciated.
If I understand your problem correctly you want to copy the value to a new cell, for logging purposes? What I would do in this case is have another sheet for logging the values named "logger_sheet" I paste a value in cell a1 when the blomberg cell updates, copy the value into my logger_sheet cell a2 when it changes copy it to a3 then a4 etc.
Here is your updated code. It assumes you have a sheet named "logger_sheet" (if you dont have one, create it) to store all the previous values. When the blomberg cell updates, it copies the value and pastes it to the next avaliable logging_sheet cell. I have developed a function that finds the last used row in a specified sheet and column. Try it out
Also there is a line you can uncomment if you want to prevent excel from flashing, I labeled it in the code
Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
target_cell = "E4"
col_to_log_data = "A"
logging_Sheet = "logger_sheet"
If Not Intersect(Target, Range("E4")) Is Nothing Then
'uncomment this line to stop the "flashing"
'Application.ScreenUpdating = False
'gets the name of the current sheet
data_sheet = Range(target_cell).Parent.Name
Range(target_cell).Select
Selection.Copy
'gets the next free row from column a of the logging sheet (the next free row is
'the last used row + 1)
next_free_row = GetLastRowByColumn(CStr(col_to_log_data), CStr(logging_Sheet)) + 1
'pastes the value
Sheets(logging_Sheet).Range(col_to_log_data & CStr(next_free_row)).PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
'switches back to the data sheet
Sheets(data_sheet).Select
'make sure you turn screen updating on (if it was never off it still works)
Application.ScreenUpdating = True
End If
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'this finds the last row in a specific column
'PARAMS: col_to_check, the clumn we want the last row of
' Opt: sheet_name, the sheet you want to check last row of
' default is current sheet if not specified
'RETURN: the last row number used in the sheet
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetLastRowByColumn(col_to_check As String, Optional sheet_name As String)
'gets current sheet name
the_current_sheet = ActiveSheet.Name
'if the user didnt' specify a sheet use the current one
If (Len(sheet_name) = 0) Then
sheet_name = the_current_sheet
End If
'gets last row
GetLastRowByColumn = Sheets(sheet_name).Range(col_to_check & "65536").End(xlUp).Row
'returns to original sheet
Sheets(the_current_sheet).Select
End Function
If my answer solves your problem please mark it as the solution
How about this? It will transfer E4 to Sheet2 in a new row each time E4 changes.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
If Target.Address = "$E$4" Then Sheets("Sheet2").Cells(Rows.Count, "F").End(xlUp).Offset(1) = Target
End Sub
I'm making the assumption you want to log every change of values.
I would advise to keep a log in a separate sheet. Let's call it LogSheet.
Sub WriteLog(ByRef r As range)
Dim Lastrow as integer
With ThisWorkBook.WorkSheets("LogSheet")
LastRow = .Cells(.Rows.Count,"A").End(XlUp).Row
.Range("A" & LastRow + 1).Value = Now & " - " & r.Value
End With
End Sub
This sub will basically write all changes in column A of our log sheet with a timestamp!
Now, we need to make changes to your code in order to tell, to make logs whenever there is a change. To do so, we're going to make a call to our function and tell to copy the content of the range("E4") (The one that gets updated all the time)
If Not Intersect(Target, Range("E4")) Is Nothing Then
'add this line
WriteLog(ActiveSheet.Range("E4"))
Try it now.
I have a train arrival/departure timetable, each branch line is on a separate row. I need to make the spreadsheet automatically detect change in cell and change all cells to the right from it for the same amount of time. Cells have format of time. How do I do that?
What I've tried so far:
I found this piece of code in some other question on SE:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Target.Worksheet.Range("H5")) Is Nothing Then Macro
End Sub
But I wasn't able to understand where to put it and how to make it work automatically, apart from working for a specific range, which might be different from what was in the question where I found it.
UPD: The logic I'm looking for in VBA:
Wait until a cell is selected, if it has Time format, copy its value to Tmp.
Save the difference between old and new values to Tmp.
If a cell to the right contains something and its format is Time, add Tmp to it.
Continue until cell is empty.
if you know how much difference is between times, then you can calculate the new time based on that difference.
e.g. A1 is 10:07, A2 is 10:14. Instead of having to type in each time individually, you could have A2 as =A1+TIME(0,7,0). Then when you changed A1 to 10:15, A2 would automatically change to 10:22
OK this may get you started in the right direction:
You first want to store all the original cell values. So, the following VBA code stores the values in column A for the first 200 rows into an array. You need to run this code first, perhaps when the workbook is opened:
Dim contents(200) As Variant
Public Sub StoreOriginalValues()
' save all existing values
For r = 1 To 200 ' change for number of rows you have
contents(r) = Worksheets(1).Cells(r, 1).Value
Next
End Sub
Once the original cell values are stored, you can place code in the Worksheet_Change event so that whenever the user changes a cell in column A you can compare the original and new value and figure out the difference. Then you can apply this difference to the rest of the columns in that row:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Then ' only check for changes in column A
originalvalue = contents(Target.Row)
newvalue = Target.Value
contents(Target.Row) = Target.Value
difference = newvalue - originalvalue
Set chgcell = Cells(Target.Row, Target.Column + 1)
Do While Not IsEmpty(chgcell)
chgcell.Value = chgcell.Value + difference
Set chgcell = chgcell.Offset(0, 1) ' move one column to right
Loop
End If
End Sub
Now this code is by no means perfect. It does not check that they entered a valid time, for instance. It also does not check to see if the values entered in the rest of the columns in the row are times or text or whatnot. But like I said I hope it will point you in the right direction.
My solution:
Dim oldVal
Dim diff
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
oldVal = Target.Value
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
ActiveCell.Offset(-1, 0).Select
Application.EnableEvents = True
diff = Target.Value - oldVal
If Not diff = 0 Then
While Not ActiveCell.Offset(0, 1) = "#"
Application.EnableEvents = False
ActiveCell.Offset(0, 1).Select
Application.EnableEvents = True
If Not ActiveCell Is Nothing _
And Not ActiveCell = "" _
And TypeName(ActiveCell) = TypeName(ActiveCell.Offset(0, -1)) Then
Application.EnableEvents = False
ActiveCell.Value = ActiveCell.Value + diff
Application.EnableEvents = True
End If
Wend
End If
End Sub
This has been my first experience with VB in a very long time, so the code is terrible, but it works.