Master-Detail Forms & Object Scope (Context)

, , ,

Imagine a master-detail form. The top portion of the window contains a grid listing people’s names. Below is a form where the currently-selected person’s details may be edited. Should data-binding be used to connect the edit form’s fields directly to the grid’s currently selected record (row)? To put it another way, should the grid’s selected item and the edit form both be referencing the same in-memory object instance?

To find out, let’s build an imaginary application using C# .Net/WPF….

The Code

The grid is a DataGrid control whose ItemsSource property is populated with the collection of objects to display. The grid’s SelectedItem property is data-bound to a view model property named SelectedPerson. The edit form’s DataContext is bound to this same property. Whenever the grid’s selected row changes, this data-binding updates the edit form so that it displays the appropriate Person object’s details.

View Model

class PeopleViewModel : INotifyPropertyChanged
{
        // ....
        private Person selectedPerson;
        public Person SelectedPerson
        {
               get { return selectedPerson; }
               set {
                       selectedPerson = value;
                       NotifyOfPropertyChange("SelectedPerson");
               }
        }
}

View

<Window ...>
    <StackPanel>
        <DataGrid ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}" SelectionMode="Single" SelectionUnit="FullRow" />

        <StackPanel>
            <StackPanel DataContext="{Binding SelectedPerson}" Orientation="Horizontal">
                <Label>Name</Label>
                <TextBox Text="{Binding Name,UpdateSourceTrigger=PropertyChanged}" Width="125" />
                <!-- other fields.... -->
            </StackPanel>

            <Button Command="{Binding SaveCommand}">Save</Button>
        </StackPanel>
    </StackPanel>
</Window>

The Problem Exposed

When we run the application, the grid displays a list of people. Select a grid row and the relevant details appear in the edit form. So far, so good! Now, use the edit form. As soon as a change is made in one of the fields, the edit appears in the data grid. Wait a minute! We haven’t clicked “save” yet!

What’s going on? Both the data grid’s selected row and the edit form are data-bound to the same object. As soon as the value in an edit field is changed, the control’s data-binding passes the modification to the bound object. The grid is then informed of this change and immediately updates its display to reflect it.

While this makes sense, it is not what we want. We don’t want a real-time connection between the object collection displayed on the grid and the object being edited by the edit form. Instead, changes between these two contexts should be synchronized only when “save” is clicked. How do we make this happen?

Scoping the Object to a Context

Let’s bind the edit form to a separate view model property—say EditPerson. When the grid’s row selection (bound to SelectedPerson) changes, we’ll have the view model trigger logic that creates a new Person instance based off of SelectedPerson. EditPerson will then be set to point to this new instance. Clicking “save” will trigger logic that synchronizes  EditPerson with SelectedPerson.

We’re now using multiple Person instances, each within a different scope (or context), to represent the same logical person entity. The edit form is still bound to a Person instance. However, since only the edit form references that particular instance (i.e. it is scoped to the edit form), the rest of the application (e.g. the grid) doesn’t know about modifications made to it until synchronization occurs (triggered by clicking “save”).

For more discussion on object scope/context and a database-powered example implementation, check out MSDN Magazine’s Building a Desktop To-Do Application with NHibernate by Ayende Rahien (a.k.a. Oren Eini).

Leave a Reply

Your email address will not be published. Required fields are marked *