Silverlight 2 Drag, Drop, and Import Content Example

Dragging an element from one user control into another. Exporting and importing data from one user control into another.

Back To: Silverlight Tutorials

Also see:


                        Dynamically loading Silverlight controls into resizable draggable windows.

{Click Here to See the Live Example}

Download Code: SilverlightDragAndDrop.zip

This is an an example of code that will be incorporated into http://SilverlightDesktop.net.

The example is based on an earlier drag and drop code sample that was based on an example created by Keith Mahoney. In his example he shows how to drag and drop various types of elements into various types of Panels. In this example we only cover dragging and dropping an element onto a Canvas. We also cover importing and exporting content from one user control into another.

 

When you view the sample you see three movable windows (you grab the colored bar on the top of the window to drag it) with three boxes in the first window.

The "Drag Me" box can only be dragged inside the window, but the blank box and the "Drag Me Outside" box can be dragged anywhere on the screen.

To determine the different behavior of the boxes, the word "[draggable]" is added to the Tag property of the element that will be draggable outside the window. When an element with that Tag is detected, the element is removed from the Canvas it is currently on and placed on the root Canvas of the application while it is being dragged.

When the element is dropped a HitTest is performed to see if the mouse is currently in the bounds of a Canvas element that is in one of the windows. If it is in the bounds, the element is removed from the root Canvas and placed onto the Canvas that is in the respective window.

If the boxes are dragged inside another window, they will remain in the window and move with that window when that window is moved.

The window labeled "Drop Items Here" implements a "ImportContent" interface that allows content to be possibly imported and parsed rather than simply added to the window. When the blank box is dragged onto the window labeled "Drop Items Here", the box is simply added to the window.

However, when the box labeled "Drag Me Outside and Export Me" is dragged onto the window labeled "Drop Items Here", the TextBlocks in the box are parsed and their content is added to the TextBlock in the window.

The Code

The solution file contains a Silverlight project and a web project to display the Silverlight project output. The following describes the files in the SilverlightDragAndDrop project:

Creating the Windows

The Page.xaml control creates the three windows and a list of panels that elements can possibly be dragged onto. It will perform a HitTest to determine if an element is being dragged onto one of the panels.

        public Page()
        {
            InitializeComponent();
 
            // Window #1 - Contains objects
            SilverlightWindowControl objSilverlightWindowControl = new SilverlightWindowControl(50);
            // Pass an instance of this control to the child control. This will allow the child to 
            // wire-up mouse events to the parent. 
            SilverlightObjects SilverlightObjects = new SilverlightObjects(this);
            objSilverlightWindowControl.Window.Content = SilverlightObjects;
            Canvas.SetLeft(objSilverlightWindowControl, (double)50);
            Canvas.SetTop(objSilverlightWindowControl, (double)50);
 
            // SilverlightCanvas1 - Implements the "ImportContent" Interface
            SilverlightWindowControl objBlankSilverlightWindowControl1 = new SilverlightWindowControl(60);
            SilverlightCanvas1 objSilverlightCanvas1 = new SilverlightCanvas1();
            objBlankSilverlightWindowControl1.Window.Content = objSilverlightCanvas1;
            Canvas.SetLeft(objBlankSilverlightWindowControl1, (double)300);
            Canvas.SetTop(objBlankSilverlightWindowControl1, (double)50);
 
            // SilverlightCanvas2 - Does NOT implement the "ImportContent" Interface
            SilverlightWindowControl objBlankSilverlightWindowControl2 = new SilverlightWindowControl(60);
            SilverlightCanvas2 objSilverlightCanvas2 = new SilverlightCanvas2();
            objBlankSilverlightWindowControl2.Window.Content = objSilverlightCanvas2;
            Canvas.SetLeft(objBlankSilverlightWindowControl2, (double)300);
            Canvas.SetTop(objBlankSilverlightWindowControl2, (double)310);
 
            // Add Canvases to a collection that will be checked as possible drop points
            colPanels.Add(SilverlightObjects.LayoutRoot);
            colPanels.Add(objSilverlightCanvas1.LayoutRoot);
            colPanels.Add(objSilverlightCanvas2.LayoutRoot);
 
            // Add Windows to the Page
            this.LayoutRoot.Children.Add(objSilverlightWindowControl);
            this.LayoutRoot.Children.Add(objBlankSilverlightWindowControl1);
            this.LayoutRoot.Children.Add(objBlankSilverlightWindowControl2);
        }

