我意识到在拖放过程中如何添加装饰物有很多答案,但是我一直遵循它们,遇到了两个问题:第一,装饰物未显示在预期的位置;第二,显示装饰物,直到我离开窗口并返回到控件之前,其他控件都不会收到拖放事件。
我正在尝试创建一个可重用的帮助程序类,该类在任何拖放事件期间都处理用鼠标创建和移动图像(在这种情况下为装饰器)(这非常荒谬,.NET尚未提供以下功能:首先进行此操作),但我无法理解为什么我的装饰代码版本不起作用。
下面是我正在使用的装饰器类和帮助器类:
public class DragAdorner : Adorner
{
private Rectangle child = null;
private double offsetLeft = 0;
private double offsetTop = 0;
/// <summary>
/// Initializes a new instance of DragAdorner that is a rectangle of the given size and brush.
/// </summary>
/// <param name="adornedElement">The element being adorned.</param>
/// <param name="size">The size of the adorner.</param>
/// <param name="brush">A brush with which to paint the adorner.</param>
public DragAdorner(UIElement adornedElement, Size size, Brush brush)
: base(adornedElement)
{
Rectangle rect = new Rectangle();
rect.Fill = brush;
rect.Width = size.Width;
rect.Height = size.Height;
rect.IsHitTestVisible = false;
this.child = rect;
this.Height = size.Height;
this.Width = size.Width;
}
public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
{
GeneralTransformGroup result = new GeneralTransformGroup();
//if(transform != null)
// result.Children.Add(base.GetDesiredTransform(transform));
result.Children.Add(new TranslateTransform(this.offsetLeft, this.offsetTop));
return result;
}
/// <summary>
/// Gets/sets the horizontal offset of the adorner.
/// </summary>
public double OffsetLeft
{
get { return this.offsetLeft; }
set
{
this.offsetLeft = value;
UpdateLocation();
}
}
/// <summary>
/// Gets/sets the vertical offset of the adorner.
/// </summary>
public double OffsetTop
{
get { return this.offsetTop; }
set
{
this.offsetTop = value;
UpdateLocation();
}
}
/// <summary>
/// Updates the location of the adorner.
/// </summary>
/// <param name="left"></param>
/// <param name="top"></param>
public void SetOffsets(double left, double top)
{
this.offsetLeft = left;
this.offsetTop = top;
this.UpdateLocation();
}
/// <summary>
/// Override.
/// </summary>
/// <param name="constraint"></param>
/// <returns></returns>
protected override Size MeasureOverride(Size constraint)
{
this.child.Measure(constraint);
return this.child.DesiredSize;
}
/// <summary>
/// Override.
/// </summary>
/// <param name="finalSize"></param>
/// <returns></returns>
protected override Size ArrangeOverride(Size finalSize)
{
this.child.Arrange(new Rect(finalSize));
return finalSize;
}
/// <summary>
/// Override.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
protected override Visual GetVisualChild(int index)
{
return this.child;
}
/// <summary>
/// Override. Always returns 1.
/// </summary>
protected override int VisualChildrenCount
{
get { return 1; }
}
private void UpdateLocation()
{
AdornerLayer adornerLayer = this.Parent as AdornerLayer;
if (adornerLayer != null)
adornerLayer.Update(this.AdornedElement);
}
}
/// <summary>
/// Class to handle creating the adorner, adding it to the adorner layer, moving it with the mouse, and removing it
/// </summary>
public class DragAdornerHelper : IDisposable
{
private DragAdorner adorner;
private AdornerLayer adornerLayer;
private Point offsetFromMouse;
private UIElement dragAreaElement;
/// <summary>
/// Creates a drag adorner handler for dragged elements above a drag area.
/// Hooks up PreviewDragOver to update the adorner's location.
/// The dragAreaElement must have AllowDrop = true in order for the adorner to show
/// </summary>
/// <param name="dragAreaElement">The element over which the adorner should be visible</param>
public DragAdornerHelper(UIElement dragAreaElement)
{
ParamValidator.CheckNotNull(dragAreaElement, nameof(dragAreaElement));
this.dragAreaElement = dragAreaElement;
this.adornerLayer = AdornerLayer.GetAdornerLayer(dragAreaElement);
this.adornerLayer.IsHitTestVisible = false;
this.adornerLayer.Visibility = Visibility.Visible;
this.dragAreaElement.PreviewDragOver += DragAreaElement_PreviewDragOver;
this.dragAreaElement.PreviewDragEnter += DragAreaElement_PreviewDragEnter;
this.dragAreaElement.PreviewDragLeave += DragAreaElement_PreviewDragLeave;
}
/// <summary>
/// Creates an adorner for the given dragged element and adds it to the adorner layer above the drag area element,
/// </summary>
/// <param name="draggedElement">The element being dragged. The adorner will be a transparent visual copy of this element</param>
/// <param name="adornerOpacity">Optional: The opacity of the adorner image</param>
/// <param name="xOffsetFromMouse">Optional: The offset of the top left corner of the adorner from the mouse cursor in the x direction. Defaults to -10</param>
/// <param name="yOffsetFromMouse">Optional: The offset of the top left corner of the adorner from the mouse cursor in the y direction. Defaults to -(render size + 5)</param>
public void CreateAdorner(UIElement draggedElement, double adornerOpacity = 0.7, double? xOffsetFromMouse = null, double? yOffsetFromMouse = null)
{
ParamValidator.CheckNotNull(draggedElement, nameof(draggedElement));
var elementSize = draggedElement.RenderSize;
if (xOffsetFromMouse == null)
xOffsetFromMouse = -10;
if (yOffsetFromMouse == null)
yOffsetFromMouse = -elementSize.Height - 5;
this.offsetFromMouse = new Point(xOffsetFromMouse.Value, yOffsetFromMouse.Value);
VisualBrush brush = new VisualBrush(draggedElement);
this.adorner = new DragAdorner(this.dragAreaElement, elementSize, brush);
this.adorner.Opacity = adornerOpacity;
this.adornerLayer.Add(adorner);
this.MoveAdorner(Mouse.GetPosition(this.dragAreaElement));
}
/// <summary>
/// Destroys the current adorner
/// </summary>
public void DestroyAdorner()
{
if (this.adorner != null)
this.adornerLayer.Remove(this.adorner);
this.adorner = null;
}
/// <summary>
/// Moves the adorner when dragged over the drag area
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DragAreaElement_PreviewDragOver(object sender, DragEventArgs e)
{
if (this.adorner != null)
this.MoveAdorner(e.GetPosition(this.dragAreaElement));
}
/// <summary>
/// Hides the adorner when mouse leaves the bounds of drag area
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DragAreaElement_PreviewDragLeave(object sender, DragEventArgs e)
{
if (this.adorner != null)
this.adornerLayer.Remove(this.adorner);
}
/// <summary>
/// Shows the adorner when mouse enters the bounds of drag area
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DragAreaElement_PreviewDragEnter(object sender, DragEventArgs e)
{
if (this.adorner != null)
this.adornerLayer.Add(this.adorner);
}
private void MoveAdorner(Point mousePos)
{
var offsetX = mousePos.X + this.offsetFromMouse.X;
var offsetY = mousePos.Y + this.offsetFromMouse.Y;
this.adorner.SetOffsets(offsetX, offsetY);
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
this.dragAreaElement.PreviewDragOver -= DragAreaElement_PreviewDragOver;
this.dragAreaElement.PreviewDragEnter -= DragAreaElement_PreviewDragEnter;
this.dragAreaElement.PreviewDragLeave -= DragAreaElement_PreviewDragLeave;
}
disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
这就是我在后台代码中的调用方式:
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (!this.loaded)
{
// Create the adorner helper
this.dragAdornerHelper = new DragAdornerHelper(this.SequenceEditorPanel);
this.loaded = true;
}
}
/// <summary>
/// Initiates the drag/drop capabilities if the drag is over a certain threshold
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListBox_MouseMove(object sender, MouseEventArgs e)
{
if (dragStartPoint.X != 0 && dragStartPoint.Y != 0 && draggedSample != null
&& e.OriginalSource.GetType() != typeof(Thumb))
{
Point point = e.GetPosition(null);
Vector diff = dragStartPoint - point;
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
{
this.dragAdornerHelper.CreateAdorner(draggedElement);
DragDrop.DoDragDrop((DependencyObject)sender, draggedSample, DragDropEffects.Move | DragDropEffects.None | DragDropEffects.Scroll);
// DoDragDrop blocks until drop
this.OnDragDropFinished(); // <- where I call DestroyAdorner
}
}
}
如果我注释掉创建装饰器的代码,则拖放操作将按预期进行。我的猜测是装饰层会以某种方式捕获鼠标事件,这样它们就不会进入我的其他控件,但是我不确定这样做的原因或方式。奇怪的是,如果我将光标完全从窗口中拖出并重新带回,则拖放操作正确且装饰器移动了,我只是不确定内部发生了什么而导致更改。
任何帮助我克服这些最后障碍的人,将不胜感激!
编辑:
取消注释以下行会导致装饰物正确放置。我仍然不确定为什么为什么偶尔会为“ transform”获取空值。
result.Children.Add(base.GetDesiredTransform(transform));
编辑2:
我想出了使它工作的方法,但我仍然不确定为什么一开始它不工作。如果我在CreateAdorner方法中将这两行注释掉,它将起作用:
this.adornerLayer.Add(adorner);
this.MoveAdorner(Mouse.GetPosition(this.dragAreaElement));
或者,如果我删除了PreviewDragEnter和PreviewDragLeave事件处理程序。似乎试图将装饰器两次添加到装饰器层会导致拖放冻结。