Search code examples
winformscefsharpchromium-embedded

How to receive drag move and drag drop events using CefSharp winforms


I am using the windowsformshost and trying to get dragover and drop events.

I have set allowdrop on the WPF window, Windowsformshost and the ChromiumWebBrowser. I can understand that the WPF window will not get the event due to the windowsformshost airspace issues. But I dont understand why the windowsformshost or the ChromiumWebBrowser does not get any of the events. It appears they are swallowed and not passed on by CEF/CefSharp. How can I handle the events and/or what do I need to disable in CEF/CefSharp?

As I am moving from pure WPF CefSharp I have implemented a dragenter, dragmove and dragdrop with the WPF ChromiumWebBrowser (not using the IDragHandler).

What I would like to achieve is to show a different drag effect based on where the mouse is dragging and secondly I would like to be able to intercept the drop event to first check the file type where the user drops over a file upload type element.

What is the effect of disabling or calling RevokeDragDrop and which Hwnd (window) should this be called on from a Cefsharp point of view?


Solution

  • Answering my own question: it is possible to restore the drag and drop events which have been swallowed upstream.

    First I used the IOleDropTarget Interface which looks like this;

     <ComImport, Guid("00000122-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
    Interface IOleDropTarget
        <PreserveSig>
        Function OleDragEnter(
        <[In], MarshalAs(UnmanagedType.[Interface])> ByVal pDataObj As Object,
        <[In], MarshalAs(UnmanagedType.U4)> ByVal grfKeyState As Integer,
        <[In], MarshalAs(UnmanagedType.U8)> ByVal pt As Long,
        <[In], Out> ByRef pdwEffect As Integer) As Integer
        <PreserveSig>
        Function OleDragOver(
        <[In], MarshalAs(UnmanagedType.U4)> ByVal grfKeyState As Integer,
        <[In], MarshalAs(UnmanagedType.U8)> ByVal pt As Long,
        <[In], Out> ByRef pdwEffect As Integer) As Integer
        <PreserveSig>
        Function OleDragLeave() As Integer
        <PreserveSig>
        Function OleDrop(
        <[In], MarshalAs(UnmanagedType.[Interface])> ByVal pDataObj As Object,
        <[In], MarshalAs(UnmanagedType.U4)> ByVal grfKeyState As Integer,
        <[In], MarshalAs(UnmanagedType.U8)> ByVal pt As Long,
        <[In], Out> ByRef pdwEffect As Integer) As Integer
    End Interface
    

    Next and while we are on the interfaces let use create the nice icons that windows explorer shows when a file is dragged with this interface.

    Imports IDataObject_Com = System.Runtime.InteropServices.ComTypes.IDataObject
    Imports System.Windows.Interop
    Imports System.Runtime.InteropServices
    
    Namespace Browser
    <StructLayout(LayoutKind.Sequential)>
    Public Structure Win32Point
        Public x As Integer
        Public y As Integer
    End Structure
    
    <ComImport>
    <Guid("4657278A-411B-11d2-839A-00C04FD918D0")>
    Public Class DragDropHelper
    End Class
    
    <ComVisible(True)>
    <ComImport>
    <Guid("4657278B-411B-11D2-839A-00C04FD918D0")>
    <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
    Interface IDropTargetHelper
        Sub DragEnter(
     <[In]> ByVal hwndTarget As IntPtr,
     <[In], MarshalAs(UnmanagedType.[Interface])> ByVal dataObject As IDataObject_Com,
     <[In]> ByRef pt As Win32Point,
     <[In]> ByVal effect As Integer)
        Sub DragLeave()
        Sub DragOver(
     <[In]> ByRef pt As Win32Point,
     <[In]> ByVal effect As Integer)
        Sub Drop(
     <[In], MarshalAs(UnmanagedType.[Interface])> ByVal dataObject As IDataObject_Com,
     <[In]> ByRef pt As Win32Point,
     <[In]> ByVal effect As Integer)
        Sub Show(
     <[In]> ByVal show As Boolean)
    End Interface
    

    You need to implement the IOleDropTaget interface which will then supply the events of DragEnter, Over, Leave and Drop.

    To hook up these events you don't need to know anything about the messageloop or wndproc messages as was hinted at above. What you do need to know is that one of the Chromium windows with class name "Chrome_WidgetWin_0" is registered for drag and drop and this has to be first revoked before you can get the events.

    CefSharp examples show how to drill down into the Chromium windows but it is normally to get another window class. In this case I use the following (note I revoke all windows I find along the way in the call back function but it appears that only Chrome_WidgetWin_0 is registered.

    Const Chrome_WidgetWin As String = "Chrome_WidgetWin_0"
    
        Private Function TryFindHandl(ByVal browserHandle As IntPtr, <Out> ByRef chromeWidgetHostHandle As IntPtr) As Boolean
    
            Dim cbXL As New NativeMethodsEx.EnumChildCallback(AddressOf EnumChildProc_Browser)
            NativeMethodsEx.EnumChildWindows(browserHandle, cbXL, chromeWidgetHostHandle)
    
            Return chromeWidgetHostHandle <> IntPtr.Zero
    
        End Function
    
        Private Shared Function EnumChildProc_Browser(ByVal hwndChild As Integer, ByRef lParam As Integer) As Boolean
            Dim buf As New StringBuilder(128)
            NativeMethodsEx.GetClassName(hwndChild, buf, 128)
            Dim ret = NativeMethodsEx.RevokeDragDrop(hwndChild)
    
            If ret = NativeMethodsEx.DRAGDROP_E_NOTREGISTERED Then
                Debug.Print("")
            End If
    
            If buf.ToString = Chrome_WidgetWin Then
                lParam = hwndChild
                Return False
            End If
            Return True
        End Function
    

    Once you have this handle and you have revoked it as drop target then you can call RegisterDragDrop passing in the handle and your IOleDropTarget class.

    A few of my WinAPI signature look like this

     Friend Const DRAGDROP_E_NOTREGISTERED = &H80040100
    Friend Const DRAGDROP_E_INVALIDHWND = &H80040102
    Friend Const DRAGDROP_E_ALREADYREGISTERED = &H80040101
    Friend Const E_OUTOFMEMORY = &H8007000E
    Friend Const S_OK = 0
    
    
    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Friend Shared Function GetClassName(ByVal hWnd As System.IntPtr, ByVal lpClassname As System.Text.StringBuilder, ByVal nMaxCount As Integer) As Integer
    End Function
    
    Friend Delegate Function EnumChildCallback(ByVal hwnd As Integer, ByRef lParam As Integer) As Boolean
    
    <DllImport("User32.dll")>
    Friend Shared Function EnumChildWindows(ByVal hWndParent As Integer, ByVal lpEnumFunc As EnumChildCallback, ByRef lParam As Integer) As Boolean
    End Function
    
    
    <DllImport("ole32.dll")>
    Friend Shared Function RegisterDragDrop(ByVal hwnd As IntPtr, DropTarget As Browser.IOleDropTarget) As IntPtr
    End Function
    
    <DllImport("ole32.dll")>
    Friend Shared Function RevokeDragDrop(ByVal hwnd As IntPtr) As IntPtr
    End Function
    

    An example of the event and how to use the IDropTargetHelper is like this

    Public Function OleDragEnter(<[In]> <MarshalAs(UnmanagedType.Interface)> pDataObj As Object, <[In]> <MarshalAs(UnmanagedType.U4)> grfKeyState As Integer, <[In]> <MarshalAs(UnmanagedType.U8)> pt As Long, <[In]> <Out> ByRef pdwEffect As Integer) As Integer Implements IOleDropTarget.OleDragEnter
            Dim winPT As Win32Point
            winPT.x = CInt(pt And &H7FFFFFFF)
            winPT.y = CInt((pt >> 32) And &H7FFFFFFF)
            Dim eff As DragDropEffects = DragDropEffects.None
            'this is my event I am sending back to the browser class to deal with.
            RaiseEvent DBDragEnter(eff, New Point(winPT.x, winPT.y))
            'you need to pass in the effect
            pdwEffect = CInt(eff)
            'this is the helper which shows the nice icon you drag around.
            ddHelper.DragEnter(targetHwnd, CType(pDataObj, IDataObject_Com), winPT, CInt(eff))
            Return NativeMethodsEx.S_OK
        End Function
    

    Certainly something that would be good to see in CefSharp for WinForms especially as the control has a whole bunch or useless properties (AllowDrop) and events (Drag and Drop) at the moment which are not implemented.