Wednesday, March 30, 2011

Announcing Jounce 1.0 RTM - It's here!

Jounce is the result of building composite Silverlight enterprise applications for a variety of verticals since Silverlight 3.0 and finding the common problems that needed to be solved. I found many frameworks to be too heavy for the task and always wondered why more did not tap into the native advantages that the Managed Extensibility Framework brings. So, I began porting my framework around as a core engine for projects and finally decided to make it available to provide guidance for building these types of applications using the MVVM pattern and MEF.

You can see concepts about Jounce echoed in many of my posts, but the easiest way to summarize the post-release topics is to follow the jounce tag on this blog. While bits and pieces have been posted over time, probably the first comprehensive peek at what Jounce would look like happened as the result of my 2010 CodeStock presentation. I posted an application called XFit to demo a live modular application that was more than just a button and a text file. Over time that has been refined and advanced through input from the community as well as fellow developers who are using Jounce in their own projects.

I'd like to give special thanks to Page Brooks for giving Jounce a logo, adding tests, testing the heck out of it and also giving us a wonderful project template (available on the downloads page).

Anyway I hope this guidance finds you well, and provides a nice, lightweight set of examples and patterns for building Silverlight applications with the MVVM pattern and MEF. Go ahead and grab the source, read the docs, and join the discussions at the Jounce CodePlex site. Thanks!

Jeremy Likness

Tuesday, March 29, 2011

Silverlight Scaling and Anti-Aliasing Issues

I recently had the opportunity to work on a rather nagging issue with a customer related to how Silverlight scales UI elements. The customer was kind enough to approve me posting the solution. The problem is with scaling UI elements on a canvas. The scenario is simple (and easy to reproduce).

First, create a canvas exactly 1000 pixels wide. Then, place two blue rectangles in the canvas that are exactly 500 pixels wide. Offset the second rectangle by 500, like this:

<Canvas Width="1000" Height="800" >
        <Rectangle Fill="Blue" Width="500" Height="800"/>
        <Rectangle Fill="Blue" Width="500" Height="800" Canvas.Left="500"/>
</Canvas>

So far, so good. You'll get the illusion of a single, solid blue rectangle because the edges of the rectangles are flush.

Now, let's toss the rectangle inside a Viewbox. You can also simply apply a scale transform to the containing grid or canvas, but a view box makes this easier. Put the canvas inside a grid and add some controls to adjust the height dynamically.

<Grid x:Name="LayoutRoot" Background="White">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Viewbox Width="{Binding ElementName=slider, Path=Value}" Stretch="Uniform" Grid.Row="1">
        <Canvas Width="1000" Height="800" >
            <Rectangle Fill="Blue" Width="500" Height="800"/>
            <Rectangle Fill="Blue" Width="500" Height="800" Canvas.Left="500"/>
        </Canvas>
    </Viewbox>        
    <StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center">
            <Slider x:Name="slider" Width="1000" Minimum="100" Maximum="1024" Value="2000"/>
    </StackPanel>
</Grid>

Launch the program, and slide the slider to scale the rectangles. You'll almost immediately catch the issue with anti-aliasing:

At certain zoom levels, the algorithm that computes the shape is obviously allowing the background to bleed through. Here's the same picture above, magnified several times:

The anti-aliasing happens by blending the background into the edge. You can confirm the background is bleeding through by adding a rectangle behind the two that is a different color. Here's an example with a red rectangle:

<Canvas Width="1000" Height="800" >
        <Rectangle Fill="Red" Width="1000" Height="800"/>
        <Rectangle Fill="Blue" Width="500" Height="800"/>
        <Rectangle Fill="Blue" Width="500" Height="800" Canvas.Left="500"/>
</Canvas>

... and the magnified result:

After discovering this and scouring the forums for an answer, it appears the problem has not been resolved. Overlapping the edges is unacceptable in cases where the borders contain intricate designs that break when they are shifted. Even with the solid rectangles and a full pixel overlap, the issue can still be seen at certain zoom resolutions. So, finally, I decided to take advantage of the defect and use the "background" to my advantage.

For the blue rectangles, this was simple. I created a new "hack panel" as a blue rectangle. To see the difference, I added a button with code behind to toggle the visibility. Here is the XAML with the blue correction panel. Notice it is only 5 pixels wide and positioned just right to overlap the edges of the panels on top of it:

<Canvas Width="1000" Height="800" >
                    <Rectangle x:Name="HackPanel" Fill="Blue" Width="5" Height="800" Visibility="Visible" Canvas.Left="499"/>
                    <Rectangle Fill="Blue" Width="500" Height="800"/>
                <Rectangle Fill="Blue" Width="500" Height="800" Canvas.Left="500"/>
            </Canvas>

The new toggle button simply toggles the visibility of the strip:

private void _ButtonClick(object sender, RoutedEventArgs e)
        {
            _visibility = !_visibility;
            HackPanel.Visibility = _visibility ? Visibility.Visible : Visibility.Collapsed;
        }

So the problem is solved with a solid color, but what about a more complex border? To solve this, I took my Jounce logo and hacked it in half. It's not a perfect job because I threw this together quickly, so you'll notice I shift the right side down by a pixel and it doesn't line up perfectly, but the point is the anti-aliasing effect. With the hacks turned off, you can see a very clear line appearing up and down the edge. But what to put behind it? Green will bleed on the white, and vice versa.

The solution? A hack grid. I dropped this onto the canvas:

<Grid x:Name="HackGrid" Loaded="HackGrid_Loaded" Width="5" Height="800" Visibility="Collapsed" Canvas.Left="499"/>

And in the loaded event, transform the image into it. What essentially happens is we get a 5 pixel strip behind the panels that is a shifted view of the left seam of the right picture (the grid width will clip the rest). Because the anti-alias bleeds through the background, the pixels will overlap just enough to blend the seams and reduce the impact of the effect. Here's the code that can easily be turned into a behavior:

private void HackGrid_Loaded(object sender, RoutedEventArgs e)
{
    var bitmap = new WriteableBitmap(RightImage, null);
    var imageClone = new Image
                            {
                                Width = bitmap.PixelWidth,
                                Height = bitmap.PixelHeight,
                                Source = bitmap,
                                Stretch = Stretch.None
                            };
    imageClone.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Left);
    imageClone.SetValue(VerticalAlignmentProperty, VerticalAlignment.Top);
    HackGrid.Children.Clear();
    HackGrid.Children.Add(imageClone);
}

And here is the chopped up picture before:

...and after applying the strip behind the two parts:


(The blue strip on the top is from the rectangles).

