2

I have an suite of existing Silverlight applications using the MVVM pattern to separate Views and ViewModels. We use Unity 2.0 for an IoC container to inject dependencies into the ViewModel classes (and supporting types). I have an existing ViewModelLocator class that uses the Unity Container to resolve the ViewModel.

All of this is working great at runtime; however, because the ViewModelLocator relies on the Unity Container being created and configured by a Bootstrapper class that is "Run" from the Application_Start method in App.xaml.cs, I've lost the ability to open the views in the designer or in Blend.

I am looking for suggestions how I can rework the ViewModelLocator to support "Blendability".

Note that I'm not willing to force our ViewModel classes to implement default parameterless constructors just for the sake of Blendability. We also have our ViewModels check the IsInDesignMode property (from the MVVM Light ViewModelBase class) to supply design-time data versus making service calls so we don't have different ViewModel implementations for design-time and run-time.

Let me know what you think.

SonOfPirate
  • 5,642
  • 3
  • 41
  • 97

1 Answers1

2

You want UI elements to have a good design-time experience (including in Blend). That sounds like a reasonable goal.

Let’s look at what a UI element is. For the rest of this answer I’m going to call it a Control. The responsibility of a Control is to render UI and respond to user events. Insofar as it should have behavior, it should only have behavior that relates to UI rendering and user events.

In addition to that, the framework itself (Silverlight as well as WPF) imposes a rule: all Controls must have a default constructor. Furthermore, since the DataContext is a property, it’s optional to assign it.

We should keep encapsulation in mind. Any Control we develop should work nicely within the constraints given above. This means that, apart from having a default constructor, it should also work correctly without a DataContext. In other words, the Blendability experience should be supplied by the Control itself, not any external container that needs to be bootstrapped to assign a DataContext.

When a Control must respond to user events I’ve always found the ICommand interface more than sufficient. Attach ICommands to any applicable event handler in the Control. The ICommands are defined by the View Model, but the beauty of ICommand is that it’s basically a void method which means that it’s trivial to supply a (no-op) Local Default in the case where the DataContext is null. However, that’s rarely necessary, as the Commands aren’t invoked by designers.


Here's an example from my book:

<Window x:Class="Ploeh.Samples.ProductManagement.WpfClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Product Management"
        Height="300"
        Width="300"
        MinHeight="300"
        MinWidth="300">
    <Window.Resources>
        <Style x:Key="ProductStyle" TargetType="{x:Type ListViewItem}">
            <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
        </Style>
    </Window.Resources>
    <DockPanel FocusManager.FocusedElement="{Binding ElementName=productsListView}">
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="_File">
                <Separator />
                <MenuItem Header="E_xit" Command="{Binding Path=CloseCommand}" />
            </MenuItem>
            <MenuItem Header="_Actions">
                <MenuItem Header="_Refresh" InputGestureText="F5" Command="{Binding Path=RefreshCommand}" />
                <MenuItem Header="_Add Product" InputGestureText="Ins" Command="{Binding Path=InsertProductCommand}" />
                <MenuItem Header="_Edit Product" InputGestureText="Enter" Command="{Binding Path=EditProductCommand}" />
                <MenuItem Header="_Delete Product" InputGestureText="Del" Command="{Binding Path=DeleteProductCommand}" />
            </MenuItem>
        </Menu>
        <ToolBarTray DockPanel.Dock="Top" HorizontalAlignment="Stretch">
            <ToolBar HorizontalAlignment="Stretch" HorizontalContentAlignment="Left">
                <Button Command="{Binding Path=RefreshCommand}">Refresh</Button>
                <Button Command="{Binding Path=InsertProductCommand}">Add</Button>
                <Button Command="{Binding Path=EditProductCommand}">Edit</Button>
                <Button Command="{Binding Path=DeleteProductCommand}">Delete</Button>
            </ToolBar>
        </ToolBarTray>
        <ListView x:Name="productsListView" ItemContainerStyle="{StaticResource ProductStyle}" ItemsSource="{Binding Path=Products}" SelectionMode="Single">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Id" DisplayMemberBinding="{Binding Path=Id}" />
                    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}" />
                    <GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=UnitPrice}" />
                </GridView>
            </ListView.View>
        </ListView>
    </DockPanel>
</Window>

and the code-behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        this.InitializeComponent();
    }
}
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • While it's a great and correct answer - I use the technique myself - I think it'd be even better with a short-ish snippet explaining in code what you mean in more detail, or a link to where it's done. – Johann Gerell Sep 01 '11 at 09:00
  • While this is great, the point of supplying design-time data is so the control will render in the designer in a way that is similar to how it will appear at runtime. This allows designers to provide styles in a more accurate way as grids, for example, will have rows that are rendered as they will be at runtime. Without design-time data, the grid will render, columns will appear, but there will be no data (ergo, no rows). Not only will this affect designing the control but could also affect layout as UI elements flow based on their contents. I appreciate the detailed response, though. – SonOfPirate Sep 01 '11 at 11:38
  • No problem, just create an internal class with hard-coded sample data and attach it to the `DataContext` property from the constructor. It can always be overwritten in the fully composed application by assigning the `DataContext` from the outside, effectively overwriting the Local Default. – Mark Seemann Sep 01 '11 at 11:44
  • That could be done but it doesn't seem very MVVM-ish and requires that developers create two classes for every view model which, as I stated, goes against our design approach. Not saying we won't change this, just pursuing all options before making such a decision. – SonOfPirate Sep 01 '11 at 12:05
  • It's the responsibility of the Control to be Blendable - not any other class' responsibility, so I don't see why it's not MVVM-ish. Yes, you'll have to create two classes instead of one, but think of all the other crappy infrastructure code you don't have to write to support Blendability :) – Mark Seemann Sep 01 '11 at 12:11