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.
Related
I've read various posts about using VBA to prevent format changes to spreadsheets caused by the user pasting data.
I didn't like the approaches binding CTRL+V to a macro because the user may not use the shortcut, and some examples had the effect of pasting into any cell that was subsequently clicked on.
I've had most success with the code from https://www.mrexcel.com/board/threads/vba-for-pastespecial-values-only.355553/ which uses the undo function. This works (although it's a little slow) for copy and paste within the workbook, and with single or multiple cells, but not from other instances of Excel or other programs.
I found Excel vba paste special method fails whenever i try to paste which works with pastes into a single cell.
Is there a way to combine the two and achieve the aim of preventing paste from any source changing cell(s) formatting?
I think I've written something useful that seems to work well for me.
I didn't implement your suggestion as it seemed fairly complex, and I found another forum post with a slightly different form of the 'undo' based scripts that worked well for copy and paste of text or single cells, or ranges from within an instance of excel or from external programs. I've combined that with the more common version I'd found which deals with 'Auto Fill' events too, and I've added some other little enhancements.
I've acknolwedged the other forum posts which I used too. Here it is in case it is of use to other people. I'm not an expert and I expect the code could be improved, but it does what I need.
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
'Prevents user changing cell formatting by undoing the paste and pasting the value only without formatting
'Macro restricted to run on paste events to prevent the pasting of formats into the spreasheet, but not run on delete or type events
'Works with ranges and individual cells, both within the workbook Excel instance and from other instances or external programs
'A combination of:
'https://www.mrexcel.com/board/threads/force-paste-special-values.230718/
'https://stackoverflow.com/questions/45135039/excel-vba-code-to-force-values-only-paste-causes-strange-behavior-when-pasting-o
'And:
'https://answers.microsoft.com/en-us/msoffice/forum/all/how-do-you-lock-formats-but-allow-data-entry/afccc43e-e191-417f-826c-d10a464a1b9a?page=4
'A disadvantage of many macros, including this one, is that the undo history is cleared
'Macro can be disabled for spreadsheet developement by changing the Devel value used in the first IF statement of the macro
Dim Devel As Boolean
Devel = "False" 'Remember to set back to False after spreadsheet development work
If Devel = "False" Then
'In normal user mode, prevent pastes etc from changing formatting
Dim SavedVal As Variant
Dim UndoString As String
Dim srce As Range
On Error GoTo ErrHan
'Detect 'Paste' and 'Auto Fill' events in the undo list and only continue macro if these are found
UndoString = Application.CommandBars("Standard").Controls("&Undo").List(1) 'This always gives an error on spreadsheet open as the undo list is empty, but the error handling deals with it
If Left(UndoString, 5) <> "Paste" And UndoString <> "Auto Fill" Then
Exit Sub
End If
'Save the pasted value for later. This form and the use of type Variant is important in the flexibility of this macro
SavedVal = Target.Value
'Switch off events to prevent infinite loop
Application.EnableEvents = False
'Switch off screen updates
Application.ScreenUpdating = False
'Undo the user's paste
Application.Undo
'Handle 'Auto Fill' events differently. These can use the Target.PasteSpecial approach
If UndoString = "Auto Fill" Then
Set srce = Selection
srce.Copy
Target.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
Application.SendKeys "{ESC}"
Union(Target, srce).Select
Else
'Set target value. This form works with all selected data which the Target.PasteSpecial approaches don't
Target.Value = SavedVal
End If
Else
'In Devel mode so the developer can unlock the spreadsheet and change formats when pasting
End If
ErrExit:
'Remember to re-enable events
Application.EnableEvents = True
'Re-enable screen updates
Application.ScreenUpdating = True
Exit Sub
ErrHan:
Resume ErrExit
End Sub
This works perfectly, but I only want it to reference a single sheet instead of every sheet in the workbook
Private Sub Worksheet_Change()
Dim LastRow As Long, c As Range
Application.EnableEvents = False
LastRow = Cells(Cells.Rows.Count, "AA120").End(xlUp).Row
On Error Resume Next
For Each c In Range("AA5:AA120" & LastRow)
If c.Value = "0" Then
c.EntireRow.Hidden = True
ElseIf c.Value > "0" Then
c.Activate
c.EntireRow.Hidden = False
End If
Next
On Error GoTo 0
Application.EnableEvents = True
End Sub
As i can see you only need to insert this code in one sheet which ever you want. Rightclick on sheet name(tab) and select "view code", then paste this code. BUT! If this runs on every sheet, then you also need to delete code from "This Workbook" --> (Alt+F11) and select "This workbook", then erase this code from there.
You have to use
Private Sub Worksheet_Change(ByVal Target As Range)
instead.
According to the great Chip Pearson, "The events and their procedure declarations are fixed. You must not alter the name or parameter list of an event procedure." (I do not have a system with Office to test it).
You would insert your Sub in a sheet module, and it will work only on that sheet.
I have used an Excel macro that automatically pastes all data as values, by undoing the pasting and pasting it as value.
This is the 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
I am using the English Excel version where it works fine, however when other users, who e.g. have a German Excel version paste something, they get a procedure error in this line:
UndoList = Application.CommandBars("Standard").Controls("&Undo").List(1)
I guess the Undo command is named differently in German.
Is there a way to define the Undo list independent of the language the user is using?
Use the Control's Id number instead.
debug.Print Application.CommandBars("Standard").Controls("&Undo").Id
=> 128
debug.Print Application.CommandBars("Standard").FindControl(Id:=128).caption
=> &Undo
You should also substitute in the code the words "Paste", "Auto fill" and "Text" for your local language words.
Use the index as name:
undoname = Application.CommandBars("Standard").FindControl(ID:=128).Index
UndoList = Application.CommandBars("Standard").Controls(undoname).List(i)
Very useful code. Thank you.
Can do this also:
UndoList = Application.CommandBars("Standard").FindControl(ID:=128).List(1)
I have the following senario: whenever the user selects a certain cell, I'm copying a table from a hidden sheet to the active sheet. Then, when the user changes his selection, I need to clear the contents of the copied table and copy another table from the hidden sheet.
To copy the tables from the hidden sheet I'm using:
source.Cells(leftRow, leftCol).CurrentRegion.Copy target.Range("A1")
The problem is that this action seems to cause selectionChanged to get fired again, which triggers my ClearContents command.
Is there a way to use this command without getting selectionChanged fired?
Thanks,
Li
Use Application.EnableEvents = False.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
On Error GoTo Whoa
Application.EnableEvents = False
'
'~~> YOUR CODE
'
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
i have an excel with 75 columns and some thousands of rows of data. Out of 75 columns I am using 5 columns for my vba coding purpose. These 5 columns hold flags (either 0 or 1) based on which I am locking the cells in the corresponding row (Flags are coming from Database). As user doesn't want these flag columns I just hid those columns but when ever user tries to copy data from my workbook to another workbook user is able to copy the hidden columns which client doesn't want.
So is there anyway to restrict them not to copy the hidden columns through VBA or with any setting? Actually for this issue what I thought is like on key press of Ctrl + C, I tried to change the Selection.Copy as Selection.Range.SpecialCells(xlCellTypeVisible). But I am getting some error like wrong number of arguments or invalid property assignment.
The lines of code is
Private Sub Workbook_Open()
Application.OnKey "^c", "Copy"
End Sub
Sub Copy()
If Selection Is Nothing Then
Else
Selection.Copy = Selection.Range.SpecialCells(xlCellTypeVisible)
End If
End Sub
Any ideas to restrict users not to copy the hidden columns. Any help would be appreciated greatly.
Try this
Sub Copy()
Dim rng As Range
On Error GoTo Whoa
If Not Selection Is Nothing Then
Set rng = Selection.Cells.SpecialCells(xlCellTypeVisible)
rng.Copy
End If
LetsContinue:
Exit Sub
Whoa:
MsgBox Err.Description, vbCritical, "Error Number : " & Err.Number
Resume LetsContinue
End Sub
Note: I have used Error Handling which is a must because the user might select non contiguous ranges and the code will break if the error handling is not done :) See Screenshot below