Drag and Drop Pseudo-Event !

Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
9,779
Office Version
  1. 2016
Platform
  1. Windows
Motivated by the question posted here , I have managed (I hope) to simulate a Before drop event for cells/ranges

Workbook Example

The signature of the pseudo event is written in line with the standard office events format for an easy and a more intuitive use .. setting the Cancel argument to True simply aborts the Drop operartion

Public Sub Cells_BeforeDrop _
(ByVal SourceRange As Range, ByVal DestRange As Range, ByRef Cancel As Boolean)

The above event handler procedure MUST reside in the ThisWorkbook module and MUST be Public

Known limitations:

1- Works only in 32bit systems ( code needs updating for 64bits)
2- Event doesn't work when dragging cells accross different workbooks and/or windows
3- Event doesn't fire when overriding existing data in the drop range (still working on this)

A Word Of Caution:
Because the code makes use of APIs and subclasses the CLIPBRDWNDCLASS window , any compile error inside the event procedure will crash excel ! .. so save your work often when editing/experimenting with the event code.

1- This code goes in a standard module :
Code:
Option Explicit

Private Type POINTAPI
  X As Long
  Y As Long
End Type

Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Private Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long

Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, _
ByVal lpsz1 As String, ByVal lpsz2 As String) As Long

Private Declare Function IsWindowVisible Lib "user32" _
(ByVal hwnd As Long) As Long