It's not a perfect solution, but it reduces the impact of the effect and hopefully will help others who run into the same issue. Here's the source code for you to play with it yourself: Side-by-Side Source.

Jeremy Likness

Sunday, March 27, 2011

Jounce Part 14: Using MEF for Non-Shared Views and View Models

Even if you don't use Jounce, this post will help you better understand how to create non-shared views and view models with the Managed Extensibility Framework (MEF). The architecture I prefer in Silverlight is to keep a shared view and view model where possible. If only one instance of the view is visible at a time, the view model can be manipulated to provide the correct bindings and information when different data is selected for that view. Keeping a single view avoids issues with waiting for multiple views in garbage collection or keeping track of multiple views that might not unhook from the visual tree correctly.

There are several cases where this is not plausible. In a master/detail scenario with dockable views, you may end up with multiple "detail" views on the screen. Each view requires a separate copy and possibly a separate view model as well.

Lazy Shared Views and View Models

In MEF, the Lazy<T,TMetadata> syntax is used to import multiple implementations of a contract and associated metadata. The pattern allows you to inspect the metadata before creating the object instances, so you have full control over what is happening. Jounce uses this feature to bind views and view models by inspecting the metadata and using that to examine "routes" created by the user (a route simply asserts that view "X" requires view model "Y").

The view model import looks like this:

[ImportMany(AllowRecomposition = true)]
public Lazy<IViewModel, IExportAsViewModelMetadata>[] ViewModels { get; set; }

The array can be queried with LINQ. In Jounce, the view model metadata simply provides a user-defined tag for the view model so it can be referenced without knowing the type (this is useful, for example, when the view model must be referenced before the XAP that contains it has been loaded). Here is the metadata contract:

public interface IExportAsViewModelMetadata
{
    string ViewModelType { get; }
}

Here is a simple query to get the associated information for the view model. What's important to note is that part of this information is a Lazy<T> property for the view model. When the value property is accessed, the view model is created, but any subsequent references will use the same copy, or a "shared" view model:

var vmInfo = (from vm in ViewModels
                where vm.Metadata.ViewModelType.Equals(viewModelType)
                select vm).FirstOrDefault();

Using the Export Factory

To get a fresh copy, instead of using the lifetime management attributes that MEF provides, I chose to go with the ExportFactory. Unlike a Lazy import, the export factory does not provide a copy of the target object. Instead, it provides the means of creating a new copy. In other words, you are provided with an actual factory to generate new copies. The convention for this is very similar to the lazy version:

[ImportMany(AllowRecomposition = true)]
public List<ExportFactory<IViewModel, IExportAsViewModelMetadata>> ViewModelFactory { get; set; }

Notice that again metadata exists, so you can inspect the factory to make sure it is the right factory before asking it to generate a new object. Now it is a simple matter to query for the right view model and then ask the export factory to generate a new copy:

public IViewModel GetNonSharedViewModel(string viewModelType)
{
    return (from factory in ViewModelFactory
            where factory.Metadata.ViewModelType.Equals(viewModelType)
            select factory.CreateExport().Value).FirstOrDefault();
}

The Ties that Bind

Now you are able to generate a view and a view model on the fly. Jounce adds helper methods to view models to help synchronize with the view. For example, InitializeViewModel is called the first time the view model is bound to the view, and ActivateView is called when a view is loaded that is bound to the view model. There is also a binding for the visual state manager so that the view model can transition states without being aware of the view.

In order to bind property, Jounce provides a simple mechanism for generating a new copy of a view that allows you to pass in the data context:

UserControl GetNonSharedView(string viewTag, object dataContext);

If you have simple views, this can take a model or other class that is not a view model and will still bind it to the data context for you. However, if the bound object is a Jounce view model, the binding will add some additional calls and hooks to ensure that visual states and other bindings are updated as needed. This is what the view creation method looks like:

public UserControl GetNonSharedView(string viewTag, object dataContext)
{
    var view = (from factory in ViewFactory
                where factory.Metadata.ExportedViewType.Equals(viewTag)
                select factory.CreateExport().Value).FirstOrDefault();

    if (view == null)
    {
        return null;
    }

    _BindViewModel(view, dataContext);
                
    var baseViewModel = dataContext as BaseViewModel;
    if (baseViewModel != null)
    {
        baseViewModel.RegisterVisualState(viewTag,
                (state, transitions) => JounceHelper.ExecuteOnUI(
                  () => VisualStateManager.GoToState(view, state, transitions)));
        baseViewModel.RegisteredViews.Add(viewTag);
        baseViewModel.Initialize();
        RoutedEventHandler loaded = null;
        loaded = (o, e) =>
                        {
                            ((UserControl) o).Loaded -= loaded;
                            baseViewModel.Activate(viewTag, new Dictionary<string, object>());
                        };
        view.Loaded += loaded;
    }
    return view;
}

Notice the use of the variable to allow unbinding the lambda expression after the first loaded event is fired (the load event is triggered every time the control is placed in the visual tree, but the bindings only need to be updated once).

DataTemplate Selectors

The final step for Jounce was to create a special value converter for spinning views. This is similar to the concept I wrote about in Data Template Selector using the Managed Extensibility Framework. Only in this case, the converter will do one of two things: if passed a Jounce view model, it will automatically find the view the view model is associated with, generate a new copy of the view, and bind it, or if passed a parameter, it will create a view with the tag provided in the parameter.

The Jounce quickstart (available by grabbing the latest Jounce source download) contains an example of this. The target model is a contact class:

public class Contact
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

The view model for the model simply passes through to the underlying properties. In a full implementation, it would save the orignal model for roll back and have other options such as commands to manipulate the data:

[ExportAsViewModel("ContactVM")]
public partial class ContactViewModel : BaseViewModel
{
    public ContactViewModel()
    {
        if (DesignerProperties.IsInDesignTool)
        {
            SetDesignerData();
        }
    }

    public Contact SourceContact { get; set; }
        
    public string FirstName
    {
        get { return SourceContact.FirstName; }
        set
        {
            SourceContact.FirstName = value;
            RaisePropertyChanged(()=>FirstName);
        }
    }

    public string LastName
    {
        get { return SourceContact.LastName; }
        set
        {
            SourceContact.LastName = value;
            RaisePropertyChanged(()=>LastName);
        }
    }

    public string Address
    {
        get { return SourceContact.Address; }
        set
        {
            SourceContact.Address = value;
            RaisePropertyChanged(()=>Address);
        }
    }

    public string City
    {
        get { return SourceContact.City; }
        set
        {
            SourceContact.City = value;
            RaisePropertyChanged(()=>City);
        }
    }

