Home > Articles > Programming > ASP .NET

  • Print
  • + Share This
This chapter is from the book

Binding the View Model to the View

Binding the view model to the view is one of the key problems many MVVM frameworks try to solve. It can be done various ways, and there is no preferred method; otherwise, all experienced developers would be doing it the same way. The most popular method seems to be using a view model locator, whereas some people prefer to spin up controllers that perform the binding. My preferred method is to use the built-in design-time view model functionality provided by Silverlight using the design-time extensions. With Silverlight 5, it is also possible to use custom markup extensions.

In this section, you explore various methods to bind the view model to the view, and you can decide which method makes the most sense for your applications. The view model in each case processes a selection from a drop-down and render a shape.

View Model Locators

View model locators are simply classes that expose the view models as properties—typically as interfaces. The locator can determine the state of the application and return the appropriate view model based on whether it is called during design time or runtime. To see how a view model locator works, create a new Silverlight Application project called ViewModelLocators. Specify a grid with four equally sized quadrants (two rows and two columns). Provide an interface for the view model:

public interface IViewModelInterface
{
    List<string> Shapes { get; }
    string SelectedShape { get; }
}

Now implement a design-time view model:

public class DesignViewModel : IViewModelInterface
{
    public List<string> Shapes
    {
        get
        {
            return new List<string>
                        {"Circle", "Square", "Rectangle"};
        }
    }

    public string SelectedShape
    {
        get { return "Circle"; }
    }
}

Create the runtime view model. It will implement the property-change notification interface and provide a delegate for transitioning the visual state. The code for the view model is shown in Listing 7.1.

Listing 7.1. The Main View Model

public class ViewModel : IViewModelInterface, INotifyPropertyChanged
{
    public ViewModel()
    {
        GoToVisualState = state => { };
    }

    private readonly List<string> _shapes
        = new List<string>
                {
                    "Circle",
                    "Square",
                    "Rectangle"
                };

    public List<string> Shapes
    {
        get { return _shapes; }
    }

    public Action<string> GoToVisualState { get; set; }

    private string _selectedShape;
    public string SelectedShape
    {
        get { return _selectedShape; }
        set
        {
            _selectedShape = value;
            var handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs("SelectedShape"));
            }

            if (!string.IsNullOrEmpty(value))
            {
                GoToVisualState(value);
            }

        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Now you can create the ViewModelLocator class. The locator will simply determine whether it is being called during design time or runtime and return the appropriate view model.

public class ViewModelLocator
{
    private IViewModelInterface _viewModel;

    public IViewModelInterface ViewModel
    {
        get
        {
            return _viewModel ??
                    (_viewModel = DesignerProperties.IsInDesignTool
                                        ? (IViewModelInterface)
                                        new DesignViewModel()
                                        : new ViewModel());
        }
    }
}

Now you can create a control to implement the behavior. The control will include the view model locator in its resources (the locator is typically declared at the App.xaml level to make it available to the entire application). Add a new control called LocatorControl.xaml and fill it with the following Xaml:

<UserControl.Resources>
    <ViewModelLocators:ViewModelLocator x:Key="Locator"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White"
        DataContext="{Binding Source={StaticResource Locator},
Path=ViewModel}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <ComboBox ItemsSource="{Binding Shapes}"
                SelectedItem="{Binding SelectedShape, Mode=TwoWay}"/>
</Grid>

Now define the shapes and place them in the second row of the LocatorControl:

<Ellipse x:Name="CircleShape"
    Width="50" Height="50" Fill="Green" Grid.Row="1"/>
<Rectangle x:Name="SquareShape"
    Width="50" Height="50" Fill="Blue" Grid.Row="1"/>
<Rectangle x:Name="RectangleShape"
    Width="100" Height="50" Fill="Red" Grid.Row="1"/>

Add the visual state groups. Part of the Xaml for the first state is shown in the following code. Repeat the ObjectAnimationUsingKeyFrames for the square and the rectangle, but set their visibility to the collapsed state. Next, copy and paste the visual state twice more for the square state and the rectangle state and then update which shapes are visible and which shapes are not.

