I have a python script that edits contents of a certain column and VBA that checks to see if this column has changed. When it detects a change, it locks the column from user edits. It is successful in protecting the worksheet but the cells can still be edited. I can't understand why this is happening.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Protect As String
Dim NotProtectRange As String
ProtectRange = "A1:A1049576"
NotProtectRange = "B1:XFD1048576"
If Not Intersect(Target, Target.Worksheet.Range(ProtectRange)) Is Nothing Then
Worksheets("Sheet1").Unprotect
Range(NotProtectRange).Locked = False
Worksheets("Sheet1").Protect UserInterfaceOnly:=True, Contents:=True
End If
End Sub
I expect that when I double click the cells in the range ProtectRange, I should not be able to edit them. But instead, I can edit them. So how do I fix this?
If the cells are not locked then protecting the sheet does not prevent editing.
Me.Range(ProtectRange).Locked = True
should resolve the problem.
Related
How can I automatically execute an Excel macro each time a value in a particular cell changes?
Right now, my working code is:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("H5")) Is Nothing Then Macro
End Sub
where "H5" is the particular cell being monitored and Macro is the name of the macro.
Is there a better way?
Your code looks pretty good.
Be careful, however, for your call to Range("H5") is a shortcut command to Application.Range("H5"), which is equivalent to Application.ActiveSheet.Range("H5"). This could be fine, if the only changes are user-changes -- which is the most typical -- but it is possible for the worksheet's cell values to change when it is not the active sheet via programmatic changes, e.g. VBA.
With this in mind, I would utilize Target.Worksheet.Range("H5"):
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Target.Worksheet.Range("H5")) Is Nothing Then Macro
End Sub
Or you can use Me.Range("H5"), if the event handler is on the code page for the worksheet in question (it usually is):
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Me.Range("H5")) Is Nothing Then Macro
End Sub
I spent a lot of time researching this and learning how it all works, after really messing up the event triggers. Since there was so much scattered info I decided to share what I have found to work all in one place, step by step as follows:
1) Open VBA Editor, under VBA Project (YourWorkBookName.xlsm) open Microsoft Excel Object and select the Sheet to which the change event will pertain.
2) The default code view is "General." From the drop-down list at the top middle, select "Worksheet."
3) Private Sub Worksheet_SelectionChange is already there as it should be, leave it alone. Copy/Paste Mike Rosenblum's code from above and change the .Range reference to the cell for which you are watching for a change (B3, in my case). Do not place your Macro yet, however (I removed the word "Macro" after "Then"):
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Me.Range("H5")) Is Nothing Then
End Sub
or from the drop-down list at the top left, select "Change" and in the space between Private Sub and End Sub, paste If Not Intersect(Target, Me.Range("H5")) Is Nothing Then
4) On the line after "Then" turn off events so that when you call your macro, it does not trigger events and try to run this Worksheet_Change again in a never ending cycle that crashes Excel and/or otherwise messes everything up:
Application.EnableEvents = False
5) Call your macro
Call YourMacroName
6) Turn events back on so the next change (and any/all other events) trigger:
Application.EnableEvents = True
7) End the If block and the Sub:
End If
End Sub
The entire code:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Me.Range("B3")) Is Nothing Then
Application.EnableEvents = False
Call UpdateAndViewOnly
Application.EnableEvents = True
End If
End Sub
This takes turning events on/off out of the Modules which creates problems and simply lets the change trigger, turns off events, runs your macro and turns events back on.
Handle the Worksheet_Change event or the Workbook_SheetChange event.
The event handlers take an argument "Target As Range", so you can check if the range that's changing includes the cell you're interested in.
I prefer this way, not using a cell but a range
Dim cell_to_test As Range, cells_changed As Range
Set cells_changed = Target(1, 1)
Set cell_to_test = Range( RANGE_OF_CELLS_TO_DETECT )
If Not Intersect(cells_changed, cell_to_test) Is Nothing Then
Macro
End If
I have a cell which is linked to online stock database and updated frequently. I want to trigger a macro whenever the cell value is updated.
I believe this is similar to cell value change by a program or any external data update but above examples somehow do not work for me. I think the problem is because excel internal events are not triggered, but thats my guess.
I did the following,
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Target.Worksheets("Symbols").Range("$C$3")) Is Nothing Then
'Run Macro
End Sub
I have a table where I want to be able to hide individual rows at a mouse click. The (seemingly) easiest solution I've found is to have a column filled with hyperlinks that call a macro to hide the row that they're in.
There are two ways of calling macros from hyperlinks: using Worksheet_FollowHyperlink with manual hyperlinks, and using =HYPERLINK.
The former works fine, except there's no way (that I've found) to have them generate automatically when new rows are added to the table. I would have to either manually copy them down every time, which is unviable, or add them with VBA, which adds a bunch of complexity to an otherwise simple task.
The latter generates fine, being a formula, but it doesn't actually work. It doesn't trigger Worksheet_FollowHyperlink, and when using =HYPERLINK("#MyFunction()") it just doesn't hide rows (or do much other than editing cells contents).
Function MyFunction()
Set MyFunction = Selection
Selection.EntireRow.Hidden = True
End Function
Is there a good solution to this?
Rather than a Hyperlink, you could handle a Double Click event on the table column
Something like
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim NameOfTableColumn As String
On Error GoTo EH:
NameOfTableColumn = "DblClickToHide" ' update to suit your table
If Not Application.Intersect(Target, Target.ListObject.ListColumns(NameOfTableColumn).DataBodyRange) Is Nothing Then
Target.EntireRow.Hidden = True
Cancel = True
End If
Exit Sub
EH:
End Sub
Please, copy the next code in the sheet code module where the table to be clicked exists. Clicking on each cell in its first column (dynamic to rows adding/insertions/deletions), the clicked row will be hidden:
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim tbl As ListObject
If Target.cells.CountLarge > 1 Then Exit Sub
Set tbl = Me.ListObjects(1) 'you may use here the table name
If Not Intersect(Target, tbl.DataBodyRange.Columns(1)) Is Nothing Then
Application.EnableEvents = False
Target.EntireRow.Hidden = True
Application.EnableEvents = True
End If
End Sub
It would be good to think about a way to unhide the hidden row if/when necessary. If only a row should be hidden at a time, it is easy to unhide all the rest of column cells...
I am trying to activate a macro by clicking in a cell.
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Selection.Count = 1 Then
If Not Intersect(Target, Range("B37")) Is Nothing Then
Worksheets("DaysEditor").Activate
Sheets("DaysEditor").Columns("C:LY").Hidden = False
Sheets("DaysEditor").Columns("C:EX").Hidden = True
Sheets("DaysEditor").Range("A1").Select
End If
End If
End Sub
This code works on a cell that doesn't have a formula but will not work on cells with a formula in them.
A few minor tweaks I would change with your code. Firstly, if you are going to make multiple calls to a worksheet, I suggest that you either make a variable of it or use it within a With <obj> statement.
Worksheets() is essentially a function. Every time you use it it has to get the function's "value" - hence not the most efficient way of doing things.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Address = "$B$37:$C$37" Then
With Worksheets("DaysEditor")
.Columns("C:LY").Hidden = False
.Columns("C:EX").Hidden = True
.Activate
.Range("A1").Select
End With
End If
End Sub
Generally I advise against using the Activate method. But since you appear to actually want to be on the worksheet when the code runs then it's not a big deal here. Just remember that Activate is not necessary for the remainder of your code to function.
One last tweak is that I moved your Activate method after you hide/unhide the columns. Probably not a big deal either, but no need to watch the screen redraw unnecessarily.
Thanks to #K.Davis he helped me figure out that the problem was the that cell I was attaching this to was a merged cell and that was the problem. I changed the Selection.Count = 1 to Selection.Count = 2 and the code works perfectly!
Is it possible for worksheet_change to not process the pending/already happened user update by exiting the sub should a condition not be met? I have a logging system and do not want the user to delete more than one cell at a time, since my code handles one-cell-at-a-time updates - Eventually the code should be improved to allow this. But for the meantime:
Private Sub Worksheet_Change(ByVal Target As Excel.Range)
If Selection.Count > 1 Then
MsgBox ("please delete items one at a time")
Exit Sub
End If
This displays the msgbox ok, but proceeds with the deletion of the contents of all cells in the selection, should multiple cells be selected. The deleted contents then gets missed by the logging system.
Ok, trying to get to "final destination" in response to #Gary's Student answer since it isn't really illegal to delete multiple cells.
Partially using #binil 's solution gets me to this pseudocode but I am not good enough to complete it (the cells in the range may be non-contiguous)
Dim theoldvals As Variant
Private Sub Worksheet_Change(ByVal Target As Range)
Dim i As Integer
for each c in the oldvals
Sheets("log").ActiveCell.Value = "OLD value" = oldvals(c)
.ActiveCell.Offset(1, 0) = "NEW value" = Target(c) ' where c is the corresponding cell/index in the target range
Next c
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Set theoldvals = Target
End Sub
You can have another sheet (set Visible = xlSheetVeryHidden so users can't just show it through the regular unhiding process) that is a clone of your sheet then. Then any time you are in the Worksheet_Change event you can check your validation conditions. if your conditions fail you grab the old value from your clone sheet, if you pass validation then you write to your regular sheet like normal and duplicate that write to your clone sheet.
You can't prevent something after it has already happened. However:
If Selection.Count > 1 Then
MsgBox ("please delete items one at a time")
ActiveWorkbook.Saved = True
ActiveWorkbook.Close
End If
will prevent the illegal changes from being saved.
My purpose is to run a macro automatically on some 20 cells across my active worksheet whenever these are edited. Instead of having the same macro in place for every cell individually (makes the code very long and clumsy), I want to create a for loop which goes something like this:
for i="A10","A21","C3" ... etc
if target.address = "i" then
'execute macro
end if
I'm not quite sure how to do this... maybe another way would be a better option?
I'd really appreciate your help in the matter - thank you very much indeed.
You can use the Worksheet_Change event. Below is sample code. You need to put the code on the sheet code section
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Dim rng As Range
Set rng = Range("A1:B5")
' If there is change in this range
If Not Intersect(rng, Target) Is Nothing Then
MsgBox Target.Address & " range is edited"
' you can do manipulation here
End If
Application.EnableEvents = True
End Sub
You can use the Worksheet_Change event to capture the edits. See http://msdn.microsoft.com/en-us/library/office/ff839775.aspx.
The event body receives a Range object that represents the modified cells. You can then use Application.Intersect to determine if one of your target cells is in the modified range.