Silverlight 2.0 In Examples: Part ? Drag and Drop Inside Out

Introduction

This is a continuation of a tutorial, see Silverlight 2.0 In Examples: Part 0. Introduction” and “Silverlight 2.0 In Examples: Part 1. Silverlight Elements: Panels and Controls.

Since recently I’ve been working on Silverlight Drag and Drop, I decided to break the continuity of this tutorial and skip several parts, writing right away about how to implement Drag and Drop in Silverlight. In addition to information from the previous sections of the tutorial, this part requires some knowledge of DataTemplates and binding (I will give brief explanations of both).

The article that started me on Silverlight 2.0 Drag and Drop can be accessed via the following link: Drag and Drop in Silverlight. At the bottom of the article you can find a link to download the source code.

Here, however, I go over Drag and Drop with more details and examples. A special feature of this article is a Drag and Drop custom control designed to absorb most of the Drag and Drop complexity. Based on this control, I created an example functionally similar to the one described in Drag and Drop in Silverlight.

Silverlight demos, including those corresponding to the samples presented here can be found at AWebPros Demos.

Simple Silverlight Drag and Drop Example

Here is the screen capture of the first example:

One can drag and drop the red circle anywhere within the Silverlight application area.
The source code for this sample can be downloaded from simpledragdropzip.doc. As always, please remove .doc extension, rename the file to simpledragdrop.zip and then open it as a zip file.
Below the code of the sample is explained in detail.
Here are the contents of Page.xaml file:

<UserControl

    x:Class=”SimpleDragDrop.Page”

    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation&#8221;

    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml&#8221;

    Background=”Yellow”

    Width=”400″

    Height=”300″>

    <UserControl.Resources>

        <DataTemplate x:Key=”Circle”>

            <Ellipse Width=”20″ Height=”20″ Fill=”Red”></Ellipse>

        </DataTemplate>

    </UserControl.Resources>

    <Grid x:Name=”LayoutRoot” Background=”White”>

        <Popup x:Name=”MyPopup”IsOpen=”False”>

            <ContentControl

   ContentTemplate=”{StaticResource Circle}”

   Opacity=”0.5″/>

        </Popup>

        <ContentControl

            ContentTemplate=”{StaticResource Circle}”

            x:Name=”MyControlToMove”>

        </ContentControl>

    </Grid>

</UserControl>
The object that we are dragging and dropping is ContentControl at the very bottom of the XAML file. Its name is “MyControlToMove”.

ContentControl is a control that has two major properties: Content (which contains some data) and ContentTemplate (which specifies how to present this data).

In our case the presentation of MyControlToMove is not data dependent (Content property does not play any role). This presentation is determined only by the DataTemplate which is defined as a resource called “Circle” within UserControl.Resources section. In this example this ContentControl is always displayed as a red circle.
Another control is used as a drag cursor to show where the circle is dragged. It is represented by a similar red circle with its opacity property set to 0.5, making it semitransparent. This control is of Popup type.