<VisualStateManager.VisualStateGroups>
   <VisualStateGroup x:Name="ShapeGroups">
      <VisualStateGroup.States>
         <VisualState x:Name="Circle">
            <Storyboard>
               <ObjectAnimationUsingKeyFrames
                   Storyboard.TargetName="CircleShape"
                   Storyboard.TargetProperty="(UIElement.Visibility)">
                  <DiscreteObjectKeyFrame KeyTime="0:0:0">
                     <DiscreteObjectKeyFrame.Value>
                        <Visibility>Visible</Visibility>
                     </DiscreteObjectKeyFrame.Value>
                  </DiscreteObjectKeyFrame>
               </ObjectAnimationUsingKeyFrames>
            </Storyboard>
         </VisualState>
      </VisualStateGroup.States>
   </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Set the locator control to navigate to a default visual state and set the delegate for transition states. This is done in the control code-behind:

public LocatorControl()
{
    InitializeComponent();

    if (DesignerProperties.IsInDesignTool) return;

    VisualStateManager.GoToState(this, "Circle", false);

    var locator = (ViewModelLocator) Resources["Locator"];
    var vm = locator.ViewModel as ViewModel;
    vm.GoToVisualState =
        state => VisualStateManager.GoToState(this, state, true);
}

The binding to the view model is the side effect of using the locator. You want to change the visual states from the view model, but the UserControl implements the functionality. Because of the context it is used in, it is not possible to get access to the host control and automatically bind the delegate. This must be done in the control itself by passing a delegate for the GoToState method to the view model.

Now you can declare an instance of the control in the main page in the upper-left quadrant and run the application. The shapes will swap out each time you select a new control. You will also see values in the combo box during design time because the view model locator returns the design-time view model for you.

Controllers

One of the less-common methods for binding the view model to the view is by using a controller. Contrary to popular belief, there is no law that MVVM cannot be mixed with other patterns. The controller pattern involves creating a class specifically tied to the view it is managing and then using the controller to spin up the view model and wire the logic.

First, create a new control called ControllerControl.xaml and copy the contents of the locator control. Remove the resources collection that references the view model locator, and remove the data context attribute on the grid that uses the locator resource. Next, create a new class called Controller and provide it with a constructor, like this:

public class Controller
{
    private readonly IViewModelInterface _vm;

    public Controller(Control control)
    {
        if (DesignerProperties.IsInDesignTool)
        {
            _vm = new DesignViewModel();
        }
        else
        {
            _vm = new ViewModel
                        {
                          GoToVisualState =
                             state => VisualStateManager.GoToState(
                                 control, state, true)
                        };
            VisualStateManager.GoToState(
                control, "Circle", false);
        }
        control.DataContext = _vm;
    }
}

Note that for testing, the element passed in would normally be an interface that includes the data context element. This would allow the controller to be tested without passing an actual control, as is done here to keep the example simple. Now that the controller is defined, it simply needs to be created in the code-behind of the control:

public partial class ControllerControl
{
    public Controller Controller { get; set; }

    public ControllerControl()
    {
        InitializeComponent();
        Controller = new Controller(this);
    }
}

Create an instance of the control in the upper-right quadrant of the main page and compile the application. You’ll see design-time data in the main page, and when you run the application you’ll be able to swap the shapes in the control in the upper right.

Design-Time View Models

My favorite method to use because it works directly with the attributes supplied by the runtime is the use of design-time view models. In this approach, the design-time data is supplied using the design-time extensions. The runtime binding is done using an external mechanism. You’ll learn a more elegant way to perform the binding in Chapter 8, “The Managed Extensibility Framework (MEF).” For now, a simple class will perform the necessary wiring.

Create a new control called DesignControl and copy the contents of the controller control (basically everything from the main grid down). Add the following attribute to the LayoutRoot grid to specify the design-time view model:

d:DataContext="{d:DesignInstance Type=ViewModelLocators:DesignViewModel, IsDesignTimeCreatable=True}"

Listing 7.2 shows what the binder might look like—although there is only one entry in the dictionary, you can see how it would be easy to add additional entries mapping the control type to the view type. The dictionary could also reference instances of the view models to share the same one between views, and the wiring of the delegate can be made generic by implementing a common view model interface that specifies the delegate.

Listing 7.2. A View Model Binder

public class Binder
{
    private readonly Dictionary<Type, Type> _bindings =
        new Dictionary<Type, Type>
            {
                { typeof(DesignControl), typeof(ViewModel) }
            };

