Worksheet_Change determine value not content - vba

I am trying to detect if there are changes in a cell value, not particularly the cell contents. I have found multiple solutions to find out if a cell contents has changed, but it does not work when a cell is equal to another cell.
For example, I have cell A1 set to equal B1 and then B1 has a formula that calls in multiple other cells, so I am not able to go back to the beginning and determine whether the cell has changed from that. It needs to come directly from A1.
This is one of the examples I found on this site, but does not determine if the value of A1 has changed, just whether the contents has changed.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Then
Cells(Target.Row, 3).Value = Date
End If
End Sub

The function application.volatile TRUE at the top of your sub will make your sub calculate each time any value in Excel changes. So then you need a global variable which stores the last-known value of your specified range, and any time the sub runs, start with an
If new_cell_value <> stored_global_variable then...
and close with
stored_global_variable = new_cell value'
End If
See here for further info [h/t to vzczc for the original answer and method]: Refresh Excel VBA Function Results

Related

Run VBA code in Excel based on a cell value change by a function

Kindly I need help after trying for hours myself.
I have this code to run a code base on a cell value change, but works only if I type myself the change.
I need to be automatic with the function I have inside the cell, but I can get it right.
This is the code I have now that works well manually:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$A$1" Then
Application.EnableEvents = False
Range("D10").Select
Selection.ClearContents
Application.EnableEvents = True
End If
End Sub
Anything that I should add to make it automatic with the formula inside?
This is a tricky one since there is no built in worksheet event that triggers when a particular cell has a value change due to a formula. There is, however, a Worksheet.Calculate event that could be used.
First we make a global string variable. This is a variable that is declared outside of a subroutine or function. It will hold it's value between executions of subroutines and functions. So we will declare it, and then set it to the value of A1 when A1 is changed.
Dim lastValue As String
Private Sub Worksheet_Calculate()
If Range("A1").Value <> lastValue Then
lastValue = Range("A1").Value
Range("D10").ClearContents
End If
End Sub
lastValue here is our Global that will hold the last known value of A1. When worksheet.calculate is triggered it will compare that value against A1 to see if it's changed, if it is then it executes your code to clear the contents of D1
Instead of a Global you could also just stick that last known value of A1 into a Cell somewhere too. Then do you comparison to that. The advantage of using a cell over a global variable, is that the cell value will hold it's value even if you close and reopen the sheet. So, depending on your needs, it may be a better option:
Private Sub Worksheet_Calculate()
lastValue = Range("Z10").value
If Range("A1").Value <> lastValue Then
Range("Z10").value = Range("A1").Value
Range("D10").ClearContents
End If
End Sub
Where Range Z10 is the spot that holds the last known value.
Lastly I reduced your code to clear contents of D10 to a single line. Anytime you see Something.Select and then in the next line see Selection.DoSomething you can just get rid of that Select/Selection bit. Selecting a cell is generally something that only a human needs to do to interact with the sheet. VBA can do whatever it likes to cells without have to Select them.

Excel macro select two ranges and compare

This is a question that was asked to me in an interview. I have a excel list. It is copied to another location and then by mistake a row in the new location gets deleted.
Now I need to write a macro to compare the old and new ranges and then provide the missing data as result.
I can perhaps perform the comparison part. But the problem is I don't know how to get the selected range as input in a macro.
For eg. as soon as I select a range, it should be sent as input to the macro, then the macro should wait for another selection. As soon as I select the new range, the macro should compare and find the missing lines in new range.
Regarding the selection per mouse click you could look at the link I sent in the comments of the other answer. Selection_Change is an event which gets triggered when you change the selection of a worksheet (not only mouseclick but move-by-keys as well). The target coming in is the cell which you have selected. You can pass this as a range on to a function.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
showMsg Target
End Sub
Private Function showMsg(r As Range)
MsgBox r.Address
End Function
You can just as well use another event like BeforeDoubleClick or BeforeRightClick. Check out the events of Excel and choose the one you feel fits best.
If you only want the function to be triggered for a certain range you can filter it.
If target.column <> 1 then exit function
If you don't want the event to trigger your function each time you change a selection you can choose one cell to be the switch which gets triggered by the same event.
If target.address = "$A$1" Then Call toggleSearch()
with toggleSearch being the switching function.
This is a classical diff (and a simple one at that), you shouldn't select by hand or anything. Just sort the two lists in an identical way, then run a Sub which loops over the number of rows in the source sheet comparing each row with the same row in the target sheet. The first mismatch you get is the missing line.
This example assumes both sheets are in the same workbook but you can easily adapt it
Public Sub diffThem()
Dim src as Worksheet, trg as Worksheet
Dim r as Range, i as Integer
Set src = ThisWorkbook.Sheets("Source")
Set trg = ThisWorkbook.Sheets("Destination")
Set r = src.Range("A1")
For i = 1 to ThisWorkbook.Sheets("Source").UsedRange.Rows.Count
If r.EntireRow <> trg.Range("A" & r.Row).EntireRow Then
MsgBox("The missing row is " & r.Row)
Exit Sub
End if
Set r = r.Offset(1,0)
Next i
End Sub
If EntireRow cannot be run due to different layouts or whatever then loop the columns at that point.

