前一段时间项目里面要实现一个鼠标拖动一个元素到另外一个元素上面并且赋值的功能,由于要在surface上运行,拖动的时候手指会挡住系统默认的拖动图标,导致用户意识不到自己是不是在拖动着东西,所以要解决这个问题。
初始想法
一开始在的设想是,拖动开始时新建一个元素要拖动的元素,然后设置次元素跟随鼠标移动,这里遇到个问题就是,当使用DoDragDrop事件的时候,捕捉不到鼠标的坐标变动,以至于无法让新建的元素跟随移动。要实现功能必须放弃DoDragDrop事件,但是当时很多已经写好的功能都是围绕这个事件的,不想再改,于是开始探索新的方式,虽然一开始浪费了一点时间,但是好处也不是没有,比如发现了GiveFeedback事件,于是就想到了第二种方案。
由于本来就是没有实现的方法,所以在此就不上代码了。
第二种方案
ButtonDown触发的时候,给元素添加MouseMove事件,当MouseMove触发的时候,获取到当前元素并生成图像转换为Cursor格式,在GiveFeedback中用以改变鼠标样式,添加GiveFeedback事件并启动DoDrapDrop。
public void ViewElemenMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var viewElement = sender as ViewElement; if (viewElemen == null) return; viewElemen.MouseMove += ViewElemenOnPreviewMouseMove; } private void PatientTitleOnPreviewMouseMove(object sender, MouseEventArgs mouseEventArgs) { var viewElement = sender as ViewElement; if (viewElement == null) return; HoloCursor = FormUtility.CreateCursor(viewElement); viewElemen.RockStart(); viewElemen.GiveFeedback += DragSource_GiveFeedback; DragDrop.DoDragDrop(viewElemen, Model, DragDropEffects.Copy); viewElemen.MouseMove -= ViewElemenOnPreviewMouseMove; viewElemen.GiveFeedback -= DragSource_GiveFeedback; Mouse.SetCursor(Cursors.Arrow); } void DragSource_GiveFeedback(object sender, GiveFeedbackEventArgs e) { var viewElement = sender as ViewElement; if (viewElemen == null) return; Mouse.SetCursor(HoloCursor); e.UseDefaultCursors = false; e.Handled = true; }
根据元素生成鼠标 FormUtility.CreateCursor:
public static Cursor CreateCursor(UIElement element) { element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); element.Arrange(new Rect(new Point(), element.DesiredSize)); var rtb = new RenderTargetBitmap( (int)element.DesiredSize.Width, (int)element.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32); rtb.Render(element); var encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(rtb)); using (var ms = new MemoryStream()) { encoder.Save(ms); using (var bmp = new System.Drawing.Bitmap(ms)) { return InternalCreateCursor(bmp); } } }
private static Cursor InternalCreateCursor(System.Drawing.Bitmap bmp) { var iconInfo = new NativeMethods.IconInfo(); NativeMethods.GetIconInfo(bmp.GetHicon(), ref iconInfo); iconInfo.xHotspot = 125; iconInfo.yHotspot = 65; iconInfo.fIcon = false; SafeIconHandle cursorHandle = NativeMethods.CreateIconIndirect(ref iconInfo); return CursorInteropHelper.Create(cursorHandle); }
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] private class SafeIconHandle : SafeHandleZeroOrMinusOneIsInvalid { public SafeIconHandle() : base(true) { } override protected bool ReleaseHandle() { return NativeMethods.DestroyIcon(handle); } }
private static class NativeMethods { public struct IconInfo { public bool fIcon; public int xHotspot; public int yHotspot; public IntPtr hbmMask; public IntPtr hbmColor; } [DllImport("user32.dll")] public static extern SafeIconHandle CreateIconIndirect(ref IconInfo icon); [DllImport("user32.dll")] public static extern bool DestroyIcon(IntPtr hIcon); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo); }
RockStart是一个元素效果,即开始拖动的时候,该元素会左右摇晃,让用户更清楚的知道,自己拖动的是哪个元素。
private Storyboard _sb = new Storyboard(){ FillBehavior = FillBehavior.Stop }; public ViewElement() { InitializeComponent(); _sb.AutoReverse = true; var dbAscending1 = new DoubleAnimation(0, 3, new Duration(TimeSpan.FromMilliseconds(100))); _sb.Children.Add(dbAscending1); Storyboard.SetTarget(dbAscending1, Border); Storyboard.SetTargetProperty(dbAscending1, new PropertyPath("(Rectangle.RenderTransform).(RotateTransform.Angle)")); var dbAscending2 = new DoubleAnimation(3, -3, new Duration(TimeSpan.FromMilliseconds(200))); _sb.Children.Add(dbAscending2); Storyboard.SetTarget(dbAscending2, Border); Storyboard.SetTargetProperty(dbAscending2, new PropertyPath("(Rectangle.RenderTransform).(RotateTransform.Angle)")); } public void RockStart() { Dispatcher.InvokeAsync(() => _sb.Begin(), DispatcherPriority.Background); }
至此,功能完成。