Now, let us deconstruct the C# code. While XAML contains mostly design and static information, C# carries information about actions. There are 3 main actions within the Drag and Drop process:

  • Drag beginning – occurs when the left mouse button is pressed on a draggable object.
  • Drag process – occurs when the mouse pointer is moved with the left mouse button still pressed.
  • Drop – occurs when the left mouse button is released.
  • Correspondingly, we have 3 event handlers for 3 events:

  • MouseLeftButtonDown – to handle start of the drag process
  • MouseMove – to handle visual moving of the dragged item
  • MouseLeftButtonUp – to handle drop operation
  • All of the events are registered with the top level panel “LayoutRoot”. This is possible because of the event “Bubbling”. Even if a child of “LayoutRoot” panel is clicked, the event eventually “bubbles” up to it, unless it is handled before.
    Here is how we set the event handlers within the application:

    LayoutRoot.MouseLeftButtonDown +=

        new MouseButtonEventHandler(LayoutRoot_MouseLeftButtonDown);

    LayoutRoot.MouseMove +=

        new MouseEventHandler(LayoutRoot_MouseMove);

    LayoutRoot.MouseLeftButtonUp +=

        new MouseButtonEventHandler(LayoutRoot_MouseLeftButtonUp);

     

    Now let us describe these 3 operations in detail.

    Starting the Drag Operation

    Starting the drag operation is handled by the function LayoutRoot_MouseLeftButtonDown which in turn calls StartDragDrop.

    Here is the code for LayoutRoot_MouseLeftButtonDown function:

    void

    LayoutRoot_MouseLeftButtonDown(object sender,

                                                   MouseButtonEventArgs e)

    {

        // obtain all UI elements at the

        // current mouse pointer location

        List<UIElement> elements =

           (List<UIElement>)this.HitTest(e.GetPosition(null));

     

        // get the first element of type Ellipse and

        // start drag operation on it.

        foreach (UIElement element in elements)

        {

            if (element is Ellipse)

            {

                StartDragDrop(element, e);

                break;

            }

        }

    }

    First HitTest gets all the elements “pierced” by the current mouse pointer. Then we iterate over each of of the elements, find the “Ellipse” (this is our circle we want to move) and start drag operation on it by calling StartDragDrop function.

    Here is the StartDragDrop function code:

    private void

    StartDragDrop(UIElement element,

                  MouseButtonEventArgs e)

    {

        // make the popup visible

        MyPopup.IsOpen = true;

     

        // make the mouse events connected to

        // the popup

        MyPopup.CaptureMouse();

     

        // figure out the mouse coordinates at the onset

        // of Drag operation.

        _horisontalOffset = e.GetPosition(null).X;

        _verticalOffset = e.GetPosition(null).Y;

     

        // set _captured flag to true

        // (this is to check later if drag drop

        //  operation is in progress)

        _captured = true;

     

        // move the popup to the current mouse location.

        MyPopup.HorizontalOffset = _horisontalOffset;

        MyPopup.VerticalOffset = _verticalOffset;

    }

    The StartDragDrop functionality is explained in the comments within its code.

    Drag Operation During the Mouse Move

    Here is how we implemented Drag operation when mouse moves:

    void

    LayoutRoot_MouseMove(object sender,

                         MouseEventArgs e)

    {

        // if no drag and drop started,

        // we do not need to do anything.

        if (!_captured)

            return;

     

        // update the popup location to the

        // current mouse pointer location.

        MyPopup.HorizontalOffset = e.GetPosition(null).X;

        MyPopup.VerticalOffset = e.GetPosition(null).Y;

    }

    All we need to do is to move the Drag popup to the current location of the mouse pointer. 

    Drop Operation

    Finally, here is the function in charge of Drop operation:

    void

    LayoutRoot_MouseLeftButtonUp(object sender,

                                 MouseButtonEventArgs e)

    {

        // if drag is not started we do not need

        // any drop functionality to execute

        if (!_captured)

            return;

     

        // mouse capture is released

        MyPopup.ReleaseMouseCapture();

     

        // popup becomes invisible

        MyPopup.IsOpen = false;

     

        // set captured flag to false

        // (no drag operation in progress)

        _captured = false;

     

        // the rest of the code just moves

        // our content control to the new location

        // using TranslateTransform

        GeneralTransform gt =

            this.TransformToVisual(MyControlToMove);

     

        PointstartPoint = gt.Transform(new Point(0, 0));

     

        Pointp = e.GetPosition(null);

     

        TranslateTransformtt = new TranslateTransform();

     

        tt.X = p.X – _horisontalOffset – startPoint.X;

        tt.Y = p.Y – _verticalOffset – startPoint.Y;

     

        MyControlToMove.RenderTransform = tt;

    }

    As you can see, we stop the DragDrop operation by releasing the mouse capture, making the Drag popup invisible and setting _captured flag to false. Then we do the actual drop operation by moving the original dragged control to the new location using TranslateTransform.

    Drag and Drop with Forbidden Area

    Now, let us consider a more complicated example.
    In addition to the previously discussed functionality it has the following:

  • Area in which it is forbidden to drop.
  • A special template for the Drag popup to appear when the mouse pointer is in the area in which it is forbidden to drop.
  • The drag operation does not start right away, but only when the mouse is far enough from the origin.
  • Here is the screen capture for the example:

    The yellow circle signifies the area where the drop is allowed. Outside of it, the drop is forbidden.
    Here is the code for the example: dragdropwithforbiddenareazip.doc.
    As one can see from the code, the capture process now begins during MouseMove action and only when the distance between the original and current locations of the mouse is greater than _captureRadius:

    private void BeginCapture(MouseEventArgs e)

    {

        // check if the mouse pointer position

        // within _captureRadius

        double diffX =

            e.GetPosition(null).X – _horisontalOffset;

        double diffY =

            e.GetPosition(null).Y – _verticalOffset;

     

        // if it is within _captureRadion,

        // do not start capture

        if ((diffX * diffX + diffY * diffY) <

            _captureRaduis * _captureRaduis)

        {

            return;

        }

     

        // make popup visible

        MyPopup.IsOpen = true;

     

        // capture mouse

        MyPopup.CaptureMouse();

     

        // set _captured flag to true.

        _captured = true;

    }

    There is also a function InAllowedArea that returns true if the mouse pointer is within an area in which we are allowed to drop and false otherwise:

    // checks if drop is allowed

    // at the current location of the

    // mouse pointer.

    bool InAllowedArea(MouseEventArgs e)

    {

        Point p = e.GetPosition(null);

        IEnumerable<UIElement> hitTestResult =

      AllowedArea.HitTest(p);

     

        // see if mouse pointer hits AllowedArea

        // if yes, return true,

        // if no, return false.

        return(hitTestResult.Count() != 0);

    }

    Then, during the Drag process, we check if we are within the area in which we are allowed to drop and set the ContentTemplate of the PopupControl correspondingly:

    if(InAllowedArea(e))

    {

        // if we are in an area

        // in which we are

        // allowed to drop

        // set the template accordingly

        // to our Circle

        PopupControl.ContentTemplate = CircleTemplate;

    }

    else

    {

        // if we are in an area in which it

        // is forbidden to drop, set

        PopupControl.ContentTemplate = DropForbidden;

    }

     

    Generic Drag and Drop Control

    Based on the above examples and also taking into account that the Drag popup display can be data driven (as will be shown later) I came up with a generic Drag/Drop control implementation. While it is still rather complex to use, since drag and drop is a complex operation, it also absorbs a lot of complexity into itself, eliminating the need to implement and debug a large chunk of the Drag/Drop functionality over and over again.

    Here is the code for generic Drag/Drop control together with a couple of samples showing how to use it: genericdragdropzip.doc.

    DragDrop control is defined within GenericDragDropLib project within DragDropControl.cs file. The generic.xaml file contains default control template for DragDropControl.
    DragDropControl contains a number of properties and events that allow customization of the Drag/Drop functionality. Here is the list of these properties and events with the explanations as to why they are needed.

    // mouse is captured and the popup

    // indicating the beginning of Drag

    // operation to the user becomes

    // visible on when the distance between

    // the mouse pointer and the original

    // point when the mouse button was pressed

    // during the Drag operation is

    // greater than CaptureRadius.

    public doubleCaptureRadius { get; set; }

     

    // the popup to show during Drag operation

    public Popup DragDropPopup { get; set; }

     

    // The content control within the DragDropPopup

    // that can assume any shape defined by

    // its data template and date content.

    public ContentControl PopupContentControl { get; set; }

     

    // default data template for areas in

    // which drop is allowed

    public DataTemplate DropAllowedTemplate { get; set; }

     

    // data template for areas in which

    // drop is forbidden

    public DataTemplate DropForbiddenTemplate { get; set; }

     

    // element to be dragged

    public UIElementDraggedElement { get; set; }

     

    // dragged business logic object

    // (specified by Content property of DraggedElement)

    public objectDraggedObject { get; set; }

    // DragContainer property is used when DraggedElement is

    // one of the items within and ItemsControl e.g. a

    // ListBox this property contains the reference to this

    // container control of the dragged item

    public UIElementDragContainer {get; set;}

     

    // does the drop operation

    public event DoDropDelegateDoDrop = null;

     

    // used to filter in the element that can be

    // dragged

    public event IsDraggableDelegateIsDraggable = null;

     

    // used to filter in the container of the element

    // that can be dragged

    public event IsContainerDelegate IsDragContainer = null;

     

    // returns true if, during the drag operation,

    // the mouse pointer is inside the area

    // it which drops are allowed, false otherwise.

    public event IsInAllowedAreaDelegateIsInAllowedArea = null;

     

    // returns data template for the case when the drops are

    // allowed during the drag operation

    public event GetDataTemplateDelegate GetDataTemplate = null;

     

    //used to set the DraggedObject from the

    // DraggedElement.

    // Most of the times, this is just DraggedElement.Content

    public event GetBusinessLogicObjectDelegate

        GetBusinessLogicObject = null;

     

    // used to record the origin of the drag operation

    public Point StartDragPoint { get; set; }

    DragDropControl also contains three functions that need to be triggered by the Button Down, Mouse Move and Button Up events of the application that uses DragDropControl. These functions are

  • StartDragDrop
  • OnMove
  • OnButtonUp
  • Below we describe two examples using DragDropControl.

    Drag Drop with Forbidden Area Implemented Using DragDropControl

    The solution for this test is called GenericDragDropTest.sln and it is located under GenericDragDropTest within genericdragdrop.zip file. It produces exactly the same result as our Drag/Drop with forbidden area example above, but it uses DragDropControl. Below we show how DragDropControl is used.
    Here is the code showing how we connect the events of DragDropControl:

    MyDragDropControl.DoDrop +=

        new GenericDragDropLib.

            DoDropDelegate(MyDragDropControl_DoDrop);

     

    // we only drag ellipses!

    MyDragDropControl.IsDraggable +=

        delegate(UIElement element)

        {

            if (element is Ellipse)

                return true;

            return false;

        };

     

    // data template for the drag popup is

    // provided by CircleTemplate resource

    MyDragDropControl.GetDataTemplate +=

        delegate(UIElement element)

        {

            return (DataTemplate) this.Resources[“CircleTemplate”];

        };

     

    // we are in an area in which are

    // are allowed to drop if the

    // mouse pointer is

    // inside AllowedArea circle.

    MyDragDropControl.IsInAllowedArea +=

        delegate(Point p)

        {

            IEnumerable<UIElement> hitTestResult =

                AllowedArea.HitTest(p);

            return(hitTestResult.Count() != 0);

        };

    And here is the code that connects the mouse button events to the DragDropControl functions:

    // connect the mouse button events to

    // the corresponding functions

    // of our DragDropControl.

    LayoutRoot.MouseLeftButtonDown +=

        delegate(object sender, MouseButtonEventArgs e)

        {

            MyDragDropControl.StartDragDrop(this, e);

        };

     

    LayoutRoot.MouseMove +=

        delegate(object sender, MouseEventArgs e)

        {

            MyDragDropControl.OnMove(e);

        };

     

    LayoutRoot.MouseLeftButtonUp +=

        delegate(object sender, MouseButtonEventArgs e)

        {

            MyDragDropControl.OnButtonUp(e);

        };

    Drag and Drop between Two ListBox Elements

    Here we show an implementation of Drag/Drop analogous to that of Drag and Drop in Silverlight. The ListBoxItem objects are dragged and dropped between two ListBoxes.

    The code for this sample can be found under ListBoxDragDropTest directory of genericdragdrop.zip file.
    Here is the screen capture for this sample:

    This sample also provides an example of having data driven visual presentation of the dragged item (the first and last names of the students provide the data that can change depending on the dragged item).
    Here is the code showing setting the event handlers for DragDropControl events:

    MyDragDropControl.DoDrop +=

        new GenericDragDropLib.DoDropDelegate(DoDrop);

     

    // we only Drag and Drop ListBoxItem objects!

    MyDragDropControl.IsDraggable +=

        delegate(UIElement element)

        {

            if (element is ListBoxItem)

                return true;

            return false;

        };

     

    // our drag container is ListBox

    MyDragDropControl.IsDragContainer +=

        delegate(UIElement element)

        {

            if (element is ListBox)

                return true;

            return false;

        };

     

    // we use the same DataTemplate

    // to display the drag popup as

    // we use to display the items in the list

    MyDragDropControl.GetDataTemplate +=

        delegate(UIElement element)

        {

            ListBoxItemlbi = MyDragDropControl.DraggedElement as ListBoxItem;

     

            if(lbi == null)

                return null;

     

            return (DataTemplate)lbi.ContentTemplate;

        };

     

    // we are in allowed area if and only if we are

    // inside a ListBox

    MyDragDropControl.IsInAllowedArea +=

        delegate(Point p)

        {

            ListBoxlb = GetListBox(p);

            if (lb == null)

                return false;

            return true;

        };

     

    // returns Content property of the ListBoxItem

    MyDragDropControl.GetBusinessLogicObject +=

        delegate(UIElement element)

        {

            ContentControl cc = element as ContentControl;

     

            if (cc == null)

                return null;

     

            return cc.Content;

        };

    One can see that the DoDrop function (called during the drop operation) checks if the target ListBox is not the same as the original ListBox from where the item was dragged and if true, removes the item from the original collection and adds it to the collection of the target ListBox.

    Conclusion

    This article describes the implementation of drag and drop functionality in Silverlight. It starts with a simple example and progresses to more complex ones. It features custom DragDropControl which is used to absorb a lot of complexity of Drag/Drop implementation.

    Tags: ,

    17 Responses to “Silverlight 2.0 In Examples: Part ? Drag and Drop Inside Out”

    1. Rob Gibson Says:

      Small update required for Silverlight 2:

      DragDropControl.cs – Line: 132 – Replace [TopLevelDragContainer.HitTest(startPoint);] with [VisualTreeHelper.FindElementsInHostCoordinates(startPoint, TopLevelDragContainer);]

      ListBoxDragDropTest – Page.xaml.cs – Line: 97 – Replace [hitTestResults = LayoutRoot.HitTest(p);] with [VisualTreeHelper.FindElementsInHostCoordinates(p, LayoutRoot);]

    2. name Says:

      Failed at DragDropPopup.IsOpen .
      This is null;

    3. npolyak Says:

      Sorry, can you be more specific. What were you trying to do when the failure occured?

    4. jocknuke Says:

      Failed at DragDropPopup.IsOpen while trying to click on the object. Object is null.

    5. Brian Says:

      I got the same error when attempting to initiate a drag operation. It’s because the the template was not being applied so your DragDropPopup was never being initialized (since OnApplyTemplate was never called) and that’s what leads to the null reference exception on the DragDropPopup.IsOpen line.

      The solution is to move the generic.xaml file that’s contained in the GenericDragDropLib project into a new sub-folder called Themes as required by the final version of SL.

    6. Jim Says:

      Very nice job. Well commented and the article was very clear. Thanks, it taught me a lot. I’m new to Silverlight and did run into something I just can’t figure out. I’m using the GenericDragDropLib for copying items from one list box to others. I have a control template for my list items that basically makes the items magnify when the mouse moves over them (CommonStates MouseOver). When an item gets dragged the source (original) item does not shrink down when the mouse leaves with the dragging copy. It’s like the item does not change states when the mouse is no longer over the item. I have to run the mouse over it again after the drop and it restores.

      Any ideas?

      • npolyak Says:

        Hey Jim,
        apparently for some reason MouseLeave event does not fire. You can probably add the shrinking functionality to DoDrop function.
        If you send me your code, I’ll be happy to try to figure it out.

      • npolyak Says:

        Actually, re-reading your comment more carefully, apparently you are using the VSM to expand and shrink each item. VisualStateManager can be controlled programmatically.
        Basically you can get the VisualStateGroups from the visual state manager and then states from the groups and then you can get a needed storyboard from the state. Then, you can probably run the story board whenever you need by using Begin method.
        Examples of getting the visual states programmatically can be found at http://silverlight.net/forums/p/17802/60387.aspx.
        Again I’ll be able to say more if I have the code.

    7. MawashiKid Says:

      Hi,

      I’ve tested your code, very interesting indeed…
      I had to make some minor modifications in the code though.
      As Rob Gibson said, the function HitTest is no longer supported by the Silverlight 2 RTM version (and the Silverlight 3 Beta also…)
      so we have to use VisualTreeHelper class instead.

      Example: In the SimpleDragAndDrop application the following line:

      void LayoutRoot_MouseLeftButton(object sender, MouseButtonEventArgs e)
      {
      List element = (List)this.HitTest(e.GetPosition(null)); ….

      will generate an error with Silverlight 2 RTM version
      and should therefore be replace with:

      void LayoutRoot_MouseLeftButton(object sender, MouseButtonEventArgs e)
      {
      IEnumerable elements = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), this);

      in order to run properly.

      I’ve also made the same type of modifications in the ListDragAndDrop application so that
      the code no longer generates any errors during compilation. However when I run
      the ListDragAndDrop application, the elements in the listbox “Box1” just won’t respond to any behaviour… Strange…

      I’m sure that any update of your code would be very much appreciated.
      Thank you and keep on your ggod work.

      • npolyak Says:

        Hi MavashiKid,
        thanks for your remarks. I am a little out of date on my blog matters because I am working on a big Silverlight project.
        Can you send an ftp or http url to download your code that does not work. I can try to figure out what is wrong.
        Concerning the code update – I’ll try to post it within a week or two.

    8. dykin's me2DAY Says:

      리거니의 생각…

      Silverlight 2.0 In Examples: Part ? Drag and Drop Inside Out « Nick Polyak’s Software Blog(고급)…

    9. Eduardo Says:

      Great man,
      Thanks!

      You are de first who used the events on the Grid “LayoutRoot”. I see many peoples who used the events on the objects and not maked the test on the function
      ” foreach (UIElement element in elements)
      {

      if (element is Ellipse)”

      I was looking for this. I will make an example based on your’s post in my blog(Portuguese).

    10. programming Says:

      programming…

      […]Silverlight 2.0 In Examples: Part ? Drag and Drop Inside Out « Nick Polyak’s Software Blog[…]…

    11. Alexis Says:

      certainly like your web site but you need to check the spelling on several
      of your posts. Many of them are rife with spelling problems and I in finding it very troublesome to tell the reality however I will certainly
      come again again.

    12. deep freezer costco Says:

      excellent submit, very informative. I wonder why the
      other specialists of this sector do not understand this.
      You should continue your writing. I am confident, you’ve
      a huge readers’ base already!

    Leave a reply to npolyak Cancel reply