MVC without eventing

Jonathan Rasmusson, The Agile Warrior, posted a Silverlight MVC example that clearly demonstrates how eventing can get out of hand. He concludes:

Phew! That’s a lot of work to update one textbox based on the contents of another…

imageIt would be less work to put this logic in the View, but Model/View separation is his goal. In order to achieve that, you need to wire up a bunch of events.

Or do you?

Consider how you would solve this problem with a spreadsheet. You would enter a formula in the Calculated Taxes cell. You don’t store the value in the cell. You don’t notify anybody that the value has changed. It just changes. Can we do this in code, and still retain Model/View separation?

Of course, the answer is yes … if you use Update Controls.

I’m going to modify Jonathan’s example code so you can see how much code you don’t have to write. Go to his blog to get the starting point. Then add a reference to UpdateControls.Light.dll and follow along. If you get stuck, download the modified source files here.

The Model

Remove the event declaration from the model. In addition, remove the setter from the calculated Taxes property. This property should be read-only, since its value depends upon Salary.

public interface ICalculatorModel
{
    double Salary { get; set; }
    double Taxes { get; /*set;*/ }

    /*event EventHandler<CalculatorEventArgs> TaxesChanged;*/
}

Make the same changes in the CalculatorModel class. Move the tax calculation down to the Taxes getter and get rid of the field. Add an Independent sentry and call OnGet() and OnSet() whenever Salary is modified or accessed.

public class CalculatorModel : ICalculatorModel
{
    private double salary;
    private Independent _indSalary = new Independent();
    /*private double taxes;*/
    public readonly double TAX_RATE = 0.3d;

    public double Salary
    {
        get { _indSalary.OnGet(); return salary; }
        set
        {
            if (value == this.salary) return;
            _indSalary.OnSet();
            salary = value;
            /*Taxes = salary*TAX_RATE;*/
        }
    }

    public double Taxes
    {
        get { return /*taxes*/ Salary*TAX_RATE; }
        /*
        set
        {
            if (value == this.taxes) return;
            taxes = value;
            OnTaxesChanged(new CalculatorEventArgs(this));
        }
        */
    }

    /*
    public event EventHandler<CalculatorEventArgs> TaxesChanged;

    protected void OnTaxesChanged(CalculatorEventArgs e)
    {
        if (TaxesChanged != null)
            TaxesChanged(this, e); // fire event
    }
    */
}

The View

The view is going to use data binding. Delete all of the code in the code-behind except for the constructor. Then add Binding markup to the XAML:

public partial class CalculatorView : Page
{
    /*
    private ICalculatorModel model;
    private ICalculatorController controller;
     * */

    public CalculatorView()
    {
        InitializeComponent();
    }

    /*
    public void SetModel(ICalculatorModel calculatorModel)
    {
        model = calculatorModel;
        SalaryTextBox.TextChanged += SalaryTextBoxChanged;
        model.TaxesChanged += ModelTaxesChanged;
    }

    private void ModelTaxesChanged(object sender, CalculatorEventArgs e)
    {
        TaxesTextBox.Text = e.Taxes.ToString();
    }

    private void SalaryTextBoxChanged(object sender, TextChangedEventArgs e)
    {
        controller.SetSalary(Double.Parse(SalaryTextBox.Text));
    }

    public void SetController(ICalculatorController calculatorController)
    {
        controller = calculatorController;
    }
    */
}
<navigation:Page x:Class="MVC1.View.CalculatorView" 
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
           mc:Ignorable="d"
           xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
           d:DesignWidth="640" d:DesignHeight="480"
           Title="CalculatorView Page">
    <Grid x:Name="LayoutRoot">
        <ListBox Grid.Row="1">
            <TextBlock Text="Enter salary:" />
            <TextBox Name="SalaryTextBox" Width="100" Text="{Binding Salary, Mode=TwoWay}" />
            <TextBlock Text="Calculated Taxes:" />
            <TextBox Name="TaxesTextBox" Width="100" Text="{Binding Taxes}" />
        </ListBox>
    </Grid>
</navigation:Page>

The Controller

The controller gives the model to the view, and then gets out of the way. Let’s modify the interface to reflect that responsibility.

public interface ICalculatorController
{
    /*
    void SetSalary(double salary);
    void SetTaxes(double taxes);
    */
    void Bind();
}

As it binds, it needs to wrap that model so Update Controls can discover dependencies for its properties.

public class CalculatorController : ICalculatorController
{
    private CalculatorView view;
    private ICalculatorModel model;

    public CalculatorController(CalculatorView view, ICalculatorModel model)
    {
        this.view = view;
        this.model = model;

        /*
        view.SetModel(model);
        view.SetController(this);
        */
    }

    /*
    public void SetSalary(double salary)
    {
        model.Salary = salary;
    }

    public void SetTaxes(double taxes)
    {
        model.Taxes = taxes;
    }
    */

    public void Bind()
    {
        view.DataContext = ForView.Wrap(model);
    }
}

Now call the Bind method.

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        ICalculatorModel model = new CalculatorModel();
        ICalculatorController controller = new CalculatorController(view, model);
        controller.Bind();
    }
}

The application still works, but we’ve deleted all of the eventing code.

Update on every keystroke

WPF lets you set UpdateSourceTrigger=PropertyChanged in order to update on every keystroke. Silverlight doesn’t have that feature, yet. So to make up for it, we need a little code behind.

<TextBox Name="SalaryTextBox" Width="100" Text="{Binding Salary, Mode=TwoWay}" TextChanged="SalaryTextBoxChanged" />

private void SalaryTextBoxChanged(object sender, TextChangedEventArgs e)
{
    ((TextBox)sender).GetBindingExpression(TextBox.TextProperty).UpdateSource();
}

Now it works exactly like Jonathan’s example.

Model/View separation is essential to making software easy to maintain. But If you have to add a bunch of event handlers in order to achieve that separation, you end up paying far to high a cost. Update Controls discovers dependencies for you so you don’t have to fire events. It helps you to stay truly agile.

Comments

Good stuff!

Hey Michael,

Just quickly browsed your example and I must say, this looks like a much clear implementation of MVC.

I love how you've gotten rid of most of the eventing.
Bind is the next thing I want to try (looks like it really cleans things up).

I am going to go over this more closely on the weekend to see how this all hooks together.

Great example. Thanks for sharing.

Jonathan Rasmusson
agilewarrior.wordpress.com