VBA code to autoupdate cell with current date, when cell changed, not working - vba

I have a sheet where I daily change the stats of some rows and I have to know when the stats changed. To make my life easier, I found on the internet this VBA code:
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Range("R1:R2000")) Is Nothing Then Exit Sub
Target.Offset(0, 1) = Date
End Sub
The code works wonderfully. Everytime I change de stats on column R, it update the date on column S.
The problem is that I change this stat in a table linked to an outside source (Oracle Database) and if I try to refresh the table, it change the entire content of the table to different dates. What I have to do now is, everytime I have to refresh the table, I just comment the entire VBA Code.
Is there anything I can change on the code so it doesn't update when I refresh the table?
Edit 1:
Sub RefreshOracleTable()
Application.EnableEvents = False
If Intersect(Target, Range("R1:R200000")) Is Nothing Then Exit Sub
Target.Offset(0, 1) = Date
ThisWorkbook.Sheets("wsTables").ListObjects("OracleTable").QueryTable.Refresh BackgroundQuery:=Application.EnableEvents = True
End Sub

Disable events, then refresh table
You can write a Sub, which you will use for refreshing the table. Before refresh you will disable excel events. Like so:
Sub RefreshOracleTable
Application.EnableEvents = False
'the refresh itself code something like
Thisworkbook.Sheets("wsTables").ListObjects("OracleTable").QueryTable.Refresh BackgroundQuery:=False
Application.EnableEvents = TRUE
End Sub
Manual entry of a Date with Keyboard shortcut
Consider using a Keyboard shortcut to manualy enter the current date. CTRL + ;
Explanation of current situation and proposed solution

Related

Excel VBA - Timestamps for all Modifications to Rows in Multiple Tables

