Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement the common "Drag and Drop" icon in WinForms

I'm currently designing a simple WinForms UserControl in C# where a user can drag and drop an excel file onto a panel instead of browsing for the file. I have it technically working, but it's very crude.

In short, my code currently looks like this for the DragEnter and DragDrop events on the panel (removed error handling):

    private void dragPanel_DragEnter(object sender, DragEventArgs e)
    {
        var filenames = (string[])e.Data.GetData(DataFormats.FileDrop, false);
        if (Path.GetExtension(filenames[0]) == ".xlsx") e.Effect = DragDropEffects.All;
        else e.Effect = DragDropEffects.None;
    }

    private void dragPanel_DragDrop(object sender, DragEventArgs e)
    {
        var filenames = (string[])e.Data.GetData(DataFormats.FileDrop, false);
        string filename = filenames[0];
        // Do stuff
    }

I'm trying to get the Excel icon to show up as I drag the file, but all I can get is this thing: enter image description here

Anywhere I've looked online (mostly on this forum) has said I need to implement my own custom cursor if I want a specific icon to show up, but I honestly don't believe that. I took screenshots of multiple applications from different companies all using the exact same control (this is just a subset). Note that none of them are even cursors, the icons just follow the cursor:

Windows Explorer:

enter image description here

Google Chrome:

enter image description here

Adobe Acrobat:

enter image description here

Microsoft Edge:

(Same icon, but DragDropEffects are likely set to None)

enter image description here

So my conclusion is there must be a common windows control for this, but where is it? There's no way all of these companies just coincidentally built the exact same design and functionality!

Any help would be appreciated!

Bonus Question: Apparently in Windows 10 you're not allowed to drag-and-drop onto a program that's running as Administrator, however Chrome definitely lets you do this. You can run Chrome as Admin and drag a file onto it without any issue. What magic did Google use to bypass this security feature? I'd like to implement it as well as my control could potentially be used inside a program running as admin.

like image 426
AnotherProgrammer Avatar asked Jun 09 '17 20:06

AnotherProgrammer


Video Answer


1 Answers

Standard way to do that is to delegate drop icon rendering DragDropHelper COM Object provided by Shell.

It allows application to negotiate image and icon to be displayed. In your case, Explorer already uses IDragSourceHelper for drag icon negotiation, so all you have to do is to delegate drop events to IDropTargetHelper exposed by DragDropHelper:

Interop:

using IDataObject_Com = System.Runtime.InteropServices.ComTypes.IDataObject;

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

[ComImport]
[Guid("4657278A-411B-11d2-839A-00C04FD918D0")]
public class DragDropHelper { }

[ComVisible(true)]
[ComImport]
[Guid("4657278B-411B-11D2-839A-00C04FD918D0")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDropTargetHelper
{
    void DragEnter(
        [In] IntPtr hwndTarget,
        [In, MarshalAs(UnmanagedType.Interface)] IDataObject_Com dataObject,
        [In] ref Win32Point pt,
        [In] int effect);
    void DragLeave();

    void DragOver(
        [In] ref Win32Point pt,
        [In] int effect);

    void Drop(
        [In, MarshalAs(UnmanagedType.Interface)] IDataObject_Com dataObject,
        [In] ref Win32Point pt,
        [In] int effect);

    void Show(
        [In] bool show);
}

Form:

private IDropTargetHelper ddHelper = (IDropTargetHelper)new DragDropHelper();

private void Form1_DragDrop(object sender, DragEventArgs e)
{
    e.Effect = DragDropEffects.Copy;
    Point p = Cursor.Position;
    Win32Point wp;
    wp.x = p.X;
    wp.y = p.Y;

    ddHelper.Drop(e.Data as IDataObject_Com, ref wp, (int)e.Effect);
}

private void Form1_DragEnter(object sender, DragEventArgs e)
{
    e.Effect = DragDropEffects.Copy;
    Point p = Cursor.Position;
    Win32Point wp;
    wp.x = p.X;
    wp.y = p.Y;

    ddHelper.DragEnter(this.Handle, e.Data as IDataObject_Com, ref wp, (int)e.Effect);
}

private void Form1_DragLeave(object sender, EventArgs e)
{
    ddHelper.DragLeave();
}

private void Form1_DragOver(object sender, DragEventArgs e)
{
    e.Effect = DragDropEffects.Copy;
    Point p = Cursor.Position;
    Win32Point wp;
    wp.x = p.X;
    wp.y = p.Y;

    ddHelper.DragOver(ref wp, (int)e.Effect);
}

WPF Version is basically the same, with minor changes:

private void Window_DragEnter(object sender, DragEventArgs e)
{
    e.Effects = DragDropEffects.Copy;
    e.Handled = true;
    Point p = this.PointToScreen(e.GetPosition(this));
    Win32Point wp;
    wp.x = (int)p.X;
    wp.y = (int)p.Y;
    ddHelper.DragEnter(new WindowInteropHelper(this).Handle, e.Data as IDataObject_Com, ref wp, (int)e.Effects);
}

References:

  • PInvoke on IDropTargetHelper
  • MSDN Blogs: Shell Style Drag and Drop in .NET (WPF and WinForms) - covers both source and target implementation.
  • Chrome / Chromium DragDrop Target Helper.
like image 81
PashaPash Avatar answered Sep 17 '22 16:09

PashaPash