Conditional cell disablement in Excel

I'm using Excel 2007. I want the following behaviour:
If cell A2 is empty or contains value "Create" then cell B2 is inactive and the user cannot enter a value.
If cell A2 contains the value "Modify" or "Retire" then cell B2 is active and the user can enter a value.
These behaviours need to be limited to their specific rows, so cell B3 needs to be unaffected by the value in cell A2. These behaviours need to be functional in rows 2 to 501.
I guess that this will require some VBA
The simplest solution (yet not fail proof) would be to create a data validation for the cell by going to data, data-validation, and custom. In the formula input, you could put in
=A2<>"Create"
if you dont want B2 to be editable if A2 contains "Create". Then you can highlight A2 and B2 and drag it down to row 501. thats the quickest solution that I've stumbled across. Hope this helps
2 ways you can persue here I think, either you protect the worksheet and lock those specific cells based on the conditions you stated, or you write a routine for the worksheet_change event.
The first is rather devious I think, so I would opt for the latter.
Something like this perhaps?
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 2 Then
If Target.Value = vbNullString Then Exit Sub 'to prevent endless loops
If Cells(Target.Row, 1) = "Create" Or Cells(Target.Row, 1) = vbNullString Then
Target.Value = vbNullString
End If
End If
End Sub
All this in the worksheet.
Some explanation:
This sub gets called by Excel whenever the worksheet is changed (hence the name). So if you type a value into any cell, the sub gets called when you leave the cell (hit enter or click another cell, etc).
The Target argument in the sub is the range that has just changed.
So if you type 33 in cell B1, Target will be Range("B1"). So Target.Column is the column of the cell that has just changed (in this case, 2), Target.Row is the row (1) and Target.Value hold the value you just entered (33). So Cells(Target.Row, 1) is (in this case) cell A1.
This sub will work for all the rows in the sheet, but I'm sure you can adapt it to check which row it is in.
EDIT: added some explanation

How do i insert a new blank cell before current cell that has just been populated

