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
 Monday, September 22, 2008
« Run time is design time for AGT[9] | Main | Roger Ebert and the age of credulity »

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

Selection

Now that we’ve got things on the surface, we need to think about selecting, moving, editing, and resizing them.  It makes sense for Selection to come first.

Selection is slightly more complicated than it sounds.  In a tool like VS2008 or Visio things could be added or removed from the selection in several ways:

·         Clicking on a component

·         Control+Click on multiple components to select and deselect

·         Shift+Click to add contiguous components to the selection

·         Click and draw a rectangle, selecting components within the bounds of said rectangle.

Since I’m extending the olive branch to the dozens and dozens of unfortunate Mac users in the world, I’ll have to make any gesture code I write smart enough to not use any normal keys that are missing from the Mac, such as: Control, Window Key, Right-click Key, Pause/Break, up arrow, and the letter Q.

Back in the Dark Ages when I was writing code to host the WinForms 2 design surface, I think my selection service implementation was quite a few lines of code.  While I still like the idea of a selection service, I’m not going to go look at the old code and corrupt my pure thoughts.  First, let’s create a cross platform Keyboard service:

    public class CrossPlatformKeyboardService : IKeyboardService

    {

        /// <summary>

        /// Control key, or open-apple on the Mac "platform"

        /// </summary>

        public bool ControlKeyDown

        {

            get

            {

                return ( Test(ModifierKeys.Control) || Test(ModifierKeys.Apple));

            }

        }

 

        public bool AltKeyDown

        {

            get { return Test(ModifierKeys.Alt); }

        }

 

        public bool ShiftDown

        {

            get { return Test(ModifierKeys.Shift); }

        }

 

        protected bool Test(ModifierKeys target)

        {

            return target == (target & Keyboard.Modifiers);

        }

Next I’ll define what I expect the interface for a selection service to be.

    public interface ISelectionService : IService

    {

        void SetSelectedControls(IList<IDesignableControl> selection);

        IList<IDesignableControl> GetSelection();

        int SelectionCount { get; }

        IDesignableControl PrimarySelection { get; }

        event EventHandler SelectionChanged;

    }

In WinForms, adding to the ISelectionService can optionally come with a parameter of type “SelectionType”, which is a discriminator for things like adding to a selection vs. starting a new selection.  I’d rather leave those details behind the ISelectionService wherever possible.  You will also note that I am using a plain old .Net event here and not something from EventAggregator.  I feel this is highly appropriate in this case.  The EventAggregator exists so that components can respond to interesting events without referencing each other, but selection events are specific to this Service, and components that need ISelectionService will magically receive it from the container.   Now I can start working on a default ISelectionService implementation, with a dependency on IKeyboardService.

Siteing, not Sighting

We need to take another step before Selection can make sense.  The WinForms designer has the notion of an ISite, where an ISite is an adapter between a designed component and its designer.  Once sited, the component can request various things of its container.  While not quite what I’m about to do, it’s a point for interested users to research.   I will explain this concept further in the next article.  I have created a UserControl called DesignSite.  DesignSite contains a border whose thickness is set to zero by default.  Inside DefaultDesignTypeCreator I shove the real IDesignableControl inside my DesignSite and let DesignSite handle the selection code when the real IDesignableControl is clicked on.

Back to Selection

The Site mentioned above wires up selection by resolving the ISelectionService.  Here, then, is the selection service implementation.

    public class DefaultSelectionService : ISelectionService

    {

        public DefaultSelectionService()

        {

            _selection = new List<IDesignableControl>();

        }

 

        public IKeyboardService KBService { get; set; }

 

        private List<IDesignableControl> _selection;

 

        public void Select(System.Collections.Generic.IList<IDesignableControl> incoming)

        {

            if (!KBService.ControlKeyDown)

            {

                _selection.Clear();

                _selection.AddRange(incoming);

                OnSelectionChanged();           

            }

            else

            {

                foreach (IDesignableControl idc in incoming)

                {

                    if (!_selection.Contains(idc))

                    {

                        _selection.Add(idc);

                    }

                    OnSelectionChanged();           

                }

            }           

        }

 

        public System.Collections.Generic.IList<IDesignableControl> GetSelection()

        {

            return _selection;

        }

 

        public int SelectionCount

        {

            get { return _selection.Count; }

        }

 

        public IDesignableControl PrimarySelection

        {

            get

            {

                return _selection[0];

            }

        }

 

        public event EventHandler SelectionChanged;

 

        protected void OnSelectionChanged()

        {

            if (null != SelectionChanged)

            {

                SelectionChanged(this, EventArgs.Empty);

            }

        }

Much simpler and shorter than I remember the WinForms implementation I remember.  We’ll see if I eat crow for saying so later.  Here is my site-style selection with a single component selected.

Shown here with multiple components selected by holding in Control or Open Apple:

You will note I don’t have any little “knobs” on the corners or anything.  I like how this looks better with a solid border and a gradient brush.  As we saw with Couch v1, perhaps my taste in visual appeal should not be trusted.  Speaking of couches, I have a chair now too.  You can take a look at my freshly painted Red Chair in the live demo.  Live demo….?

More ControlExtensions

Ever since being enlightened by Generics (the answer to everything) using the (cast) bothers me.  Casting using “as” bothers me more.  For this reason, I don’t know why the whole DependencyProperty framework deals with getting and setting of values as Object.  When I ported some code from Silverlight 2 beta 1 to beta 2, some of it was broken because I was using SetValue(Canvas.Left, 3) when in fact Canvas.Left wanted to be of type double and some auto-conversion was lost in the move to beta 2.  This error only pops up at runtime since SetValue takes type Object.  At least I can save myself some casting here and there by adding another extension method:

        public static T GetValue<T>(this DependencyObject d, DependencyProperty p)

        {

            return (T)d.GetValue(p);

        }

Which allows glorious code like:

double left = SiteBorder.GetValue<double>(Canvas.LeftProperty);

It isn’t much but I’ll take it.

Conclusion & Next Steps

I have created a live demo at http://www.damonpayne.com/agt ; obviously this is a work in progress but feedback is welcome. 

I have a pretty good idea how I’m going to do resizing, that will come next.  After that I will do multi-select by drawing a rectangle and then Moving.  After moving works I will have to think of how I want object editing to work, some kind of Property Grid I’m sure.  From there, I’ll have to take stock of where I’m at, review my goals, and decide what to do next.

Source code:DamonPayne.AGT[10].zip (1.28 MB)