Damon Payne: Hand waving software architect

103db signal to noise ratio at < .03% total harmonic distortion
Solution Architect, software developer, geek
Damon Payne at Blogged
2009 Microsoft MVP - Client App Dev
2007 Microsoft MVP - Solution Architecture
 Thursday, September 24, 2009
« History for View Based Navigation in Sil... | Main | Argentum Tela Design Surface [21] &ndash... »

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 Surface for Silverlight 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

Atrophy

As of AGT[19] I was getting ready to extend Commanding and introduce some other concepts in order to get Undo/Redo support.  Of course, that was many months ago and I’ve let this sit for a while haven’t I?  Since that time a lot has happened.  Silverlight 3 has shipped, there are many more class libraries available, and Silverlight in general is being taken more seriously.  It’s time to continue working on the design surface project.

Ok, fine, I also need AGT for some other projects.  I need to take some time to bring the project up to the state of the art and change direction slightly to move the road map around to be more in line with my own short term needs.  Let’s get started.

If you’re new to this project, you can also check out the Codeplex site.

Silverlight 3 Upgrade

Since I’ve been through a machine repave or two since my last work I had to get Team System set back up for Codeplex.  I didn’t realize this until after I’d opened the solution and done the conversion wizard so I was concerned.  Sure enough, the project upgrade hozed the source control bindings but I was able to get it fixed using the Change Source Control command.  With this done I built and ran with no problems, however trying to change anything revealed that source control was most likely irrevocably ruined so I got latest and went back to work.

Once I got through all this, the Silverlight 3 upgrade went off without a hitch.  I found exactly zero breaking changes in the application so far; this is much nicer than some of the previous Silverlight 2 cycles I had gone through.

While no changes were strictly necessary, I had to think about taking advantage of new Silverlight 3 features.  I quickly cycled through my mental list of new Silverlight 3 features.  I may use things like Behaviors in the future.  One thing I wasn’t thrilled with from Silverlight 2 was the copy/scale/glass approach I had to take for creating my drag & drop “thumbnails” of controls.  I sat down to see how much work it would take to create an IDragTypeCreator implementation using WritableBitmap.  WriteableBitmap still has some bizarre behavior in some situations so unfortunately I had to stick with the old implementation.

Refactoring

Pretty much any time I look at old code, I’m eager to apply whatever I’ve since learned to it.  AGT is no exception.  In order to get my head back into this codebase I needed to come up with a refactoring goal that would cause me to review everything…

Moving to Unity

Now that Unity for Silverlight is in good shape, there’s no reason for me not to use it.  I have now taken a dependency on Unity 1.2.  It was hard to say goodbye to my own IoC container but it needed to be done.  Were there any issues in this upgrade?  Oh, you might say so…

  1. I removed my IService interface and replaced with Unity [Dependency] attributes.  This of course involved touching nearly every file in the project.  Find an IService interface, find the implementing classes and the importing classes; lather, rinse repeat.  This was super-painful.

  2. I decided to keep my own EventAggregator for now.

  3. I am also keeping my lame Region Manager for now

  4. The old Creative Commons declarations were removed since AGT is really MS-PL now.

While this resulted in a massive change set, it felt good.  This was a very eye-opening experience:

Changing your IoC container might mean touching most of the files in your solution!

Design Surface

Once I got the solution back up and running with Unity and Silverlight 3, I started looking at areas where I could clean up the design.  The DesignSurface implementation is only 300 lines right now, but is also likely to grow in the future.  I decided to use a concept from another article to encapsulate drawing on the design surface.

namespace DamonPayne.AGT.Design.Behaviors.Drawing
{
    public abstract class DrawingBehavior
    {
        public DrawingBehavior(Canvas surface)
        {
            Surface = surface;
        }

        /// <summary>
        /// The surface we're drawing on
        /// </summary>
        public Canvas Surface { get; private set; }

        /// <summary>
        /// Mouse click point relative to Surface    
        /// </summary>    
        public Point StartPoint { get; set; }

        public abstract void StartDrawing(Point s);

