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 18, 2008
« A badge of honor | Main | Run time is design time for AGT [5] »

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

Toolbox Service UI

It is now time to do some slightly more meaningful XAML work and create the visual representation of the toolbox service: this is where we’ll stow the designable components and drag them off onto the yet-to-be-created designer surface.  I am NOT going to worry about making it look snappy until later.

In order to test the ToolboxView, I have created the first designable item.  It is a big purple “Couch”.  I must stress to you that it does look like a couch.  It’s purple and has cushions, as anyone can clearly see.

 

What did I tell you?  Couchtastic.  I’m working on getting a better XAML couch or finding some patience to draw a better one.  This will be good enough to test with.

Looking back at the previous article, I chose to support a categorized toolbox like VS2008.  Therefore I’m going to make another UserControl called ToolboxCategory which is simply a heading stating the category name and a ListBox full of some representation of the ToolboxItems.  Simple XAML for the Category to start with:

<UserControl x:Class="DamonPayne.HTLayout.Controls.ToolboxCategoryControl"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Width="205" Height="Auto">

    <Grid x:Name="LayoutRoot" Background="White">

        <Grid.RowDefinitions>

            <RowDefinition></RowDefinition>

            <RowDefinition></RowDefinition>

        </Grid.RowDefinitions>

        <TextBlock x:Name="_categoryHeader" Grid.Row="0">Category Header</TextBlock>

        <ListBox x:Name="_itemLst" Grid.Row="1" DisplayMemberPath="Name">           

        </ListBox>

    </Grid>

</UserControl>

I’ll add some public properties to ToolboxCategoryControl to communicate with its parent.  I’m not ready to rewrite Silverlight data binding so I’ll have to make some concessions.  I’ll go ahead and start writing the little code I need to populate the IToolboxService.

Create the ToolboxView and hand it over to the region manager as soon as the Page (our entry point UserControl) is loaded:

            ToolboxView tbView = ComponentBuilder.Create<ToolboxView>();

            mgr.AddView(tbView, "Toolbox");

Create a RootDesignerPresenter to handle logic for the DamonPayne.HTLayout.Page:

        [Presenter()]

        public RootDesignerPresenter Presenter { get; set; }

             Presenter.InitializeDesignableControls(); …

When I run this code I realize I made a mistake earlier.  The whole point of making the ComponentBuilder helper methods internal and not private was to use them to wire-up dependencies from service types as I manage them in ServiceManager.  Adding these calls to ServiceManager.CreateDefaultInstance fixes the issue and I’ve added another todo item to take a look at this after I have some more real code.   

                //TODO: maybe use the ComponentBuilder to create the damn thing in the first place

                ComponentBuilder.ResolveServiceDependencies(o);

                ComponentBuilder.BuildPresenters(o);

And another mistake pops up when I run this code. Page is created on AppStartup.  Page depends on RootDesignerPresenter, which depends on IToolboxService.  The IToolboxSerivce implementation is also our ToolboxView UserControl, which does not get created until the Page.UserControl_Loaded event completes.  So while we are wiring up the dependencies for Page, we get “A provider for DamonPayne.AGT.Design.Contracts.IToolboxService was not found.”  Now the previous design decisions come back to haunt us again and I’m facing another round of decisions.

1.       I could build the UI components in the proper order in Application_Startup.  This requires me, the developer, to be smart about what depends on what.  In fact, this also begs the question of what to do later if I find that there is a circular dependency between services.

2.       Return to the idea of making a class that implements IToolboxService and interacts with the visual representation using this other mechanism. 

3.       Create a mechanism for late-binding IService implementations.  By this I mean, rather than throw an exception from ServiceManager.Resolve() if an implementation cannot be found, store the fact that I’m missing an implementation for a certain interface on a certain type and inject this IService later if it becomes available.

4.       Make ServiceManager smarter.  Suppose we gave ServiceManager 100 types to manage at once, and some of those had dependencies on each other.  We could make ServiceManager either build a dependency tree, or even easier, take multiple passes over the collection of 100 types in a sort of poor-man’s (lame programmer’s) tree-flattening.

 

#1 would get me on my way the fastest, and also seems lame.  If I have circular dependencies, #1 will never work, although circular dependencies should be avoided.  I may also find myself spending a lot of time later keeping track of dependencies in my head and change the order of type registration around.

