Drag drop with feedback

I put together a little Silverlight app demonstrating how to drag and drop between different areas of your Silverlight app.  (This isn't drag and drop in the OLE sense, for security reasons Silverlight doesn't support dragging between processes)  I actually did two versions, one in JavaScript and one in C# ((Silverlight 1.0 and Silverlight 1.1 alpha, respectively), although the approach is the same for both.

The experience I wanted to build was that some areas of the application used auto layout, so that when you dropped an item in that area, the item would snap into place.  And I wanted some kind of visual feedback to show where the item would be put.  So I created an element which I called the shadow element, which is that feedback -- shadow element goes where the item will when you release the mouse button.  So when the mouse moves during a drag, we update the location of the shadow element:

 function handleMouseMove (sender, args) {
        var item = sender;
        if (isMouseCaptured) {
            ...
            moveShadow(overPanel);
        }
 }

        function moveShadow(overPanel)
        {
            hideShadow();

            if (overPanel != null && !overPanel.Equals(workspace2)) {
                overPanel.Children.Add(shadowElement);
                arrangePanel(overPanel);
            }
        }

I wanted some of the panels to have auto-layout, a stack panel-like effect, so I wrote a method to do that:

        function arrangeInRow(canvas)
        {
            var next = 10;
            var i;
            for (i = 0; i <canvas.Children.count; i ++) {
                var element = canvas.children.GetItem(i);
                if (element.toString() == "Ellipse") {
                    var child = element;
                    child["Canvas.Left"] = next;
                    child["Canvas.Top"] = 10;
                    next += child.Width;
                    next += 10;
                }
            }
        }

(The if statement in there is a bit of a hack, I was lazy when I wrote the xaml and so my container had some elements I wanted to be laid out -- the ellipses -- and some elements I didn't want layout to move...)  Dave Relyea has a series of layout samples on https://blogs.msdn.com/devdave/archive/2007/05/17/silverlight-1-1-alpha-layout-system-and-controls-framework.aspx.

As the mouse moves around, you need to figure out what it's over.  Scott Barnes describes a technique for doing this in https://blogs.msdn.com/msmossyblog/archive/2007/06/16/performing-a-hittest-with-silverlight.aspx, my code is slightly different since I wrote the first iteration of couple months ago, but it's the same technique.

As you drag an item between containers, you want the item to be on top of all the other containers.  If you don't do anything special, the item you're moving around will remain part of its original container, and the item will be part of its container's z-order -- so your item will appear on top of some containers but underneath others.  Simplest solution is to change the item's parent, remove it from its container and add it to the root element.  When you do that, you'll need to adjust the Canvas.Left and Canvas.Top properties, which is what this function does:

        function changeParentKeepPosition(item, newParent, e)
        {
            var parent = item.getParent();
            item["Canvas.Top"] = translateY(item["Canvas.Top"], parent, newParent, e);
            item["Canvas.Left"] = translateX(item["Canvas.Left"], parent, newParent, e);
            item.getParent().Children.Remove(item);
            newParent.Children.Add(item);
        }

I wrote the C# version of first, in that version there's a single Translate method that takes a Point.  Unfortunately, creating points in JavaScript is fairly awkward (and createFromXaml doesn't support <Point>), so easiest solution for me was to write separate translateX and translateY functions.  This ended up being the single biggest difference between the JavaScript and C# versions...

        function translateX(point, from, to, e)
        {
            var fromPoint = e.GetPosition(from);
            var toPoint = e.GetPosition(to);
            var delta = fromPoint.X - toPoint.X;
            var result = point - delta;
            return result;
        }

        function translateY(point, from, to, e)
        {
            var toPoint = e.GetPosition(to);
            var fromPoint = e.GetPosition(from);
            var delta = fromPoint.Y - toPoint.Y;
            var result = point - delta;
            return result;
        }

where e is any mouse eventargs...  It's an interesting little trick, although GetPosition was designed for getting the mouse position, with a couple extra lines you can turn it into a general-purpose coordinate transformation routine like the above.

dragdrop JavaScript and CS.zip