The SilverlightObjects.xaml control uses an instance of the parent control to wire-up the mouse events to detect when an element is being dragged.

        public SilverlightObjects(Page objPage)
        {
            InitializeComponent();
 
            // Add delegates to the parent to allow it to handle the drag and drop
            FrameworkElement objFrameworkElement1 = (FrameworkElement)this.DragElement1;
            objFrameworkElement1.MouseLeftButtonDown += 
                new MouseButtonEventHandler(objPage.objFrameworkElement_MouseLeftButtonDown);
            FrameworkElement objFrameworkElement2 = (FrameworkElement)this.DragElement2;
            objFrameworkElement2.MouseLeftButtonDown += 
                new MouseButtonEventHandler(objPage.objFrameworkElement_MouseLeftButtonDown);
            FrameworkElement objFrameworkElement3 = (FrameworkElement)this.DragElement3;
            objFrameworkElement3.MouseLeftButtonDown += 
                new MouseButtonEventHandler(objPage.objFrameworkElement_MouseLeftButtonDown);
        }

Note, the only way to get an instance of the parent control when a control has been dynamically added is to pass an instance of that parent to the control when the control is added. The mouse events must be attached to, and handled by, the parent not the child control for the communication between windows to work.

When the mouse starts to drag an element, the element is checked to see if it's tag contains "[draggable]". If it does, the element is removed from the Canvas it is on and placed on the main Canvas.

        void objFrameworkElement_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            //Start Drag
            FrameworkElement objFrameworkElement = (FrameworkElement)sender;
            objFrameworkElement.CaptureMouse();
 
            // Set the starting point for the drag
            StartingDragPoint = e.GetPosition(objFrameworkElement);
 
            // Remove the element from it's control and move it to the parent
            // if it's Tag contains the words [draggable]
            if (objFrameworkElement.Tag.ToString().Contains("[draggable]"))
            {
                Panel objParent = objFrameworkElement.Parent as Panel;
                objParent.Children.Remove(objFrameworkElement);
 
                this.LayoutRoot.Children.Add(objFrameworkElement);
                MoveToTop(objFrameworkElement);
                UpdateElementPosition(objFrameworkElement, e.GetPosition(this.LayoutRoot));
            }
 
            objFrameworkElement.MouseMove += new MouseEventHandler(objFrameworkElement_MouseMove);
            objFrameworkElement.MouseLeftButtonUp += new MouseButtonEventHandler(objFrameworkElement_MouseLeftButtonUp);
        }

The code to move an element being dragged is the same whether or not the element has been moved to the main Canvas or not.

        #region MouseMove
        void objFrameworkElement_MouseMove(object sender, MouseEventArgs e)
        {
            FrameworkElement objFrameworkElement = (FrameworkElement)sender;
            Canvas objCanvas = (Canvas)objFrameworkElement.Parent;
            Point Point = e.GetPosition(objCanvas);
 
            UpdateElementPosition(objFrameworkElement, Point);
        }
        #endregion

