In case you haven't added the word ‘Blendability' to your XAML Jargon yet, I’m sure this post will inspire you in doing so.
The Blend-ability term describes how a piece of data model or view model is viewable or designable at design time, whether by Expression Blend or Visual Studio Designer.
Building Silverlight or WPF applications, everybody loves using the MVVM pattern. This pattern greatly decouples the view from its logic and domain model, hence enabling relatively easy unit testing and provides great flexibility. In most cases, there should be no code behind XAML except for the InitializeComponent
s in the view’s ctor (in case of UserControl
). This very important aspect of the MVVM pattern should help the XAML designer in changing the view using any favorite tool, such as Blend, without being bothered about the view’s logic.
Let’s have a look at a little application which uses the MVVM pattern for displaying a simple view.
public class Camera2dInspectionViewModel : NotificationObject
{
public ObservableCollection<DefectViewModel> Defects { get; private set; }
public double Fps { get; set; }
public double Brightness { get; set; }
public ImageSource CurrentFrame { get; set; }
}
The code snippet above is the ViewModel
. It represents the view of a 2D camera inspection unit. For simplicity, it exposes only relevant properties to the relevant view.
Some of the properties are straightforward, such as Fps, and others represent collection: Defects.
Let’s take a look at the view itself.
<UserControl d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Image Source="{Binding CurrentFrame}" />
<ListBox ItemsSource="{Binding Defects}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="{Binding Zone.X}"
Y="{Binding Zone.Y}" />
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Run Text="Defect #" />
<Run Text="{Binding Id}" />
</TextBlock>
<Rectangle Width="{Binding Zone.Width}"
Height="{Binding Zone.Height}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Text="Brightness" />
<Slider Value="{Binding Brightness}" />
<TextBlock Text="{Binding Fps}" />
</Grid>
</UserControl>
For clarity, I’ve removed brushes and layout properties.
Now, there should be a piece of code gluing the View
with the ViewModel
by setting the View.DataContext
with the ViewModel
instance. There are several techniques for doing that: Hardcoded, MEF, Service Locator, Others.
No matter what your favorite technique is, both Expression Blend and Visual Studio are keen to locate the data-context object inside the XAML file at design time. Failure to do so results in null
data-context and no data being presented at design time.
How to Instantiate the view-model so it will be Rendered by the View at Design Time?
Well, we can create an instance of the view-model directly within the view’s XAML, but I’m sure that you agree that this is not a best practice and it is not possible in several circumstances such when the view-model has no default constructor and/or have to be instantiated using a DI container or should be created and populated by the business layer itself.
There are several ways of solving this problem. In this post, I’ll cover my favorite one using Design Time only DataContext
.
Working with Blend 3, 4 and VS2010, there is a magical designer property called d:DataContext
. This property is DataContext
for design time only. Using this property, you can keep setting the view’s regular DataContext
property with a view-model as usual and then set the view’s magical d:DataContext
designer property with a different design-time view-model instance created in XAML. Opening the view at design-time using Blend or Visual Studio, the d:DataContext
will take place. Running the application, the regular DataContext
will be used.
This solution is almost perfect. Now all we have to do is to generate a design-time view-model from within the XAML file.
Now let’s face it, no one, including the XAML designer likes generating fake data with bare hands, and here comes Blend to the rescue. There is a cool feature in Blend for generating data within XAML. There are several options of course, but one of them best fits our need. We’ll generate data based on our view-model class.
To do so, open the project with Blend, open the relevant view, go to data tab on the right, select project, click on the first small icon on the right and choose “Create Sample Data from class…”. Pick the relevant view-model and then click ok.
At this moment, Blend created a new folder called SampleData and a new XAML file.
If you open the XAML file created, you’ll find the following generated markup:
<Blendability_Modules_ViewModels:Camera2dInspectionViewModel
xmlns:Blendability_Modules_ViewModels="clr-namespace:Blendability.Modules.ViewModels"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Brightness="638.53"
Fps="990.7">
<Blendability_Modules_ViewModels:Camera2dInspectionViewModel.Defects>
<Blendability_Modules_ViewModels:DefectViewModel Id="63">
<Blendability_Modules_ViewModels:DefectViewModel.Zone>
<Rect Height="315.65" Width="181.51"
X="468.12" Y="991.84">
<Rect.Location>
<Point X="988.41" Y="166.96"/>
</Rect.Location>
<Rect.Size>
<Size Height="422.96"
Width="258.6"/>
</Rect.Size>
</Rect>
</Blendability_Modules_ViewModels:DefectViewModel.Zone>
</Blendability_Modules_ViewModels:DefectViewModel>
</Blendability_Modules_ViewModels:Camera2dInspectionViewModel.Defects>
</Blendability_Modules_ViewModels:Camera2dInspectionViewModel>
Amazingly Blend generated design-time instance from our view-model and populated it with auto-generated random data, no matter if the view-model has no default constructor, or in our case the Fps
property is read-only. Also it dug inside the view-model, generated all public
properties including collections. I had to cut some XAML for clarity.
Of course, I had to change the data and add missing properties such as CurrentFrame
, as nothing is perfect in this world especially software.
Here is the final result of the data I changed:
<vm:Camera2dInspectionViewModel
xmlns:vm="clr-namespace:Blendability.Modules.ViewModels"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Brightness="0.4"
Fps="29.7">
<vm:Camera2dInspectionViewModel.CurrentFrame>
<BitmapImage UriSource="/SampleData/Penguins.jpg" />
</vm:Camera2dInspectionViewModel.CurrentFrame>
<vm:Camera2dInspectionViewModel.Defects>
<vm:DefectViewModel Id="1">
<vm:DefectViewModel.Zone>
<Rect Height="40" Width="40" X="68" Y="15" />
</vm:DefectViewModel.Zone>
</vm:DefectViewModel>
<vm:DefectViewModel Id="2">
<vm:DefectViewModel.Zone>
<Rect Height="25" Width="25" X="180" Y="50" />
</vm:DefectViewModel.Zone>
</vm:DefectViewModel>
<vm:DefectViewModel Id="3">
<vm:DefectViewModel.Zone>
<Rect Height="50" Width="50" X="50" Y="90" />
</vm:DefectViewModel.Zone>
</vm:DefectViewModel>
</vm:Camera2dInspectionViewModel.Defects>
</vm:Camera2dInspectionViewModel>
Now that we have design-time only view-model, let's connect it with the view’s d:DataContext
by simply opening the view and drag the sample view-model from the Blend’s data tab into the view’s root element located in the Objects and Timeline tab on the left.
Hurrah… now you can be happy, watching the view rendering the sample view-model at design-time and you can now edit the style of your view’s controls, edit the data template of the Defects collection and see the big picture of your view without running the whole application again and again.
Please feel free to download the demo code from here.
In the next post, I’ll reveal my little research on Blendability of Prism Modules. This one is really a neat feature, so stay tuned.
CodeProject