I have been working on debugging this code and I am running into an issue where the code runs fine (no errors) but the changes it is supposed to be making aren't happening in the actual sheet.
What the code is supposed to do:
The goal is to be able to check on saving if any cells have been changed. If they have, it locks all cells with non-blank values and protects the sheet to avoid having those cells edited in future instances.
Here is the code itself:
Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim sMSG As String
sMSG = "Saving the workbook will lock the cells you have entered data into." & vbLf
sMSG = sMSG & "Do you want to go ahead?"
If Not bRangeEdited Then GoTo Xit
If Not Me.ReadOnly Then
Sheet1.Unprotect "password"
With Sheet1.Range("A1:I20")
If MsgBox(sMSG, vbExclamation + vbYesNo) = vbNo Then
Cancel = True
GoTo Xit
End If
If .SpecialCells(xlCellTypeBlanks).Address <> .Address Then
.SpecialCells(xlCellTypeConstants).Locked = True
bRangeEdited = False
End If
End With
Sheet1.Protect Password:="password", UserInterFaceOnly:=True, DrawingObjects:=False, AllowFiltering:=True
End If
Xit:
End Sub
This is based on a common piece of code found on multiple forums which seems to work for everyone else. This code USED to work and then something broke it. I thought it was the Range being wrong (which I fixed) but that didn't solve the problem.
What I've tried:
Running the separate code lines in the Immediate window - everything runs properly
Stepping through the code with F8 and debug.print to check what is being pulled by .SpecialCells(xlCellTypeBlanks).Address - this pulls the entire input range every time, regardless of what is in the cells but pulls the correct range when run in the Immediate window
Stepping through also produces no errors and shows that every if and with statement is working correctly
Running the code from different locations (Sheet1, Module, ThisWorkbook) including separating into multiple subs across different locations - no change
Running the code on a brand-new test workbook with no other macros - still doesn't work
Different methods for locking the cells such as a loop through all cells in the range instead of using SpecialCells - didn't work
Even the Protect/Unprotect lines are not working which leads me to believe that the code is somehow disconnected from the worksheet itself.
Anyone have any ideas how that is possible? Or how to fix it?
I have a spreadsheet set up with large text entries stored for selective copy-and-paste into another application; I am trying to devise a simple way for the user to (selectively) copy the text from any cell into the Clipboard, so that they can easily paste it into another application; all the while protecting the integrity of the source material.
The following code WORKS as desired:
**Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim OutputObject As DataObject
Dim CellText As String
CellText = Target.Text
Set OutputObject = New DataObject
OutputObject.SetText CellText
OutputObject.PutInClipboard
End Sub**
However, the result of the double-click leaves the cellpointer inside the cell (in EDIT mode), which puts the contents of the cell at risk of being overwritten.
Inserting "CANCEL = True" should solve the problem by cancelling the effects of the double-click, leaving the cell SELECTED but not in EDIT mode. However this results in the Clipboard being empty as nothing pastes into the outside application (Notepad).
The exact same code -- for some inexplicable reason -- DOES NOT work as expected within the Worksheet_SelectionChange(ByVal Target As Range) subroutine!?!?!
I have also tried creating an ActiveX button on the spreadsheet; the button uses relative direction logic to determine which cell's text to copy, but AGAIN ... nothing pastes into the outside application (Notepad).
Any thoughts?
Using SendKeys in an Event Procedure
SendKeys statement
You could add the following to your Worksheet BeforeDoubleClick event code.
The Code
With Application
.ScreenUpdating = False
.SendKeys "{ENTER}{UP}"
.ScreenUpdating = True
End With
I have a worksheet_change macro embedded in two sheets within my workbook. They are there to prevent anyone making changes to the sheets. However, I still want the data within the sheets to be refreshed every so often. This does not work.
Two sheets within the workbook are connected via a query to another workbook. Essentially those sheets are a copy of the sheets within the other workbook.
I have embedded Code1 into the two worksheets. This is to prevent anyone making changes to the worksheet but still allow them to view the sheet and copy data from it. It brings up an message box and then undoes the change made by the user. This works fine and I am happy with it.
At the same time I want to be able to refresh the workbook so that the connected sheets are up to date with respect to the other workbook that they are connected to.
To do this I have added a button into the workbook called "Refresh". This button calls Code2. This was done with the intention of disabling events so that the worksheet_change macro is paused to allow for the data to be refreshed.
However, this does not work as the worksheet_change macro still works. I.e after clicking the button, the workbook is refreshed and then any update is undone and the message box is displayed - which isn't what I need.
CODE1
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
' The variable KeyCells contains the cells that will
' cause an alert when they are changed.
Set KeyCells = Range("A1:Z1000")
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
With Application
.EnableEvents = False
.Undo
.EnableEvents = True
End With
' Display a message when one of the designated cells has been
' changed.
' Place your code here.
MsgBox "DO NOT MODIFY THIS SHEET - Any necessary modifications should be made in 'Master Invoice Template' and this sheet will automatically be updated!"
End If
End Sub
CODE2
Sub refresh()
On Error GoTo ErrorHandler
Application.EnableEvents = False
ThisWorkbook.RefreshAll
ErrorHandler:
Application.EnableEvents = True
End Sub
I have scoured the internet for a solution and pretty much everything that I find points me in the direction of enableevents=false, but as described in my post this does not work. Do I need to change the method of solving my problem or am I doing something wrong within my code?
I suspect the undo line of code is causing the problem, but I am not sure!
Any help would be greatly appreciated!
I think I have figured out what was wrong with the code; correct me if I am wrong. The data was taking too long to refresh when Code2 was ran. This meant that the Application.EnableEvents = Ture in Code2 took effect before the data could be fully refreshed and when it finally did complete its update, the Worksheet_Change event was triggered.
I tried using DoEvents after the RefreshAll command but this didn't work either. I have used what I found in this post to work around the problem and the refresh button now works!
Specifically the code that helped is below: I replaced Code2 with this:
Sub Refresh_All_Data_Connections()
For Each objConnection In ThisWorkbook.Connections
'Get current background-refresh value
bBackground = objConnection.OLEDBConnection.BackgroundQuery
'Temporarily disable background-refresh
objConnection.OLEDBConnection.BackgroundQuery = False
'Refresh this connection
objConnection.Refresh
'Set background-refresh value back to original value
objConnection.OLEDBConnection.BackgroundQuery = bBackground
Next
MsgBox "Finished refreshing all data connections"
End Sub
Please let me know if my logic in explaining why the code didn't work is correct - I am still new to VBA and would like to understand the problem fully!
I am relatively new to vba, so please be gentle :)
I have reviewed various scripts which supposedly preserve the formatting of cells on a spreadsheet when ctrl c/ctrl v or copy & paste is being used. Unfortunately I cannot seem to get any of the variations to work for my intentions. I suppose this might be due to the fact that a lot of the data being copy & pasted is being copied from other programs and pasted into the worksheet(therefore copying and keeping the formatting of the program from which it came). All of the macros I've tried to work with all seem to attempt to preserve formatting when copying between cells/worksheets or workbooks and doesn't address the data format when copying from another program.
I'm looking for an alternate approach. From a logical standpoint, I'm thinking that there should be a way on ctrl v or paste event, to have the copied data stored as a variable, stripped of its formatting and to only paste the raw value. I've tried playing around with pastespecial, but I'm not sure how to force a pastespecial (or replace paste with pastespecial).
Here is some code sample, but it doesn't seem to work for me. I keep getting:
cannot run the macro "C:...Test.xlsm'!MyPaste'. The macro may not be available in this workbook or all macros may be disabled
Macros are definitely enabled and the code is pasted into [ThisWorkbook(Code)]
Option Explicit
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim UndoList As String
Application.ScreenUpdating = False
Application.EnableEvents = False
On Error GoTo Whoa
'~~> Get the undo List to capture the last action performed by user
UndoList = Application.CommandBars("Standard").Controls("&Undo").List(1)
'~~> Check if the last action was not a paste nor an autofill
If Left(UndoList, 5) <> "Paste" And UndoList <> "Auto Fill" Then GoTo LetsContinue
'~~> Undo the paste that the user did but we are not clearing the clipboard
'~~> so the copied data is still in memory
Application.Undo
If UndoList = "Auto Fill" Then Selection.Copy
'~~> Do a pastespecial to preserve formats
On Error Resume Next
'~~> Handle text data copied from a website
Target.Select
ActiveSheet.PasteSpecial Format:="Text", Link:=False, DisplayAsIcon:=False
Target.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
On Error GoTo 0
'~~> Retain selection of the pasted data
Union(Target, Selection).Select
LetsContinue:
Application.ScreenUpdating = True
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume LetsContinue
End Sub
The reason for the error message is that your code is an event handler
see:
MSDN Office Online Reference: Creating VBA Macros to Manipulate Worksheets in Excel 2007
MSDN Office Online Reference: Running VBA Code When Events Occur in Excel 2010
and
Chip Pearson MVP and all round Excel Chieftan: Events And Event Procedures In VBA
Ozgrid MVP and all round Excel VBA Top Banana: Worksheet Events In Excel VBA
basically the Worksheet.Change Event (Excel) is fired when the user changes a cell in the worksheet. Excel passes in the Worksheet Object object as sh and the Range Object (Excel) as Target. Your code then uses these objects (Ozgrid Excel VBA Crash Course Lesson 4 - Common objects).
as David Zemens has suggested, you need to use the PasteSpecial method of the Sheet object. for further info, see MSDN libray: PasteSpecial Method [Excel 2003 VBA Language Reference].
and when you have finished all that reading, you will be ready to copy paste my code below:
Option Explicit
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim UndoList As String
Application.ScreenUpdating = False
Application.EnableEvents = False
On Error GoTo Whoa
'~~> Get the undo List to capture the last action performed by user
UndoList = Application.CommandBars("Standard").Controls("&Undo").List(1)
'~~> Check if the last action was not a paste nor an autofill
If Left(UndoList, 5) <> "Paste" And UndoList <> "Auto Fill" Then GoTo LetsContinue
'~~> Undo the paste that the user did but we are not clearing the clipboard
'~~> so the copied data is still in memory
Application.Undo
If UndoList = "Auto Fill" Then Selection.Copy
'~~> Do a pastespecial to preserve formats
On Error Resume Next
'~~> Handle text data copied from a website
Target.PasteSpecial Paste:=xlPasteValues
On Error GoTo 0
'~~> Retain selection of the pasted data
Target.Select
LetsContinue:
Application.ScreenUpdating = True
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume LetsContinue
End Sub
so, bada bing bada bing, you have your working code, and some reading that should help you to understand better what your code is doing and how it does it.
I'm using the following code to lock the content of certain cells
Sub LockCell(ws As Worksheet, strCellRng As String)
With ws
.Unprotect
.Cells.Locked = False
.Range(strCellRng).Locked = True
.Protect Contents:=True, AllowFormattingCells:=True, AllowFormattingColumns:=True, AllowFormattingRows:=True, AllowInsertingColumns:=True, AllowInsertingRows:=True, AllowSorting:=True, AllowFiltering:=True, AllowUsingPivotTables:=True, DrawingObjects:=True
End With
End Sub
It locks the content of those specific columns. The problem is users cannot sort, neither filter, nor apply borders to the cells since those Excel menu items are disabled.
I thought the AllowSorting:=True, AllowFiltering:=True and DrawingObjects:=True would allow that the same way the AllowFormattingColumns:=True and AllowFormattingRows:=True allowed resizing.
There are a number of people with this difficulty. The prevailing answer is that you can't protect content from editing while allowing unhindered sorting. Your options are:
1) Allow editing and sorting :(
2) Apply protection and create buttons with code to sort using VBA. There are other posts explaining how to do this. I think there are two methods, either (1) get the code to unprotect the sheet, apply the sort, then re-protect the sheet, or (2) have the sheet protected using UserInterfaceOnly:=True.
3) Lorie's answer which does not allow users to select cells (https://stackoverflow.com/a/15390698/269953)
4) One solution that I haven't seen discussed is using VBA to provide some basic protection. For example, detect and revert changes using Worksheet_Change. It's far from an ideal solution however.
5) You could keep the sheet protected when the user is selecting the data and unprotected when the user has the header is selected. This leaves countless ways the users could mess up the data while also causing some usability issues, but at least reduces the odds of pesky co-workers thoughtlessly making unwanted changes.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If (Target.row = HEADER_ROW) Then
wsMainTable.Unprotect Password:=PROTECTION_PASSWORD
Else
wsMainTable.Protect Password:=PROTECTION_PASSWORD, UserInterfaceOnly:=True
End If
End Sub
This was a major problem for me and I found the following link with a relatively simple answer. Thanks Voyager!!!
Note that I named the range I wanted others to be able to sort
Unprotect worksheet
Go to "Protection"--- "Allow Users to Edit Ranges" (if Excel 2007, "Review" tab)
Add "New" range
Select the range you want allow users to sort
Click "Protect Sheet"
This time, *do not allow users to select "locked cells"**
OK
http://answers.yahoo.com/question/index?qid=20090419000032AAs5VRR
I just came up with a tricky way to get almost the same functionality. Instead of protecting the sheet the normal way, use an event handler to undo anything the user tries to do.
Add the following to the worksheet's module:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Locked = True Then
Application.EnableEvents = False
Application.Undo
Application.EnableEvents = True
End If
End Sub
If the user does anything to change a cell that's locked, the action will get immediately undone. The temporary disabling of events is to keep the undoing itself from triggering this event, resulting in an infinite loop.
Sorting and filtering do not trigger the Change event, so those functions remain enabled.
Note that this solution prevents changing or clearing cell contents, but does not prevent changing formats. A determined user could get around it by simply setting the cells to be unlocked.
Here is an article that explains the problem and solution with alot more detail:
Sorting Locked Cells in Protected Worksheets
The thing to understand is that the purpose of locking cells is to prevent them from being changed, and sorting permanently changes cell values. You can write a macro, but a much better solution is to use the "Allow Users to Edit Ranges" feature. This makes the cells editable so sorting can work, but because the cells are still technically locked you can prevent users from selecting them.
I know this is super old, but comes up whenever I google this issue. You can unprotect the range as given in the above cells and then add data validation to the unprotected cells to reference something outrageous like "423fdgfdsg3254fer" and then if users try to edit any those cells, they will be unable to, but you're sorting and filtering will now work.
Lorie's answer is good, but if a user selects a range that contains locked and unlocked cells, the data in the locked/protected cells can be deleted.
Isaac's answer is great, but doesn't work if the user highlights a range that has both locked and unlocked cells.
I modified Isaac's code a bit to undo changes if ANY of the cells in the target range are locked. It also displays a message explaining why the action was undone. Combined with Lorie's answer, I was able to achieve the desired result of being able to sort/filter a protected sheet, while still allowing a user to make changes to an unprotected cell.
Follow the instructions in Lorie's answer, then put the following code in the worksheet module:
Private Sub Worksheet_Change(ByVal Target As Range)
For Each i In Target
If i.Locked = True Then
Application.EnableEvents = False
Application.Undo
Application.EnableEvents = True
MsgBox "Your action was undone because it made changes to a locked cell.", , "Action Undone"
Exit For
End If
Next i
End Sub
If the autofiltering is part of a subroutine operation, you could use
BioSum.Unprotect "letmein"
'<Your function here>
BioSum.Cells(1, 1).Activate
BioSum.Protect "letmein"
to momentarily unprotect the sheet, filter the cells, and reprotect afterwards.
This is a very old, but still very useful thread. I came here recently with the same issue. I suggest protecting the sheet when appropriate and unprotecting it when the filter row (eg Row 1) is selected. My solution doesn't use password protection - I don't need it (its a safeguard, not a security feature). I can't find an event handler that recognizes selection of a filter button - so I gave the instruction to my users to first select the filter cell then click the filter button. Here's what I advocate, (I only change protection if it needs to be changed, that may or may not save time - I don't know, but it "feels" right):
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Const FilterRow = 1
Dim c As Range
Dim NotFilterRow As Boolean
Dim oldstate As Boolean
Dim ws As Worksheet
Set ws = ActiveSheet
oldstate = ws.ProtectContents
NotFilterRow = False
For Each c In Target.Cells
NotFilterRow = c.Row <> FilterRow
If NotFilterRow Then Exit For
Next c
If NotFilterRow <> oldstate Then
If NotFilterRow Then
ws.Protect
Else
ws.Unprotect
End If
End If
Set ws = Nothing
End Sub
In Excel 2007, unlock the cells that you want enter your data into. Go to Review
> Protect Sheet
> Select Locked Cells (already selected)
> Select unlocked Cells (already selected)
> (and either) select Sort (or) Auto Filter
No VB required
I had a simular problem. I wanted the user to be able to filter "Table3" in a
protected worksheet. But the user is not able to edit the table. I accomplished above,
using the vba code below:
Range("Table3").Select
Selection.Locked = True
Selection.FormulaHidden = False
ActiveSheet.Protect DrawingObjects:=True, Contents:=True, Scenarios:=True _
, allowfiltering:=True
In the following code I filtered the code using VBA:
Range("Table3[[#Headers],[Aantal4]]").Select
ActiveSheet.ListObjects("Table3").Range.AutoFilter Field:=8, Criteria1:= _
Array("1", "12", "2", "24", "4", "6"), Operator:=xlFilterValues