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();
}
}