View Model and Navigation Model

Update Controls automatically discovers dependencies upon your data model, and updates the UI when that data changes. It does not require that you register for or fire PropertyChanged events.

Step 1: Write your data model
Your data model is a set of plain-old CLR objects (POCOs). These objects have fields that store data, and properties that expose that data to consumers. They may also have validation logic, business logic, and other methods. The data model will be persisted, probably via a web service.

public class Customer
{
    private string _name;
    private List<Invoice> _invoices = new List<Invoice>();

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public IEnumerable<Invoice> Invoices
    {
        get { return _invoices; }
    }

    public Invoice NewInvoice(string number)
    {
        Invoice invoice = new Invoice(number);
        _invoices.Add(invoice);
        return invoice;
    }
}

public class Payment
{
    private Customer _customer;
    private List<Invoice> _paidInvoices = new List<Invoice>();

    public Payment(Customer customer)
    {
        _customer = customer;
    }

    public Customer Customer
    {
        get { return _customer; }
    }

    public IEnumerable<Invoice> PaidInvoices
    {
        get { return _paidInvoices; }
    }

    public void AddPaidInvoice(Invoice invoice)
    {
        _paidInvoices.Add(invoice);
    }

    public void RemovePaidInvoice(Invoice invoice)
    {
        _paidInvoices.Remove(invoice);
    }
}


Step 2: Add Independent sentries
Add a reference to UpdateControls.Light.dll. Then create an Independent sentry for each field in your data model that can change. Call OnGet whenever you read the field, and OnSet whenever you change it.

public class Customer
{
    private string _name;
    private List<Invoice> _invoices = new List<Invoice>();

    private Independent _indName = new Independent();
    private Independent _indInvoices = new Independent();

    public string Name
    {
        get { _indName.OnGet(); return _name; }
        set { _indName.OnSet(); _name = value; }
    }

    public IEnumerable<Invoice> Invoices
    {
        get { _indInvoices.OnGet(); return _invoices; }
    }

    public Invoice NewInvoice(string number)
    {
        _indInvoices.OnSet();
        Invoice invoice = new Invoice(number);
        _invoices.Add(invoice);
        return invoice;
    }
}

public class Payment
{
    private Customer _customer;
    private List<Invoice> _paidInvoices = new List<Invoice>();

    private Independent _indPaidInvoices = new Independent();

    public Payment(Customer customer)
    {
        _customer = customer;
    }

    public Customer Customer
    {
        get { return _customer; }
    }

    public IEnumerable<Invoice> PaidInvoices
    {
        get { _indPaidInvoices.OnGet(); return _paidInvoices; }
    }

    public void AddPaidInvoice(Invoice invoice)
    {
        _indPaidInvoices.OnSet();
        _paidInvoices.Add(invoice);
    }

    public void RemovePaidInvoice(Invoice invoice)
    {
        _indPaidInvoices.OnSet();
        _paidInvoices.Remove(invoice);
    }
}


Step 3: Write your navigation model
The navigation model keeps track of all of the user's selection. It helps the user navigate through the data model. Use Independent sentries, just like you did for the data model. But unlike the data model, the navigation model is not persistent.

public class PaymentNavigationModel
{
    private Invoice _selectedUnpaidInvoice;
    private Invoice _selectedPaidInvoice;

    private Independent _indSelectedUnpaidInvoice = new Independent();
    private Independent _indSelectedPaidInvoice = new Independent();

    public Invoice SelectedUnpaidInvoice
    {
        get { _indSelectedUnpaidInvoice.OnGet(); return _selectedUnpaidInvoice; }
        set { _indSelectedUnpaidInvoice.OnSet(); _selectedUnpaidInvoice = value; }
    }

    public Invoice SelectedPaidInvoice
    {
        get { _indSelectedPaidInvoice.OnGet(); return _selectedPaidInvoice; }
        set { _indSelectedPaidInvoice.OnSet(); _selectedPaidInvoice = value; }
    }
}


Step 4: Write your view model
The view model interprets the data model for the view. It has no data of its own. It just references the data model and the navigation model. It has a property for every bindable control in the view. It also has an ICommand property for every action the user can perform.

public class PaymentViewModel
{
    private Payment _payment;
    private PaymentNavigationModel _navigation;

    public PaymentViewModel(Payment payment, PaymentNavigationModel navigation)
    {
        _payment = payment;
        _navigation = navigation;
    }

    public string CustomerName
    {
        get { return _payment.Customer.Name; }
        set { _payment.Customer.Name = value; }
    }

    public IEnumerable<Invoice> PaidInvoices
    {
        get { return _payment.PaidInvoices; }
    }

    public IEnumerable<Invoice> UnpaidInvoices
    {
        get { return _payment.Customer.Invoices.Except(_payment.PaidInvoices); }
    }

    public Invoice SelectedPaidInvoice
    {
        get { return _navigation.SelectedPaidInvoice; }
        set { _navigation.SelectedPaidInvoice = value; }
    }

    public Invoice SelectedUnpaidInvoice
    {
        get { return _navigation.SelectedUnpaidInvoice; }
        set { _navigation.SelectedUnpaidInvoice = value; }
    }

    public ICommand AddPaidInvoices
    {
        get
        {
            return MakeCommand
                .When(() => _navigation.SelectedUnpaidInvoice != null)
                .Do(() =>
                {
                    _payment.AddPaidInvoice(_navigation.SelectedUnpaidInvoice);
                    _navigation.SelectedPaidInvoice = _navigation.SelectedUnpaidInvoice;
                    _navigation.SelectedUnpaidInvoice = null;
                });
        }
    }

    public ICommand RemovePaidInvoices
    {
        get
        {
            return MakeCommand
                .When(() => _navigation.SelectedPaidInvoice != null)
                .Do(() =>
                {
                    _payment.RemovePaidInvoice(_navigation.SelectedPaidInvoice);
                    _navigation.SelectedUnpaidInvoice = _navigation.SelectedPaidInvoice;
                    _navigation.SelectedPaidInvoice = null;
                });
        }
    }
}


Step 5: Set the data context
Now comes the magic. At no point did you fire or register for PropertyChanged events. You just wrote code that was concerned with the rules of your business (data model) and your user interface (view model and navigation model). Update Controls will fire those PropertyChanged events for you, if you wrap your view model before giving it to the view:

Customer customer = new Customer();
// Load the customer from the web service.

Payment payment = new Payment(customer);
// Load the payment from the web service.

DataContext = ForView.Wrap(
    new PaymentViewModel(
        payment,
        new PaymentNavigationModel()));


Update Controls makes it much easier to create line-of-business applications in Silverlight 3, because you don't have to deal with INotifyPropertyChanged.