Silverlight MVVM: Commands, CommandManager and scoping with Photon

Bit of a long title…  But commanding in photon has some sweet features which help create really simple view models.  There’s no need for ICommand, DelegateCommands or constantly calling RequerySuggested.  The view simply uses the view model methods.  You TDD out your view model and stick a view on later.  This is a big part of our goal because we don’t have all the UX requirements at the start of the sprint.  I’ll resist the temptation of talking about how we work with UX, I’ll save that for another post…

This example is slightly contrived.  It’s as real as I could come up with in as few lines of code as possible…  Let’s imagine you need to deliver a screen which lets you set user contact preferences:

  • The user should be able to capture if the caller would like to be contacted in the future
  • If the caller would like to be contacted the user must record at least one contact method

So first story, what does our view model look like?

public class CustomerContactPreferenceViewModel : ModelBase
{
    private bool isContactable;
 
    public bool IsContactable
    {
        get
        {
            return isContactable;
        }
        set
        {
            this.SetProperty(ref isContactable, value, () => IsContactable);
        }
    }
}

 

First thing to notice is our view model inherits ModelBase.  Photon provides a base class which implements INotifyPropertyChanged (and the validation interfaces, more on this later).  Next we’re setting the property using the SetProperty method.  This will fire the notify changed and also hooks into the commanding infrastructure (we’ll see this working shortly).

Next, let’s add to it to support recording the contact methods:

public class CustomerContactPreferenceViewModel : ModelBase
{
    private bool isContactable;
    private readonly ObservableCollection<ContactMethodViewModel> contactMethods = new ObservableCollection<ContactMethodViewModel>();
 
    public CustomerContactPreferenceViewModel()
    {
        ContactMethods = new ReadOnlyObservableCollection<ContactMethodViewModel>(contactMethods);
    }
 
    public bool IsContactable
    {
        get
        {
            return isContactable;
        }
        set
        {
            this.SetProperty(ref isContactable, value, () => IsContactable);
        }
    }
 
    public ReadOnlyObservableCollection<ContactMethodViewModel> ContactMethods { get; private set; }
 
    public void AddContactMethod()
    {
        contactMethods.Add(new ContactMethodViewModel());
    }
 
    public void RemoveContactMethod(ContactMethodViewModel contactMethod)
    {
        contactMethods.Remove(contactMethod);
    }
}
 
public class ContactMethodViewModel : ModelBase
{
    private string contactMethod;
 
    public string ContactMethod
    {
        get
        {
            return contactMethod;
        }
        set
        {
            this.SetProperty(ref contactMethod, value, () => ContactMethod);
        }
    }
}

Ok, so we added a new view model for holding the contact method.  I’ve also written some methods which let people add and remove preferences…  Now we need to put a view on this.  One thing I’ve been using recently is designer data, it makes life so much easier in Blend because you can see what you’re doing.

d:DataContext="{d:DesignInstance ViewModel:CustomerContactPreferenceViewModel, IsDesignTimeCreatable=True}"

Important thing with IsDesignTimeCreatable must be true, otherwise it doesn’t work!

Tip here is to use ReSharper to bring in the namespace reference.  Type <ContactPreferenceViewModel in the XAML, ReSharper will pop up asking if you want to import the namespace, hit alt enter.  Done.

Now you can data bind up to the view model…

image

Anyway, I digress…

We need a button to add contact methods, and we need to hook the button up to our AddContactMethod method…  This is done by adding an event trigger with an InvokeMethod action and setting the MethodName property accordingly:

image 

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Click">
        <photon:InvokeMethod MethodName="AddContactMethod"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

Now we need somewhere to show the Contact Methods, so let’s use a list box and stick a TextBox (bound to the ContactMethod string) and a Remove button.

Now this is where Scoping comes in…  The data context in our list box is the ContactMethodViewModel.  This doesn’t have the remove method, it’s on the ContactPreferenceViewModel…  We can use the Invoke.Target attached property to tell the method binding action where to look for the method.  Now you can drop on the MethodInvoke action as before, only this time you set a parameter (the first parameter of the method is the contact method to remove).  The list box ends up looking like this:

<ListBox Grid.Row="2" ItemsSource="{Binding ContactMethods}" photon:Invoke.Target="{Binding}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150" />
                    <ColumnDefinition Width="*"    />        
                </Grid.ColumnDefinitions>
                <TextBox Text="{Binding ContactMethod, Mode=TwoWay}" Grid.Column="0" Margin="0,0,10,0" />
                <Button Content="Remove" Grid.Column="1" >
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <photon:InvokeMethod MethodName="RemoveContactMethod">
                                <photon:InvokeMethod.Arguments>
                                    <photon:Argument Value="{Binding}"/>
                                </photon:InvokeMethod.Arguments>
                            </photon:InvokeMethod>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Button>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
    
</ListBox>

So got a working view…  Two things to do…  You shouldn’t be able to add contact methods if the customer is not contactable.  Also, if they are contactable you have to capture at least one contact method.  So let’s add these method to the view model.

public bool CanAddContactMethod
{
    get
    {
        return this.IsContactable;
    }
}
 
public bool CanRemoveContactMethod(ContactMethodViewModel contactMethod)
{
    return this.ContactMethods.Count > 1;
}

Now photon will automatically re-evaluate the can methods for you…  It does this when properties change, no need for any other code:

image image image

So in summary, you get some interaction actions you can drop on in blend which call methods (we may add a short hand syntax for non-blenders).  There’s a base view model which sorts out all the boiler plate Property Changed logic.  This also hooks in with the Photon CommandManager so you don’t need to re-evaluate Can Execute methods.  There’s also Invoke.Target so you can call methods which are not associated with the current data context.

There’s more documentation of Method Invocation on the Photon CodePlex site, along with all the Source Code…  I hope to post some more stuff up about how we use Photon along with how we work with blend, UX and more…

Advertisements

About Tom Peplow

C# .Net developer based in London and the South Coast
This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.

One Response to Silverlight MVVM: Commands, CommandManager and scoping with Photon

  1. Pingback: Using Visual States to keep view models simple… and not a converter to be seen | pep => lowdown

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s