[英]Showing Image thumbnail with mouse cursor while dragging
我有一个小的 WPF 应用程序,它有一个带有图像控件的 window。 图像控件显示来自文件系统的图像。 我希望用户能够将图像拖放到其桌面或任何地方以保存它。 它工作正常。
但是当用户拖动它时,我想与鼠标 cursor 一起显示小图像缩略图。 就像我们将图像从 Windows 文件资源管理器拖到其他地方一样。 如何实现?
拖放的当前行为
期望的行为
这是我的 XAML 代码
<Grid>
<Image x:Name="img" Height="100" Width="100" Margin="100,30,0,0"/>
</Grid>
这是 C# 代码
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;
}
}
正式来说,您应该使用IDragSourceHelper接口将预览 bitmap 添加到拖放操作中。
不幸的是,此接口使用了IDataObject::SetData 方法,该方法未在 COM 级别由 .NET DataObject class 实现,仅在 Z303CB0EF9EDB972AZ 级别。
解决方案是重新使用 Shell 提供的 IDataObject,而不是任何 Shell 项目(此处为文件),使用SHCreateItemFromParsingName ZC1C425268E68385D14AB5074C17A::BindToHandler 方法和 theShellItemA::4F14AB5074C17A
注意这些函数会自动添加像 FileDrop 这样的剪贴板格式,但我们仍然必须使用 IDragSourceHelper 来添加预览图像。
这是您可以使用它的方式:
...
// 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);
...
这是代码:
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://docs.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://docs.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);
}
}
来,试试这个。 它“拾起”鼠标 position 周围的红色透明方块,并在您再次单击时将其“放下”。
实际上,您希望创建在单击时执行互操作的线程,并在您放下时停止(而不是中止)它。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Button x:Name="Clicker" Click="OnClick">Click Me!</Button>
<Popup Placement="Absolute" PlacementRectangle="{Binding Placement}" AllowsTransparency="True" IsOpen="{Binding IsOpen}"
MouseUp="Cancel">
<Grid Margin="10" Background="#7fff0000">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="400"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock>Hello!</TextBlock>
</Grid>
</Popup>
</Grid>
</Window>
和后面的代码:
[StructLayout(LayoutKind.Sequential)]
public struct InteropPoint
{
public int X;
public int Y;
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private bool isOpen;
private int xPos;
private int yPos;
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(ref InteropPoint lpPoint);
private Thread t;
public MainWindow()
{
InitializeComponent();
t = new Thread(() =>
{
while (true)
{
//Logic
InteropPoint p = new InteropPoint();
GetCursorPos(ref p);
//Update UI
Dispatcher.BeginInvoke(new Action(() =>
{
XPos = (int) p.X;
YPos = (int) p.Y;
}));
Thread.Sleep(10);
}
});
t.Start();
}
protected override void OnClosing(CancelEventArgs e)
{
t.Abort();
}
private void OnClick(object sender, RoutedEventArgs e)
{
IsOpen = !IsOpen;
}
public int XPos
{
get => xPos;
set
{
if (value == xPos) return;
xPos = value;
OnPropertyChanged();
OnPropertyChanged(nameof(Placement));
}
}
public int YPos
{
get => yPos;
set
{
if (value == yPos) return;
yPos = value;
OnPropertyChanged();
OnPropertyChanged(nameof(Placement));
}
}
public bool IsOpen
{
get => isOpen;
set
{
if (value == isOpen) return;
isOpen = value;
OnPropertyChanged();
}
}
public Rect Placement => new Rect(XPos - 200, YPos - 200, 400, 400);
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void Cancel(object sender, MouseButtonEventArgs e)
{
IsOpen = false;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.