I have created a UDF to display "Success" (to keep this simple) every time one of the cells in the range D1:D4 changes.
However, 3 out of 4 of the cells contain real-time data that is refreshed every 1500 milli-secs and so every time the cells refresh the function triggers the MsgBox to display even though the values haven't changed. I only want the MsgBox to display if the values in those cells change.... can anyone help, I have spent the last 3 days trolling the net for a resolution but to no avail?
=Trigger(D1:D4)
Appreciation in advance.Function Trigger (theParameter As Variant) MsgBox "Success" End Function
I can't reproduce the problem, at least in Excel 2007. Every time i enter the same data in one of the D1:D4 cells I get no response, but if I enter something different, the "success" msg appears. How do you do your 1.5 sec refresh?
Try putting "Application.Volatile False" as the first line in your UDF().
Volatile UDF()s are executed every time the sheet calculates.
Another solution would be to have the UDF() check all the values with a copy of the values somewhere else in the workbook and if they are different then put the message up and copy the values to the copy of the values for the next refresh.
Thanks for responding, I've tried Application.Volatile (false) but it didn't work and I can't cache or copy the data elsewhere to compare the changes because I need the function to work across many rows and data will be changing on different rows periodically so it will always be different.
Any other suggestions?
I'm using a third party add-in that provides the real-time data and the setting on the refresh is 1500 millisecs. It's definitiely the real time data because if I remove the input from the function cell for these 3 cells the function works only when I go in manually and change the value of the fourth cell.
I don't think you put "()" around the work "False" in the volatile statement.
Put "()" around the parameters when you want to return a value.
I don't understand why you can't make a copy of the data.
I also don't understand why it's a problem that the data is always different. Isn't that what you're looking for?
Just so were are on the same page.
Function Trigger (theParameter As Variant) Dim rCopy as Range Dim lRow as Long, lCol as Long set rCopy = <the copy of data> With theParameter For lRow = 1 to .Rows.Count For lCol = 1 to .Columns.Count If .Cells(lRow, lCol).Value <> rCopy.Cells(lRow, lColl).Value Then MsgBox "Success" .Copy rCopy Exit Sub Endif Next lCol Next lRow End With End Function
You could cache them locally:
If you used a lot of these, you'd bring the workbook to its knees.Option Explicit Dim dic As Object Function Trigger(r As Range) Dim cell As Range Dim sAdr As String If dic Is Nothing Then Set dic = CreateObject("Scripting.Dictionary") With dic For Each cell In r sAdr = cell.Address(External:=True) If .Exists(sAdr) Then If cell.Value <> .Item(sAdr) Then MsgBox sAdr .Item(sAdr) = cell.Value End If Else .Add Key:=sAdr, Item:=cell.Value End If Next cell End With End Function
Last edited by shg; 11-18-2011 at 06:28 PM.
Microsoft MVP - Excel
Entia non sunt multiplicanda sine necessitate
I would use hundreds of these and I need to avoid resource strain on the workbook. Thanks for the input though. I am seeking a way to disregard the data refresh thats triggering the function and just trigger on change in values.
Maybe I didn't explain the scenario as clearly as I could have, hopefully this helps.
I will have the same function name on each row that will read in four fields of data on the corresponding row if any of the values change. Ie. =Trigger (D3:G3), =Trigger(D4:G4), =Trigger(D5:G5), etc.
http://www.designcymru.com/images/example.jpg
If a field on row 3 changes the function will pull in the values of those fields for me to do something specific with them. If I copy the values from row 3 to compare them next time I get a change and then a value in row 5 changes it will pull in a seperate set of data and of course it will be different. If I somehow store the data by each line to compare against the next time it will be very resource intensive on the workbook given I need this function to cover 1000+ lines.
I tried Application.Volatile (False) as one of the first solutions but this doesn't work with the real time data refreshing in these cells every 1500 millisecs. This is my real issue, initiating the function on a row when one of the correspinding cells it's watching on that row actually changes value.... instead of the functon triggering across 1000 rows every 1500 millisecs.
Hope this helps...
What you're saying is that you only want to trigger a UDF() if a value actually changes to a different value, not if it changes to the same value.
I've never heard of a way to tell Excel to not trigger a UDF() if the new value is the same as the old value. As far as I know you have to store the old value and compare the new value to the old value.
The UDF() could be made faster by having it trigger just once for the entire 1000 rows and keep the old values in memory instead of on a worksheet. So at least it doesn't take the time to save the values to disk.
Sub Trigger(ByVal r as Range) Static aOldValues Dim aNewValues Dim lRow as Long, lCol as Long If uBound(aOldValues,1) <= 0 Then aOldValues = r.Value Else aNewValues = r.Value For lRow = 1 to r.Rows.Count For lCol = 1 to r.Columns.Count If aNewValues(lRow, lCol) <> aOldValues(lRow, lCol) Then MsgBox "Success" aOldValues = aNewValues Exit Sub End If Next lCol Nes lRow End If End Sub
Last edited by foxguy; 11-18-2011 at 10:25 PM.
Hi foxguy,
You'll have to excuse me, my VBA skills leave alot to be desired. I've spent some time looking at this and I think the best way would be to create a 2d array with 3 columns that will store the data in memory that I can compare against:
1st col = index (can be derived from Application.ThisCell.Row)
2nd col = YesNo (1st item in array I read in when function triggers)
3rd col = Price (2nd item in array I read in when function triggers)
Could you help me write some code to do the following please?
Create 2d public array with 3 cols and 500+ rows (can this be dynamic?)
Function Trigger (theParameter As Variant)
Dim vArr As Variant
Dim Index As Integer
Dim YesNo As String
Dim Price As Double
vArr = theParameter
'Assign row number as unique index number
Index = Application.ThisCell.Row
'Assign Price as second item read in from variant arrray
Price = vArr (1, 3)
With 2d array
If Index exists in col1 of 2d array Then
If YesNo <> corresponding yesno in col2 of 2d array Then
MsgBox "YesNo different"
yesno in col2 of 2d array = YesNo
ElseIf Price <> corresponding price in col3 of 2d array Then
MsgBox "Price different"
price in col3 of 2d array = Price
End If
ElseIf Index does not exist in 2d array Then
Create Entry in 2d array with Index(1), YesNo(2), Price(3)
End If
End With
End Function
Much appreciation for any help on this in advance.
I don't think this will work completely the way you want.
Please go back and put code tags around your code, and indent between "If...End If", "For...Next", "With....End With", etc. like this:
This makes it easier to read and shows where each block of code starts and ends.Function Trigger (theParameter As Variant Dim vArr As Variant ' code With 2d array If Index exists in col1 of 2d array Then If YesNo <> corresponding yesno in col2 of 2d array Then MsgBox "YesNo different" ElseIf Price <> corresponding price in col3 of 2d array Then MsgBox "Price different" End If End With End Function
This will allow you to look more critically at your code to see where there may be errors in your logic.
At first glance:
1) You Dim YesNo as string, but never assign a value.
2) theParameter will probably be a single cell passed to the Function. Assigning a single cell to a variable doesn't make an array.
3) the 1st time the Function is called the arrays will be empty so there is nothing to compare to. So you will want to create an array BEFORE the Function is called the first time and possibly use an "Undo" if it's empty when it's called in order to allow you to create the array.
4) Arrays can only be dynamic in the last dimension. I would initially advise setting up the array in the Workbook_Open() event like this vArray1 = Sheets1.Usedrange, except that would make the # of rows fixed but allow you to add columns to the array, so you would want to transpose it so that the columns are fixed and you can add rows to the array.
What you're doing is possible, and you're in the ballpark of a way to do it.
I'll wait for the refined code (with code tags and indents)
Apologies, here's my next shot. theParameter will pass in the 2 variables I need as an array from a range so the function will be: =Trigger(D1:E1)
I was thinking that given the array won't be populated the first time the function is triggered on each row, if an entry with the index (Row) doesn't exist then it will create it. As you can see from my requests in the code below, I need help on the global array front and as you suggested to initialize it before it can be used.
Function Trigger (theParameter As Variant) Dim vArr As Variant Dim Index As Integer Dim YesNo As String Dim Price As Double vArr = theParameter 'Assign row number as unique index number Index = Application.ThisCell.Row 'Assign YesNo & Price as two fields read in from variant arrray YesNo = vArr (1, 1) Price = vArr (1, 2) With 2d array If Index exists in col1 of 2d array Then If YesNo <> corresponding yesno in col2 of 2d array Then MsgBox "YesNo different" yesno in col2 of 2d array = YesNo ElseIf Price <> corresponding price in col3 of 2d array Then MsgBox "Price different" price in col3 of 2d array = Price End If ElseIf Index does not exist in 2d array Then Create Entry in 2d array with Index(1), YesNo(2), Price(3) End If End With End Function
It occurred to me that when you open the file it will run the function at least once for each instance of the Function() in your worksheet.
So no need to initialize the vars before running.
I'm just giving you the skeleton here for you to study and figure out what is going on. This can get a bit complicated and you will probably want to add on to it in the future. So you need to understand what is happening.
I did not test this. I left that for you.
'Function Trigger(theParameter As Variant) 'Since you're passing it a range you might as well tell the function you passed a range Function Trigger(ByVal rCells2Check As Range) As Date 'Although it may not be necessary, ' it's a good idea to return a value to put in the cell that called it 'Tell Excel to NOT run this function unless something changes ' remember that a cell can be changed to the same value Application.Volatile False If rCells2Check.Rows.Count > 1 Then MsgBox "This Function Will Only Work On 1 Row" Exit Function End If 'Dim vArr As Variant 'create varable that will will hold values between triggers ' No point in making a public variable since no other subs will use it. Static vArr As Variant Dim Index As Integer Dim YesNo As String Dim Price As Double Dim lCols As Long Dim lCol As Long 'Tell VBA where to go if an error occurrs On Error GoTo EF 'Turn off features that slow things down With Application .EnableEvents = False .ScreenUpdating = False End With 'vArr = theParameter 'Ignore next error On Error Resume Next 'check to see if the array exists lCols = UBound(vArr, 1) 'reset On Error On Error GoTo EF 'Assign row number as unique index number Index = rCells2Check.Row If lCols <> rCells2Check.Columns.Count Then 'either vArr has not been initialized ' or the user has changed the # of columns to check ReDim vArr(1 To 1, 1 To 1) 'just create a blank array to get things started End If If UBound(vArr, 2) < Index Then 'add a new column to array if this data row has not been created yet ReDim Preserve vArr(1 To lCols, 1 To Index) 'Remember that only the last dimension can be dynamic ' so put each Row of data in a Column in the array. ' That allows you to add more columns to the array if a new row shows up in the data 'This creates an empty Column for Rows 1:2 (assuming the 1st formula is in row 3) ' but it's a small price to spend for keeping the numbering simple 'put the values from rCells2Check into the correct column in array For lCol = 1 To lCols vArr(lCol, Index) = rCells2Check.Columns(lCol).Value Next Else 'vArr was already initilized so check values End If 'Assign row number as unique index number 'Index = Application.ThisCell.Row 'move above initializing array 'you now have an array with the data in it. ' Column 3 of the array (2nd dimension) has the data from row 3 in it. ' Column 17 of the array has the data from row 17 in it. 'Try and compare the data to the array by yourself '******************************************************************** 'This part is a decent start, but the 2d array is unnecessary ' just compare the values in rCells2Check to the correct column of data in vArr Assign YesNo & Price as two fields read in from variant arrray YesNo = vArr(1, 1) Price = vArr(1, 2) With 2d array If Index exists in col1 of 2d array Then If YesNo <> corresponding yesno in col2 of 2d array Then MsgBox "YesNo different" yesno in col2 of 2d array = YesNo ElseIf Price <> corresponding price in col3 of 2d array Then MsgBox "Price different" price in col3 of 2d array = Price End If ElseIf Index does not exist in 2d array Then Create Entry in 2d array with Index(1), YesNo(2), Price(3) End If End With ******************************************************************** EF: 'Turn Features back on With Application .ScreenUpdating = True .EnableEvents = True End With 'return the data & time to put into the cell with "=Trigger(.....)" in it. Trigger = Now End Function
Last edited by foxguy; 11-22-2011 at 08:12 PM.
Thanks for this foxguy, I've been looking at this all morning but it seems it's not populating the array correctly.I had 2 cols of values for 50 rows and then the function in the 3rd col. It returned the time nicely and updated every time I manually changed a value but I couldn't get it further than initializing the array to test comparing the values.
I placed a break at Trigger = vArr to see how the array was populating in the Locals window and this is what it looked like:
Screenshot of Locals window
Also, is the code to compare rCells2Check values versus corresponding values in the array accurate?
Function Trigger(ByVal rCells2Check As Range) As Date Application.Volatile False If rCells2Check.Rows.Count > 1 Then MsgBox "This Function Will Only Work On 1 Row" Exit Function End If 'create variable that will will hold values between triggers Static vArr As Variant Dim Index As Integer Dim YesNo As String Dim Price As Double Dim lCols As Long Dim lCol As Long 'Tell VBA where to go if an error occurrs On Error GoTo EF 'Turn off features that slow things down With Application .EnableEvents = False .ScreenUpdating = False End With 'Ignore next error On Error Resume Next 'check to see if the array exists lCols = UBound(vArr, 1) 'reset On Error On Error GoTo EF 'Assign row number as unique index number and YesNo and Price values from range Index = rCells2Check.Row YesNo = rCells2Check(1).Value Price = rCells2Check(2).Value If lCols <> rCells2Check.Columns.Count Then 'either vArr has not been initialized 'or the user has changed the # of columns to check ReDim vArr(1 To 1, 1 To 1) 'just create a blank array to get things started End If If UBound(vArr, 2) < Index Then 'add a new column to array if this data row has not been created yet ReDim Preserve vArr(1 To lCols, 1 To Index) 'put the values from rCells2Check into the correct column in array For lCol = 1 To lCols vArr(lCol, Index) = rCells2Check.Columns(lCol).Value Next 'Trigger = vArr Else 'vArr was already initilized so check values If rCells2Check.Columns(lCol).Value <> vArr(lCol, Index) Then MsgBox "Value in one of the cells has changed" End If End If EF: 'Turn Features back on With Application .ScreenUpdating = True .EnableEvents = True End With 'return the data & time to put into the cell with "=Trigger(.....)" in it. Trigger = Now End Function
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks