Challenging Post - Override cell drag & drop behavior

  • Thread starter Thread starter Legacy 98055
  • Start date Start date
L

Legacy 98055

Guest
For example. Dragging c7 to e5.

My range before dragging and dropping...
b4Drag.JPG


After the drop.
afterDrag.JPG



What do you think? I think it can be done but do not want to waste time going about it the wrong way. Please give me any ideas on how you would approach this problem.

Some information to start with. The modal dialog that pops up can be dealt with.

This one.
modalReplaceCell.JPG


I can determine if I want to insert down or up. I am not concerned with shifting left or right at this point.

I can also determine the original address and destination address of the drag and drop operation by using the change event.

The ranges being dragged will be limited to a single cell.

Do you have any other suggestions?

I appreciate the input.
 
This seems to work for me

The code below seems to work.

It will be triggered if you cancel a drag and drop.


You may want to add a message box before the actual insert/copy/delete code triggers to allow for cancelling an uninteded drag and insert and to prevent the code from triggering if there's another time that the target and the selection are not the same.


Code:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Static DropTarget As Range
Static PerformPaste As Boolean


'when using drag and drop on ranges, the first time the selection
'change triggers, the target address and selection address don't
'match. As far as I can tell, this is the only time they don't
'match so it can be used to determine when a drag and drop is performed.


If Not Target.Address = Selection.Address Then
    'this is the drop target
    Set DropTarget = Target
    PerformPaste = True
    
Else
    'this is not the drop target
    If PerformPaste = True Then

        'insert row
        DropTarget.Insert Shift:=xlDown
        'copy target to drop target
        Target.Copy DropTarget.Offset(-1)
        'delete target
        Target.Delete Shift:=xlUp
        PerformPaste = False
    End If
End If


End Sub




Actually, looking at it again, you probably don't need the PerformPaste variable. Just change it so DropTarget is set to nothing after every paste and the meat of the code isn't triggered if DropTarget is nothing.
 
Upvote 0
OK, this didn't allow me to go to sleep... It was too tempting...

If you can add this part:
The modal dialog that pops up can be dealt with.
then the code below is all you need :-D
I'm sharing it, though it might contain too much lines or missing some details, but anyway now I'm happy enough to get some rest :-)

Code:
Option Explicit

'Erik Van Geit
'071015 0002

'Combination of WorksheetEvents to get the following drag&drop behaviour

'BEFORE
'  A  B
'1 A1 B1
'2 A2 B2
'3 A3 B3

'DRAG A3 to B2

'AFTER
'  A  B
'1 A1 B1
'2 A2 A3
'3    B2
'4    B3

'Only dragging single cell to different column

Dim trigger As Boolean
Dim flag As Boolean
Dim busy As Boolean

Private Sub Worksheet_Change(ByVal Target As Range)

    With Target
        If .Count = 1 And trigger Then
            If flag Then
            If busy Then Exit Sub
            busy = True
            Call MyDrag
            flag = False
            Else
            flag = True
            End If
        End If
    End With

End Sub

Private Sub Worksheet_SelectionChange(ByVal Target As Range)

    flag = False
    busy = False
    trigger = Target.Count = 1

End Sub

Sub MyDrag()
Dim DragAddress As String
Dim DropAddress As String

    With Application
    .EnableEvents = False
    .ScreenUpdating = False
    
    DropAddress = ActiveCell.Address
    .Undo
    DragAddress = ActiveCell.Address
    
        If Range(DropAddress).Column = Range(DragAddress).Column Then
        .Undo
        Else

            With Range(DropAddress)
            .Activate
            .Insert Shift:=xlDown
            .Offset(-1) = Range(DragAddress)
            End With
    
        Range(DragAddress).Delete Shift:=xlUp
        End If
        
    .ScreenUpdating = True
    .EnableEvents = True
    End With

'busy = False

End Sub
A checkbox or other control to enable/disable the activation of "MyDrag" can be added.

kind regardzzzzzzzsleeping,
Erik
 
Upvote 0
Ken and Eric. Thanks for the input but I already have the problem solved to the degree that you are providing advise. Were you able to download my file? I'll post the code.

I have the drag and drop working as I wish except for one little problem. You must click the mouse twice to drag the range. The drop is performed on mouse up. The project I am developing requires extensive repositioning of cells and cannot be automated. It is graphical and will vary with each session. Typical drag and drop behavior in most windows application is:

mouse down, mouse move, mouse up

I have it to:

mouse down, mouse up, mouse down, mouse move, mouse up

I can account for any other issues that I have thought of thus far such as conditionally shifting cells based upon the presence of data or lack thereof in the adjacent cells, dragging and dropping multiple adjacent selections, highlighting hovered-over ranges, ect...

I just do not know a good way to begin the drag on a single mouse click. As it stands, I am depending on the selection change event to begin the drag operation. Unfortunately, selection change does not fire until mouse up. Hence the requirement of the extra mouse click.

The method used below takes a snapshot of the selected range and assigns the picture to the picture property of an image control. The user then "drags" the image control to the drop point.

The first thing that comes to mind to eliminate the need for the extra click would be to "cover" the range with a transparent control and determine the range clicked over by way of this control's mouse down event. This would bypass the orksheet's selection change event. This idea is likely what I am going to go with Unless you guys have any other suggestions...

Thanks for the input so far...

Example containing the following code. DragDrop3.zip


[Code removed as it was changing page display~VP]
 
Upvote 0
1. but I already have the problem solved to the degree that you are providing advise.
2. Were you able to download my file? I'll post the code.
Tom,

2. Yes, I downloaded the file & looked a bit to the code.
1. I'm wondering if you tried my solution, because it performs the operation exactly as you describe in your initial post. So I wouldn't call it an "advise", because it "works", unless I'm missing something?

best regards,
Erik
 
Upvote 0
Re: Challenging Post - Override cell drag & drop behavio

Bump !



For example. Dragging c7 to e5.

My range before dragging and dropping...
http://home.fuse.net/tstom/b4Drag.JPG

After the drop.
http://home.fuse.net/tstom/afterDrag.JPG


What do you think? I think it can be done but do not want to waste time going about it the wrong way. Please give me any ideas on how you would approach this problem.

Some information to start with. The modal dialog that pops up can be dealt with.

This one.
http://home.fuse.net/tstom/modalReplaceCell.JPG

I can determine if I want to insert down or up. I am not concerned with shifting left or right at this point.

I can also determine the original address and destination address of the drag and drop operation by using the change event.

The ranges being dragged will be limited to a single cell.

Do you have any other suggestions?

I appreciate the input.

[Removed images, changed to links~VP]
 
Upvote 0
rafaaj,

I downloaded the file as well. I assumed that most of the code was related to something other than the drag and insert function. It seems to me that there must be a solution that is less complicated than the code you posted.
 
Upvote 0
Jaafar

Thanks for all of your efforts. Perhaps its the version difference you mentioned, but it did not work. Nevertheless, Excel did not blow up when I tried it. Previously, my single attempt at creating a mouse hook in Excel led me to stay away from them altogether but I am going to look into your code and see if I can adapt it.

The dragging behavior was irratic and sometimes the cursor became trapped in various postitions on the screen. Also, the shape of the dragged object did not maintain the same dimensions as the source range.

However, the actual dragging was done with a single click and, graphically, was very smooth. I'll dig into your code and get back to you.

Thanks again!



Eric.

2. Yes, I downloaded the file & looked a bit to the code.

The example I posted already provides for insertions.

1. I'm wondering if you tried my solution, because it performs the operation exactly as you describe in your initial post. So I wouldn't call it an "advise", because it "works", unless I'm missing something?

I did try your code and it works just as you stated. The only remaining question I had was posted before before you provided your example. Dragging with a single mouse down.

This is from my post with attached DragDrop3.xls
Here is what I have so far. The only problem that seems too much for me is to allow the drag and drop operation to begin with a single mouse click selection.

Yes. This is above and beyond the OP but is still well on topic and was posted a day before your reply.

