I have an excel file in which I have several tables with the field "Last change:". If any change is made to that table, the field should update to the current date.
I implemented a macro that did what it should, but unfortunately it disabled the "Reverse" (Strg + Z) function which was very annoying. Therefore I edited the macro such that the Reverse-functionality would work again. My macro now looks like this:
Option Explicit
Public Merker
Private Sub Worksheet_Change(ByVal Target As Range)
If Not ThisWorkbook.ReadOnly Then
Application.OnUndo "Rev. Change", "Wiederherstellen"
Merker = Cells(3, 2)
Cells(3, 2) = Date
End If
End Sub
Sub Wiederherstellen()
Cells(3, 2) = Merker
End Sub
When I apply this macro, Excel crashes as soon as I make a change to the document.
Is there something invalid in my code? I am really wondering since there is no error message or wrong behavior but only the application crashing. This crash happens reliably every time.
Thanks for any help!
You event is likely to be calling itself in an infinite loop. Try disabling Events to stop the code re-calling itself on this line Cells(3, 2) = Date
Private Sub Worksheet_Change(ByVal Target As Range)
If Not ThisWorkbook.ReadOnly Then
Application.EnableEvents = False
Application.OnUndo "Rev. Change", "Wiederherstellen"
Merker = Cells(3, 2)
Cells(3, 2) = Date
Application.EnableEvents = True
End If
End Sub
Related
I have a sheet with a lot of code that I have worked on throughout the last couple of years but I am new to VBA and did not incorporate any error handling. I tried a few things but I could never make it work. It did not really affect me until recently. Most of my routines are driven by a cell that contains a sales price (cell D5). I am using Worksheet_Change ByVal target as range to change things when different cells are changed so when the sales price is changed it starts running routines.
What I have noticed is that this particular routine is the first one that gives me a VBA error if I delete the sales price in cell D5. So I thought I can ask someone to give me a simple code to catch the empty cell before the routine fires and maybe a popup saying "Sales Price cannot be blank" and perhaps revert to the previous value of that cell. The debug error takes me to the line of code that starts with "If Sheets("Main").Range("D6").Value < 0.8001"
Sub Calc_MI()
If Sheets("Main").Range("D12").Value = "FHA" Then
Sheets("Main").Range("D16").Value = 0.85
Else
If Sheets("Main").Range("D6").Value < 0.8001 Or
Sheets("Main").Range("D12").Value = "VA" Then
Sheets("Main").Range("D16").Value = ""
Else
If Sheets("Main").Range("G14").Value > 0.45 Then
Sheets("Main").Range("D16").Value = (Sheets("Closing
Costs").Range("BP100").Value + Sheets("Closing
Costs").Range("BP101").Value + Sheets("Closing
Costs").Range("BP102").Value)
Else
Sheets("Main").Range("D16").Value = (Sheets("Closing
Costs").Range("BP100").Value + Sheets("Closing
Costs").Range("BP102").Value)
End If
End If
End If
End Sub
any help would be greatly appreciated :-)
you could place this in "Main" sheet code pane
Option Explicit
Dim oldVal As Variant
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$D$5" Then
If IsEmpty(Target) Then
MsgBox "Sales Price cannot be blank"
Application.EnableEvents = False ' disable events to prevent change event fire in an infinite loop
Target.Value = oldVal ' restore old backup value
Application.EnableEvents = True ' enable events back
End If
End If
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Address = "$D$5" Then oldVal = Target.Value ' if D5 selected then backup its value
End Sub
I have an Excel sheet in which I am accepting value from the user when user enter a value a VBA will run which check the data is valid or not and if the data is not valid it will prompt a message saying invalid data. Here is my code:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$D$12" Or Target.Address = "$D$13" Or Target.Address = "$D$14" Or Target.Address = "$D$15" Then
Call Room
End If
End Sub
Room method is
Sub Room()
Dim lastRow As Long
If IsNumeric(Range("i17")) Then
If [I17] < 0 Then
MsgBox "msg "
End If
End If
End Sub
In I17 cell I have a formula
=C6-(D12+(2*D13) + (2*D14) + (3*D15))
My problem is when wrong data is enter in any of the cells (D12, D13, D14, D15) then the cell should be clear automatically after showing prompt message.
How can this be done?
The first thing that you should do is clean up how you check what Target is. It could be multiple cells (Fill Down, paste a range, ...). This is accomplished by intersecting Target with the range you are interested in, and We'll store into a range variable, for later. If there is no overlap, then intersect will return an empty object, which we can test for with is Nothing.
The next thing to note is that odd things (infinite recursion) can happen if we allow the Worksheet_Change event to fire by changing a cell. To prevent this, we will turn off events before calling Room, and turn it back on after we're done.
Next we pass the range that has changed into room, so we can modify it from within that subroutine.
And, finally we modify the affected range after displaying the message. Note that I have used a command to literally clear the cell. Since you are performing calculations based on that data, you might prefer to set it to default value, like 0, using a.value = 0 instead.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim a As Range
Set a = Intersect(Target, Range("D12:D15"))
If Not a Is Nothing Then
Application.EnableEvents = False
Room a
Application.EnableEvents = True
End If
End Sub
Sub Room(a As Range)
Dim lastRow As Long
If IsNumeric(Range("I17")) Then
If Range("I17").Value < 0 Then
MsgBox "msg "
a.ClearContents
End If
End If
End Sub
As a side note, I have a used a bad variable name a, since I don't know what that range represents. You should pick something that describes to future maintainers what is going on.
use this
Private Sub Worksheet_Change(ByVal Target As Range)
Dim t As Range
Set t = Intersect(Target, [D12:D15])
Application.EnableEvents = 0
If Not t Is Nothing Then
Call Room
If [I17] < 0 Then Target.Value = ""
End If
Application.EnableEvents = 1
End Sub
I would like my Macro to launch whenever a value in a cell containing a formula changes.
i.e. the user is modifying another cell thus changing the value of the cell in question.
I have noticed that using the statement (found herein), only works if the user modifies the cell itself but not if the cell changes automatically - due to a formula as specified above.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A20")) Is Nothing Then ...
Any thoughts??
I tried to follow the answers from this question "automatically execute an Excel macro on a cell change" but it did not work...
Thanks in advance :)
A possible work-around comes from the fact that, to change a value, the user needs to change the selection first. So I would:
1) Declare a global variable called "oldValue" on top of the WS source code module:
Dim oldValue As Variant
2) Register the old value of your formula before the user types anything (let's say it's in Range("A4"), I let you adapt with the others):
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
oldValue = Range("A4")
End Sub
3) Check if the change has affected the formula in the Change event:
Private Sub Worksheet_Change(ByVal Target As Range)
If Range("A4") <> oldValue Then
MsgBox "User action has affected your formula"
End If
End Sub
I've tested with a simple sum, I'm able to write cells that are not involved without any prompt but if I touch one of the cells involved in the sum the MsgBox will show up. I let you adapt for multiple cases, for user adding/removing rows (in that case I suggest to name the ranges containing the formulas you want to track) and the worksheet references.
EDIT I'd like to do it at once, not by going through 2 processes, is it possible? The problem is my macro involves a range containing more than one cell so it will be hard to store old values for 10 cells.
If ranges are next to each other, then instead of using a variable you can use a collection:
Dim oldValues As New Collection
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
For j = 1 To 10
oldValues.Add Range("A" & j).Value
Next j
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
For j = 1 To 10
If Range("A" & j).Value <> oldValues(j) Then
MsgBox "The value of Range(A" & j & ") has changed"
End If
Next j
End Sub
Of course, if ranges are not close to each other, you can just store them anyway in the SelectionChange event like this:
oldValues.Add Range("A1").Value
oldValues.Add Range("B7").Value
'...
and if you done this ONCE, with 10 ranges only, it should be a reasonable solution to your problem.
You said, "I would like my Macro to launch whenever a value in a cell containing a formula changes..."
If having your code run whenever a cell containing a formula is recalculated (which is not exactly what you asked for), one solution might be to create a VBA function that simply returns that value passed to it, plus does whatever else you want to do when the formula is recalculated...
Public Function Hook(ByVal vValue As Variant) As Variant
Hook = vValue
' Add your code here...
End Function
...then "wrap" your formula in a call to this function. For example, if the formula you are interested in is =A1+1, you would change this to =Hook(A1+1), and the Hook function would be called whenever A1+1 is recalculated (for example, when the value in A1 changes). However, it is possible that recalculating A1+1 will yield the same result and still call the Hook function (for example, if the user re-enters the same value in A1).
You can have a go at this:
First, in a Module Code declare a Public Variable.
Public r As Range, myVal '<~~ Place it in Module
Second, initialize your variables in Workbook_Open event.
Private Sub Workbook_Open()
Set r = Sheet1.Range("C2:C3") '<~~ Change to your actual sheet and range
myVal = Application.Transpose(r)
End Sub
Finally, set up your Worksheet_Calculate event.
Private Sub Worksheet_Calculate()
On Error GoTo halt
With Application
.EnableEvents = False
If Join(myVal) <> Join(.Transpose(r)) Then
MsgBox "Something changed in your range"
'~~> You put your cool stuff here
End If
myVal = .Transpose(r)
forward:
.EnableEvents = True
End With
Exit Sub
halt:
MsgBox "Error " & Err.Number & ": " & Err.Description
Resume forward
End Sub
Above will trigger the event when values in C2:C3 changes.
Not really very neat but works in detecting changes in your target range. HTH.
Declaring a module -level variable like Matteo describes is definitely one good way to go.
Brian 's answer is on the right track with regards to keeping all is the code in the same place, but it's missing one critical part : Application.Caller
When used in function that is called by a single cell, Application.Caller will return the Range object of that cell. This way you can store the old value within the function itself when it is called, then once you're done with calculating the new value you can compare it with the old and run more code as required.
Edit: The advantage with Application.Caller is that the solution scales in and of itself, and does not change no matter how the target cells are arranged (I.e. Continuous or not).
One of my spreadsheets deals with various calculations involving, among other things, the current date and time and it would be nice to have it automatically refresh itself once in a while instead of manually having to either press F9 or altering one of the cells.
Is there some way in Excel to set a spreadsheet to automatically recalculate itself every x seconds?
I haven't been able to find a setting in Excel itself, perhaps indicating that no such feature exists. If not, can this be achieved with VBA? (The latter may or may not sound like a silly question, but I have no prior experience with writing Excel macros and as such have no idea what its capabilities are in terms of manipulating spreadsheets.)
This code will create a clock, updated every 10 seconds.
Note that it only refreshes specific cells, and not the entire workbook - this means that you can leave the calculation options at whatever you are happy with:
Dim SchedRecalc As Date
Sub Recalc()
'Change specific cells
Range("A1").Value = Format(Now, "dd-mmm-yy")
Range("A2").Value = Format(Time, "hh:mm:ss AM/PM")
'or use the following line if you have a cell you wish to update
Range("A3").Calculate
Call StartTime ' need to keep calling the timer, as the ontime only runs once
End Sub
Sub StartTime()
SchedRecalc = Now + TimeValue("00:00:10")
Application.OnTime SchedRecalc, "Recalc"
End Sub
Sub EndTime()
On Error Resume Next
Application.OnTime EarliestTime:=SchedRecalc, _
Procedure:="Recalc", Schedule:=False
End Sub
and, to make sure it stops, in the This Workbook module:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
EndTime
End Sub
Goto Developer Visual basic Editor - Right Click workbook - insert module (make sure you have manual calculation
in the module
Sub run_over
Timetorun = Now + timevalue("00:00:10")
application.ontime timetorun,"Refresh_all"
End Sub
Sub Refresh_all
Activeworkbook.Refreshall
End Sub
Sub auto_close()
Application.OnTime timetorun, Refresh_all, , False
End Sub
Change the timing in "00:00:00" format as required
I am building a template/form in excel that will be used by different people on different computers to fill in some information and then send it by email to me.
When the template is being filled I need to assign an unique ID number to a field along with other info(kind of like a request ID). I am generating this unique ID by using
CONCATENATE("NER-";DEC2HEX(RANDBETWEEN(0;4294967295);8))
This formula serves me good for the task at hand.
My challenge is to evaluate this formula only one time in the template and then keep it the same when I open the file once it gets to me. Something along the lines of a time stamp. I have already looked into some methods but I cannot seem to get it to work.
I have tried making use of:
Private Sub Worksheet_Change(ByVal Target As Excel.Range)
With Target
If .Count > 1 Then Exit Sub
If Not Intersect(Range("A2:A10"), .Cells) Is Nothing Then
Application.EnableEvents = False
If IsEmpty(.Value) Then
.Offset(0, 1).ClearContents
Else
With .Offset(0, 1)
.NumberFormat = "dd mmm yyyy hh:mm:ss"
.Value = Now
End With
End If
Application.EnableEvents = True
End If
End With
End Sub
But I do not know how to integrate my concatenate function into the code. I am also not extremely sure if this will keep my unique value untouched when I open the template on my computer.
I would guess that a method that would limit my iterations in the entire sheet would also serve me good.
You could generate and store the ID right when the user first opens the workbook/template, placing this code in the ' ThisWorbook module:
Private Sub Workbook_Open()
'ID already set?
If Sheet1.Range("A2").Value <> "" Then Exit Sub
'Prevent that ID is generated on your machine
If Environ$("Username") = "YOURUSERNAME" Then Exit Sub
'Store ID
Sheet1.Range("A2").Value = _
"NER-" & [DEC2HEX(RANDBETWEEN(0,4294967295),8)]
End Sub