When an element is dropped, this code is used to determine if the element will simply be placed inside a control, or if the ImportContent method will be called:

        #region MouseLeftButtonUp
        void objFrameworkElement_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            //Stop Drag
            FrameworkElement objFrameworkElement = (FrameworkElement)sender;
            objFrameworkElement.ReleaseMouseCapture();
 
            objFrameworkElement.MouseMove -= 
                new MouseEventHandler(objFrameworkElement_MouseMove);
            objFrameworkElement.MouseLeftButtonUp -= 
                new MouseButtonEventHandler(objFrameworkElement_MouseLeftButtonUp);
 
            // If it is an element marked [draggable]
            // try to drop it on a panel stored in the colPanels collection
            if (objFrameworkElement.Tag.ToString().Contains("[draggable]"))
            {
                Point tmpPoint = e.GetPosition(null);
                // Build a list of elements at the current mouse position
                List<UIElement> hits = (List<UIElement>)this.HitTest(tmpPoint);
                // Loop through all the Panels in the colPanels collection
                foreach (Panel objPanel in colPanels)
                {
                    if (hits.Contains(objPanel))
                    {
                        // Grab the position of the element being dragged in relation to it's position on the 
                        // main canvas and it's position in relation to the panel it may be dropped on
                        Point mousePos1 = e.GetPosition(objPanel);
                        Point mousePos2 = e.GetPosition(objFrameworkElement);
 
                        // Remove the element from the main canvas
                        this.LayoutRoot.Children.Remove(objFrameworkElement);
 
                        // Import content
                        // Get a reference to the parent of the current panel
                        UserControl objUserControl = (UserControl)objPanel.Parent;
                        // See if that parent implements an interface called "ImportContent"
                        object objObject = objUserControl.GetType().GetInterface("ImportContent", true);
 
                        // If the object is not null then the parent object has a method called "ImportContent"
                        if (!(objObject == null))
                        {
                            // Create a parmeters array
                            object[] parameters = new object[1];
                            // Add the elemnt that is being dragged to the array
                            parameters.SetValue(objFrameworkElement, 0);
                            // invoke the "ImportContent" on the parent object passing the parameters array that 
                            // contains the element being dragged
                            bool boolImport = (bool)objUserControl.GetType().InvokeMember("ImportContent", 
                                BindingFlags.InvokeMethod, null, objUserControl, parameters);
 
                            // If the import was not successful simply add the element to the panel
                            if (!boolImport)
                            {
                                // Add the element to the panel
                                objPanel.Children.Add(objFrameworkElement);
                                Canvas.SetLeft(objFrameworkElement, mousePos1.X - mousePos2.X);
                                Canvas.SetTop(objFrameworkElement, mousePos1.Y - mousePos2.Y);
                            }
                        }
                        else
                        {
                            // The parent object does not implement the "ImportContent" Interface
                            // Add the element to the panel
                            objPanel.Children.Add(objFrameworkElement);
                            Canvas.SetLeft(objFrameworkElement, mousePos1.X - mousePos2.X);
                            Canvas.SetTop(objFrameworkElement, mousePos1.Y - mousePos2.Y);
                        }
                        break;
                    }
                }
            }
        }
        #endregion

The ImportContent Method

Each control determines what content it will import. The control must implement an interface called ImportContent that accepts a FrameworkElement as a single parameter and returns a bool:

    interface ImportContent
    {
        bool ImportContent(FrameworkElement objFrameworkElement);
    }

In this example the SilverlightCanvas1.xaml control is coded to only import text from any TextBlocks.

        #region ImportContent
        public bool ImportContent(FrameworkElement objFrameworkElement)
        {
            // This import method will only import text content contained
            // in TextBlocks that are placed on a Canvas
 
            StringBuilder StringBuilder = new StringBuilder();
            Canvas objCanvas = objFrameworkElement as Canvas;
 
            // If the element being imported is not a canvas return false
            if (objCanvas == null)
            {
                return false;
            }
 
            try
            {
                // Loop through all the UIElements in the Canvas 
                foreach (UIElement objUIElement in objCanvas.Children)
                {
                    // Try to cast the UIElement as a TextBlock
                    TextBlock objTextBlock = objUIElement as TextBlock;
                    if (objTextBlock != null)
                    {
                        // Add the contents of the TextBlock to the output
                        StringBuilder.Append(String.Format(" {0}", objTextBlock.Text));
                    }
                }
            }
            catch
            {
                return false;
            }
 
            // Was content in the output ?
            if (StringBuilder.Length > 0)
            {
                // Add output to the Textbox display
                this.txtContent.Text = StringBuilder.ToString();
                return true;
            }
            else
            {
                return false;
            }
        } 
        #endregion

Summery

This example demonstrates a method for dealing with the "unknown". With SilverlightDesktop, modules can be created by different developers that have no knowledge of each other, yet using a method such as this, content can be exported and imported between them.