Jaafar's example is on track as far as the behavior I will eventually get out of this. Mouse down, mouse move, mouse up. My example requires mouse down, mouse up, mouse down, mouse move, mouse up. Yours requires mouse down, mouse up, positioning the cursor on the range border, mouse down, mouse move, mouse up.

It may seem that I am straining at gnats here but that is what the customer ordered and what I told him I "could" do. I can gain access to Excel by way of VB Net and trap the window messages related to mouse events, but I am trying to contain the solution within the workbook.


Ken...
I downloaded the file as well. I assumed that most of the code was related to something other than the drag and insert function. It seems to me that there must be a solution that is less complicated than the code you posted.

Not likely. I mentioned a mouse hook in a previous post on this thread. For what I am trying to do, we are going to have to step outside of VBA to some extent.


I do appreciate your time and efforts! :)
 
Upvote 0
Hi Tom,

I don't get the probs you mentioned when running the workbook demo i posted.The cursor getting trapped on various positions on the screen was actually meant so the cells are not dragged outside the Columns A:B. you can easily edit the code to eliminate that behaviour.

I am getting an English version of XL2003 tomorrow and see if the code behaves differently from the French one.

I am curious to know if anybody else has tried the workbook demo below and see what they got.

Here is the workbook demo: http://www.savefile.com/files/1128145

and here is the code just for the record.


Code goes in a Standard module:


Code:
Option Explicit 

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

Private Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Integer) As Long 

Private Declare Function CloseClipboard Lib "user32" () As Long 

Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" _ 
(PicDesc As uPicDesc, RefIID As GUID, ByVal fPictureOwnsHandle As Long, _ 
IPic As IPicture) As Long 

'\\ Declare a UDT to store a GUID for the IPicture OLE Interface 
Private Type GUID 
    Data1 As Long 
    Data2 As Integer 
    Data3 As Integer 
    Data4(0 To 7) As Byte 
End Type 

'\\ Declare a UDT to store the bitmap information 
Private Type uPicDesc 
    Size As Long 
    Type As Long 
    hPic As Long 
    hPal As Long 
End Type 

Private Const CF_BITMAP = 2 
Private Const PICTYPE_BITMAP = 1 

'______________________________________________________________________________ 

Private Declare Function ClipCursor Lib "user32" (lpRect As Any) As Long 

Private Declare Sub ClipCursorClear Lib "user32" _ 
    Alias "ClipCursor" _ 
    (ByVal lpRect As Long) 
    
Private Declare Function GetDeviceCaps Lib "gdi32" ( _ 
  ByVal hDC As Long, ByVal nIndex As Long) As Long 
  
Private Declare Function GetDC Lib "user32" ( _ 
  ByVal hwnd As Long) As Long 
  
Private Declare Function ReleaseDC Lib "user32" ( _ 
  ByVal hwnd As Long, ByVal hDC As Long) As Long 
  
Private Const LOGPIXELSX As Long = 88 
Private Const LOGPIXELSY As Long = 90 
Private Const PointsPerInch = 72 

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 SetWindowsHookEx Lib _ 
"user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _ 
ByVal hmod As Long, ByVal dwThreadId As Long) As Long 

Private Declare Function CallNextHookEx Lib "user32" _ 
(ByVal hHook As Long, ByVal nCode As Long, ByVal wParam As Long, lParam As Any) As Long 

Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long 

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


Private Const HC_ACTION = 0 
Private Const WH_MOUSE_LL = 14 
Private Const WM_MOUSEMOVE = &H200 
Private Const WM_LBUTTONUP = &H202 
Private Const WM_LBUTTONDOWN = &H201 

Private hhkLowLevelMouse As Long 
Private blnHookEnabled As Boolean 
Private udtCursorPos As POINTAPI 

Private bButtonDown As Boolean 



Sub EnableDrag_Drop() 

    'change cursor to look like in drag&drop mode 
    Application.Cursor = xlNorthwestArrow 
    Call Hook_Mouse 

End Sub 



Sub DisableDrag_Drop() 

    'reset cursor to normal 
    Application.Cursor = xlDefault 
    ' reset mouse default 
    Call UnHook_Mouse 
    Range("BB1:BC20").Copy Range("A1") 
    Rows("21:1000").Delete Shift:=xlUp 
    Application.CutCopyMode = False 