I'm trying to add timestamps for any modifications made to all rows in a table. I have been able to find/modify some code that will add the timestamp, but it does it for every cell in that column. As you can see it's got a line that tells it to start below a certain row. I want to make this apply only to the tables in each worksheet in my excel file. This would include any new tables added to the sheets. I'm very new to VBA, so any and all constructive help is welcome!
A little background: These sheets are being used for resource forecasting for my company for every different project we have going on. And every crew in each project has their own table. The forecasts get updated each week. I want to be able to quickly find what has changed when I get these at the end of the week. This isn't meant to be a permanent solution for our forecasting needs, but it will make my life easier in the meantime.
This is the code I've been working with. As I mentioned I know this only does part of what I want. I was trying to modify it to do what I want, but I just don’t have the know how yet:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim r As Range, c As Range
Const DateStampColumn As Long = 2
'Date stamp column number
For Each r In Target.Rows
If r.Row > 10 Then
For Each c In r.Cells
If Not IsEmpty(c) Then
Application.EnableEvents = False
Cells(r.Row, DateStampColumn).Value = Date
Application.EnableEvents = True
Exit For
End If
Next c
End If
Next r
End Sub
I was able to modify it to do what I needed. I removed the message box lines because I think that will frustrate the users entering the data (I'm the one that has to extract it). There is one issue that I'm hoping you could help me solve. Whenever I add or delete a row within the tables a timestamp appears. Is there a way to keep that from happening? Here is what my code looks like currently:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim t As ListObject
For Each t In ActiveSheet.ListObjects
If Not Intersect(Target, t.DataBodyRange) Is Nothing Then
Application.EnableEvents = False
Cells(Target.Row, "B").Value = Date
Application.EnableEvents = True
Exit Sub
End If
Next t
End Sub
You need something along these lines I think. This checks whether Target is in a table, I haven't added the Date as I'm not sure where it should go.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim t As ListObject
For Each t In ActiveSheet.ListObjects
If Not Intersect(Target, t.DataBodyRange) Is Nothing Then
MsgBox "Cell changed in " & t.Name
Exit Sub
End If
Next t
MsgBox "Cell changed not in any table"
End Sub

Excel VBA If Then Function conditional on cell values

I want to calculate a price based on a proposed discount % or calculate a discount based on a proposed price.
I need users to be able to use either parameter and adjust back and forth to determine their ideal price.
I have coded the formulas so that nothing is overwritten by the user but have a problem as the 'value if false' element of the "IF" formula is dependent on a zero value in one column and so resets if the user doesn't manually adjust this before inputting a figure into another. This results in the new figure being reset.
Ideally I would like the sheet to reset the value in column "Proposed Promo POR%" to zero if a value is input in "Promo Price per Unit". Is this possible?
This is the code I have at the moment.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Intersect(Target, Range("b21:b26")) Is Nothing Then Exit Sub
Application.EnableEvents = False
ActiveSheet.Range("$i21:$i26").Formula ="=IF(Proposal!$b21>Formulae!$a$5,Proposal!$g21-(Proposal!$g21*Proposal!$b21), (Proposal!$g21-Proposal!$i21)/Proposal!$g21)"
Application.EnableEvents = True
End Sub
For clarity Formulae!$a$5 is zero and it is column $i21:$i26 that the user will enter a price in if they want to. If they do enter something here then column b should reset to zero.
This is a screenshot of the columns and headers in my sheet
The Worksheet_Change event will help you. Worksheet_SelectionChange fires each time a user changes the active cell, whereas Worksheet_Change fires anytime a user makes an edit to any cell(s).
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("$i21:$i26"")) Is Nothing _
And Target.Rows.Count = 1 and Target.Columns.Count = 1 Then
'assumes event will fire only on changing one cell at a time
Application.EnableEvents = False
Target.Offset(0,-7).Value = 0
Application.EnableEvents = True
End If
End Sub

Hide Active Column when cell value is changed

I have been trying to work this out myself for the last few days and caught myself in a bit of a one step forward three steps back cycle. I've been reluctant to bother you thinking this would have been answered somewhere else before now.
The idea is that I have a spreadsheet that has criteria in rows with separate entries in rows; in row 6 it is the status of each column entry, which when changed to "Completed" I would like the column to be hidden.
I've been floundering around with Worksheet_Change and been able to hide specific columns, but not the active column.
Any help offered would be much appreciated and I'm sorry if this has been covered elsewhere, but I've not been able to successfully apply any examples out there.
Thanks.
Whenever you have to work with worksheet_change events, you have to consider a cycle for it, due to user may delete multiple data at the same time or do a copy paste, if you only consider "Target" It would give a debugger error.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ItemMultipleData As Range
For Each ItemMultipleData In Target 'handles multiple cells, paste, del, etc
'your code (instead of using "Target" change to ItemMultipleData. IE:
'If ItemMultipleData.Value = "Completed" Then
Next ItemMultipleData
End Sub
Here is a starting point. It only checks row # 6:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Set rng = Range("6:6")
If Intersect(Target, rng) Is Nothing Then Exit Sub
If Target.Count <> 1 Then Exit Sub
If Target.Value = "Completed" Then
Application.EnableEvents = False
Target.EntireColumn.Hidden = True
Application.EnableEvents = True
End If
End Sub
EDIT#1:
This approach assumes that only one cell at a time is being changed........that makes it easy to "find the active cell"

Default values for fields in a new row of a data table

When you have a data table in Excel, part of the standard functionality is that pressing tab in the last cell adds a new row at the bottom of the table. I want to auto-populate that new row with useful default values. In particular I want to put current date-time in one cell, and copy values into some other cells from the previous row of the table.
It is not workable to do that using formulae -- e.g. using =now() for the date-time stamp is inadequate because it will be auto-updated every time the spreadsheet recalculates, whereas I want it to retain the date-time at the moment when the row was added.
So I am trying to write VBA to be triggered by the event of the row being added, and in that code to write values into the cells of the new row. From MS documentation I thought DataTable.TableNewRow would be the appropriate event. But when I try to write any code for that event it is not being executed. When I look up DataTable in the VBA object browser the TableNewRow event is not listed.
Versions:
VBA for Applications 7.1
Excel 2013
So my questions:
Is the direction of my thinking right, or can you suggest a better approach?
Can you offer any working code that does something like this?
Is DataTable.TableNewRow the event I should be working with?
What do I need to do to get that event accessible in my VBA code?
You can try this:
Write this code in Thisworkbook.
Private Sub Workbook_Open()
Set ref_tbl = Sheet1.ListObjects(1).DataBodyRange
End Sub
Then below code in a Worsksheet Object.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
On Error GoTo halt
Application.EnableEvents = False
Dim tbl_rng As Range
Set tbl_rng = Me.ListObjects(1).DataBodyRange
If Not Intersect(Target, tbl_rng) Is Nothing Then
If tbl_rng.Rows.Count > ref_tbl.Rows.Count Then
MsgBox "Table increase in size"
'~~> Do your stuff here
Set ref_tbl = tbl_rng
End If
End If
forward:
Application.EnableEvents = True
Exit Sub
halt:
MsgBox Err.Number & ": " & Err.Description
Resume forward
End Sub
You will also need a Module to declare the public variable
Public ref_tbl As Range
So basically, this will tell you when your table increase in size.
If we're able to capture that, then you can do your stuff when that condition is met.
This works in the situation you describe in your question.
It will not work though when you insert row between entries in the table. Anyways, HTH.

OnClick in Excel VBA

Is there a way to catch a click on a cell in VBA with Excel? I am not referring to the Worksheet_SelectionChange event, as that will not trigger multiple times if the cell is clicked multiple times. BeforeDoubleClick does not solve my problem either, as I do not want to require the user to double click that frequently.
My current solution does work with the SelectionChange event, but it appears to require the use of global variables and other suboptimal coding practices. It also seems prone to error.
Clearly, there is no perfect answer. However, if you want to allow the user to
select certain cells
allow them to change those cells,
and
trap each click,even repeated clicks
on the same cell,
then the easiest way seems to be to move the focus off the selected cell, so that clicking it will trigger a Select event.
One option is to move the focus as I suggested above, but this prevents cell editing. Another option is to extend the selection by one cell (left/right/up/down),because this permits editing of the original cell, but will trigger a Select event if that cell is clicked again on its own.
If you only wanted to trap selection of a single column of cells, you could insert a hidden column to the right, extend the selection to include the hidden cell to the right when the user clicked,and this gives you an editable cell which can be trapped every time it is clicked. The code is as follows
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'prevent Select event triggering again when we extend the selection below
Application.EnableEvents = False
Target.Resize(1, 2).Select
Application.EnableEvents = True
End Sub
In order to trap repeated clicks on the same cell, you need to move the focus to a different cell, so that each time you click, you are in fact moving the selection.
The code below will select the top left cell visible on the screen, when you click on any cell. Obviously, it has the flaw that it won't trap a click on the top left cell, but that can be managed (eg by selecting the top right cell if the activecell is the top left).
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'put your code here to process the selection, then..
ActiveWindow.VisibleRange.Cells(1, 1).Select
End Sub
SelectionChange is the event built into the Excel Object model for this. It should do exactly as you want, firing any time the user clicks anywhere...
I'm not sure that I understand your objections to global variables here, you would only need 1 if you use the Application.SelectionChange event. However, you wouldn't need any if you utilize the Workbook class code behind (to trap the Workbook.SelectionChange event) or the Worksheet class code behind (to trap the Worksheet.SelectionChange) event. (Unless your issue is the "global variable reset" problem in VBA, for which there is only one solution: error handling everywhere. Do not allow any unhandled errors, instead log them and/or "soft-report" an error as a message box to the user.)
You might also need to trap the Worksheet.Activate() and Worksheet.Deactivate() events (or the equivalent in the Workbook class) and/or the Workbook.Activate and Workbook.Deactivate() events so that you know when the user has switched worksheets and/or workbooks. The Window activate and deactivate events should make this approach complete. They could all call the same exact procedure, however, they all denote the same thing: the user changed the "focus", if you will.
If you don't like VBA, btw, you can do the same using VB.NET or C#.
[Edit: Dbb makes a very good point about the SelectionChange event not picking up a click when the user clicks within the currently selected cell. If you need to pick that up, then you would need to use subclassing.]
I don't think so. But you can create a shape object ( or wordart or something similiar ) hook Click event and place the object to position of the specified cell.
This has worked for me.....
Private Sub Worksheet_Change(ByVal Target As Range)
If Mid(Target.Address, 3, 1) = "$" And Mid(Target.Address, 2, 1) < "E" Then
' The logic in the if condition will filter for a specific cell or block of cells
Application.ScreenUpdating = False
'MsgBox "You just changed " & Target.Address
'all conditions are true .... DO THE FUNCTION NEEDED
Application.ScreenUpdating = True
End If
' if clicked cell is not in the range then do nothing (if condttion is not run)
End Sub
NOTE: this function in actual use recalculated a pivot table if a user added a item in a data range of A4 to D500. The there were protected and unprotected sections in the sheet so the actual check for the click is if the column is less that "E" The logic can get as complex as you want to include or exclude any number of areas
block1 = row > 3 and row < 5 and column column >"b" and < "d"
block2 = row > 7 and row < 12 and column column >"b" and < "d"
block3 = row > 10 and row < 15 and column column >"e" and < "g"
If block1 or block2 or block 3 then
do function .....
end if
I had a similar issue, and I fixed by running the macro "onTime", and by using some global variables to only run once the user has stopped clicking.
Public macroIsOnQueue As Boolean
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
macroIsOnQueue = False
Application.OnTime (Now() + TimeValue("00:00:02")), "addBordersOnRow"
macroIsOnQueue = True
End sub
Sub addBordersOnRow()
If macroIsOnQueue Then
macroIsOnQueue = False
' add code here
End if
End sub
This way, whenever the user changes selection within 2 seconds, the macroIsOnQueue variable is set to false, but the last time selection is changed, macroIsOnQueue is set to true, and the macro will run.
Hope this helps,
Have fun with VBA !!
Just a follow-up to dbb's accepted answer: Rather than adding the immediate cell on the right to the selection, why not select a cell way off the working range (i.e. a dummy cell that you know the user will never need). In the following code cell ZZ1 is the dummy cell
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Application.EnableEvents = False
Union(Target, Me.Range("ZZ1")).Select
Application.EnableEvents = True
' Respond to click/selection-change here
End Sub