添加拖动装饰器会冻结并禁用拖放事件

时间:2018-07-06 01:38:53

标签: c# wpf drag-and-drop drag adorner

我意识到在拖放过程中如何添加装饰物有很多答案,但是我一直遵循它们,遇到了两个问题:第一,装饰物未显示在预期的位置;第二,显示装饰物,直到我离开窗口并返回到控件之前,其他控件都不会收到拖放事件。

我正在尝试创建一个可重用的帮助程序类,该类在任何拖放事件期间都处理用鼠标创建和移动图像(在这种情况下为装饰器)(这非常荒谬,.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
          }
     }
}

如果我注释掉创建装饰器的代码,则拖放操作将按预期进行。我的猜测是装饰层会以某种方式捕获鼠标事件,这样它们就不会进入我的其他控件,但是我不确定这样做的原因或方式。奇怪的是,如果我将光标完全从窗口中拖出并重新带回,则拖放操作正确且装饰器移动了,我只是不确定内部发生了什么而导致更改。

任何帮助我克服这些最后障碍的人,将不胜感激!

Example behavior

编辑:

取消注释以下行会导致装饰物正确放置。我仍然不确定为什么为什么偶尔会为“ transform”获取空值。

result.Children.Add(base.GetDesiredTransform(transform));

编辑2:

我想出了使它工作的方法,但我仍然不确定为什么一开始它不工作。如果我在CreateAdorner方法中将这两行注释掉,它将起作用:

this.adornerLayer.Add(adorner);
this.MoveAdorner(Mouse.GetPosition(this.dragAreaElement));

或者,如果我删除了PreviewDragEnter和PreviewDragLeave事件处理程序。似乎试图将装饰器两次添加到装饰器层会导致拖放冻结。

0 个答案:

没有答案