Private Declare Function SetWindowLong Lib "user32" _
Alias "SetWindowLongA" _
(ByVal hwnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long
 
Private Declare Function CallWindowProc Lib "user32" _
Alias "CallWindowProcA" _
(ByVal lpPrevWndFunc As Long, _
ByVal hwnd As Long, _
ByVal Msg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
 
Private Declare Function SendMessage Lib "user32" _
 Alias "SendMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
 
Private Declare Function SetProp Lib "user32" Alias "SetPropA" _
(ByVal hwnd As Long, _
ByVal lpString As String, _
ByVal hData As Long) As Long

Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" _
(ByVal hwnd As Long, _
ByVal lpString As String) As Long

Private Declare Function GetProp Lib "user32" Alias "GetPropA" _
(ByVal hwnd As Long, _
ByVal lpString As String) As Long

Private Declare Function GetCursorPos Lib "user32" _
(lpPoint As POINTAPI) As Long

 Private Declare Function SetTimer Lib "user32" _
(ByVal hwnd As Long, ByVal nIDEvent _
As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long

Private Declare Function KillTimer Lib "user32" _
(ByVal hwnd As Long, ByVal nIDEvent As Long) As Long

Private Declare Function GetDC Lib "user32" _
    (ByVal hwnd As Long) As Long
 
Private Declare Function GetDeviceCaps Lib "gdi32" _
    (ByVal hdc As Long, _
    ByVal nIndex As Long) As Long
 
Private Declare Function ReleaseDC Lib "user32" _
    (ByVal hwnd As Long, _
    ByVal hdc As Long) As Long
    
Private Declare Sub ClipCursor Lib "user32" (lpRect As Any)
    
Private Declare Function InvalidateRect Lib "User32.dll" _
(ByVal hwnd As Long, _
ByVal lpRect As Long, _
ByVal bErase As Long) As Long
    
Private Declare Function GetSystemMetrics Lib "user32" _
(ByVal nIndex As Long) As Long
    
Private Const LOGPIXELSX As Long = 88
Private Const LOGPIXELSY As Long = 90
Private Const PointsPerInch = 72
Private Const WM_SETREDRAW = &HB
Private Const SM_CXSCREEN = 0
Private Const SM_CYSCREEN = 1
Private Const WM_DESTROYCLIPBOARD = &H307
Private Const GWL_WNDPROC As Long = (-4)

Private RowOffset As Long
Private ColOffset As Long

Sub Start_Drag_And_Drop_Hook()
    Dim oldProcAddr As Long
    Dim lClipBrdWndHwnd As Long
    Dim Wbhwnd As Long
    If GetProp(Application.hwnd, "oldProcAddr") = 0 Then
        SetTimer Application.hwnd, 0, 1, AddressOf TimerProc
        Wbhwnd = FindWindowEx(Application.hwnd, 0, "XLDESK", vbNullString)
        Wbhwnd = FindWindowEx(Wbhwnd, 0, "EXCEL7", vbNullString)
        SetProp Application.hwnd, "WbHwnd", Wbhwnd
        lClipBrdWndHwnd = FindWindow("CLIPBRDWNDCLASS", vbNullString)
        SetProp Application.hwnd, "ClipBrdHwnd", lClipBrdWndHwnd
        oldProcAddr = SetWindowLong _
        (lClipBrdWndHwnd, GWL_WNDPROC, AddressOf WindowProc)
        SetProp Application.hwnd, "oldProcAddr", oldProcAddr
    End If
End Sub
 
 Sub Stop_Drag_And_Drop_Hook()
    SetWindowLong GetProp(Application.hwnd, "ClipBrdHwnd"), _
    GWL_WNDPROC, GetProp(Application.hwnd, "oldProcAddr")
    KillTimer Application.hwnd, 0
    RemoveProp Application.hwnd, "oldProcAddr"
    RemoveProp Application.hwnd, "ClipBrdHwnd"
    RemoveProp Application.hwnd, "WbHwnd"
    RemoveProp Application.hwnd, "EXCELF"
    RemoveProp Application.hwnd, "EXCELFvisible"
End Sub

Private Function WindowProc _
(ByVal hwnd As Long, ByVal uMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
    Dim tMousePtr As POINTAPI
    Dim oDropRange As Range
    Dim bCancel As Boolean
    
    On Error Resume Next
    If Application.CutCopyMode = False Then Exit Function
        Select Case uMsg
        Case WM_DESTROYCLIPBOARD
            If CBool(GetProp(Application.hwnd, "EXCELFvisible")) Then
                GetCursorPos tMousePtr
                Set oDropRange = ActiveWindow.RangeFromPoint(tMousePtr.X, tMousePtr.Y)
                With oDropRange
                    If .Column <= ColOffset Then
                        Set oDropRange = Range(Cells(.Row - RowOffset, 1), _
                        Cells(.Row + (Selection.Rows.Count - RowOffset) - 1, Selection.Columns.Count))
                    End If
                    If .Row <= RowOffset Then
                        Set oDropRange = Range(Cells(1, .Column - ColOffset), _
                        Cells(Selection.Rows.Count, .Column + (Selection.Columns.Count - ColOffset) - 1))
                    End If
                    If .Column <= ColOffset And .Row <= RowOffset Then
                        Set oDropRange = Range(Cells(1, 1), _
                        Cells(Selection.Rows.Count, Selection.Columns.Count))
                    End If
                    Set oDropRange = _
                    .Offset(-RowOffset, -ColOffset).Resize(Selection.Rows.Count, Selection.Columns.Count)
                End With
                Call ThisWorkbook.Cells_BeforeDrop(Selection, oDropRange, bCancel)
                If bCancel Then
                    Call SendMessage(GetProp(Application.hwnd, "WbHwnd"), WM_SETREDRAW, False, 0&)
                    Application.EnableEvents = False
                    SetTimer GetProp(Application.hwnd, "ClipBrdHwnd"), 0, 1, AddressOf Undo
                Else
                    Application.EnableEvents = True
                End If
            End If
        End Select
    WindowProc = CallWindowProc _
    (GetProp(Application.hwnd, "oldProcAddr"), hwnd, uMsg, wParam, lParam)
End Function


Private Sub Undo()
    On Error Resume Next
    KillTimer GetProp(Application.hwnd, "ClipBrdHwnd"), 0
    Application.Undo
    Application.EnableEvents = True
    Call SendMessage(GetProp(Application.hwnd, "WbHwnd"), WM_SETREDRAW, True, 0&)
    InvalidateRect GetProp(Application.hwnd, "WbHwnd"), 0, 0
End Sub

Private Sub TimerProc()
    Dim tMousePtr As POINTAPI
    Dim tWbScreenArea As RECT
    Static tTopLeftCell As RECT
    Dim EXCELFhwnd As Long
    Dim SelRow As Long, selCol As Long
    Dim RangeUnderMouseCol As Long, RangeUnderMouseRow As Long
    Dim oDropRange As Range
    Dim bIsEXCELFvisible As Boolean

    On Error Resume Next
    SelRow = Selection.Row: selCol = Selection.Column
    GetCursorPos tMousePtr
   Set oDropRange = ActiveWindow.RangeFromPoint(tMousePtr.X, tMousePtr.Y)
    With oDropRange
        RangeUnderMouseCol = .Column
        RangeUnderMouseRow = .Row
        If .Row < SelRow Then SelRow = SelRow - 1
        If .Row > Selection.Rows.Count + SelRow - 1 Then SelRow = SelRow + 1
        If .Column < selCol Then selCol = selCol - 1
        If .Column > Selection.Columns.Count + selCol - 1 Then selCol = selCol + 1
        RowOffset = .Row - SelRow
        ColOffset = .Column - selCol
    End With
        tTopLeftCell = GetRangeRect(ActiveWindow.VisibleRange.Cells(1, 1))
    With tWbScreenArea
        .Left = tTopLeftCell.Left
        .Top = tTopLeftCell.Top
        .Right = GetSystemMetrics(SM_CXSCREEN)
        .Bottom = GetSystemMetrics(SM_CYSCREEN)
    End With
    EXCELFhwnd = FindWindow("EXCELF", vbNullString)
    SetProp Application.hwnd, "EXCELF", EXCELFhwnd
    SetProp Application.hwnd, "EXCELFvisible", IsWindowVisible(EXCELFhwnd)
    If IsWindowVisible(EXCELFhwnd) Then ClipCursor tWbScreenArea Else ClipCursor ByVal 0
End Sub

Private Function GetRangeRect(ByVal rng As Range) As RECT
    Dim OWnd  As Window
    Set OWnd = rng.Parent.Parent.Windows(1)
    With rng
        GetRangeRect.Left = PTtoPX(.Left * OWnd.Zoom / 100, 0) _
        + OWnd.PointsToScreenPixelsX(0)
        GetRangeRect.Top = PTtoPX(.Top * OWnd.Zoom / 100, 1) _
        + OWnd.PointsToScreenPixelsY(0)
        GetRangeRect.Right = PTtoPX(.Width * OWnd.Zoom / 100, 0) _
        + GetRangeRect.Left
        GetRangeRect.Bottom = PTtoPX(.Height * OWnd.Zoom / 100, 1) _
        + GetRangeRect.Top
    End With
End Function

Private Function PTtoPX _
(Points As Long, bVert As Boolean) As Long
    PTtoPX = Points * ScreenDPI(bVert) / PointsPerInch
End Function
Private Function ScreenDPI(bVert As Boolean) As Long
    Static lDPI(1), lDc
    If lDPI(0) = 0 Then
        lDc = GetDC(0)
        lDPI(0) = GetDeviceCaps(lDc, LOGPIXELSX)
        lDPI(1) = GetDeviceCaps(lDc, LOGPIXELSY)
        lDc = ReleaseDC(0, lDc)
    End If
    ScreenDPI = lDPI(Abs(bVert))
End Function

2- This code goes in the ThisWorkbook module :
Code:
Option Explicit

Private Sub Workbook_Open()
    Call Start_Drag_And_Drop_Hook
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
    [COLOR=#008000]'Caution: Not stopping the hook before closing will crash the application ![/COLOR]
    Call Stop_Drag_And_Drop_Hook
End Sub

[COLOR=#008000]'********************************************************
'Pseudo-Event to intercept cell drag and drop operations
'CAUTION : Any compiler error will crash the application !
'*********************************************************[/COLOR]
Public Sub Cells_BeforeDrop _
(ByVal SourceRange As Range, ByVal DestRange As Range, ByRef Cancel As Boolean)
    Dim sPromptMsg1 As String
    Dim sPromptMsg2 As String
    Dim sPromptMsg3 As String

    On Error GoTo Oops

    sPromptMsg1 = "You are about to drop the range : '"
    sPromptMsg2 = "onto the range : '"
    sPromptMsg3 = "Go ahead ? "
    
    If MsgBox(sPromptMsg1 & "'" & SourceRange.Address & "'" & vbCrLf & _
    sPromptMsg2 & DestRange.Address & "'" & vbCrLf & vbCrLf & sPromptMsg3, _
    vbYesNo + vbExclamation, "Before Drop Event") = vbNo Then
    
        Cancel = True
    
    End If
Exit Sub
Oops:
MsgBox Err.Description
End Sub

I have written and tested the code in xl2007 WinXP and works as expected
I would be grateful to hear from you any suggestions or problems encountered

Regards
 

Excel Facts

When they said...
When they said you are going to "Excel at life", they meant you "will be doing Excel your whole life".
This looks like a very useful event.
Unfortunately, I have not been able to get it working so far. Maybe, because I am on Microsoft® Excel® für Microsoft 365 MSO (Version 2207 Build 16.0.15427.20182) 64 Bit / Windows10 Pro 64bit.

Regarding the "known limitations":
1) I do not have a 32bit system available for testing, so I have tried to port the code to 64bit (see code below). The compiler is happy with it, but though the routine Start_Drag_And_Drop_Hook gets executed, the event never fires to call WindowProc. What could be wrong?
2) Good to know.
3) Are there any results from "still working on this"?

VBA Code:
Option Explicit

' https://www.mrexcel.com/board/threads/convert-32-bit-code-to-64-bit.1215754/#post-5942518
' https://www.cadsharp.com/docs/Win32API_PtrSafe.txt
Private Type POINTAPI
  X As Long
  Y As Long
End Type

Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Private Declare PtrSafe Function FindWindow Lib "user32" _
    Alias "FindWindowA" _
    (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr

Private Declare PtrSafe Function FindWindowEx Lib "user32" _
    Alias "FindWindowExA" _
    (ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr

Private Declare PtrSafe Function IsWindowVisible Lib "user32" _
    (ByVal hwnd As LongPtr) As Long

' Provided for reference only.  Please use the LongPtr versions instead.
Private Declare PtrSafe Function SetWindowLong Lib "user32" _
    Alias "SetWindowLongA" _
    (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare PtrSafe Function SetWindowLongPtr Lib "user32" _
    Alias "SetWindowLongPtrA" _
    (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As LongPtr) As LongPtr
    
 
Private Declare PtrSafe Function CallWindowProc Lib "user32" _
    Alias "CallWindowProcA" _
    (ByVal lpPrevWndFunc As LongPtr, ByVal hwnd As LongPtr, ByVal Msg As Long, _
     ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
 
Private Declare PtrSafe Function SendMessage Lib "user32" _
    Alias "SendMessageA" _
    (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
 
Private Declare PtrSafe Function SetProp Lib "user32" _
    Alias "SetPropA" _
    (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long

Private Declare PtrSafe Function RemoveProp Lib "user32" _
    Alias "RemovePropA" _
    (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr

Private Declare PtrSafe Function GetProp Lib "user32" _
    Alias "GetPropA" _
    (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr

Private Declare PtrSafe Function GetCursorPos Lib "user32" _
    (lpPoint As POINTAPI) As Long

Private Declare PtrSafe Function SetTimer Lib "user32" _
    (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr

Private Declare PtrSafe Function KillTimer Lib "user32" _
    (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long

Private Declare PtrSafe Function GetDC Lib "user32" _
    (ByVal hwnd As LongPtr) As LongPtr
 
Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" _
    (ByVal hdc As LongPtr, ByVal nIndex As Long) As Long
 
Private Declare PtrSafe Function ReleaseDC Lib "user32" _
    (ByVal hwnd As LongPtr, ByVal hdc As LongPtr) As Long
    
' sub -> Function
Private Declare PtrSafe Function ClipCursor Lib "user32" _
    (lpRect As Any) As Long
    
' dev!
Private Declare PtrSafe Function InvalidateRect Lib "User32.dll" _
    (ByVal hwnd As LongPtr, ByVal lpRect As Long, ByVal bErase As Long) As Long
    
Private Declare PtrSafe Function GetSystemMetrics Lib "user32" _
    (ByVal nIndex As Long) As Long
    
Private Const LOGPIXELSX As Long = 88
Private Const LOGPIXELSY As Long = 90
Private Const PointsPerInch = 72
Private Const WM_SETREDRAW = &HB
Private Const SM_CXSCREEN = 0
Private Const SM_CYSCREEN = 1
Private Const WM_DESTROYCLIPBOARD = &H307
Private Const GWL_WNDPROC As Long = (-4)

Private RowOffset As Long
Private ColOffset As Long

Sub Start_Drag_And_Drop_Hook()
    Dim oldProcAddr As LongPtr      '64bit
    Dim lClipBrdWndHwnd As LongPtr  '64bit
    Dim Wbhwnd As LongPtr           '64bit
    If GetProp(Application.hwnd, "oldProcAddr") = 0 Then
        SetTimer Application.hwnd, 0, 1, AddressOf TimerProc
        Wbhwnd = FindWindowEx(Application.hwnd, 0, "XLDESK", vbNullString)
        Wbhwnd = FindWindowEx(Wbhwnd, 0, "EXCEL7", vbNullString)
        SetProp Application.hwnd, "WbHwnd", Wbhwnd
        lClipBrdWndHwnd = FindWindow("CLIPBRDWNDCLASS", vbNullString)
        SetProp Application.hwnd, "ClipBrdHwnd", lClipBrdWndHwnd
        oldProcAddr = SetWindowLongPtr(lClipBrdWndHwnd, GWL_WNDPROC, AddressOf WindowProc)   ' 64bit
        SetProp Application.hwnd, "oldProcAddr", oldProcAddr
    End If
End Sub
 
 Sub Stop_Drag_And_Drop_Hook()
    SetWindowLongPtr GetProp(Application.hwnd, "ClipBrdHwnd"), GWL_WNDPROC, GetProp(Application.hwnd, "oldProcAddr")    ' 64bit
    KillTimer Application.hwnd, 0
    RemoveProp Application.hwnd, "oldProcAddr"
    RemoveProp Application.hwnd, "ClipBrdHwnd"
    RemoveProp Application.hwnd, "WbHwnd"
    RemoveProp Application.hwnd, "EXCELF"
    RemoveProp Application.hwnd, "EXCELFvisible"
End Sub

Private Function WindowProc _
(ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As LongPtr ' 64bit
    Dim tMousePtr As POINTAPI
    Dim oDropRange As Range
    Dim bCancel As Boolean
    
    On Error Resume Next
    If Application.CutCopyMode = False Then Exit Function
        Select Case uMsg
        Case WM_DESTROYCLIPBOARD
            If CBool(GetProp(Application.hwnd, "EXCELFvisible")) Then
                GetCursorPos tMousePtr
                Set oDropRange = ActiveWindow.RangeFromPoint(tMousePtr.X, tMousePtr.Y)
                With oDropRange
                    If .Column <= ColOffset Then
                        Set oDropRange = Range(Cells(.Row - RowOffset, 1), _
                        Cells(.Row + (Selection.Rows.Count - RowOffset) - 1, Selection.Columns.Count))
                    End If
                    If .Row <= RowOffset Then
                        Set oDropRange = Range(Cells(1, .Column - ColOffset), _
                        Cells(Selection.Rows.Count, .Column + (Selection.Columns.Count - ColOffset) - 1))
                    End If
                    If .Column <= ColOffset And .Row <= RowOffset Then
                        Set oDropRange = Range(Cells(1, 1), _
                        Cells(Selection.Rows.Count, Selection.Columns.Count))
                    End If
                    Set oDropRange = _
                    .Offset(-RowOffset, -ColOffset).Resize(Selection.Rows.Count, Selection.Columns.Count)
                End With
                Call ThisWorkbook.Cells_BeforeDrop(Selection, oDropRange, bCancel)
                If bCancel Then
                    Call SendMessage(GetProp(Application.hwnd, "WbHwnd"), WM_SETREDRAW, False, 0&)
                    Application.EnableEvents = False
                    SetTimer GetProp(Application.hwnd, "ClipBrdHwnd"), 0, 1, AddressOf Undo
                Else
                    Application.EnableEvents = True
                End If
            End If
        End Select
    WindowProc = CallWindowProc _
    (GetProp(Application.hwnd, "oldProcAddr"), hwnd, uMsg, wParam, lParam)
End Function


Private Sub Undo()
    On Error Resume Next
    KillTimer GetProp(Application.hwnd, "ClipBrdHwnd"), 0
    Application.Undo
    Application.EnableEvents = True
    Call SendMessage(GetProp(Application.hwnd, "WbHwnd"), WM_SETREDRAW, True, 0&)
    InvalidateRect GetProp(Application.hwnd, "WbHwnd"), 0, 0
End Sub

Private Sub TimerProc()
    Dim tMousePtr As POINTAPI
    Dim tWbScreenArea As RECT
    Static tTopLeftCell As RECT
    Dim EXCELFhwnd As LongPtr               ' 64bit
    Dim SelRow As Long, selCol As Long
    Dim RangeUnderMouseCol As Long, RangeUnderMouseRow As Long
    Dim oDropRange As Range
    Dim bIsEXCELFvisible As Boolean

    On Error Resume Next
    SelRow = Selection.Row: selCol = Selection.Column
    GetCursorPos tMousePtr
   Set oDropRange = ActiveWindow.RangeFromPoint(tMousePtr.X, tMousePtr.Y)
    With oDropRange
        RangeUnderMouseCol = .Column
        RangeUnderMouseRow = .Row
        If .Row < SelRow Then SelRow = SelRow - 1
        If .Row > Selection.Rows.Count + SelRow - 1 Then SelRow = SelRow + 1
        If .Column < selCol Then selCol = selCol - 1
        If .Column > Selection.Columns.Count + selCol - 1 Then selCol = selCol + 1
        RowOffset = .Row - SelRow
        ColOffset = .Column - selCol
    End With
        tTopLeftCell = GetRangeRect(ActiveWindow.VisibleRange.Cells(1, 1))
    With tWbScreenArea
        .Left = tTopLeftCell.Left
        .Top = tTopLeftCell.Top
        .Right = GetSystemMetrics(SM_CXSCREEN)
        .Bottom = GetSystemMetrics(SM_CYSCREEN)
    End With
    EXCELFhwnd = FindWindow("EXCELF", vbNullString)
    SetProp Application.hwnd, "EXCELF", EXCELFhwnd
    SetProp Application.hwnd, "EXCELFvisible", IsWindowVisible(EXCELFhwnd)
    If IsWindowVisible(EXCELFhwnd) Then ClipCursor tWbScreenArea Else ClipCursor ByVal 0
End Sub

Private Function GetRangeRect(ByVal rng As Range) As RECT
    Dim OWnd  As Window
    Set OWnd = rng.Parent.Parent.Windows(1)
    With rng
        GetRangeRect.Left = PTtoPX(.Left * OWnd.Zoom / 100, 0) _
        + OWnd.PointsToScreenPixelsX(0)
        GetRangeRect.Top = PTtoPX(.Top * OWnd.Zoom / 100, 1) _
        + OWnd.PointsToScreenPixelsY(0)
        GetRangeRect.Right = PTtoPX(.Width * OWnd.Zoom / 100, 0) _
        + GetRangeRect.Left
        GetRangeRect.Bottom = PTtoPX(.Height * OWnd.Zoom / 100, 1) _
        + GetRangeRect.Top
    End With
End Function

Private Function PTtoPX _
(Points As Long, bVert As Boolean) As Long
    PTtoPX = Points * ScreenDPI(bVert) / PointsPerInch
End Function
Private Function ScreenDPI(bVert As Boolean) As Long
    Static lDPI(1), lDc
    If lDPI(0) = 0 Then
        lDc = GetDC(0)
        lDPI(0) = GetDeviceCaps(lDc, LOGPIXELSX)
        lDPI(1) = GetDeviceCaps(lDc, LOGPIXELSY)
        lDc = ReleaseDC(0, lDc)
    End If
    ScreenDPI = lDPI(Abs(bVert))
End Function
 
Upvote 0
Thanks traveler4 for the interest and for updating the code.

I did a quick test and, like you said, it doesn't work on excel 64bit.

To be honest, I wouldn't make the effort to update this particular code to work in x64 because it is very unstable. I don't like it since it uses subclassing which will inevitable crash excel at some point or another. I would limit subclassing to modal userforms only.

Maybe a more stable alternative would be setting up a mouse hook in a remote workbook opened in a separate hidden excel application. I have successfully used this trick before

There is also another low level technique called API hooking (api redirection) which redirects api calls to our cutom user functions... Using this technique, we can hijack the GetClipboardData api function hence intercepting the missing DragNDrop as well as the worksheet Paste events and handling those two actions before they actually happen. This is advanced stuff but very neat.
 
Upvote 0
Thank you @Jaafar Tribak for the speedy and substantial reply.
OK, I understand regarding instability.
The "trick" used before is interesting, however requiring an additional addin makes handling difficult if you have to do with Excel-unexperienced users.
Even more interesting sounds the API hooking/redirection technique. Do I understand correctly that this allows to "steal away" the events and thus make them invisible to e.g. Excel? So then, in a DragNDrop-event we could prevent that Excel does what it normally does (i.e. cut cells from the origin and paste them to the target destination)? Is there some example around where one can see how such advanced stuff can be implemented?
 
Upvote 0
... however requiring an additional addin makes handling difficult if you have to do with Excel-unexperienced users.
We can actually have the remote hooking code without needing an addin at all... The code can be located in a normal remote workbook automatically created on the fly behind the scenes without requiring any user intervention.
Do I understand correctly that this allows to "steal away" the events and thus make them invisible to e.g. Excel? So then, in a DragNDrop-event we could prevent that Excel does what it normally does (i.e. cut cells from the origin and paste them to the target destination)? Is there some example around where one can see how such advanced stuff can be implemented?
Yes. In theory, that's what api redireting should accomplish.

There is this code snippet I got sometime ago from an advanced vb6\asm user (The trick) . That code does work for trapping paste and drag-drop operations . It works although I don"t really fully understand exactly how since it takes some understanding of what goes on deep under the hood memory-wise.

Also, the code is not completed yet as it requires some tweaking to make it stable\safe. I never got around finishing it but I will do so at some point and then I will post back if anything good comes up.
 
Upvote 0

Forum statistics

Threads
1,223,276
Messages
6,171,140
Members
452,381
Latest member
Nova88

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top