    public void Bind(Control control)
    {
        if (!_bindings.ContainsKey(control.GetType()))
        {
            return;
        }

        var vm = Activator
            .CreateInstance(
                _bindings[control.GetType()]);

        ((ViewModel) vm).GoToVisualState =
            state => VisualStateManager
                            .GoToState(control, state, true);

        control.DataContext = vm;
    }
}

The control can then call the binder and set the default state, like this:

public DesignControl()
{
    InitializeComponent();
    new Binder().Bind(this);
    VisualStateManager.GoToState(this, "Circle", false);
}

Declare an instance of this control in the lower-left quadrant and run the application.

Custom Markup Extensions

Custom markup extensions can extend the Xaml model to include your own tags and properties. View model binding is a perfect example of how custom extensions can be used. First, create a control called MarkupControl and copy the content of the controller control (should be just the grid and so on, with no resources and no data context). In the code-behind, just set the default visual state:

public MarkupControl()
{
    InitializeComponent();
    VisualStateManager.GoToState(this, "Circle", false);
}

Now create a custom markup extension. You can revisit Chapter 3, “Extensible Application Markup Language (Xaml),” to learn more about how to create the extensions. This extension grabs the object root to get a reference to the control that is hosting it. See Listing 7.3 for the full extension. It spins up the view model based on the type of the control, binds the visual state transitions, and returns the view model (in design time, it just returns a new instance of the design view model).

Listing 7.3. A Custom Markup Extension for View Model Binding

public class ViewModelBinderExtension : MarkupExtension
{
    public override object ProvideValue(
        IServiceProvider serviceProvider)
    {
        var targetProvider
            = serviceProvider
                    .GetService(typeof (IRootObjectProvider))
                as IRootObjectProvider;

        if (targetProvider == null) return null;

        var targetControl = targetProvider.RootObject
                            as UserControl;

        if (targetControl == null) return null;

        if (targetControl is MarkupControl)
        {
            if (DesignerProperties.IsInDesignTool)
            {
                return new DesignViewModel();
            }

            var vm
                = new ViewModel
                        {
                            GoToVisualState =
                                state =>
                                VisualStateManager.GoToState(
                                    targetControl, state, true)
                        };


            return vm;
        }
        return null;
    }
}

With the extension created, you can add a namespace reference in the control:

xmlns:local="clr-namespace:ViewModelLocators"

Then set the data context of the grid using the extension. It will return the design-time view model in the designer and resolve the runtime view model when you run the application.

<Grid x:Name="LayoutRoot" Background="White"
        DataContext="{local:ViewModelBinder}">

Add the control to the lower-right quadrant of the main page and run the application. You will now have four separate controls that perform the same thing using the MVVM pattern. The view model is the same in each case; the only difference is how it is bound to the control.

View-Model-First Approach Versus View-First Approach

All of the examples in this chapter have focused on what is referred to as a view-first approach. The view is instantiated and then the view model is found and bound to the view. I believe this makes sense because most users operate by moving through screens and most developers comprehend their applications in terms of screens. It’s important to note that this is not the only way to manage the application.

Some frameworks such as Caliburn.Micro (http://caliburnmicro.codeplex.com/) follow what is known as a view-model-first approach. In this approach, the application logic spins up view models and the view models determine what views are provided. In a sense the binding is more straightforward because you can use a convention-based approach to map the view to the view model. (Convention-based simply means the bindings can be made based on how you name your classes, so a view is always named SomethingView and a view model is always named SomethingViewModel. Therefore, the Something in common creates the binding.)

The disadvantage to this approach is that it is often not design-time friendly. It is difficult to design the application in Visual Studio or Blend because the designer doesn’t know the right way to spin up views based on the view models. The designers are inherently view-first because you navigate to a Xaml view in order to render the view in the designer.

Although I strongly prefer the view-first approach, the view-model-first frameworks are very popular and have been used in many applications. Proponents of this approach highly prefer it over the view-first approach. It’s not a question of one approach being correct but more a style preference. It makes sense to follow the model that not only is easiest for you to understand, but will also be easy for your team to follow and maintain.

  • + Share This
  • 🔖 Save To Your Account

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020