5

I've created a custom behaviour (without blend) for dragging UIElements and getting a translucent thumbnail of what you're dragging under the cursor (blends version moves the target object which isn't what I need)

Anyhoo, the code is actually very simple and works nicely, the problem I'm having is that onDetaching() isn't being called, which means my events to the UIElement aren't being unhooked.

This worries me a little as I'm guessing the only reason why the behaviour isn't being detached is because it's still being referenced by something. It shouldn't be the UIElement it's self though as we had leakage problems with it at one stage but we've now resolved them and this is clarified through WinDbg.

The only interesting(?) thing about it is the behaviour is attached to an image in an items control, but this shouldn't make a difference right?

Here's my current code:

public class DragBehavior : Behavior<UIElement>
{
    private const double DRAG_DISTANCE = 20;
    private const int DRAG_ICON_HEIGHT = 100;

    Point m_firstPoint;
    private bool m_isDragging = false;
    private Popup m_dragPopup = null;
    private Image m_draggedImage;        

    protected override void OnAttached()
    {
        this.AssociatedObject.MouseLeftButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
        this.AssociatedObject.MouseMove += new MouseEventHandler(AssociatedObject_MouseMove);
        this.AssociatedObject.MouseLeftButtonUp += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);

        base.OnAttached();
    }

    void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        m_firstPoint = e.GetPosition(null);
        m_isDragging = true;
    }

    void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
    {
        if (m_isDragging)
        {
            Point currentPosition = e.GetPosition(null);

            if (m_dragPopup == null)
            {
                double deltaX = currentPosition.X - m_firstPoint.X;
                double deltaY = currentPosition.Y - m_firstPoint.Y;

                double movement = Math.Sqrt((deltaX * deltaX) + (deltaY * deltaY));
                if (movement > DRAG_DISTANCE)
                {
                    // Get a screen shot of the element this behaviour is attached to
                    WriteableBitmap elementScreenshot = new WriteableBitmap(AssociatedObject, null);

                    // Create an image out of it
                    m_draggedImage = new Image();
                    m_draggedImage.Height = DRAG_ICON_HEIGHT;
                    m_draggedImage.Stretch = Stretch.Uniform;
                    m_draggedImage.Source = elementScreenshot;
                    m_draggedImage.Opacity = 0.4;

                    // Add the image to the popup
                    m_dragPopup = new Popup();
                    m_dragPopup.Child = m_draggedImage;

                    m_dragPopup.IsOpen = true;
                    m_dragPopup.UpdateLayout();

                    m_dragPopup.HorizontalOffset = currentPosition.X - m_draggedImage.ActualWidth/2;
                    m_dragPopup.VerticalOffset = currentPosition.Y - m_draggedImage.ActualHeight/2;

                    AssociatedObject.CaptureMouse();
                }
            }
            else
            {
                m_dragPopup.HorizontalOffset = currentPosition.X - m_draggedImage.ActualWidth/2;
                m_dragPopup.VerticalOffset = currentPosition.Y - m_draggedImage.ActualHeight/2;
            }
        }
    }

    void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (m_isDragging == true && m_dragPopup != null)
        {
            m_isDragging = false;
            m_dragPopup.IsOpen = false;
            m_dragPopup = null;
        }

        AssociatedObject.ReleaseMouseCapture();

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }

    protected override void OnDetaching()
    {
        this.AssociatedObject.MouseLeftButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
        this.AssociatedObject.MouseMove -= new MouseEventHandler(AssociatedObject_MouseMove);
        this.AssociatedObject.MouseLeftButtonUp -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);

        base.OnDetaching();
    }
}

I know of two posts which I've seen about this...

This one - forums.silverlight.net/forums/p/142038/317146.aspx doesn't help as I've tried forcing a GC to no avail, and this one - Automatically calling OnDetaching() for Silverlight Behaviors I don't really get their explination as they claim its the hooking up of the UIElement to the behaviour that causes it, but surely when the root reference UIElement is broken, the root reference to the behaviour will be removed too and consequently both become eligable for GC.

I was hoping this'd be simple, but if not I'll get started with WinDbg to see what's actually going on!

Any help is much appreciated! :)

Thanks,

Andy.

Community
  • 1
  • 1
Andy
  • 2,977
  • 2
  • 39
  • 71
  • 1
    Noone can help? Guess I'll try positing in the silverlight forums! I'll update this thread if I get anywhere! – Andy Feb 16 '11 at 09:30

1 Answers1

0

You don't show where the Detach is being called from. Something needs to actually call the object.Detach() (DragBehavior) for the OnDetaching() method to be called.

If you wanted to detach it in the AssociatedObject_MouseLeftButtonUp event handler you could call detach at the end of the method as long as you realy don't need to do anything more with it.

void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 
{ 
    if (m_isDragging == true && m_dragPopup != null) 
    { 
        m_isDragging = false; 
        m_dragPopup.IsOpen = false; 
        m_dragPopup = null; 
    } 

    AssociatedObject.ReleaseMouseCapture(); 

    this.Detach()
} 

I definately would not be making any calls to GC which can have a huge performance impact.

Bernie White
  • 4,780
  • 4
  • 23
  • 21
  • 4
    I think that's the point he is making. Silverlight should be calling 'Detach' when the view is removed from the visual tree. – Chris Shepherd Jan 09 '12 at 10:39