Saturday, September 20, 2008
« Run time is design time for AGT [6] | Main | Run time is design time for AGT[8] »

The AGT (Argentum Tela) series of articles is an effort to do two things.  Usually an idea is presented only in its finished form.  The first goal is to do some Reality Blogging, to show an idea evolve over time without pulling any punches.  The second goal, and the example vehicle for the evolution aspect, is an extensible design-time environment similar to what we have in Visual Studio 2008.  This type of application has all sorts of interesting uses.  My example is a Home Theater layout tool.  Read the entire saga: http://www.damonpayne.com/2008/09/14/RunTimeIsDesignTimeForAGT0.aspx

Part 8 in a 50 part series begins...

Drag and Drop

It’s time to try dragging something out of the Toolbox and onto the DesignSurface.

We start dragging by wiring up events inside the ToolboxCategoryControl.  We’ve already established a mechanism for making sure only one ToolboxItem is ever selected, and that the ToolboxView owns the ToolboxCategoryControl.  We define a normal local dragstart event:

        void cat_DragStart(object sender, EventArgs e)

        {

            ToolboxItem tbi = SelectedItem;//Will already be populated from event wireup

            DragDropManager.BeginDrag(tbi);

        }

I have implemented the cleverly named DefaultDragDropManager in the DamonPayne.AGT.Design.Services namespace to handle the task.  The first thing DDDM does is obtain a drag-time representation of the Control visual.  This is one of the places where we start diverging more and more from how VS2008 works and looks today. While I’m not ready for the Visual Refactoring stage yet, we can clearly do better than a tiny mouse cursor with a “+” next to it.  This is what IDragTypeCreator is for: to provide a pluggable mechanism for building a better representation of what you’re dragging around than a mouse cursor.  I created a special DragContainer control for this purpose in DamonPayne.AGT.Design.Controls.  Here is the entire DefaultDragTypeCreator.CreateDragRepresentation method:

        public Control CreateDragRepresentation(Type t)

        {

            //Default behavior:

            DragContainer dc = new DragContainer();

            Control c = (Control)Activator.CreateInstance(t);

            //Thanks to http://www.jeffblankenburg.com/2008/04/how-about-some-code-simple-resizing-in.html for scaling code

            //TODO, extension method for uniform scale size?

            double originaltWidth = c.Width;

            double originalHeight = c.Height;

            double uniformScaleAmount = Math.Min(MAX_DIMENSION / originaltWidth, MAX_DIMENSION / originalHeight);

            ScaleTransform st = new ScaleTransform();

            st.ScaleX = uniformScaleAmount;

            st.ScaleY = uniformScaleAmount;

            c.RenderTransform = st;

            c.InvalidateMeasure();           

            c.UpdateLayout();

           

            //TODO: why couldn't I force this to recalc size here after setting render transform?

            double estimatedNewWidth = c.Width * uniformScaleAmount;

            double estimatedNewHeight = c.Height * uniformScaleAmount;

            double left = (dc.Width / 2.0D) - (estimatedNewWidth / 2.0D);

            double top = (dc.Height / 2.0D) - (estimatedNewHeight / 2.0D);

            c.SetValue(Canvas.LeftProperty, left);

            c.SetValue(Canvas.TopProperty, top);

            dc.LayoutRoot.Children.Add(c);

            dc.Opacity = .65D;

            dc.LayoutRoot.Opacity = .65D;

           

            return dc;

        }

I am basically creating an instance of whatever Type I was given, which ultimately came from ToolboxItem.Type.  Then I scale the instance down to where it will fit in my DragContainer and return the DragContainer.  With this in hand, DefaultDragDropManager wires up the mouse events for dragging around: basically when mouse left button goes up we do a hit check to see if there is an IDropTarget registered with us that would be under the current mouse position.  With the events set, we add the DragContainer control to the root visual of the Silverlight application.

Design Surface

The next thing I need to actually implement is the DesignSurface control, implementing IDropTarget.   This needs to be added into the IRegionManager way back when the ToolboxView and MessageConsole are created.  I’ve resized the whole Page such that I can fit a 480x640 gray DesignSurface in there.  You can now see me take my drag-time couch representation over to the design surface:

As soon as I got into developing the drop part, I realized I was in the Silverlight world, thinking like a WinForms developer.  Silverlight has built in hit test code already and I don’t need to do anything with Bounds.  The IDropTarget interface is thefore modified to use UIElement.HitTest(Point), so the EndDrag method can now do this:

        public virtual void EndDrag(MouseButtonEventArgs e)

        {

            RegionManager.TopLevelContainer.Children.Remove(_dragRepresentation);

            _dragRepresentation = null;

           

            foreach (IDropTarget target in _dropTargets)

            {

                if (target.IsHitTestVisible)

                {

                    IEnumerable<UIElement> elements = target.HitTest(_mousePos);

                    if (elements.GetEnumerator().MoveNext())//hit test succeed

                    {

                        IDesignableControl ctrl = DesignCreator.CreateInstance(_draggingType);

                        target.OnDrop(ctrl);

                    }

                }

            }

_draggingType = null;

        }

As soon as we enter OnDrop, the design time representation is worthless and we throw it away.  We’ll either be building the real control the surface or simply cleaning up.  I believe in the last article I described the interaction as the DesignSurface calling the creation method from OnDrop, but it makes more sense to put it here now.    For now, the IDesignTypeCreator simply instantiates the appropriate Type.  DesignSurface.OnDrop is incredibly simple for now:

        public void OnDrop(IDesignableControl dc)

        {

            dc.Visual.SetValue(Canvas.LeftProperty, _localMousePos.X);

            dc.Visual.SetValue(Canvas.TopProperty, _localMousePos.Y);

            LayoutRoot.Children.Add(dc.Visual);

        }

Success!  Here’s the very nice looking purple couch on the design surface.

Clearly there is tons of work to be done still, but I was shocked when I actually ran metrics: this just a little over 500 lines of code so far!

Source code: DamonPayne.AGT[7].zip (613.16 KB)