    public string State
    {
        get { return SourceContact.State; }
        set
        {
            SourceContact.State = value;
            RaisePropertyChanged(()=>State);
        }
    }
}

The view simply shows the last name, first name in bold and then the address on the second line:

<Grid x:Name="LayoutRoot" Background="White" d:DataContext="{d:DesignInstance vm:ContactViewModel, IsDesignTimeCreatable=True}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal" Margin="5">
        <TextBlock FontWeight="Bold" Text="{Binding LastName}"/>
        <TextBlock FontWeight="Bold" Text=", "/>
        <TextBlock FontWeight="Bold" Text="{Binding FirstName}"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal" Grid.Row="1" Margin="5">
        <TextBlock Text="{Binding Address}"/>
        <TextBlock Text=" "/>
        <TextBlock Text="{Binding City}"/>
        <TextBlock Text=", "/>
        <TextBlock Text="{Binding State}"/>
    </StackPanel>
</Grid>

Here is the view with sample data in the designer:

The view itself is exported in the code-behind, as well as a binding mapping it to the corresponding view model:

[ExportAsView("ContactView")]
public partial class ContactView
{
    public ContactView()
    {
        InitializeComponent();
    }

    [Export]
    public ViewModelRoute Binding
    {
        get { return ViewModelRoute.Create("ContactVM", "ContactView"); }
    }
}

The main view model starts out by providing a list of contacts (this one generates sample data for the sake of the demo):

[ExportAsViewModel("MainVM")]
public class MainViewModel : BaseViewModel
{
    private readonly List<Contact> _sampleData = new List<Contact>
                                            {
                                                new Contact
                                                    {
                                                        FirstName = "Jeremy",
                                                        LastName = "Likness",
                                                        Address = "1212 Hollywood Blvd",
                                                        City = "Hollywood",
                                                        State = "California"
                                                    },
                                                new Contact
                                                    {
                                                        FirstName = "John",
                                                        LastName = "Doe",
                                                        Address = "12 Driving Parkway",
                                                        City = "St. Petersburg",
                                                        State = "Florida"
                                                    },
                                                new Contact
                                                    {
                                                        FirstName = "Jane",
                                                        LastName = "Doe",
                                                        Address = "1414 Disk Drive",
                                                        City = "Lead",
                                                        State = "South Dakota"
                                                    },
                                                new Contact
                                                    {
                                                        FirstName = "Sam",
                                                        LastName = "Iam",
                                                        Address = "12 Many Terrace",
                                                        City = "Figment",
                                                        State = "Imagination"
                                                    },

                                            };

    public MainViewModel()
    {
        Contacts = new ObservableCollection<Contact>(_sampleData);
    }

    public ObservableCollection<Contact> Contacts { get; private set; }
}

The problem is that our view requires a view model (it might handle edits or deletes in the future, so the model itself isn't enough). No problem! First, create a set of extension methods: one to pass the contact into a view model, and another to handle a list of contacts that will return a list of contact view models:

public static ContactViewModel ToViewModel(this Contact contact, IViewModelRouter router)
{
    var vm = router.GetNonSharedViewModel("ContactVM") as ContactViewModel;

    if (vm == null)
    {
        throw new Exception("Couldn't create view model for contact.");
    }

    vm.SourceContact = contact;
    return vm;
}

public static IEnumerable<ContactViewModel> ToViewModels(this IEnumerable<Contact> contacts, IViewModelRouter router)
{
    return contacts.Select(contact => contact.ToViewModel(router)).ToList();
}

Notice the router is used to get a non-shared copy of the view model, then the contact is passed in. The list function simply applies the conversion to the entire list. Of course, the view model tag can be specified as a constant to avoid the "magic string" and make it easier to refactor down the road.

The main view model can now be tweaked to expose a list of view models:

public IEnumerable<ContactViewModel> ViewModels
{
    get { return Contacts.ToViewModels(Router); }
}

It's a very simple conversion using the helper extension methods. To ensure the enumerable is re-loaded anytime the underlying collection changes, simply add this after the observable collection is created:

Contacts.CollectionChanged += (o, e) => RaisePropertyChanged(() => ViewModels);

Next the XAML is updated to use the special converter:

<ListBox ItemsSource="{Binding ViewModels}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <ContentControl Content="{Binding Converter={StaticResource ViewConverter}}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

In this example, the binding is a list of contact view models. The special converter will inspect the binding, figure out the view model and find the corresponding view (the contact view we showed earlier), then create a new copy of the view and bind the view model. This could also have been done directly with the contact model if a view model wasn't being used. More importantly, if the list is type IViewModel it can contain different types of view models, and the converter will automatically find the appropriate view - this is a "View Model First" approach to binding but serves as a data template selector.

When the application is run, the non-shared views and view models spin up and bind as expected:

The full source code for Jounce and the example shown here is available from the Jounce CodePlex site.

Jeremy Likness

Thursday, March 24, 2011

Clean Design-Time Friendly ViewModels: A Walkthrough

This is a quick walkthrough to demonstrate how to make what I call "clean" design-time view models. These are view models that provide design-time data for your application, but don't embed that data in the final product. The release DLL and XAP files will only contain run-time support.

First, fire up Visual Studio and create a new Silverlight application. Just use the default template.

Keep all of the defaults on the dialog that prompts for hosting the application in a new web site (leave this checked) and use Silverlight 4. There is no need to check the "RIA Services" box for this example. When you confirm the dialog, the default project is created for you.

Next, add a folder called "ViewModels."



In the ViewModels folder, add a new class named MainViewModel. Make it a partial class. Create a string property called "Name", an observable collection of strings called "Widget," and implement INotifyPropertyChanged. Here is the code for the view model:

public partial class MainViewModel : INotifyPropertyChanged 
{
    public MainViewModel()
    {
        Widgets = new ObservableCollection<string>();
    }

    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            RaisePropertyChanged("Name");
        }
    }

    public ObservableCollection<string> Widgets { get; private set; }

    protected void RaisePropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

As you can see, it is a fully functional view model, and no design-time data has been included yet. Next add a special compiler symbol to allow switching from design mode to release mode. Right click on the Silverlight project and go to the properties tab. Click on the "Build" sub-tab, and add the "DESIGN" compiler symbol:

Next, add a class that extends the view model partial class. Name it MainViewModel.sampledata.cs. Place this code in the file:

public partial class MainViewModel
{
#if DESIGN
    private void _WireDesignerData()
    {
        if (!DesignerProperties.IsInDesignTool) return;

        Name = "This is a design-time name.";
        var widgets = new[] { "Widget One", "A Second Widget", "The Third and final Widget" };
        foreach (var widget in widgets)
        {
            Widgets.Add(widget);
        }
    }
#endif
}