#2 will almost certainly just move the problem elsewhere, unless this new magical class does not have its visual representation provided to it as an automatic service.  This may be the cleanest option.

#4 might be useful on its own, but would also involve re-thinking how I create and manage Views and such.  I’m going to reject this one for now.

#3 has some appeal.  This would even allow for (accursed) circular dependencies.  The first drawback that comes to mind is that throwing an exception right away gives an immediate and unambiguous message that something is wrong and what that something is.  Attempting to use an IService that is waiting around for someone to create it by registering it with ServiceManager or ComponentBuidler would just result in a null reference – a ubiquitous and annoying exception.

There is an additional, and totally evil, option I could tack on to #3.  Silverlight is a partial implementation of the base class libraries we are used to, like the Compact Framework.  Because of this, one always has to go back and inspect class availability using Intellisense or the reference documentation.  Probably because of Silverlight’s dynamic language support – a fairly complete System.Reflection.Emit implementation is present.  In addition to remembering that I’m “waiting around for an ISuck implementation”, I could dynamically create a class that implements ISuck and throws an exception saying “Sorry, this is a placeholder and we never found an implementation for you” in case any methods are called before the implementation can be found. 

Using the great sanity check mechanism known as “Who’s on MSN Messenger” I happened to find a respectable software architect online to bounce this notion off of.  The conversation went something like this:

Damon: <explains situation and #3 solution>
Damon: So, is this overkill just to keep from throwing a null reference?

OtherDude: Uh, yeah, way overkill.  Save that for when your whole application works and you want to start gold-plating.  Version 3 or something.

Unfortunately I was 90% done with the reflection emit idea before this dissenting opinion arrived.  Honestly this was a ludicrous tangent but is also fun.  I will cover the solution in the next article.  Suffice to say that we are supporting Clever Late Binding for IService implementations and we can continue with the Toolbox Service.

The RootDesignerPresenter contains logic, such as “What am I putting in this toolbox?” For now I’ll only add one thing and be sure this all works.

        public void InitializeDesignableControls()

        {

            ToolboxItem couchItem = new ToolboxItem("Purple Couch", "This is clearly a couch", typeof(Couch));

            Toolbox.AddItem(couchItem, "Furniture");

        }

Making this work is painful because Silverlight does not support RelativeSource for data binding.  I can’t use a property on “this” for setting the Category Name TextBlock.  I’m now actually terrified of what I’m going to discover for data binding in Silverlight, but for now I’ll just make the property set the value. 

Of course the very next thing I try to do exposes another data binding shortcoming.  List<T> does not implement INotifyPropertyChange so that if I add a ToolBoxItem to a List<ToolBoxItem> which service as ItemsSource the GUI does not update itself.   I have three primary choices here.  I can build a special ScreenModel object for this screen which contains a List property I can fire INotifyPropertyChange for, I can try to implement a custom List<T> that does what I want, or I can mess with everything completely manually.  For now, I will use a ViewModel, which also erases my previous manual setting of the Category Name.  It’s worth reproducing the whole View Model here to point out how it’s implemented for automatic data binding goodness.

using System.Collections.ObjectModel;

using System.ComponentModel;

using DamonPayne.AGT.Design.Types;

 

namespace DamonPayne.HTLayout.ViewModels

{

    public class ToolboxCategoryModel : INotifyPropertyChanged

    {

        public ToolboxCategoryModel()

        {

            ToolboxItems = new ObservableCollection<ToolboxItem>();

        }

 

        private string _CategoryName;

        public string CategoryName

        {

            get { return _CategoryName; }

            set

            {

                _CategoryName = value;

                OnPropertyChanged("CategoryName");

            }

        }

 

        private ObservableCollection<ToolboxItem> _ToolboxItems;

        public ObservableCollection<ToolboxItem> ToolboxItems

        {

            get { return _ToolboxItems; }

            set

            {

                _ToolboxItems = value;

                OnPropertyChanged("ToolboxItems");

            }

        }

 

        public event PropertyChangedEventHandler PropertyChanged;

 

        public void OnPropertyChanged(string pname)

        {

            if (null != PropertyChanged)

            {

                PropertyChanged(this, new PropertyChangedEventArgs(pname));

            }

        }

    }

}

 INotifyPropertyChange does not work for collection types.  INotifyCollectionChanged  does not seem to be used by data binding if I put it on my ToolboxCategoryViewModel.  The answer is System.Collections.ObjectModel.ObservableCollection<T>, which implements INotifyCollectionChanged  in the way the data binding code in Silverlight is expecting .  I’ve made myself some code snippets for the INotifyPropertyChanged code since I suspect I’ll need them later.