End Sub 




Private Sub Hook_Mouse() 
  
    ' Prevent Hooking more than once 
    If blnHookEnabled = False Then 
       hhkLowLevelMouse = SetWindowsHookEx _ 
       (WH_MOUSE_LL, AddressOf LowLevelMouseProc, Application.Hinstance, 0) 
       ' set flag 
       blnHookEnabled = True 
    End If 
    
End Sub 

Private Sub UnHook_Mouse() 
  
    If hhkLowLevelMouse <> 0 Then UnhookWindowsHookEx hhkLowLevelMouse 
    'reset Flag 
    blnHookEnabled = False 
    
End Sub 


Private Function LowLevelMouseProc _ 
(ByVal nCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long 

    Static oCellToDrag As Range 
    Static dPosX As Double 
    Static dPosY As Double 
    Static dOldPosX As Double 
    Static dOldPosY As Double 
    
    'Prevent crashing XL in case of unhandled errors !!!!!!! 
    On Error Resume Next 
    If (nCode = HC_ACTION) Then 
        'when Mouse is moved 
        GetCursorPos udtCursorPos 
        ' store the cell under the mouse pointer 
        Set oCellToDrag = ActiveWindow.RangeFromPoint(udtCursorPos.x, udtCursorPos.y) 
        ' wParam holds the mouse current status 
        Select Case wParam 
        '********************************************************** 
            Case Is = WM_LBUTTONDOWN 
                ' set this flag 
                bButtonDown = True 
                ' as soon as the mouse left button is pressed 
                ' take a snapshot of the cell under the mouse pointer 
                ' and show the image control pop up 
                ' this fires before the sheet selection event 
                SaveRangePic oCellToDrag, "C:\MyRangePic.bmp" 
                With Sheets(1).Image1 
                    .Visible = True 
                    .Picture = LoadPicture("C:\MyRangePic.bmp") 
                    .AutoSize = True 
                    .Left = ActiveWindow.RangeFromPoint(udtCursorPos.x, udtCursorPos.y).Left 
                    .Top = ActiveWindow.RangeFromPoint(udtCursorPos.x, udtCursorPos.y).Top 
                End With 
                'cleanup file 
                Kill "C:\MyRangePic.bmp" 
                '********************************************************** 
            Case Is = WM_LBUTTONUP 
                ' reset flag 
                bButtonDown = False 
                ' avoid too much screen flickering 
                Application.ScreenUpdating = False 
                ' after the drop operation is over 
                ' we no longer need any cursor restriction 
                ClipCursorClear 0 
                Sheets(1).Unprotect 
                ' do the actual cells drag&drop here 
                Sheets(1).Image1.TopLeftCell.Insert Shift:=xlDown 
                oCellToDrag.Copy Destination:=Sheets(1).Image1.TopLeftCell.Offset(-1) 
                oCellToDrag.Delete xlUp 
                ' drog&drop over so hide the image control 
                Sheets(1).Image1.Visible = False 
                '************************************************************** 
            Case Is = WM_MOUSEMOVE 
                ' convert pixels to points 
                dPosX = udtCursorPos.x * 0.75 
                dPosY = udtCursorPos.y * 0.75 
                ' do nothing if mouse outside our named ranges : "RangeA" and "RangeB" 
                ' note that "RangeA" and "RangeB" are Columns A and B of Sheets(1) 
                If IsCellWithinRange(Sheets(1).Image1.TopLeftCell, Range("RangeA")) _ 
                Or IsCellWithinRange(Sheets(1).Image1.TopLeftCell, Range("RangeB")) Then 
                    'see if the mouse is moving while the left button is held down 
                    'ie: see if dragging is underway 
                    If bButtonDown Then 
                        ' if so,temporarly protect sheet 
                        ' this is to avoid unwanted selection of ceels while dragging is under way 
                        With Sheets(1) 
                            .EnableSelection = xlNoSelection 
                            .Protect Contents:=True, UserInterfaceOnly:=True 
                        End With 
                        ' now, adjust the pos of the image cntrl to follow the moving mouse pointer 
                        With Sheets(1).Image1 
                            .Left = (.Left) - (dOldPosX - dPosX) 
                            .Top = (.Top) - (dOldPosY - dPosY) 
                        End With 
                        ' this is to cater for fast dragging ! 
                        RestrictCursorToControl Sheets(1).Image1 
                    End If 
                Else 
                    ' if the mouse pointer is outside our named range hide image ctrl 
                    Sheets(1).Image1.Visible = False 
                    Sheets(1).Unprotect 
                End If 
                ' store previous mouse pos 
                dOldPosX = dPosX 
                dOldPosY = dPosY 
        End Select 
        Exit Function 
    End If 
    'Call next hook if any 
    LowLevelMouseProc = CallNextHookEx(0, nCode, wParam, ByVal lParam) 
    
End Function 


Private Sub SaveRangePic(ByVal SourceRange As Range, FilePathName As String) 

    Dim IID_IDispatch As GUID 
    Dim uPicinfo As uPicDesc 
    Dim IPic As IPicture 
    Dim hPtr As Long 
    
    'Copy Range to ClipBoard 
    SourceRange.CopyPicture Appearance:=xlScreen, Format:=xlBitmap 
    OpenClipboard 0 
    hPtr = GetClipboardData(CF_BITMAP) 
    CloseClipboard 
    'Create the interface GUID for the picture 
    With IID_IDispatch 
        .Data1 = &H7BF80980 
        .Data2 = &HBF32 
        .Data3 = &H101A 
        .Data4(0) = &H8B 
        .Data4(1) = &HBB 
        .Data4(2) = &H0 
        .Data4(3) = &HAA 
        .Data4(4) = &H0 
        .Data4(5) = &H30 
        .Data4(6) = &HC 
        .Data4(7) = &HAB 
    End With 
    'Fill uPicInfo with necessary parts. 
    With uPicinfo 
        .Size = Len(uPicinfo) '\\ Length of structure. 
        .Type = PICTYPE_BITMAP '\\ Type of Picture 
        .hPic = hPtr '\\ Handle to image. 
        .hPal = 0 '\\ Handle to palette (if bitmap). 
    End With 
    'Create the Range Picture Object 
    OleCreatePictureIndirect uPicinfo, IID_IDispatch, True, IPic 
    'Save Picture Object 
    stdole.SavePicture IPic, FilePathName 
    
End Sub 

Private Sub RestrictCursorToControl(oControl As Object) 

    Dim uRect As RECT 
    Dim lhDC As Long 
    lhDC = GetDC(0) 
    
    With uRect 
        .Left = ActiveWindow.PointsToScreenPixelsX(oControl.Left * _ 
        (GetDeviceCaps(lhDC, LOGPIXELSX) / PointsPerInch * (ActiveWindow.Zoom / 100))) 
        .Top = ActiveWindow.PointsToScreenPixelsY(oControl.Top * _ 
        (GetDeviceCaps(lhDC, LOGPIXELSY) / PointsPerInch * (ActiveWindow.Zoom / 100))) 
        .Right = ActiveWindow.PointsToScreenPixelsX((oControl.Left + oControl.Width) * _ 
        (GetDeviceCaps(lhDC, LOGPIXELSX) / PointsPerInch * (ActiveWindow.Zoom / 100))) 
        .Bottom = ActiveWindow.PointsToScreenPixelsY((oControl.Top + oControl.Height) * _ 
        (GetDeviceCaps(lhDC, LOGPIXELSY) / PointsPerInch * (ActiveWindow.Zoom / 100))) - 1 
    End With 
    ClipCursor uRect 
    ReleaseDC 0, lhDC 

End Sub 

Private Function IsCellWithinRange(Cell As Range, Parent_Range As Range) As Boolean 
    
    IsCellWithinRange = (Union(Cell, Parent_Range).Address = Parent_Range.Address) 

End Function


Regards.
 
Upvote 0
Same problems with this file. Is it the same one? I am just going to go ahead and use the mouse hook in my current project and see how it goes.

Thanks again
 
Upvote 0

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