        /// <summary>    
        /// Give derrived classes a chance to determine if they can or should return a valid     
        /// Shape in their current state    
        /// </summary>    
        /// <param name="g"></param>    
        /// <returns></returns>    
        public virtual bool ShouldStopDrawing(DrawingGestures g)
        {
            return (g == DrawingGestures.MouseLeftButtonUp);
        }

        /// <summary>    
        /// Stop drawing and return the final Shape    
        /// </summary>    
        /// <returns></returns> 
        public abstract Shape StopDrawing();


        /// <summary>    
        /// Pass Mouse events through to the DrawingBehavior    
        /// </summary>    
        /// <param name="e"></param>    
        public abstract void MouseMove(MouseEventArgs e);
    }

}

If you are a long-time follower of this series or you’ve read the road map on Codeplex, you’ll know that I intend to have an extensible behavior mechanism for making things happen on the design surface.  DrawingBehvior is the first step in that direction, and also allows me to take a lot of code out of the design surface and move it to a dedicated construct.

namespace DamonPayne.AGT.Design.Behaviors.Drawing
{
    public class RectangleDrawingBehavior : DrawingBehavior
    {
        public static readonly double SELECT_RECT_STROKE_WIDTH = 3.0;
        public static readonly double SELECT_RECT_RADIUS_X = 0.0;
        public static readonly double SELECT_RECT_RADIUS_Y = 0.0;

        public RectangleDrawingBehavior(Canvas surface) : base(surface) { }

        public Rectangle DrawingRect { get; set; }

        public override Shape StartDrawing(Point s)
        {
            StartPoint = s; 
            DrawingRect = new Rectangle { Width = 5.0, Height = 5.0, };
            ApplyShapeStyle(DrawingRect);
            DrawingRect.SetValue(Canvas.LeftProperty, s.X);
            DrawingRect.SetValue(Canvas.TopProperty, s.Y);
            Surface.Children.Add(DrawingRect);
            return DrawingRect;
        }

        public override Shape StopDrawing()
        {
            var rVal = DrawingRect;
            DrawingRect = null;
            return rVal;
        }

        public override void MouseMove(MouseEventArgs e)
        {
            Point localMousePos = e.GetPosition(Surface);
            if (StartPoint.X < localMousePos.X)
            {
                double width = localMousePos.X - StartPoint.X;
                double height = localMousePos.Y - StartPoint.Y;
                if (width > 0 && height > 0)
                {
                    DrawingRect.Width = width;
                    DrawingRect.Height = height;
                }
            }        
            else // northwest drag        
            {            
                double width = StartPoint.X - localMousePos.X;
                double height = StartPoint.Y - localMousePos.Y;
                if (width > 0 && height > 0)//need this safety here in case a resize rectangle "crosses" itself.           
                {
                    DrawingRect.Width = width;
                    DrawingRect.Height = height;
                    DrawingRect.SetValue(Canvas.LeftProperty, localMousePos.X);
                    DrawingRect.SetValue(Canvas.TopProperty, localMousePos.Y);   
                }       
            }         
        }

        public virtual void ApplyShapeStyle(Shape s)
        {
            DrawingRect.StrokeThickness = SELECT_RECT_STROKE_WIDTH;
            DrawingRect.RadiusX = SELECT_RECT_RADIUS_X;
            DrawingRect.RadiusY = SELECT_RECT_RADIUS_Y;
            DrawingRect.Stroke = new SolidColorBrush(Color.FromArgb(0xFF, 0x15, 0x05, 0xFF));
            DrawingRect.Fill = new SolidColorBrush(Color.FromArgb(0xFF, 0x49, 0x59, 0xFF));
            DrawingRect.Opacity = .50;
            DrawingRect.SetValue(Canvas.ZIndexProperty, 1000);
        }

    }
}

Now the design surface code is a bit smaller.  The design surface is still assuming the selecting shape is a rectangle but I can fix that in the future.  I will probably also make the selection strategy pluggable at some point. 

Conclusion

Since there are no functional changes I have not updated the live demo.  You can get the most recent code from codeplex.  The final change set as of the end of this article is 28467.  The next AGT few articles will bring more interesting features, and yes I will get back to the Undo framework eventually.