Notice the use of the compilation symbol that was added earlier. When you save the file, your solution should look like this:

Wire the call to the new method from the main view model class, wrapping it in the conditional tag - you may have to compile first to get Intellisense for the design-time method:

public MainViewModel()
        {
            Widgets = new ObservableCollection<string>();
#if DESIGN
            _WireDesignerData();
#endif
        }

Build the solution so the view model is available for wiring into the designer surface. Finally, go into the MainPage.xaml and add a grid and a list box bound to the view model. Be sure to add the namespace for your view model:

xmlns:ViewModels="clr-namespace:DesignFriendlyExample.ViewModels" mc:Ignorable="d"

Then wire the remaining XAML:

<Grid x:Name="LayoutRoot" Background="White">
    <Grid.DataContext>
        <ViewModels:MainViewModel/>
    </Grid.DataContext>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <TextBlock Grid.Row="0" Text="{Binding Name}"/>
    <ListBox Grid.Row="1" ItemsSource="{Binding Widgets}"/>
</Grid>

You should immediately see the design-time data appear in the designer:

You can now design with designer data. If you compile and run the application, you'll see the page is blank because the design-time data only is populated in the designer. The only problem is that the XAP and DLL right now are "dirty." If you look at the DLL generated and open it using ILDASM this is what you'll find:

The design-time data has made the DLL larger by adding extra methods and text literals to load into the view models, and adds code that is not used in the run time. When you are ready to release your Silverlight application, simply go back to the properties for the Silverlight project and remove the "DESIGN" compilation symbol. Rebuild the project - it will build and run fine. However, if you take a look at the generated DLL now, the design-time data is no longer there and the DLL is smaller because the data has not been included - as you can see, the method to wire the data doesn't even exist on the view model:

Obviously this example worked with a very specific way of adding the view model to the view. Different frameworks have different ways of binding views and view models. This walkthrough should give you an idea of how to keep te design-time data separate regardless of the method you use and produce cleaner applications when design-time data is no longer needed.

Addendum

Thanks for the tip on this (see comments) ... here's an even cleaner way to build the sample data:

public partial class MainViewModel
{
    [Conditional("DESIGN")]
    private void _WireDesignerData()
    {
        if (!DesignerProperties.IsInDesignTool) return;

        Name = "This is a design-time name.";
        var widgets = new[] { "Widget One", "A Second Widget", "The Third and final Widget" };
        foreach (var widget in widgets)
        {
            Widgets.Add(widget);
        }
    }
}    

And because the directive is conditional, you don't have to condition calls to the method. The main view model constructor now looks like this:

public MainViewModel()
{
    Widgets = new ObservableCollection();
    _WireDesignerData();
}

And the compiler will automatically ignore the method call if the DESIGN symbol is not present. How cool and easy is that?

PS - Bonus round. If you name your sample data as [viewmodel].designer.cs, you automatically get association between the sample class and the view model. The result will look like this in the IDE:



Jeremy Likness

Saturday, March 12, 2011

Jounce Part 13: Navigation Parameters

This was a busy morning for Jounce. I checked in some changesets that unfortunately break backwards compatibility but address a few highly requested changes. The first was CLS compliance, and that involved refactoring some weirdly named methods. The second was consistent namespaces (had view vs. views, etc.) so that was cleaned up. The final and the focus of this post was the addition of navigation parameters.

This was something definitely lacking in the navigation harness for Jounce. In most projects I use direct view model to view model communication to pass setup information. For example, if I want to display an order detail, the order master passes the order detail to the detail view model, then raises the navigation event for the order detail. This, of course, creates coupling between view models and requires a coordination of actions to make one event happen.

The addition of parameters changes that. You can now raise a navigation event with a payload, and the payload will be passed into the view model when the view is activated. This not only provides some flexibility around initializing the view model with context, but also enhances the "back stack" experience. If you like, you can make your view models completely stateless and dependent on the parameters passed in. Then, if you implement a back stack as in the previous post, the back stack itself can simply hold the navigation structure. This means a back event can restore the initial payload and the view model can reconstruct the state based on the prior request.

The parameters come with a simple set of extension helpers that make it easy to fluently pass values when navigation events are raised. To demonstrate the new functionality, I changed the "Simple Navigation" quick start to pass a GUID payload. Only the green circle has a view model, but this was updated to listen for the payload and update it to a property that is then bound and displayed on the circle.

I've been asked why I don't implement the back stack functionality in Jounce directly or use a URI-based navigation system. I don't discount the merits of those systems but I feel a lot of URI-based navigation comes from the memory of the web and trying to get a Silverlight application to behave like a web one, which I don't believe is the right approach. I also don't believe in imposing a navigation paradigm because users may have their own requirements. For example, a back stack sounds simple until you are dealing with nested regions. What if I am three levels deep, initiate a pop-up with a wizard, and "go back"? In this scenario, there are multiple levels of what "back" means, and you may want to handle those differently. This is why I've focused on giving Jounce just the basics: an event to trigger navigation, now a payload to pass, the binding of the view to the view model and a region manager to get the view on the visual tree for you.

To add a parameter to a navigation request, you provide a name and pass the value of the parameter, like this:

EventAggregator.Publish(view.AsViewNavigationArgs().AddNamedParameter("Guid", Guid.NewGuid()));

The "add named parameter" extends the navigation args and returns the same instance, but will use the internal parameters collection to add the requested parameter.

Consuming it is just as simple. The ParameterValue extension allows you to extract the parameter to a type and will provide the default if it doesn't exist (so if you wish to take exception to a missing parameter, check for its existence first):

var guid = viewParameters.ParameterValue<Guid>("Guid");

The view model used to have a method called _Activate that was passed the tag for a view whenever the view was navigated to. That method is now more appropriately named ActivateView and is passed both the view tag and the parameter dictionary. It's also important to note that the actual dictionary is passed, not a copy. This means the navigation args will stay as long as you keep the reference to the dictionary. This isn't necessarily a bad thing because the object is lightweight, but most of the time you'll simply extract the values and it will go out of scope anyway. Because you have the actual dictionary, it is possible to manipulate the dictionary. I allow this on purpose in case you need to add meta data or other information for historical purposes if you are saving a navigation stack.

The latest version of Jounce and the examples for this feature can be downloaded from CodePlex.

Jeremy Likness

Wednesday, March 9, 2011

Jounce Part 12: Providing History-Based Back Navigation