For the basic implementation:

<?xml version="1.0" encoding="utf-8"?>

<CodeSnippets

    xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">

  <CodeSnippet Format="1.0.0">

    <Header>

      <Title>OnPropertyChanged</Title>

      <Author>DamonPayne.com</Author>

      <Description>Insert a default OnPropertyChanged method and INotifyPropertyChanged event</Description>

      <Shortcut>OnPropertyChanged</Shortcut>

    </Header>

    <Snippet>

      <Code Language="CSharp">

        <![CDATA[

        public event PropertyChangedEventHandler PropertyChanged;       

       

        protected void OnPropertyChanged(string pname)

        {

            if (null != PropertyChanged)

            {

                PropertyChanged(this, new PropertyChangedEventArgs(pname));

            }

        }       

        ]]>

      </Code>

    </Snippet>

  </CodeSnippet>

</CodeSnippets>

 

Per property:

<?xml version="1.0" encoding="utf-8" ?>

<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">

    <CodeSnippet Format="1.0.0">

        <Header>

            <Title>INotifyPropertyChanged property</Title>

            <Shortcut>inotifyprop</Shortcut>

            <Description>INotifyPropertyChanged prop, assumes OnPropertyChanged</Description>

            <Author>DamonPayne.com</Author>

            <SnippetTypes>

                <SnippetType>Expansion</SnippetType>

            </SnippetTypes>

        </Header>

        <Snippet>

            <Declarations>

                <Literal>

                    <ID>type</ID>

                    <ToolTip>Property Type</ToolTip>

                    <Default>string</Default>

                </Literal>

                <Literal>

                    <ID>property</ID>

                    <ToolTip>Property Name</ToolTip>

                    <Default>BindingProperty</Default>

                </Literal>

            </Declarations>

            <Code Language="csharp">

                <![CDATA[

 

private $type$ _$property$;

public $type$ $property$

{

    get { return _$property$; }

            set

            {

                  _$property$ = value;

      OnPropertyChanged("$property$");

            }

}

$end$]]>

            </Code>

        </Snippet>

    </CodeSnippet>

</CodeSnippets>

 

Next I’ll add some Item selection code using my event aggregator.  I’m going to define a generic SelectedDataChangedEvent:

    public class SelectedDataChangedEvent<T>:AggregateEvent<T>

    {

        public T Payload { get; set; }

    }

Now my constructor for the View Model looks like this:

        public ToolboxCategoryModel()

        {

            ToolboxItems = new ObservableCollection<ToolboxItem>();

            _selectionEvent = EventAggregator.Get<SelectedDataChangedEvent<ToolboxItem>, ToolboxItem>();

            _selectionEvent.Subscribe(item => { SelectedToolboxItem = null; }, item => !ToolboxItems.Contains(item));

        }

 

        private SelectedDataChangedEvent<ToolboxItem> _selectionEvent;

Behold the simple power of the event filters.  These two lambdas just say “If anyone anywhere else selects a toolbox item that is not part of my collection, I’ll be a nice citizen and clear my own selection.”  The selected item now participates in data binding AND event aggregation and it feels cleaner than I expected:

SelectedItem="{Binding Path=SelectedToolboxItem, Mode=TwoWay}"

        private ToolboxItem _SelectedToolboxItem;

        public ToolboxItem SelectedToolboxItem

        {

            get { return _SelectedToolboxItem; }

            set

            {

                _SelectedToolboxItem = value;

                OnPropertyChanged("SelectedToolboxItem");

                if (null != value)

                {

                    _selectionEvent.Raise(value);

                }

            }

        }

Back in the real IToolboxService implementation, we need to subscribe to this event too.  And now we have a toolbox service with gui!

 

It doesn’t look like much, but in the future there will be some “visual refactoring” going on.  I’ll sure miss my awesome purple couch though.

Conclusion

This step ended up being very long since there were several rabbit holes to go down.  We’ve created a Toolbox service, proven the EventAggregator is going to work, learned some things about Silverlight data binding I didn’t know before, and introduced the concept of the View Model into our lexicon.  In the next few steps, things will get really interesting!

Source code: DamonPayne.AGT[4].zip (571.61 KB)



Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview