Did you find something
helpful on my site? Consider a donation!
Heeft u iets gevonden waar u wat aan had? Overweeg dan een donatie!
Catching Paste Operations
Introduction
You know the situation: You have carefully setup a workbook with intricate
Validation schemes. But then along comes your user and he copies and pastes at
will. Result: Validation zapped, workbook structure violated.
What to do? The only way I find reliable is to catch all possible paste
operations. But this isn't very easy, since there are a zilion ways to paste:
Control+v
Control+Insert
Shift + Insert
Enter
And of course various menu items and toolbar buttons, which may be located
anywhere.
This article shows one way to intercept all these paste operations.
NB: as it is now, the article focuses on Excel 2000 to 2003. I will add the
needed steps for Excel 2007 later on.
Explanation Of Parts Of The Code
Catching Keyboard Shortcuts
The only way to catch paste keyboard shortcuts is by assigning them to your
own paste code. E.g. like this:
(the MyPasteValues routine is shown further below)
Catching Clicks On Toolbar And Menu Controls
To catch clicks on Commandbar controls, I used a class module in which a
variable is declared of type commandbarcontrol, using the WithEvents keyword.
Then an instance of this class module is created for each control I want
to intercept.
The class (named clsCommandBarCatcher),
contains this code:
'------------------------------------------------------------------------- ' Module : clsCommandBarCatcher ' Company : JKP Application Development Services (c) ' Author : Jan Karel Pieterse ' Created : 4-10-2007 ' Purpose : This class catches clicks on Excel's commandbars to be able to prevent pasting. '------------------------------------------------------------------------- OptionExplicit
PublicWithEvents oComBarCtl As Office.CommandBarButton
PrivateSub Class_Terminate() Set oComBarCtl = Nothing EndSub
And the code to create an instance of this class could look like this:
Dim oCtl As CommandBarButton Dim CCatcher As clsCommandBarCatcher Set oCtl = Application.CommandBars("Cells").FindControl(ID:=3185, recursive:=True) Set CCatcher = New clsCommandBarCatcher Set CCatcher.oComBarCtl = oCtl
To keep the instances of the classes "alive" I create a (module) collection
variable and add each instance to the collection.
The paste is handled by this routine:
PublicSub MyPasteValues() '------------------------------------------------------------------------- ' Procedure : EnableDisableControl ' Author : Jan Karel Pieterse www.jkp-ads.com ' Created : 24-9-2007 ' Purpose : Propriatary paste values routine called from control event ' handler in clsCommandBarCatcher and from various OnKey macros. '------------------------------------------------------------------------- If Application.CutCopyMode <> FalseThen If MsgBox("Normal paste operation has been disabled. You are about to Paste Values (cannot be undone), proceed?" & vbNewLine & "Tip: to be able to undo a paste, use the paste values button on the toolbar.", vbQuestion + vbOKCancel,
_ "CatchPaste Demo") = vbOK Then OnErrorResumeNext Selection.PasteSpecial Paste:=xlValues IsCellValidationOK Selection EndIf ElseIf Application.MoveAfterReturn Then OnErrorResumeNext SelectCase Application.MoveAfterReturnDirection Case xlUp ActiveCell.Offset(-1).Select Case xlDown ActiveCell.Offset(1).Select Case xlToRight ActiveCell.Offset(, 1).Select Case xlToLeft ActiveCell.Offset(, -1).Select End Select EndIf EndSub
Note that the code above also mimicks the MoveAfterReturn behaviour of Excel.
Cell Drag And Drop Mode
Dragging the fill handle is another way to wreck your validation. Hence this
is turned off too. Unfortunately changing this setting empties the clipboard, so
this setting cannot be toggled at will when switching workbooks. If you do turn
it on and off when switching to and from your workbook, copying and pasting from
another workbook to the one with the code becomes impossible.
Validation
The code I showed above redirects all paste operations to one routine, which
does a paste special, values. But pasting does not trigger any validation
checks. Also, there is no way of knowing what cells may be affected by the paste
before the actual paste has been done. Luckily, after the paste, the Selection
object is equal to the cells affected by the paste. Using the "Validation"
object of the Range object it is possible to check whether the cells adhere to
their validation settings. I created this function, to which I pass the range
that has been changed. The function returns False as soon as any cell violates
its validation rule:
PublicFunction IsCellValidationOK(oRange AsObject) AsBoolean '------------------------------------------------------------------------- ' Procedure : ValidateCells ' Author : Jan Karel Pieterse www.jkp-ads.com ' Created : 21-11-2007 ' Purpose : This routine checks if entries pasted into the cells in oRange ' are not violating a validation rule. ' Returns False if any cell's validation is violated '------------------------------------------------------------------------- Dim oCell As Range If TypeName(oRange) <> "Range" ThenExitFunction IsCellValidationOK = True ForEach oCell In oRange IfNot oCell.Validation IsNothingThen If oCell.HasFormula Then Else If oCell.Validation.Value = FalseThen IsCellValidationOK = False ExitFor EndIf EndIf EndIf Next If IsCellValidationOK = FalseThen MsgBox "Warning!!!" & vbNewLine & vbNewLine & _ "The paste operation has caused illegal entries to appear" & vbNewLine & _ "in one or more cells containing validation rules." & vbNewLine & vbNewLine & _ "Please check all cells you have just pasted " & vbNewLine & _ "into and correct any errors!", vbOKOnly + vbExclamation,
_ "CatchPaste Demo" oRange.Select EndIf EndFunction
Switching On And Off
Of course I want this paste restriction to work on just the workbook with the
code. This means some code in the ThisWorkbook module is needed, to be precise
in the Workbook_DeActivate and Workbook_Activate events. This is all code I have
in ThisWorkbook:
OptionExplicit
'Holds time of scheduled ontime macro '(Workbook_Before_Close and Workbook_Deactivate) Private mdNextTimeCatchPaste AsDouble
PrivateSub Workbook_Activate() CatchPaste EndSub
PrivateSub Workbook_BeforeClose(Cancel AsBoolean) StopCatchPaste mdNextTimeCatchPaste = Now 'Schedule
a macro to reinstate the catch in case closing is cancelled Application.OnTime mdNextTimeCatchPaste, "'" & ThisWorkbook.Name & "'!CatchPaste" Application.CellDragAndDrop = True EndSub
PrivateSub Workbook_Deactivate() StopCatchPaste OnErrorResumeNext 'Cancel scheduled macro's, 'because we
seem to be closing the file Application.OnTime mdNextTimeCatchPaste, "'" & ThisWorkbook.Name & "'!CatchPaste", , False EndSub
PrivateSub Workbook_Open() CatchPaste EndSub
Putting It All Together
The entire module that handles the initialisation and processes the paste
operation looks like this:
'------------------------------------------------------------------------- ' Module : modHandlePaste ' Author : Jan Karel Pieterse ' Created : 24-9-2007 ' Purpose : Module that ensures paste and paste formats is disabled '------------------------------------------------------------------------- OptionExplicit OptionPrivateModule
Dim mcCatchers As Collection
Sub CatchPaste() '------------------------------------------------------------------------- ' Procedure : CatchPaste ' Author : Jan Karel Pieterse www.jkp-ads.com ' Created : 24-9-2007 ' Purpose : This routine ensures all paste operations are redirected to our own. ' This way we avoid overwriting styles and validations. '------------------------------------------------------------------------- StopCatchPaste Set mcCatchers = New Collection 'Paste button AddCatch "Dummy", 22 'Paste button (with dropdown) EnableDisableControl 6002, False 'Paste Special button AddCatch "Dummy", 755 'Paste As Hyperlink button AddCatch "Dummy", 2787 'Paste Formats bottun AddCatch "Dummy", 369 'Insert Cut cells button AddCatch "Dummy", 3185 'Insert Copied Cells button AddCatch "Dummy", 3187 Application.OnKey "^v", "MyPasteValues" Application.OnKey "^{Insert}", "MyPasteValues" Application.OnKey "+{Insert}", "MyPasteValues" Application.OnKey "~", "MyPasteValues" Application.OnKey "{Enter}", "MyPasteValues"
'Changing the celldragdrop mode clears the clipboard. 'This means if you switch from another workbook back to this one, you would be unable to copy 'information and paste it into the template. This is why we do not reinstate the 'celldragdropmode when switching away from a B1 template and vice versa: switch it off 'when we return to a template. If Application.CellDragAndDrop Then 'If the user has manually changed this mode to true, the clipboard WILL be emptied due to the next line Application.CellDragAndDrop = False EndIf EndSub
Sub StopCatchPaste() '------------------------------------------------------------------------- ' Procedure : StopCatchPaste ' Author : Jan Karel Pieterse www.jkp-ads.com ' Created : 24-9-2007 ' Purpose : Resets the paste operations to their defaults '-------------------------------------------------------------------------
Dim lCount AsLong OnErrorResumeNext Set mcCatchers = Nothing EnableDisableControl 6002, True Application.OnKey "^v" Application.OnKey "^{Insert}" Application.OnKey "+{Insert}" Application.OnKey "~" Application.OnKey "{Enter}" 'Changing the celldragdrop mode clears the clipboard. 'This means if you switch from another workbook back to this one, you would be unable to copy 'information and paste it into the template. This is why we do not reinstate the 'celldragdropmode when switching away from a B1 template and vice versa: switch it off 'when we return to a template. 'Next line disabled for this reason!!! ' Application.CellDragAndDrop = True EndSub
Sub AddCatch(sCombarName AsString, lID AsLong) '------------------------------------------------------------------------- ' Procedure : AddCatch ' Author : Jan Karel Pieterse www.jkp-ads.com ' Created : 24-9-2007 ' Purpose : Adds a commandbarcontrol to be monitored '------------------------------------------------------------------------- Dim oCtl As CommandBarControl Dim cCatcher As clsCommandBarCatcher Dim oBar As CommandBar Set oCtl = Nothing On ErrorResumeNext Set oBar = Application.CommandBars(sCombarName) If oBar IsNothingThen Set oBar = Application.CommandBars.Add(sCombarName, , , True) oBar.Controls.Add ID:=lID oBar.Visible = True EndIf With oBar Set oCtl = .FindControl(ID:=lID, recursive:=True) If oCtl IsNothingThen Set oCtl = .Controls.Add(ID:=lID) EndIf EndWith 'Try Insert copied/cut cells separately through the cells shortcut menu If oCtl IsNothing And (lID = 3185 Or lID = 3187) Then Set oCtl = Application.CommandBars("Cell").FindControl(ID:=lID, recursive:=True) EndIf Set cCatcher = New clsCommandBarCatcher Set cCatcher.oComBarCtl = oCtl mcCatchers.Add cCatcher Set cCatcher = Nothing oBar.Delete Set oBar = Nothing EndSub
PrivateSub EnableDisableControl(lID AsLong, bEnable AsBoolean) '------------------------------------------------------------------------- ' Procedure : EnableDisableControl ' Author : Jan Karel Pieterse www.jkp-ads.com ' Created : 24-9-2007 ' Purpose : Enables or disables a specific control on all commandbars '-------------------------------------------------------------------------
Dim oBar As CommandBar Dim oCtl As CommandBarControl OnErrorResumeNext ForEach oBar In CommandBars Set oCtl = oBar.FindControl(ID:=lID, recursive:=True) IfNot oCtl IsNothingThen oCtl.Enabled = bEnable EndIf Next EndSub
PublicSub MyPasteValues() '------------------------------------------------------------------------- ' Procedure : EnableDisableControl ' Author : Jan Karel Pieterse www.jkp-ads.com ' Created : 24-9-2007 ' Purpose : Propriatary paste values routine called from control event ' handler in clsCommandBarCatcher and from various OnKey macros. '------------------------------------------------------------------------- If Application.CutCopyMode <> FalseThen If MsgBox("Normal paste operation has been disabled. You are about to Paste Values (cannot be undone), proceed?" & vbNewLine & "Tip: to be able to undo a paste, use the paste values button on the toolbar.", vbQuestion + vbOKCancel,
_ "CatchPaste Demo") = vbOK Then On ErrorResumeNext Selection.PasteSpecial Paste:=xlValues IsCellValidationOK Selection EndIf ElseIf Application.MoveAfterReturn Then On ErrorResumeNext SelectCase Application.MoveAfterReturnDirection Case xlUp ActiveCell.Offset(-1).Select Case xlDown ActiveCell.Offset(1).Select Case xlToRight ActiveCell.Offset(, 1).Select Case xlToLeft ActiveCell.Offset(, -1).Select End Select EndIf EndSub
PublicFunction IsCellValidationOK(oRange AsObject) AsBoolean '------------------------------------------------------------------------- ' Procedure : ValidateCells ' Author : Jan Karel Pieterse www.jkp-ads.com ' Created : 21-11-2007 ' Purpose : This routine checks if entries pasted into the cells in oRange ' are not violating a validation rule. ' Returns False if any cell's validation is violated '------------------------------------------------------------------------- Dim oCell As Range If TypeName(oRange) <> "Range" ThenExitFunction IsCellValidationOK = True ForEach oCell In oRange IfNot oCell.Validation IsNothingThen If oCell.HasFormula Then Else If oCell.Validation.Value = FalseThen IsCellValidationOK = False ExitFor EndIf EndIf EndIf Next If IsCellValidationOK = FalseThen MsgBox "Warning!!!" & vbNewLine & vbNewLine & _ "The paste operation has caused illegal entries to appear" & vbNewLine & _ "in one or more cells containing validation rules." & vbNewLine & vbNewLine & _ "Please check all cells you have just pasted " & vbNewLine & _ "into and correct any errors!", vbOKOnly + vbExclamation,
_ "CatchPaste Demo" oRange.Select EndIf EndFunction
Sample File
I prepared a sample
file so you can see how to put this all together.
Feedback
Since you have managed to reach the end of this article, maybe you would care
to write me a small message, expressing your opinion on this article?
Comment by: utham (1/28/2008 10:41:14 PM)can this been done without the help of VBAComment by: Jan Karel Pieterse (1/29/2008 3:39:55 AM)Hi utham,
No, I'm afraid not.Comment by: Brent Leslie (4/1/2008 2:28:47 AM)Hey Jan, this is an excellent article and solves a problem I have been thinking about for a while. Thanks for putting the effort into sorting this all out, it means I don't have to worry about it so much and can get on with the rest of our development.Comment by: Brent Leslie (4/1/2008 3:18:05 AM)One problem though - you don't declare the GSAPPNAME variable anywhere - I take it it's a global that normally holds the name of your application.
Otherwise, this works sensationally! I am even using it in Excel 07 without any problems.
Thanks again for saving me having to code this myself!Comment by: Jan Karel Pieterse (4/1/2008 4:26:03 AM)Hi brent,
Well spotted. I'll correct this.Comment by: Brent Leslie (4/1/2008 7:11:30 PM)Hi Jan,
No worries. One more potential improvement though I have made in my code which you may find useful.
I often used merged cells in my excel template. The "MyPasteValues" procedure uses the line "Selection.PasteSpecial Paste:=xlValues" which appears to not work for merged ranges. By "not work" I mean the value is not pasted into the merged cells.
Instead I have replaced this with the following:
If Selection.MergeCells = True Then
ThisWorkbook.Worksheets("SheetNameWithTempRange").Range("TempRange_SingleCell").PasteSpecial xlPasteValues
Range(Left(Selection.Address, InStr(1, Selection.Address, ":", vbTextCompare) - 1)).Value = _
ThisWorkbook.Worksheets("SheetNameWithTempRange").Range("TempRange_SingleCell").Value
Else
Selection.PasteSpecial Paste:=xlValues
End If
This checks whether the selected cell is merged. If it is not, the code simply works as you have above. If it is merged, the value of the clipboard range gets put into a temporary cell. The VALUE of this cell is then assigned to the VALUE of the selected merge cells. The IsCellValidationOK procedure then runs as per your code.
This indirection appears to allow merged cells to be able to obtain the copied value. This does have a small downside though - if the sheet is temporary range ("TempRange_SingleCell" in the above snippet) is not on the screen a small screen flicker occurs. This isn't a problem with my application so I am not finding a way to fix this (until a user complains!) but it could probably be fixed with a couple of Application.ScreenUpdating calls.
Thanks again!Comment by: Mike D (6/13/2008 7:56:10 AM)Jan,
I am a VBA novice so I applogize if this is a dumb question. I am trying to use
the CatchPasteDemo.xls file in Excel 2003, but I get an "Invalid procedure call or
argument (Error 5)" on the this line of code "Set oBar = Application.CommandBars
(sCombarName)" in the modHandlePaste module. Can yo tell how to fix this so the
demo will work. Thanks
Mike