I purposefully kept the navigation engine in Jounce light because there are so many opinions about how navigation should work and what makes sense for a specific application. A foundation is provided to raise navigation events and wire view models to views, but the rest of navigation is a mix of region management and user interaction. One common interaction is to give the user the ability to "go back." This is a feature users are used to in their browser, but isn't always implemented well in applications (although the Windows Phone 7 forces this paradigm through the criteria around the hardware back button).

Jounce has everything needed to store the stack of navigated pages and provide the "go back" functionality. First thing is to create the stack of pages. For this short example, I'll provide a clock, a fibonacci sequence generator, and some text. The clock view model looks like this:

[ExportAsViewModel("Clock")]
public class ClockViewModel : ContentViewModel  
{
    public ClockViewModel()
    {
        if (InDesigner)
        {
            Time = DateTime.Now.ToLongTimeString();
        }
    }

    public string Time { get; set; }

    public override void _Initialize()
    {
        WorkflowController.Begin(ClockWorkflow());
        base._Initialize();
    }

    public IEnumerable<IWorkflow> ClockWorkflow()
    {
        var workflowDelay = new WorkflowDelay(TimeSpan.FromSeconds(1));

        while (true)
        {
            yield return workflowDelay;
            Time = DateTime.Now.ToLongTimeString();
            JounceHelper.ExecuteOnUI(()=>RaisePropertyChanged(()=>Time));
        }
    }
}

Instead of using the dispatcher timer, I'm using the workflow implementation to advance the clock every second. You'll notice the clock view model is derived from a content view model. The content view model provides a standard visual state experience:

public abstract class ContentViewModel : BaseViewModel 
{
    public override void _Activate(string viewName)
    {
        GoToVisualState("VisibleState", true);
        base._Activate(viewName);
    }

    public override void _Deactivate(string viewName)
    {
        GoToVisualState("InvisibleState", true);
        base._Deactivate(viewName);
    }
}

This keeps me from having to duplicate the visual state switching in every view model and just derive from the common one instead. The view has some transitions built-in so that you can see transition effects as navigation takes place:

<Grid x:Name="LayoutRoot" Background="Transparent" d:DataContext="{d:DesignInstance sampledata:ClockViewModel, IsDesignTimeCreatable=True}">
    <Grid.RenderTransform>
        <TranslateTransform/>
    </Grid.RenderTransform>
    <Grid.Projection>
        <PlaneProjection CenterOfRotationX="0"/>
    </Grid.Projection>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="VisibilityStates">
            <VisualState x:Name="VisibleState">
                <Storyboard TargetName="LayoutRoot" TargetProperty="(UIElement.Visibility)">
                    <ObjectAnimationUsingKeyFrames>
                        <DiscreteObjectKeyFrame KeyTime="0:0:0">
                            <DiscreteObjectKeyFrame.Value>
                                <Visibility>Visible</Visibility>
                            </DiscreteObjectKeyFrame.Value>
                        </DiscreteObjectKeyFrame>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
            <VisualState x:Name="InvisibleState">
                <Storyboard TargetName="LayoutRoot" TargetProperty="(UIElement.Visibility)">
                    <ObjectAnimationUsingKeyFrames>
                        <DiscreteObjectKeyFrame KeyTime="0:0:0">
                            <DiscreteObjectKeyFrame.Value>
                                <Visibility>Collapsed</Visibility>
                            </DiscreteObjectKeyFrame.Value>
                        </DiscreteObjectKeyFrame>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
            <VisualStateGroup.Transitions>
                <VisualTransition To="VisibleState">
                    <Storyboard Duration="0:0:1">
                        <DoubleAnimation Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.Opacity)"
                                            From="0" To="1"/>
                        <DoubleAnimation Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(FrameworkElement.RenderTransform).(TranslateTransform.X)"
                                            From="20" To="0"/>
                    </Storyboard>
                </VisualTransition>
                <VisualTransition To="InvisibleState">
                    <Storyboard Duration="0:0:1">
                        <DoubleAnimation Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.Opacity)"
                                            From="1" To="0"/>
                        <DoubleAnimation Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(FrameworkElement.Projection).(PlaneProjection.RotationY)"
                                            From="0" To="-90"/>
                    </Storyboard>
                </VisualTransition>
            </VisualStateGroup.Transitions>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <Viewbox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5">
        <TextBlock Text="{Binding Time}"/>
    </Viewbox>
</Grid>

What's most important is that the view exports itself with a category and a menu name, and targets the main content region:

[ExportAsView("Clock", Category="Content", MenuName = "Big Clock")]
[ExportViewToRegion("Clock", "ContentRegion")]
public partial class Clock
{
    public Clock()
    {
        InitializeComponent();
    }
}

Next I can create a navigation view model. The navigation view model will be responsible for a few tasks. First, it will query the view model router and get the list of views that are on the "content" category. It will sort and expose these in a list for the navigation control to bind to. It will also provide a "selected view" property that is bound to the selected item of the list box, so clicking on a menu name will automatically change the selection and fire the navigation to the separate view. Finally, it will listen for navigation events so if navigation occurs somewhere else, it can update the selected item so the menu stays in synch.

[ExportAsViewModel("Navigation")]
public class NavigationViewModel : BaseViewModel, IEventSink<ViewNavigationArgs>
{
    public NavigationViewModel()
    {
        Menu = new ObservableCollection<Tuple<string, string>>();

        if (!InDesigner) return;

        Menu.Add(Tuple.Create("Clock", "A Gigantic Clock"));
        Menu.Add(Tuple.Create("Fibonacci", "The Fibonacci Ratio"));
        Menu.Add(Tuple.Create("Shapes", "Random Shapes"));
        SelectedView = Menu[0];
    }

    private Tuple<string,string> _selectedView; 

    public Tuple<string, string> SelectedView
    {
        get { return _selectedView; }
        set 
        { 
            _selectedView = value;
            RaisePropertyChanged(()=>SelectedView);

            if (!InDesigner)
            {
                EventAggregator.Publish(value.Item1.AsViewNavigationArgs());
            }
        }
    }    

    public ObservableCollection<Tuple<string, string>> Menu { get; private set; }

    public override void _Initialize()
    {
        var query = (from vi in ((ViewModelRouter) Router).Views
                        where vi.Metadata.Category.Equals("Content")
                        orderby vi.Metadata.MenuName
                        select Tuple.Create(vi.Metadata.ExportedViewType, vi.Metadata.MenuName)).Distinct();            

        foreach(var item in query)
        {
            Menu.Add(item);
        }

        SelectedView = Menu[0];

        EventAggregator.Subscribe(this);

        base._Initialize();
    }

