I recently released a new open source project called simply UltraLight.mvvm. The purpose of this project is to make it easier to build MVVM-based applications that support tombstoning (a must) on Windows Phone 7. The DLL is 22KB and the source is less than 300 lines of code.
With that, the framework supports:
Why not Jounce?
I think Jounce is overkill for the phone. Jounce specifically and explicitly relies on the Managed Extensibility Framework which is a phenomenal tool for building modular line of business applications for Silverlight. While the phone is Silverlight, and while there are certainly business applications on the phone, there are several reasons why I think it requires a completely different framework. These include:
For this reason, I wanted something quick and easy that would get me 90% of the way without much fuss. That’s what this framework is.
I created a sample application to demonstrate the use of the framework. The application is bundled with the source and of this writing there is not a formal release, only the source with two projects: the framework itself and a sample phone application. You can download this from the CodePlex site.
Getting Started: View Models
The first step is the view model. Some people won’t like this but I created an interface with the “base” properties for the view model that I can use for my design, and then another interface that includes the framework’s IViewModel
to apply to the runtime. Why won’t everybody like this? Because it really ends up being an empty interface that intersects two other interfaces. However, it makes it easier to build my design view model without having to stub out properties in the base view model that the designer doesn’t need. Here are the interfaces:
public interface IMainBase { string Text { get; } IActionCommand<object> EventCommand { get; } IActionCommand&t;object> StateCommand { get; } IActionCommand<object> DialogCommand { get; } IActionCommand<object> NavigateCommand { get; } } public interface IMainViewModel : IMainBase, IViewModel { }
With that, I can create a design-time view model with stubbed out properties:
public class DesignMainViewModel : IMainBase { public string Text { get { return "This is some design-time text."; } } public IActionCommand<object> EventCommand { get; set; } public IActionCommand<object> StateCommand { get; set; } public IActionCommand<object> DialogCommand { get; set; } public IActionCommand<object> NavigateCommand { get; set; } }
In this case I really only had to mock one thing, the text field. In the XAML for the main page, I can then reference the design view model using the design-time extensions like this:
<Grid d_DataContext="{d:DesignInstance design:DesignMainViewModel, IsDesignTimeCreatable=True}"/>
This will show up nicely in the designer. The rest of the layout is simple. I wrap a border and add some visual states to make the border red or yellow to show how the framework works with the Visual State Manager. I have a text box for you to enter text and show it bound both in the text box and in a text block. I have a button to change states, to send the text via the event aggregator, and to navigate to another page. Finally, there is an application button that allows you to “delete” (clear) the text after answering a dialog.
You can see the button/command binding in the button that changes the visual state – notice it passes the target state as a parameter:
<Button Grid.Row="4" Content="Red Border" MVVM:ButtonCommand.Command="{Binding StateCommand}" MVVM:ButtonCommand.CommandParameter="RedBorder" HorizontalAlignment="Center"/>
ActionCommand and Changing States
Let’s take a look at the view model and how this is implemented with the ActionCommand
provided by the framework. The command to change the state is valid if a non-empty string is passed. The base class is wired to the visual state manager, so the view model can simply call the GoToVisualState
method. The command is wired like this:
StateCommand = new ActionCommand<object>( s => { GoToVisualState(s.ToString(), true); _lastViewState = s.ToString(); }, s=>s is string && !string.IsNullOrEmpty((string)s));
That’s it – you can databind the command parameter, or, like I did, pass in a literal. When you click the button, the state is changed and you can see the transitions as it fades from one color to the next. I save the state for tombstoning – more on that later.
Binding the View Model
Of course, for that to work, the view model has to get bound. I don’t mind a little code-behind to get the job done if it’s straightforward and easy. First, in my application start up, I need to register the view model. I’m telling the framework to handle exactly one copy of my view model and give it back whenever I ask for it (if you want new ones, you can just create new ones – no problem there). When the application starts, I call a simple method that registers the view model:
private static void _RegisterViewModels() { UltraLightLocator.Register<IMainViewModel>(new MainViewModel()); }
Now it is set up to return that instance anytime I ask for it.
The framework provides an extension method to binding the view model to the view. I decided to go with a method that would take a framework element. Some people won’t like this, but I have a good reason. The binding can affect how the datacontext is propagated through the hierarchy, and some controls used as the “root” control can behave differently when they are bound too soon. By allowing the element to be passed, you have more control over where in the visual tree it is bound and how. Most often, you’ll do what the example does and simply bind it to the root grid, often called LayoutRoot
. Here is the code-behind for the main page:
public MainPage() { InitializeComponent(); this.BindToViewModel<IMainViewModel>(LayoutRoot); }
That’s it – I just pass the contract for the view model and the element to bind to. Behind the scenes, the framework will attach to the loaded event of that element and wait to actually data-bind until it is fully loaded. If you are working with a control like the pivot control, which will throw errors if you bind to it too soon, this will avoid that problem. It will also set up a delegate to call into the Visual State Manager, and wire in the navigation context so the view model can raise navigation events. When you bind to a user control, it will walk the visual tree to find the host page in order to grab a valid navigation context.
Navigation
That takes us to navigation. The navigation button fires a navigation command. The command is wired to call a method, and the method looks like this:
private void _Navigate() { RequestNavigation("/SecondPage.xaml"); }
Just request navigation and pass the view. The framework handles casting this to a URI and then passing it to the navigation context. You can also call GoBack()
to cancel or go to a previous page.
The Event Aggregator
I used the same code for the event aggregator that I use in Jounce as it is lightweight and robust. The command to send the event is wired to publish whatever text has been entered. It is set up like this:
EventCommand = new ActionCommand<object>( o => UltraLightLocator.EventAggregator.Publish(Text), o=>!string.IsNullOrEmpty(Text));
Notice how easy it is to send a message – just grab the instance for the aggregator and publish it. It can be any type. To receive a message, you simply implement IEventSink<T>
where T is the type of message you want to listen for. In this case, the same view model also listens for its own message (hey, this is a demo, right?) You can see in the definition the IEventSink<string>
which requires you to create a method that is called with the message.
To subscribe, you pass an instance of the class that has implemented the interface. Because this is set up for only one message, the type doesn’t have to be specified as it can be inferred from the interface:
UltraLightLocator.EventAggregator.SubscribeOnDispatcher(this);
Notice you have the option to subscribe directly, or if you know you’ll be interacting with the UI thread you can subscribe on the dispatcher and the method will be called from the UI thread. If you implement multple interfaces, you just indicate the type of message you are subscribing for like this:
UltraLightLocator.EventAggregator.SubscribeOnDispatcher<string>(this);
Dialogs
I created a simple interface to abstract the dialog mechanism. It takes a title, a message, and a boolean to determine if it is a notification (you can only click OK) or a confirmation (OK or Cancel). When the message is received, a notification is sent in the receiving method:
public void HandleEvent(string publishedEvent) { Dialog.ShowMessage("Received Text", publishedEvent, false); }
Application Button Binding
One aspect of the phone I don’t like is the fact that the application buttons are not directly bindable. There are some interesting solutions there but I decided to just make it simple: if you specify three buttons, return an array of three commands that map to the buttons. In this case, we have a “delete” button that clears the text field. It only makes sense to call it if there is text, so the command is set up like this:
DialogCommand = new ActionCommand<object>( o => _RequestDelete(), o => !string.IsNullOrEmpty(Text));
The delete request uses the dialog as well, this time to confirm the request:
private void _RequestDelete() { if (Dialog.ShowMessage("Confirm Delete","Click OK to Clear the Text.", true)) { Text = string.Empty; } }
Finally, the command is bound to the button by returning it in the button bar list:
public override IEnumerable<IActionCommand<object>> ApplicationBindings { get { return new[] { DialogCommand }; } }
If you have nothing to bind, you can just return an empty list.
Tombstoning
The final element to cover is tombstoning support. The interface ITombstoneFriendly
is provided to indicate the view model will explicitly support tombstoning. The pattern for tombstoning can be simplified to this:
The tombstone interface provides two methods to save and then restore data. In the example, the text is saved, as well as the last view state. If you make the border red and tombstone, it will still be red when you return to the application. (Hint: using Sterling makes this a whole lot easier).
Here is the implementation of the interface for the sample application:
public void Deactivate() { var settings = IsolatedStorageSettings.ApplicationSettings; settings["ViewState"] = _lastViewState; settings["Text"] = Text; } public void Activate() { var settings = IsolatedStorageSettings.ApplicationSettings; if (settings.Contains("ViewState")) { _lastViewState = (string) settings["ViewState"]; GoToVisualState(_lastViewState, false); } if (settings.Contains("Text")) { Text = (string) settings["Text"]; } }
Also, in the application object, when the application exits normally, I clear the settings:
private void Application_Closing(object sender, ClosingEventArgs e) { var settings = IsolatedStorageSettings.ApplicationSettings; if (settings.Contains("ViewState")) { settings.Remove("ViewState"); } if (settings.Contains("Text")) { settings.Remove("Text"); } }
There is a special process to bind the view to the view model for the tombstone process. The code-behind for the main page adds two overrides that look like this:
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) { this.DeactivatePage<IMainViewModel>(); base.OnNavigatedFrom(e); } protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { LayoutRoot.ActivatePage<IMainViewModel>(); base.OnNavigatedTo(e); }
Wiring these events causes the framework to cast the view model to ITombstoneFriendly
and call the appropriate method. Notice that the deactivate command extends the page and uses this
, but the activate command extends a framework element. Why is that? Once again, if you are using a pivot control or a panorama control, the navigation event happens before the control is fully loaded. Binding too soon will result in the controls throwing an exception. By extending from the element itself (i.e. if you use a pivot, you use the name of the pivot control instead of the layout root to call ActivatePage
) the framework knows to wait for that element to load. Only when the element is loaded will it call the tombstone method. It will also dispatch this on the UI thread to queue it behind any pending requests, allowing for a seamless transition from the tombstoned state.
Conclusion
As you can see, it is a very small framework but does quite a bit. It’s by no means a magic “pull the lever and I have a full blown Windows Phone 7” application, but I believe it is solid enough to help anyone hit the ground running and small enough that you can modify or extend it to fit your specific needs. I look forward to any feedback and suggestions.
Microsoft Azure and Amazon Web Services (AWS) are two of the most popular cloud platforms.…
Cloud management is difficult to do manually, especially if you work with multiple cloud…
Azure’s scalable infrastructure is often cited as one of the primary reasons why it's the…
https://www.youtube.com/watch?v=wDzCN0d8SeA Watch our "Unlocking the Power of AI in your Software Development Life Cycle (SDLC)"…
FinOps is a strategic approach to managing cloud costs. It combines financial management best practices…
Using Kubernetes with Azure combines the power of Kubernetes container orchestration and the cloud capabilities…
View Comments
Hi, I'm following your blog for some time now, for it provides me with invaluable resources regarding, SL, MVVM and so on...
I was wondering why do you use this construct:
var handler = PropertyChanged;
if (handler != null) handler(this, ...
instead of the simpler:
if(PropertyChanged != null) PropertyChanged(this, ...
Is-it just a question of style or, is using a temporary variable useful in some scenarios?
Thanks, Olivier.
My understanding is that in the rare event the handler is removed when checking the null, you could call an empty handler. By copying to the local variable, you ensure you are going to call and fire the delegates there, as opposed to checking it for null, having them removed, and then throwing an exception (in the former case, the event will empty, but the local copy will still allow the last calls to happen). Probably an extreme edge case but it's a convention I've picked up and stuck with.
WP7 App with MVVM Light
Hi Jeremy,
Thanks for sharing UltraLight. I like what I've seen so far. I'm curious, have you ever come across the issue described here:
http://forums.create.msdn.com/forums/p/70506/430303.aspx
I've found a few solutions, but none that are very clean.
-Jeff
@Jeff,
I typically create a behavior that updates the source binding on the text changed event, and then simply attach it to the text boxes where this is an issue. It's essentially the code in the post you linked to, but encapsulated as a behavior.
Hi,
thanks for this framework, I have a question. How would you do bind event to command. For example, how can I bind a command to the loaded page event, or the checked event of a checkbox ...
Thanks in advance for your answer