I have a small WPF application which has a window with an Image control. Image control shows an image from File system. I want user to be able to drag the image and drop to its desktop or anywhere to save it. It's working fine.
But I want to show small image thumbnail along with mouse cursor when user drags it. Just like we drag an image from Windows file explorer to some where else. How to achieve it?
Current Behavior of Drag/Drop
Desired Behavior
Here is my XAML Code
<Grid>
<Image x:Name="img" Height="100" Width="100" Margin="100,30,0,0"/>
</Grid>
Here is C# Code
public partial class MainWindow : Window
{
string imgPath;
Point start;
bool dragStart = false;
public MainWindow()
{
InitializeComponent();
imgPath = "C:\\Pictures\\flower.jpg";
ImageSource imageSource = new BitmapImage(new Uri(imgPath));
img.Source = imageSource;
window.PreviewMouseMove += Window_PreviewMouseMove;
window.PreviewMouseUp += Window_PreviewMouseUp;
window.Closing += Window_Closing;
img.PreviewMouseLeftButtonDown += Img_PreviewMouseLeftButtonDown;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
window.PreviewMouseMove -= Window_PreviewMouseMove;
window.PreviewMouseUp -= Window_PreviewMouseUp;
window.Closing -= Window_Closing;
img.PreviewMouseLeftButtonDown -= Img_PreviewMouseLeftButtonDown;
}
private void Window_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (!dragStart) return;
if (e.LeftButton != MouseButtonState.Pressed)
{
dragStart = false; return;
}
Point mpos = e.GetPosition(null);
Vector diff = this.start - mpos;
if (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance &&
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
{
string[] file = { imgPath };
DataObject d = new DataObject();
d.SetData(DataFormats.Text, file[0]);
d.SetData(DataFormats.FileDrop, file);
DragDrop.DoDragDrop(this, d, DragDropEffects.Copy);
}
}
private void Img_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.start = e.GetPosition(null);
dragStart = true;
}
private void Window_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
dragStart = false;
}
}
Officially, you're supposed to use the IDragSourceHelper interface to add a preview bitmap to a Drag&Drop operation.
Unfortunately, this interface uses IDataObject::SetData method which is not implemented at COM level by the .NET DataObject class, only at .NET level.
The solution is to re-use an IDataObject provided by the Shell instead for any Shell Item (here a file), using the SHCreateItemFromParsingName function and the IShellItem::BindToHandler method.
Note these functions automatically add clipboard formats like FileDrop, but we still have to use IDragSourceHelper to add the preview image.
This is how you can use it:
...
// get IDataObject from the Shell so it can handle more formats
var dataObject = DataObjectUtilities.GetFileDataObject(imgPath);
// add the thumbnail to the data object
DataObjectUtilities.AddPreviewImage(dataObject, imgPath);
// start d&d
DragDrop.DoDragDrop(this, dataObject, DragDropEffects.All);
...
And here is the code:
public static class DataObjectUtilities
{
public static void AddPreviewImage(System.Runtime.InteropServices.ComTypes.IDataObject dataObject, string imgPath)
{
if (dataObject == null)
throw new ArgumentNullException(nameof(dataObject));
var ddh = (IDragSourceHelper)new DragDropHelper();
var dragImage = new SHDRAGIMAGE();
// note you should use a thumbnail here, not a full-sized image
var thumbnail = new System.Drawing.Bitmap(imgPath);
dragImage.sizeDragImage.cx = thumbnail.Width;
dragImage.sizeDragImage.cy = thumbnail.Height;
dragImage.hbmpDragImage = thumbnail.GetHbitmap();
Marshal.ThrowExceptionForHR(ddh.InitializeFromBitmap(ref dragImage, dataObject));
}
public static System.Runtime.InteropServices.ComTypes.IDataObject GetFileDataObject(string filePath)
{
if (filePath == null)
throw new ArgumentNullException(nameof(filePath));
Marshal.ThrowExceptionForHR(SHCreateItemFromParsingName(filePath, null, typeof(IShellItem).GUID, out var item));
Marshal.ThrowExceptionForHR(item.BindToHandler(null, BHID_DataObject, typeof(System.Runtime.InteropServices.ComTypes.IDataObject).GUID, out var dataObject));
return (System.Runtime.InteropServices.ComTypes.IDataObject)dataObject;
}
private static readonly Guid BHID_DataObject = new Guid("b8c0bd9f-ed24-455c-83e6-d5390c4fe8c4");
[DllImport("shell32", CharSet = CharSet.Unicode)]
private static extern int SHCreateItemFromParsingName(string path, System.Runtime.InteropServices.ComTypes.IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv);
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellItem
{
[PreserveSig]
int BindToHandler(System.Runtime.InteropServices.ComTypes.IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppv);
// other methods are not defined, we don't need them
}
[ComImport, Guid("4657278a-411b-11d2-839a-00c04fd918d0")] // CLSID_DragDropHelper
private class DragDropHelper
{
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct SIZE
{
public int cx;
public int cy;
}
// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/ns-shobjidl_core-shdragimage
[StructLayout(LayoutKind.Sequential)]
private struct SHDRAGIMAGE
{
public SIZE sizeDragImage;
public POINT ptOffset;
public IntPtr hbmpDragImage;
public int crColorKey;
}
// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-idragsourcehelper
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("DE5BF786-477A-11D2-839D-00C04FD918D0")]
private interface IDragSourceHelper
{
[PreserveSig]
int InitializeFromBitmap(ref SHDRAGIMAGE pshdi, System.Runtime.InteropServices.ComTypes.IDataObject pDataObject);
[PreserveSig]
int InitializeFromWindow(IntPtr hwnd, ref POINT ppt, System.Runtime.InteropServices.ComTypes.IDataObject pDataObject);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With