    public void HandleEvent(ViewNavigationArgs publishedEvent)
    {
        if (publishedEvent.Deactivate)
        {
            return; 
        }

        var item = (from menuItem in Menu
                    where menuItem.Item1.Equals(publishedEvent.ViewType)
                    select menuItem).FirstOrDefault();

        if (item != null)
        {
            _selectedView = item;
            RaisePropertyChanged(()=>SelectedView);
        }
    }
}

Notice that thet selected item is set once the menu list is parsed; this will automatically trigger a navigation to the first item in the list. The XAML to display the menu is straightforward:

<Grid x:Name="LayoutRoot" Background="White" d:DataContext="{d:DesignInstance sampledata:NavigationViewModel, IsDesignTimeCreatable=True}">
    <ListBox SelectedItem="{Binding SelectedView,Mode=TwoWay}" ItemsSource="{Binding Menu}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Item2}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

Now there are several pages and the navigation to get to them. The last piece is to wire up the shell. The shell page has the navigation and content regions, and the ubiquitous back button:

<Grid x:Name="LayoutRoot" Background="White">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <ContentControl Grid.Row="0" 
                    Regions:ExportAsRegion.RegionName="NavigationRegion"
                    HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
                    />
    <ItemsControl 
        Regions:ExportAsRegion.RegionName="ContentRegion"
        Grid.Row="1" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch"
                    >
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Grid/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
    <Button Content="GoBack" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="5" Grid.Row="1" Command="{Binding BackCommand}"/>
</Grid>

The shell view model will do two things. First, it will keep track of the last page. When a new page is navigated to, it will raise a deactivate navigation event for the previous view. This will call the _Deactivate method on the view model for that view, and transition the state to the hidden state (using the transitions defined earlier). Jounce doesn't assume how you are going to use your views: you might want to have them stacked in the same items control, or overlaid, or automatically swapped out, so the view model must manage the step of deciding that the old views should be deactivated. Notice the main content is an items control but the panel is a grid. This allows the child views to stack on top of each other, and the transitions will simply collapse the pages that are not in the view.

The second thing the shell view model will do is listen to navigation events and store a stack of pages. New pages will be pushed to the stack. If the back button is clicked, it will raise a "go back" event. The handler for the event will pop the last view from the stack and navigate to it. I created it as an event in case there was some other mechanism to fire the back event. Here is the set up - notice if there is nothing in the stack, the back button will be disabled:

[ExportAsViewModel("Main")]
public class MainViewModel : BaseViewModel, IEventSink<ViewNavigationArgs>, IEventSink<GoBack>
{
    private string _currentView = string.Empty;

    private bool _goingBack; 

    private readonly Stack<string> _history = new Stack<string>();

    public MainViewModel()
    {
        BackCommand = new ActionCommand<object>(o => EventAggregator.Publish(new GoBack()),
            o => _history.Count > 0);
    }

    public override void _Initialize()
    {
        EventAggregator.Subscribe<ViewNavigationArgs>(this);
        EventAggregator.Subscribe<GoBack>(this);
        base._Initialize();
        EventAggregator.Publish("Navigation".AsViewNavigationArgs());
    }

    public IActionCommand BackCommand { get; private set; }
}

Notice when the view model is initialized, it subscribes to the actions to track navigations and process the "go back" request, and also publishes a navigation for the navigation bar so the menu will be rendered. The handler for the navigation event:

public void HandleEvent(ViewNavigationArgs publishedEvent)
{
    if (publishedEvent.Deactivate)
    {
        return;
    }

    var viewInfo = (from vi in ((ViewModelRouter) Router).Views
                    where vi.Metadata.ExportedViewType.Equals(publishedEvent.ViewType)
                    select vi.Metadata)
                    .FirstOrDefault();

    if (viewInfo == null || !viewInfo.Category.Equals("Content")) return;

    if (publishedEvent.ViewType.Equals(_currentView)) return;

    if (!string.IsNullOrEmpty(_currentView))
    {
        EventAggregator.Publish(new ViewNavigationArgs(_currentView) {Deactivate = true});

        if (_goingBack)
        {
            _goingBack = false;
        }
        else
        {
            _history.Push(_currentView);
        }
    }

    BackCommand.RaiseCanExecuteChanged();

    _currentView = publishedEvent.ViewType;
}

The handler ignores deactivations. The view information is parsed from the router, and if it's not a "content" navigation, it is also ignored. Otherwise, if it is a new view, the old view is deactivated, then pushed to the history stack. The back command is notified that the stack may have values so it can be enabled. The current view is stored.

When a back command is raised, it is handled like this:

public void HandleEvent(GoBack publishedEvent)
{
    if (_history.Count < 1)
    {
        return; 
    }

    var view = _history.Pop();

    _goingBack = true;

    EventAggregator.Publish(view.AsViewNavigationArgs());
}

The "going back" flag is used to prevent the item that was just popped from the stack from being pushed back on.

That's it ... when you run the program, you can navigate through the pages in any order, and clicking the back button will navigate back in order until the first page is reached, after which the back button will be disabled. You can see the app in action here:


And you can download the source from the Jounce site (it is one of the quick starts.)

Jeremy Likness

Saturday, March 5, 2011

A Numeric Input Control for Windows Phone 7

I'm exploring the phone more and more and came across the case of allowing the user to enter digits. One thing to keep in mind on the phone is that the form factor requires approaches to input that are different from the traditional desktop. A desktop application might handle numeric entry by simply filtering the text box, but that assumes a keyboard-centric model. On the phone, touch is key.

Of course, at the most basic level we could simply define an input scope on the phone and the SIP would give the user a number-friendly keyboard:

<TextBox Text="{Binding Weight,Mode=TwoWay,Converter={StaticResource DoubleConverter}}" 
    Grid.Row="1">
    <TextBox.InputScope>
        <InputScope>
            <InputScopeName NameValue="Digits"/>
        </InputScope>
    </TextBox.InputScope>
</TextBox>

With those hints in place, you will get a keyboard like this:

Numeric SIP on Windows Phone 7

It still isn't easy to hit the numbers and you will still have to validate the field. You could also use a slider like the infamous date control (I'll let you explore that one for yourself) but it's not the ideal experience. In fact, I thought about my favorite experience entering numbers on the phone and it had to be this:

Windows Phone 7 Calculator

Yes, it's the calculator. It's clean, easy, and famliar. So, I decided to build a control to allow a calculator-like experience for entering numbers.

Sample Data for the Control

I decided to go with a view model based control only because the application I'm working on uses both the Windows Phone 7 database called Sterling and my miniature MVVM framework called UltraLight.mvvm. This could easily use code-behind and dependency properties to be more "independent" but this approach is fine for me to build into my applications.

