Search code examples
.netwpfmvvmdrag-and-dropadorner

WPF MVVM Drag & Drop


I have an application that uses drag and drop functionality. I've implemented this functionality according to MVVM concept, using behaviors. I've tried to use adorner element to create an illusion of the moving object. But I get a very strange behavior of the adorner. It seems like the render engine adds some offset to it, so it does not appear at the desired position. And looks like that offset accumulates. I've attached my sample project, so the issue is easily reproducable.

private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
    {
        Point currentPosition = mouseEventArgs.GetPosition(RelativeElement);

        //Point currentPosition = mouseEventArgs.GetPosition(Window.GetWindow(this.AssociatedObject));
        if (!_isMousePressed || !this.AssociatedObject.IsMouseOver)
            return;

        if (Math.Abs(_originalPosition.X - currentPosition.X) < SystemParameters.MinimumHorizontalDragDistance &&
            Math.Abs(_originalPosition.Y - currentPosition.Y) < SystemParameters.MinimumVerticalDragDistance)
            return;

        IDragable context = this.AssociatedObject.DataContext as IDragable;
        if (context == null)
            return;


        Debug.WriteLine("Mouse leave");
        _adorner = new DefaultAdorner(this.AssociatedObject, new Point(0, 0), RelativeElement);

        DataObject data = new DataObject();
        data.SetData(context.DataType, context);
        System.Windows.DragDrop.DoDragDrop(this.AssociatedObject, data, DragDropEffects.Move);

        if (_adorner != null)
        {
            _adorner.Destroy();
            _adorner = null;
        }

        _isMousePressed = false;
    }


    private void AssociatedObjectOnGiveFeedback(object sender, GiveFeedbackEventArgs giveFeedbackEventArgs)
    {
        Debug.WriteLine("feedback");
        Point mouseCoordinates = Extensions.GetMouseCoordinates();
        //Debug.WriteLine("initial x: {0}; y: {1}", mouseCoordinates.X, mouseCoordinates.Y);
        Point mousePosition = RelativeElement.PointFromScreen(mouseCoordinates);

        AdornerLayer layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);
        var relative = RelativeElement.TranslatePoint(mousePosition, layer);
        Debug.WriteLine("relative to layer x: {0}; y: {1}", relative.X, relative.Y);
        var towindow = RelativeElement.TranslatePoint(mousePosition, Window.GetWindow(this.AssociatedObject));
        Debug.WriteLine("relative to layer x: {0}; y: {1}", towindow.X, towindow.Y);
        //Point mousePosition = Window.GetWindow(this.AssociatedObject).PointFromScreen(Extensions.GetMouseCoordinates());
        if (_adorner != null)
        {
            _adorner.SetMousePosition(mousePosition);
        }
    }

/// <summary>
    /// Create an adorner.
    /// The created adorner must then be added to the AdornerLayer.
    /// </summary>
    /// <param name="adornedElement">Element whose AdornerLayer will be use for displaying the adorner</param>
    /// <param name="adornerElement">Element used as adorner</param>
    /// <param name="adornerOrigin">Origin offset within the adorner</param>
    /// <param name="opacity">Adorner's opacity</param>
    public DefaultAdorner(UIElement adornedElement, Point origin, FrameworkElement relative)
        : base(adornedElement)
    {
        Rectangle rect = new Rectangle();
        rect.Width = adornedElement.RenderSize.Width;
        rect.Height = adornedElement.RenderSize.Height;

        VisualBrush visualBrush = new VisualBrush(adornedElement);
        visualBrush.Opacity = 0.5;
        visualBrush.Stretch = Stretch.None;
        rect.Fill = visualBrush;


        this._child = rect;

        this._adornerOrigin = new Point(0, 0);

        this._adornerOffset = origin;
        _relative = relative;
        AdornerLayer layer = AdornerLayer.GetAdornerLayer(adornedElement);

        Adorner[] adorners = layer.GetAdorners(adornedElement);
        if (adorners != null)
        {
            Array.ForEach(adorners, layer.Remove);   
        }

        layer.Add(this);
        InvalidateVisual();
    }

    /// <summary>
    /// Set the position of and redraw the adorner.
    /// Call when the mouse cursor position changes.
    /// </summary>
    /// <param name="position">Adorner's new position relative to AdornerLayer origin</param>
    public void SetMousePosition(Point position)
    {
        this._adornerOffset.X = position.X;
        this._adornerOffset.Y = position.Y;



        Debug.WriteLine("x: {0}; y: {1}", position.X, position.Y);
        Debug.WriteLine("x: {0}; y: {1}", position.X, position.Y);
        //this._adornerOffset.X = position.X - this._adornerOrigin.X - _child.Width / 2;
        //this._adornerOffset.Y = position.Y - this._adornerOrigin.Y - _child.Height / 2;
        UpdatePosition();
    }

    private void UpdatePosition()
    {
        AdornerLayer adornerLayer = (AdornerLayer)this.Parent;
        if (adornerLayer != null)
        {
            adornerLayer.Update(this.AdornedElement);
        }
        //AdornerLayer.GetAdornerLayer(AdornedElement).Update();
    }

    protected override int VisualChildrenCount { get { return 1; } }

    protected override Visual GetVisualChild(int index)
    {
        System.Diagnostics.Debug.Assert(index == 0, "Index must be 0, there's only one child");
        return this._child;
    }

    protected override Size MeasureOverride(Size finalSize)
    {
        this._child.Measure(finalSize);
        return this._child.DesiredSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        this._child.Arrange(new Rect(finalSize));
        return finalSize;
    }

    public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
    {
        Debug.WriteLine(transform);

        GeneralTransformGroup newTransform = new GeneralTransformGroup();
        MatrixTransform tr = transform as MatrixTransform;
        if (tr != null)
        {
            //newTransform.Children.Add(base.GetDesiredTransform(new MatrixTransform(new Matrix(tr.Matrix.M11, tr.Matrix.M12, tr.Matrix.M21, tr.Matrix.M22, 0, 0))));
            //newTransform.Children.Add(base.GetDesiredTransform(new MatrixTransform(new Matrix(tr.Matrix.M11, tr.Matrix.M12, tr.Matrix.M21, tr.Matrix.M22, this._adornerOffset.X, this._adornerOffset.Y))));
        }

        newTransform.Children.Add(base.GetDesiredTransform(transform));
        newTransform.Children.Add(new TranslateTransform(this._adornerOffset.X, this._adornerOffset.Y));
        return newTransform;
    }

    public void Destroy()
    {
        AdornerLayer.GetAdornerLayer(AdornedElement).Remove(this);
    }

Appreciate any help! Here is the link to the project: https://www.dropbox.com/s/nogt3gjwmf5e3pg/SandBox.zip?dl=0


Solution

  • Try this <sandBox:DragBehavior RelativeElement="{Binding ElementName=Label}"/> instead <sandBox:DragBehavior RelativeElement="{Binding ElementName=Canvas}"/>