6

I want to intercept drag&drop action on WebBrowser control. To be more precise, I want to be able to cancel or approve drag&drop event based on file extension (and process that file outside WebBrowser) and I want to make drag&drop icon look like regular icon: http://snag.gy/DUjMc.jpg, not like this one: http://snag.gy/ExX19.jpg.

I believe for this I need to implement custom IDocHostUIHandler and intercept GetDropTarget. Unfortunately, I have no success in doing that. I am using this code as my 'base' code: https://stackoverflow.com/a/19739699/2758677 and this part, that I made: http://pastebin.com/Ux947Eck. GetDropTarget is never called.

Community
  • 1
  • 1
hyto
  • 135
  • 1
  • 7
  • Do any other methods of your `IDocHostUIHandler` get called? – noseratio Nov 18 '13 at 20:11
  • @Noseratio, good question - no, none of them are being called. In your code there is a part that "turns private WebBrowserSiteBase.IOleClientSite into our own IOleClientSite". I was not sure how to do it properly for IDocHostUIHandler, so I guess I did it wrong. I have no idea how to fix it though. – hyto Nov 18 '13 at 20:38
  • Maybe I need to use [ICustomDoc::SetUIHandler](http://msdn.microsoft.com/en-us/library/aa753273(v=vs.85).aspx). – hyto Nov 18 '13 at 20:47

1 Answers1

3

Here's a complete WinForms-based example that works, IDocHostUIHandler.GetDropTarget does get called.

using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace CustomWebBrowser
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            var wb = new ImprovedWebBrowser();
            wb.Dock = DockStyle.Fill;
            this.Controls.Add(wb);
            wb.Visible = true;
            wb.DocumentText = "<body contentEditable='true'><b>Hello from ImprovedWebBrowser!</b></body>";
        }
    }

    public class ImprovedWebBrowser : WebBrowser
    {
        /// <summary>
        /// provide custom WebBrowserSite,
        /// where we override IDocHostUIHandler and call the base implementation
        /// More info: http://stackoverflow.com/a/19739699/1768303
        /// </summary>
        protected override WebBrowserSiteBase CreateWebBrowserSiteBase()
        {
            return new ImprovedWebBrowserSite(this);
        }

        #region ImprovedWebBrowserSite

        [ClassInterface(ClassInterfaceType.None)]
        protected class ImprovedWebBrowserSite :
            WebBrowserSite,
            NativeMethods.IDocHostUIHandler,
            IDisposable,
            ICustomQueryInterface
        {
            ImprovedWebBrowser _host;

            NativeMethods.IDocHostUIHandler _baseIDocHostUIHandler;

            IntPtr _unkInnerAggregated;
            IntPtr _unkOuter;
            Inner _inner;

            // constructor
            public ImprovedWebBrowserSite(WebBrowser host) :
                base(host)
            {
                _host = (ImprovedWebBrowser)host;

                // get the CCW object for this
                _unkOuter = Marshal.GetIUnknownForObject(this);
                Marshal.AddRef(_unkOuter);
                try
                {
                    // aggregate the CCW object with the helper Inner object
                    _inner = new Inner(this);
                    _unkInnerAggregated = Marshal.CreateAggregatedObject(_unkOuter, _inner);

                    // obtain private WebBrowserSite COM interfaces
                    _baseIDocHostUIHandler = (NativeMethods.IDocHostUIHandler)Marshal.GetTypedObjectForIUnknown(_unkInnerAggregated, typeof(NativeMethods.IDocHostUIHandler));
                }
                finally
                {
                    Marshal.Release(_unkOuter);
                }
            }

            ~ImprovedWebBrowserSite()
            {
                // need to work out the reference counting for GC to work correctly
                Debug.Print("ImprovedWebBrowserSite object finalized.");
            }

            void IDisposable.Dispose()
            {
                base.Dispose();

                _baseIDocHostUIHandler = null;

                if (_unkInnerAggregated != IntPtr.Zero)
                {
                    Marshal.Release(_unkInnerAggregated);
                    _unkInnerAggregated = IntPtr.Zero;
                }

                if (_unkOuter != IntPtr.Zero)
                {
                    Marshal.Release(_unkOuter);
                    _unkOuter = IntPtr.Zero;
                }
            }

            #region Inner
            // Inner as aggregated object
            class Inner :
                ICustomQueryInterface,
                IDisposable
            {
                object _outer;
                Type[] _interfaces;

                public Inner(object outer)
                {
                    _outer = outer;
                    _interfaces = _outer.GetType().BaseType.GetInterfaces();
                }

                public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
                {
                    if (_outer != null)
                    {
                        var guid = iid;
                        var iface = _interfaces.FirstOrDefault((t) => t.GUID == guid);
                        if (iface != null)
                        {
                            var unk = Marshal.GetComInterfaceForObject(_outer, iface, CustomQueryInterfaceMode.Ignore);
                            if (unk != IntPtr.Zero)
                            {
                                ppv = unk;
                                return CustomQueryInterfaceResult.Handled;
                            }
                        }
                    }
                    ppv = IntPtr.Zero;
                    return CustomQueryInterfaceResult.Failed;
                }

                ~Inner()
                {
                    // need to work out the reference counting for GC to work correctly
                    Debug.Print("Inner object finalized.");
                }

                public void Dispose()
                {
                    _outer = null;
                    _interfaces = null;
                }
            }
            #endregion

            #region ICustomQueryInterface
            public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
            {
                // CustomQueryInterfaceMode.Ignore is to avoid infinite loop during QI.
                if (iid == typeof(NativeMethods.IDocHostUIHandler).GUID)
                {
                    ppv = Marshal.GetComInterfaceForObject(this, typeof(NativeMethods.IDocHostUIHandler), CustomQueryInterfaceMode.Ignore);
                }
                else
                {
                    ppv = IntPtr.Zero;
                    return CustomQueryInterfaceResult.NotHandled;
                }
                return CustomQueryInterfaceResult.Handled;
            }
            #endregion

            #region IDocHostUIHandler
            int NativeMethods.IDocHostUIHandler.ShowContextMenu(int dwID, ref NativeMethods.POINT pt, IntPtr pcmdtReserved, IntPtr pdispReserved)
            {
                return _baseIDocHostUIHandler.ShowContextMenu(dwID, ref pt, pcmdtReserved, pdispReserved);
            }

            int NativeMethods.IDocHostUIHandler.GetHostInfo(ref NativeMethods.DOCHOSTUIINFO info)
            {
                Debug.Print("IDocHostUIHandler.GetHostInfo");
                return _baseIDocHostUIHandler.GetHostInfo(ref info);
            }

            int NativeMethods.IDocHostUIHandler.ShowUI(int dwID, IntPtr activeObject, IntPtr commandTarget, IntPtr frame, IntPtr doc)
            {
                return _baseIDocHostUIHandler.ShowUI(dwID, activeObject, commandTarget, frame, doc);
            }

            int NativeMethods.IDocHostUIHandler.HideUI()
            {
                return _baseIDocHostUIHandler.HideUI();
            }

            int NativeMethods.IDocHostUIHandler.UpdateUI()
            {
                return _baseIDocHostUIHandler.UpdateUI();
            }

            int NativeMethods.IDocHostUIHandler.EnableModeless(bool fEnable)
            {
                return _baseIDocHostUIHandler.EnableModeless(fEnable);
            }

            int NativeMethods.IDocHostUIHandler.OnDocWindowActivate(bool fActivate)
            {
                return _baseIDocHostUIHandler.OnDocWindowActivate(fActivate);
            }

            int NativeMethods.IDocHostUIHandler.OnFrameWindowActivate(bool fActivate)
            {
                return _baseIDocHostUIHandler.OnFrameWindowActivate(fActivate);
            }

            int NativeMethods.IDocHostUIHandler.ResizeBorder(ref NativeMethods.COMRECT rect, IntPtr doc, bool fFrameWindow)
            {
                return _baseIDocHostUIHandler.ResizeBorder(ref rect, doc, fFrameWindow);
            }

            int NativeMethods.IDocHostUIHandler.TranslateAccelerator(ref NativeMethods.MSG msg, ref Guid group, int nCmdID)
            {
                return _baseIDocHostUIHandler.TranslateAccelerator(ref msg, ref group, nCmdID);
            }

            int NativeMethods.IDocHostUIHandler.GetOptionKeyPath(string[] pbstrKey, int dw)
            {
                return _baseIDocHostUIHandler.GetOptionKeyPath(pbstrKey, dw);
            }

            int NativeMethods.IDocHostUIHandler.GetDropTarget(IntPtr pDropTarget, out IntPtr ppDropTarget)
            {
                Debug.Print("IDocHostUIHandler.GetDropTarget");
                return _baseIDocHostUIHandler.GetDropTarget(pDropTarget, out ppDropTarget);
            }

            int NativeMethods.IDocHostUIHandler.GetExternal(out object ppDispatch)
            {
                return _baseIDocHostUIHandler.GetExternal(out ppDispatch);
            }

            int NativeMethods.IDocHostUIHandler.TranslateUrl(int dwTranslate, string strURLIn, out string pstrURLOut)
            {
                return _baseIDocHostUIHandler.TranslateUrl(dwTranslate, strURLIn, out pstrURLOut);
            }

            int NativeMethods.IDocHostUIHandler.FilterDataObject(IntPtr pDO, out IntPtr ppDORet)
            {
                return _baseIDocHostUIHandler.FilterDataObject(pDO, out ppDORet);
            }
            #endregion
        }
        #endregion
    }

    public static class NativeMethods
    {
        #region IDocHostUIHandler

        public enum DOCHOSTUIDBLCLICK
        {
            DEFAULT = 0x0,
            SHOWPROPERTIES = 0x1,
            SHOWCODE = 0x2
        }

        public enum DOCHOSTUIFLAG
        {
            DIALOG = 0x1,
            DISABLE_HELP_MENU = 0x2,
            NO3DBORDER = 0x4,
            SCROLL_NO = 0x8,
            DISABLE_SCRIPT_INACTIVE = 0x10,
            OPENNEWWIN = 0x20,
            DISABLE_OFFSCREEN = 0x40,
            FLAT_SCROLLBAR = 0x80,
            DIV_BLOCKDEFAULT = 0x100,
            ACTIVATE_CLIENTHIT_ONLY = 0x200,
            NO3DOUTERBORDER = 0x00200000,
            THEME = 0x00040000,
            NOTHEME = 0x80000,
            DISABLE_COOKIE = 0x400
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct DOCHOSTUIINFO
        {
            [MarshalAs(UnmanagedType.U4)]
            public int cbSize;
            [MarshalAs(UnmanagedType.I4)]
            public int dwFlags;
            [MarshalAs(UnmanagedType.I4)]
            public int dwDoubleClick;
            [MarshalAs(UnmanagedType.I4)]
            public int dwReserved1;
            [MarshalAs(UnmanagedType.I4)]
            public int dwReserved2;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct COMRECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MSG
        {
            public IntPtr hwnd;
            public int message;
            public IntPtr wParam;
            public IntPtr lParam;
            public int time;
            POINT pt;
        }

        [ComImport(), Guid("BD3F23C0-D43E-11CF-893B-00AA00BDCE1A"),
        InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IDocHostUIHandler
        {
            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int ShowContextMenu(
                [In, MarshalAs(UnmanagedType.U4)] 
                int dwID,
                [In]
                ref POINT pt,
                [In]
                IntPtr pcmdtReserved,
                [In]
                IntPtr pdispReserved);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int GetHostInfo(
                [In, Out] 
                ref DOCHOSTUIINFO info);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int ShowUI(
                [In, MarshalAs(UnmanagedType.I4)] 
                int dwID,
                [In]
                IntPtr activeObject,
                [In]
                IntPtr commandTarget,
                [In]
                IntPtr frame,
                [In]
                IntPtr doc);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int HideUI();

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int UpdateUI();

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int EnableModeless(
                [In, MarshalAs(UnmanagedType.Bool)]
                bool fEnable);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int OnDocWindowActivate(
                [In, MarshalAs(UnmanagedType.Bool)] 
                bool fActivate);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int OnFrameWindowActivate(
                [In, MarshalAs(UnmanagedType.Bool)] 
                bool fActivate);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int ResizeBorder(
                [In]
                ref COMRECT rect,
                [In]
                IntPtr doc,
                bool fFrameWindow);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int TranslateAccelerator(
                [In]
                ref MSG msg,
                [In]
                ref Guid group,
                [In, MarshalAs(UnmanagedType.I4)]
                int nCmdID);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int GetOptionKeyPath(
                [Out, MarshalAs(UnmanagedType.LPArray)]
                String[] pbstrKey,
                [In, MarshalAs(UnmanagedType.U4)] 
                int dw);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int GetDropTarget(
                [In]
                IntPtr pDropTarget,
                [Out]
                out IntPtr ppDropTarget);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int GetExternal(
                [Out, MarshalAs(UnmanagedType.IDispatch)] 
                out object ppDispatch);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int TranslateUrl(
                [In, MarshalAs(UnmanagedType.U4)] 
                int dwTranslate,
                [In, MarshalAs(UnmanagedType.LPWStr)]
                string strURLIn,
                [Out, MarshalAs(UnmanagedType.LPWStr)]
                out string pstrURLOut);

            [return: MarshalAs(UnmanagedType.I4)]
            [PreserveSig]
            int FilterDataObject(
                [In]
                IntPtr pDO,
                [Out]
                out IntPtr ppDORet);
        }
        #endregion
    }
}
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 1
    thanks, it does work. Could you show how to implement [IDropTarget](http://msdn.microsoft.com/en-us/library/ms679679(v=vs.85).aspx)? – hyto Nov 19 '13 at 07:39
  • @hyto, this question was about intercepting `IDocHostUIHandler.GetDropTarget` of `WebBrowserSite`, and so is the answer. You should probably ask a separate question about how to implement it. – noseratio Nov 19 '13 at 08:05
  • agree, here is my [new question](http://stackoverflow.com/questions/20066447/webbrowser-idroptarget). – hyto Nov 19 '13 at 08:21
  • btw, should I worry about any of [these issues](http://snag.gy/cRBFF.jpg) suggested by Code Analysis? – hyto Nov 19 '13 at 14:33
  • @hyto, most of those CA warnings relate to the COM interface definitions which are not CLS compliant by their nature. You could try fixing some warnings, as long as you don't change method signatures and orders and struct layouts. – noseratio Nov 19 '13 at 20:26