I have a two (very long) TO-DO lists- one going across and the other going down.
What i want to achieve is for a blank cell to appear at the start of the list instead of having to scroll to the end of the lists to enter a new item.
So then when i have entered an item in a cell and hit enter, i want the cell just populated to move down the list (or across if i hit tab) and a new empty cell to appear at the start of the list.
It would be useful for the new blank cell to be pre-populated with the current date but that is not essential.
Thanks for your help.
NOT FOR POINTS.
Piggy-backing on Gary's answer, the mistake is that you set A to Range("C4:C6"). What happens is, when you enter data into any of C4, C5, and C6, they are all moved to the right because of A.Insert, which refers to all the cells assigned to A.
The trick here is to fully qualify your requirements for Target. Let's say you have a table from B1:E3, like below:
Now, let's say you want to move row 1 if you enter something into A1, row 2 if A2, etc. The following macro should do it (notice the difference with Gary's macro):
Private Sub Worksheet_Change(ByVal Target As Range)
Dim QualifyingRange As Range
'Dim OrigRng As String
Set QualifyingRange = Range("A1:A3")
If Intersect(Target, QualifyingRange) Is Nothing Then Exit Sub
Application.EnableEvents = False
'OrigRng = Target.Address
Target.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
'Range(OrigRng).Value = Date
Application.EnableEvents = True
End Sub
What is the difference in the above? Very simple but very important. When a Worksheet_Change is in a sheet's code, every time you do a valid change to the sheet, the macro fires. The range you just edited will be known to the macro as Target. Now, usually, if you don't declare what the qualifications for Target are, the Worksheet_Change macro just fires indiscriminately. How do we qualify Target properly then?
We use Intersect. First, we declare a range of cells that we want to track. These cells, when changed, should fire the macro. Otherwise, macro is kaput. This line: If Intersect(Target, QualifyingRange) Is Nothing Then Exit Sub basically reads: If Target is not inside my desired range, then nothing happens.
This is the reason why I declared A1:A3 as my QualifyingRange. This way, if my change is to any of the cells above, the macro will fire. HOWEVER, .Insert should not be applied to the whole range but to Target alone. This is because if we do QualifyingRange.Insert, every time a change is detected in any cells in A1:A3, all three rows will move. This is what happened when you set A to three cells and kept A.Insert.
Hopefully, this clears up the confusion. Let us know if this helps.
Here is a partial solution. The following event macro monitors entry to cell A1 . Once you have entered a value in A1, the macro "pushed" the values in column A down by one. This means that value you just entered has been pushed down to A2 and A1 is empty:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim A As Range
Set A = Range("A1")
If Intersect(A, Target) Is Nothing Then Exit Sub
Application.EnableEvents = False
A.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
Application.EnableEvents = True
End Sub
Because it is worksheet code, it is very easy to install and automatic to use:
right-click the tab name near the bottom of the Excel window
select View Code - this brings up a VBE window
paste the stuff in and close the VBE window
If you have any concerns, first try it on a trial worksheet.
If you save the workbook, the macro will be saved with it.
If you are using a version of Excel later then 2003, you must save
the file as .xlsm rather than .xlsx
To remove the macro:
bring up the VBE windows as above
clear the code out
close the VBE window
To learn more about macros in general, see:
http://www.mvps.org/dmcritchie/excel/getstarted.htm
and
http://msdn.microsoft.com/en-us/library/ee814735(v=office.14).aspx
To learn more about Event Macros (worksheet code), see:
http://www.mvps.org/dmcritchie/excel/event.htm
Macros must be enabled for this to work!
EDIT#1
To push across rather than down:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim A As Range
Set A = Range("A1")
If Intersect(A, Target) Is Nothing Then Exit Sub
Application.EnableEvents = False
A.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Application.EnableEvents = True
End Sub
To handle multiple cells, you must specify which cells get pushed across and which cells get pushed down.

Excel, 2 sheets, 2 columns, same value?

I have 2 sheets sheet1 and sheet2 in an excel 2007 file.
In sheet2 I have a column that is managed by a form/macro(with a tree view control). When an element has been selected, the cell is filled with an "x", when it has been unselected, the cell is filled with "" (nothing).
In sheet1 I want to create a column equal to the sheet2 column.
So for example: if sheet2!C24 = "x" then sheet1!c24 should also be "x"
I also would like it to work both ways. If the user changes sheet1!c24 to "x", then I want sheet2!c24 to take the same value.
Problems:
- in Sheet1, I tried sheet1!c24 = sheet2!c24, but then when sheet2!c24 = "", sheet1!c24 displays 0 instead of nothing
- in Sheet2, I tried sheet2!c24 = sheet1!c24, but then the cells display the formula (='sheet1!c24') instead of the value...
So basically, what I want is that whatever change you do, in sheet1 or in sheet2, both columns in sheet1 and sheet2 are updated...
How can I achieve this?
What I think you need to do is use the Worksheet_Change events for both sheets and if a change is made in the column you are interested in, then you update the same cell in the other sheet.
Something like this would go in the worksheet code module:
Private Sub worksheet_change(ByVal target As Range)
Dim c As Range
'Test to see if the cell just changed is
'in the column we are interested in
Set c = Application.Intersect(target, Range("A:A"))
If Not c Is Nothing Then
'Copy across to other sheet
If Not beingEdited Then
beingEdited = True
Sheet1.Range(target.Address) = target.Value
beingEdited = False
End If
End If
End Sub
You'd need a beingEdited variable to be declared somewhere else with larger scope so that you could avoid the events triggering themselves and Excel getting stuck in a loop.
In the other sheet you'd basically have the same procedure, except that it would reference the first worksheet, e.g. Sheet1.Range(target.Address) = target.Value.
Obviously, you'd have to tweak this to your ranges/sheets.
You've got the right idea, but you probably need to turn off events before making the change, otherwise you'll end up in a loop
Private Sub worksheet_change(ByVal target As Range)
application.enableevents = false
sheet1.range("c24").value = sheet2.("c24").value
application.enableevents = true
end sub
Just make sure you enable events again at the end.
i did something like this where i had a summary sheet and a tests sheet. When I added a new value in tests sheet and it passed (P) a cell in summary sheet will keep increment. This is to keep a count of how many tests passed. here it is:
COUNTIF(tests!$C$5:$C$1017, "P");
hope this helps.