Monday, 14 February 2011

Prism, MVVM and Silverlight – the finished article.

In my previous article I set out to create a simple Silverlight application using the prism framework and the MVVM pattern. I chose what I thought would be quite a straight forward application to build, something that would mirror the iPad application, Popplet (links: Website, get it on iTunes) – which as I said in my earlier post, is a pretty neat little app that lets you essentially drop post it notes (text or image) onto a canvas, drag them around into some layout that makes sense to you and connect the various boxes.

iPad Screenshot 1

So, this distilled down to the following user stories;

  • As a user I want to see a canvas of notes.
  • As a user I want to be able to add a new note box to my canvas.
  • As a user I want to be able to rotate or resize any of my note boxes on my canvas
  • As a user I want to be able to connect note boxes
  • As a user I want to be able to delete note boxes
  • As a user I want to be able to zoom in and out
  • As a user I want to be able to drag my canvas around
  • As a user I want to be able to click a button to zoom out to the extents of my canvas so far.

I’ve spent about 12/13 hours building this in total, over 5 sessions, from scratch, learning some of the concepts as I’ve gone and I’ve delivered all of the user stories above with the exception of the ability to connect note boxes - basically I’ve run out of time to add that, but it should be trivial to add by creating another canvas layer in our control template that is used to render connector lines.

This was an exercise not only in how best to work with Prism and MVVM but also in how I might best implement a reasonably complex control – the designer surface and whilst I’m impressed with how quickly I was able to build this little app (I think it could be done within a day easily), I also think that my approach is actually the wrong one.

The way this application works is we have a custom control, the diagram canvas which contains all the functionality for zooming, panning and so on, and then the items themselves are rendered using a data template. Two data templates in fact – one that dictates how the standard node should be rendered, the other dictating the rendering of it when it’s selected – in which case it is adorned with resize handles, rotation handles, a button to delete the entity and a textbox to change the text (there is no adornment layer in SilverLight it turns out!).

This all works, but it doesn’t feel very flexible. It’s pretty much impossible to represent more than one shape type – the list of “stuff” you bind to all gets rendered through one data template, so that makes life difficult.

It also feels quite tightly coupled and doesn’t have clean separation of concerns (eg the diagram component has code in there to find UI elements within a data template that should be refactored out). Anyway, the exercise has proven it’s point, but if I were to do this again, I’d try to be a little more flexible and use different controls (+control templates) for different types of shape (all of these shape controls would implement IShape) and expose a property that describes their adornments – so some IShapes would adorn themselves with a RotatorAdornment and SizerAdornment whilst others might just have EditTextAdornment and so on. Composing the controls in this way would be much more flexible, and the view model would have the responsibility of transforming our domain data (the model) into the shape data collection the canvas is binding to and vice versa.

Anyway, for now, this is what the application looks like, it works, it does what it says on the tin, but it’s perhaps not the best implementation. You can grab the source from http://silverpop.codeplex.com.

sp-bettershot

3 comments:

  1. Nice job Tony. I alter your replaced so it loads 1000 random notes on startup and it does it in a couple of seconds.

    Did you considered using standard ItemControl with Canvas specified as ItemPanel template?

    ReplyDelete
  2. Hi Ben,

    Thanks, and originally I did go down that route, but binding to canvas.X and canvas.Y was a little problematic as the note itself gets wrapped in another control and it is this that needs to bind the offset. I did find two ways of doing this however in the end - using transforms and another that escapes my memory right now.

    However, I've started to take this down a different path now in a separate branch in the source. This alternate approach uses controls and will use an ItemControl.......

    Cheers
    Tony

    ReplyDelete
  3. I have adhere it using ItemContainerStyle http://stackoverflow.com/questions/2383858/how-to-use-canvas-as-the-itemspanel-for-an-itemscontrol-in-silverlight-3/5007653#5007653

    But I suspect performance penalty using ItemControl - It calls a lot of layout requests that eat significant time having multiple controls involved. I am considering to follow your way now.

    ReplyDelete