The interfaces for mocking and developing against are simple: a number that displays on the screen and a set of commands to enter digits, save, or cancel.

public interface INumberPadViewModelBase
{
    string Number { get; }
}

public interface INumberPadViewModel : INumberPadViewModelBase, IViewModel
{
    void SetValue(double value, string callbackTag);
    IActionCommand<object> DigitCommand { get; }
    IActionCommand<object> AcceptCommand { get; }
    IActionCommand<object> CancelCommand { get; }
}

My design-time view model just provides a little piece of pi:

public class DesignNumberPadViewModel : INumberPadViewModelBase 
{
    public string Number
    {
        get { return "3.1415"; }
    }
}

The View

This gives me enough to design the view. For this, I went with a fluid layout and mirrored the positions of the numbers on the calculator. Eventually I should probably replace the "delete" key with the backspace symbol, but for now the following layout gave me the digits and number display along with confirm and cancel options:

<Grid x:Name="LayoutRoot" 
        d:DataContext="{d:DesignInstance design:DesignNumberPadViewModel, IsDesignTimeCreatable=True}"
        Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <TextBlock Text="{Binding Number}" HorizontalAlignment="Right" FontSize="72" Margin="5"/>
        
    <Grid Grid.Row="1" HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>
        <Button Grid.Row="0" Grid.Column="0"
                MVVM:ButtonCommand.Command="{Binding DigitCommand}"
                MVVM:ButtonCommand.CommandParameter="7">
            <Button.Content>
                <TextBlock Text="7" FontSize="56"/>
            </Button.Content>
        </Button>
        // lots of buttons omitted here 
        <Button Grid.Row="3" Grid.Column="0"
                MVVM:ButtonCommand.Command="{Binding DigitCommand}"
                MVVM:ButtonCommand.CommandParameter="0">
            <Button.Content>
                <TextBlock Text="0" FontSize="56"/>
            </Button.Content>
        </Button>
        <Button Grid.Row="3" Grid.Column="1"
                MVVM:ButtonCommand.Command="{Binding DigitCommand}"
                MVVM:ButtonCommand.CommandParameter="del">
            <Button.Content>
                <TextBlock Text="DEL" FontSize="56"/>
            </Button.Content>
        </Button>
        <Button Grid.Row="3" Grid.Column="2"
                MVVM:ButtonCommand.Command="{Binding DigitCommand}"
                MVVM:ButtonCommand.CommandParameter="dot">
            <Button.Content>
                <TextBlock Text="." FontSize="56"/>
            </Button.Content>
        </Button>
        <Button Grid.Row="4" Grid.ColumnSpan="3"
                MVVM:ButtonCommand.Command="{Binding DigitCommand}"
                MVVM:ButtonCommand.CommandParameter="clear">
            <Button.Content>
                <TextBlock VerticalAlignment="Center" Text="CLEAR" FontSize="56"/>
            </Button.Content>
        </Button>
    </Grid>        
</Grid>

As you can probably tell from looking at this, we have our "upper display" and the rest is simply a bunch of buttons bound to the same command. The digits pass the digit pressed, while the delete, clear, and decimals pass special text snippets that will be processed by the view model. In the designer, the number pad looks like this:

Number pad in the Windows Phone 7 Designer

The View Model

Now it's time to build the view model. I wanted to make it tombstone-friendly and because I'm using the MVVM framework, I decided to use messaging to transmit the number when the user confirms. First, I created a message to send:

public interface INumberPadResult
{
    string CallbackTag { get; }
    double Result { get; }
}

The result is self-explanatory. What about the callback tag? This is where it gets interesting. I'll be using a control to handle the numeric input. It will essentially pop out to the number view and then return back. This presents an interesting challenge with tombstoning because the control to return to isn't created yet (when the tombstone returns to the number pad, as I did not implement it as a pop-up) so there is nothing to listen for the value. Instead, the view model will have to pick it up.

Therefore, I decided to use the built-in Tag property to give the text box a unique name, and let the number pad share the tag value when transmitting the result. You'll see in a minute how that ties together.

The basic view model implements a few interfaces and defines constants for the "commands" such as delete and clear. I also decided to keep the digits as a simple array so I use a static array as the "initial value".

public class NumberPadViewModel : BaseViewModel, INumberPadViewModel, INumberPadResult, ITombstoneFriendly
{
    private const string DOT = "dot";
    private const string CLEAR = "clear";
    private const string DELETE = "del";
    private bool _navigating;

    private int _pos;
    private string[] _number = new string[10];

    private static readonly string[] _init = new[]
                                    {
                                        "0", string.Empty, string.Empty, string.Empty, string.Empty, string.Empty,
                                        string.Empty, string.Empty, string.Empty, string.Empty
                                    };
        
    public NumberPadViewModel()
    {
        DigitCommand = new ActionCommand<object>(o => _ProcessDigit((string) o), o => _CanProcess((string) o));
        AcceptCommand = new ActionCommand<object>(o => _ProcessResult());
        CancelCommand = new ActionCommand<object>(o => _Cancel());
        _init.CopyTo(_number, 0);
    }
}

You can see some commands are wired up and the array for the numbers is initialized. Here are the properties on the view model:

public override IEnumerable<IActionCommand<object>> ApplicationBindings
{
    get { return new[] {AcceptCommand, CancelCommand}; }
}

public bool HasDecimal
{
    get
    {
        return (from n in _number where n == "." select 1).Any();
    }
}

public string CallbackTag { get; set; }

public string Number
{
    get { return string.Join(string.Empty, _number); }            
}

public double Result { get; set; }        

public IActionCommand<object> DigitCommand { get; private set; }
public IActionCommand<object> AcceptCommand { get; private set; }
public IActionCommand<object> CancelCommand { get; private set; }

To make it easy to set up the number pad, I created a method (you saw that in the interface defined earlier) to load it with the initial information:

public void SetValue(double value, string callbackTag)
{
    CallbackTag = callbackTag;

    // remove any prior saved values
    PhitDatabaseService.Database.Delete(typeof(ValueModel), GetType().FullName);

    _init.CopyTo(_number, 0);

    // parse digits

    if (value != 0)
    {
        var digits = value.ToString();

        if (digits.Length > 10)
        {
            digits = digits.Substring(0, 10);
        }

        for (var x = 0; x < digits.Length; x++)
        {
            _number[x] = digits.Substring(x, 1);
        }

        _pos = digits.Length;
    }
    else
    {
        _pos = 0;
    }

    RaisePropertyChanged(()=>Number);
    DigitCommand.RaiseCanExecuteChanged();

    _navigating = true;
}

The processing commands handle disabling the delete key when there is nothing to delete and the decimal key when one has already been entered, and here I picked an arbitrary length of 10 ... obviously a more reusable control will have that exposed as a setting:

private bool _CanProcess(IEquatable<string> s)
{
    if (s.Equals(DOT))
    {
        return !HasDecimal;
    }

    if (s.Equals(DELETE))
    {
        return _pos > 0;
    }

    if (s.Equals(CLEAR))
    {
        return true;
    }

    return _pos < 10;
}

private void _ProcessDigit(string s)
{
    if (!_CanProcess(s)) return;

    if (s.Equals(DOT))
    {
        _number[_pos++] = ".";                
    }
    else if (s.Equals(DELETE))
    {
        _pos--;
        _number[_pos] = _pos == 0 ? "0" : string.Empty;                
    }
    else if (s.Equals(CLEAR))
    {
        _init.CopyTo(_number, 0);
        _pos = 0;
    }
    else
    {
        int digit;
        if (int.TryParse(s, out digit))
        {
            _number[_pos++] = s;
        }
    }

    RaisePropertyChanged(() => Number);
    DigitCommand.RaiseCanExecuteChanged();             
}

The processing just loads the digits into the array and stops allowing the decimal once it is entered.

Here are the save and cancel commands. The control implements the message payload interface so it is able to pass itself. Both actions will clear the call back tag, delete the tombstone entry and go back to the screen that it was called from:

private void _ProcessResult()
{
    Result = double.Parse(string.Join(string.Empty, _number));
    UltraLightLocator.EventAggregator.Publish<INumberPadResult>(this);
    _Cancel();
}

private void _Cancel()
{
    CallbackTag = string.Empty;
    PhitDatabaseService.Database.Delete(typeof(ValueModel), GetType().FullName);
    GoBack();
}

The last piece is to handle tombstoning. I have a value model class that is simply a string-based key with an object value that allows me to save just about anything. In this case I create an array of the position, the digits, and the call back tag, and use that to save and load values. Sterling will simply parse whatever is provided in the object parameter. Notice if I am navigating in from another screen (that field is set in the SetValue method) I won't clear the results but just pass through.

public void Deactivate()
{
    var value = new ValueModel { Key = GetType().FullName, Value = new object[] { _pos, _number, CallbackTag } };
    PhitDatabaseService.Database.Save(value);
}

public void Activate()
{
    var value = PhitDatabaseService.Database.Load<ValueModel>(GetType().FullName) ?? new ValueModel();
    if (value.Value is object[])
    {
        var tuple = value.Value as object[];
        _pos = (int)tuple[0];
        _number = (string[]) tuple[1];
        CallbackTag = (string) tuple[2];
    }
    else
    {
        if (_navigating)
        {
            _navigating = false;
        }
        else
        {
            _pos = 0;
            _init.CopyTo(_number, 0);
        }
    }
    DigitCommand.RaiseCanExecuteChanged();
    RaisePropertyChanged(()=>Number);
}        

That's it for the number pad.

The Numeric Textbox

We need a way to get to the number pad. The experience I think makes sense is to show the field on the screen as an editable field. However, when the user touches the field to begin editing, they are taken to the number pad. There, they can clear, edit, cancel, or confirm the value. To handle this I created the NumericTextBox control to work in conjunction with the number pad. When it is loaded, it walks up the tree to the parent page to get to the navigation context so it can call out to the number pad view when needed.

public class NumericTextBox : TextBox
{
    private NavigationService _navigation;
        
    public NumericTextBox()
    {
        MouseLeftButtonUp += _NumericTextBox;
        LostFocus += (o, e) => IsEnabled = true;
        GotFocus += (o, e) => IsEnabled = false;

        Loaded += _NumericTextBoxLoaded;
    }
        
    private void _NumericTextBoxLoaded(object sender, System.Windows.RoutedEventArgs e)
    {
        Loaded -= _NumericTextBoxLoaded;

        var parent = VisualTreeHelper.GetParent(this);
        while (parent != null)
        {
            if (parent is PhoneApplicationPage)
            {
                _navigation = ((PhoneApplicationPage) parent).NavigationService;
                break;
            }
            parent = VisualTreeHelper.GetParent(parent);
        }            
    }

    private void _NumericTextBox(object sender, System.Windows.RoutedEventArgs e)
    {
        IsEnabled = false;
        var value = GetValue(TextProperty) as string;
        double valueParsed;
        if (!double.TryParse(value, out valueParsed))
        {
            valueParsed = 0;
        }
        UltraLightLocator.GetViewModel<INumberPadViewModel>().SetValue(valueParsed, Tag.ToString());
        _navigation.Navigate(new Uri("/Controls/NumberPad.xaml", UriKind.Relative));
    }        
}

The first thing it does is stay enabled until focused, then disables so the user doesn't accidentally enter text using the SIP. Next, when the mouse left button event is fired (which is a simple tap on the phone) it copies the value in the text property to the view model, sets the tag, and navigates to the number pad.

Remember, we need the tag to listen for the right value when the user confirms, so I set up the weight text box like this:

<Controls:NumericTextBox 
    Tag="WeightMessage"
    Text="{Binding Weight,Mode=TwoWay,Converter={StaticResource DoubleConverter}}" 
    Grid.Row="1"/>   

Notice that it is bound without any regard to the number pad - there is no knowledge in the view other than the control reference. It is bound to an actual property on the view model and the double converter simply converts from text to double and back. The last step is for the view model to listen for the tag. In this case, I added the tag directly. If you think this is too much of a "magic string" you can bind the tag to the view model as well, and use that when referencing the message.

The main view model implements IEventSink<INumberPadResult> to receive the message. The method looks like this:

public void HandleEvent(INumberPadResult publishedEvent)
{
    if (publishedEvent.CallbackTag.Equals("WeightMessage"))
    {
        Weight = publishedEvent.Result;
        return;
    }

    if (publishedEvent.CallbackTag.Equals("HeightMessage"))
    {
        Height = publishedEvent.Result;
        return;
    }
}

Again, here is where you could have a local value instead that is databound to avoid the magic strings. Now the control is complete. The screen renders like this:

Sample screen asking for weight and height

When you tap on one of the fields for entry, the number pad is popped up like this, making it very easy to enter your numeric value:

Number pad for Windows Phone 7

Of course, I could shade it like the calculator, add some more filters, etc. but for now it's a working control that is also 100% tombstone-friendly.

Jeremy Likness