April 2013
Volume 28 Number 04
MVVM - Maximizing the Visual Designer’s Usage with Design-Time Data
By Laurent Bugnion | April 2013
In the two previous installments of this series about MVVM and the MVVM Light Toolkit, I talked about architectural considerations and organizing your application to benefit from the advantages of decoupled layers.
In this installment, I’ll explore another big advantage of the MVVM pattern: using design-time data to facilitate the work in a visual designer tool. Currently, two visual designers can be used with Windows 8 XAML applications: the Visual Studio designer (integrated in Visual Studio) and Blend (a standalone application).
In Visual Studio 2012, the visual designer is using parts of the Blend application directly integrated in the IDE. This integration is the reason most of what Blend offers is also possible in the Visual Studio designer. Some functionalities, such as creating animations or editing visual states, are possible only in the standalone Blend application. You can also write all these features directly in XAML. One of the nice advantages of XAML over classic frameworks such as Windows Forms is that every attribute of every UI element is visible in the XAML markup, which means you can choose the tool you prefer, either to visually edit the UI or to type XAML. Most XAML experts use a combination of both methods to achieve the visual effects they want to create.
One of the huge benefits of a visual designer is that it displays a true visual representation of how the application is going to look at run time. Not every piece of data is immediately visible, however. Only the static content is shown by default. The dynamic data that is loaded at run time from the Web, from a database or from any other source doesn’t appear during the design phase. At design time, loading this data can be very difficult or even impossible. The visual designer tool has limited access to Web services; it can’t connect to databases; and it can’t authenticate. Given these constraints, much of the dynamic data access is going to fail at design time.
The traditional approach to solving this problem is to perform what I call the “run-measure-edit” loop. Here’s what’s involved: Run the application and navigate to the page you’re currently editing (this process can be time consuming and repetitive, and it can include authentication and data entry); measure the UI elements (for example, colors, sizes and margins); and edit the XAML file to modify the UI according to the comps.
Note In designer language, comps (or design compositions) are a static view of the finished application UI as it will be rendered at run time. Comps are often created in Adobe Illustrator or Photoshop.
This process is sometimes unavoidable, especially in the last stages of application development. But there is a way you can simulate your nonstatic data during design time: you just need to trick the visual designer into executing some design-time-only code.
Design-Time Data in the MVVM Light Toolkit
There are multiple ways to create design-time data in XAML applications. In Windows Presentation Foundation, Silverlight and Windows Phone, Blend offers a way to create design-time data with the Data tab and the New Sample Data menu, as shown in Figure 1. This way has a few disadvantages, however, notably that it requires a lot of work to make the design data look like the production data. In addition, at the time I wrote this article, the Data tab and the design-time data feature were not available for Windows 8.
Figure 1 New Sample Data menu in the Blend Data tab (not available for Windows 8 apps)
Arguably, a better way is to use a design-time data service to create and populate instances of the real data classes with design-time content. MVVM Light offers this solution out of the box.
To visualize how this works, let’s create a new Windows 8 MVVM Light application in Visual Studio 2012:
- Open Visual Studio 2012 in Windows 8, and then select File|New|Project.
- In the New Project dialog box, select the Windows Store category. (You’ll see this category only in Visual Studio 2012 running on Windows 8.)
- Select the MvvmLight (Win8) project type.
- Create the project.
- Run the application. You should see a TextBlock showing the text “Welcome to MVVM Light.”
- In Solution Explorer, right-click on MainPage.xaml and select Open in Blend from the context menu.
In Blend, you should now see the design view of your page, and the TextBlock should show the text “Welcome to MVVM Light [design].” A few important things are happening here.
First, you’re discovering that Blend is able to run code from your application. It isn’t just a static design tool! You still need to deal with a few issues, however. In Blend 4 and 5, fewer operations cause Blend to fail to render the UI. But some operations (for example, certain complex multithreaded operations and operations that use a path relative to the application’s assembly) can cause an inner exception. Blend wouldn’t crash, but it wouldn’t be able to render the XAML. In the best case, a blank page would be shown, sometimes including the static UI elements. In the worst case, an error screen would be rendered instead.
To render as much of the UI as possible at design time, you need to be able to identify within the application’s code whether the code is running in a visual designer. Unfortunately, each of the XAML-based frameworks uses a slightly different syntax to detect the presence of a visual designer. In Windows 8, for example, the code below can be used.
if (DesignMode.DesignModeEnabled)
{
// This code runs in the visual designer and Blend
}
To make things easier and to allow sharing code between the XAML frameworks, the MVVM Light Toolkit offers a unified API to detect design-time operations. The IsInDesignMode property is part of the ViewModelBase class, which can be used as the basis class for most ViewModels in the application. This class also implements the INotifyPropertyChanged interface and provides a few different ways to raise the PropertyChanged event, which is crucial for updating data bindings when the value of the property is modified.
In addition to being exposed as a protected property within the ViewModelBase class (and thus accessible to all classes deriving from it), the value of IsInDesignMode is also exposed in a public static property of that class. Because of naming conflicts, it isn’t possible to give the same name to a static property and a nonstatic property within the same class, so the static property is named IsInDesignModeStatic. This property allows developers of MVVM Light–based applications to write the code shown below in any of the supported XAML-based frameworks with the exact same syntax.
public class DemoViewModel : ViewModelBase
{
public void DoSomething()
{
if (IsInDesignMode)
{
// This code runs in the visual designer and Blend
}
}
}
public class NotViewModel
{
public void DoSomethingElse()
{
if (ViewModelBase.IsInDesignModeStatic)
{
// This code runs in the visual designer and Blend
}
}
}
Once you’re able to detect whether the application code is in design mode or runtime mode, you can provide different code to be executed at design time and create design-time data.
In the first article in this series, I talked about inversion of control (IOC) containers and dependency injection in the context of an MVVM application. Dependency injection is the perfect tool for providing different code to be executed at design time and creating design-time data. Once you clearly define the responsibilities of the DataService in the IDataService interface (in the Model folder), you can create a design-time IDataService implementation and initialize the IOC container with this implementation when the application runs in design mode, as shown in the code below. This code is visible in the MVVM Light application created earlier (just below Figure 1), in the ViewModelLocator’s static constructor (in the ViewModel folder).
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IDataService,
Design.DesignDataService>();
}
else
{
SimpleIoc.Default.Register<IDataService, DataService>();
}
SimpleIoc.Default.Register<MainViewModel>();
}
The design-time DataService is cleanly separated from the rest of the code, in its own source file, and, as you’ll see a little later in this article, you can exclude this source file from the Release build of your application. As a consequence, the number of classes, methods and properties having to know about the design-time mode is reduced, which means that you can create cleaner and less complex code.
Applied to the RssReader application created in the first article in this series and extended in the second (and included with the sample code for this article), this technique works great:
- The design-time data is created in the DesignRssService class located in the Design folder, as shown below.
- This DesignRssService is registered with the SimpleIoc at design time, in the ViewModelLocator class.
public class DesignRssService : IRssService
{
public Task<IList<RssArticle>> GetArticles()
{
var result = new List<RssArticle>();
for (var index = 0; index < 15; index++)
{
result.Add(GetArticle(index));
}
return Task.FromResult<IList<RssArticle>>(result);
}
internal static RssArticle GetArticle(int index)
{
return new RssArticle
{
Title = string.Format(
"[insert design time title here for item # {0}]",
index),
Summary = "[insert design time summary here]",
};
}
}
At run time, the service provides data when the user taps a Refresh button. You can simulate this at design time by calling the Refresh method explicitly in the MainViewModel constructor as shown below. This call is only done at design time (thanks to the IsInDesignMode property), and is enclosed in a “#if DEBUG” directive to make sure that it is only included in the DEBUG version of the application, and not in the RELEASE version.
public MainViewModel(
IRssService rssService,
INavigationService navigationService)
{
_rssService = rssService;
_navigationService = navigationService;
Items = new ObservableCollection<RssArticle>();
#if DEBUG
if (IsInDesignMode)
{
Refresh();
}
#endif
}
You can use this technique in several manners, for example, to test various ways of clipping or trimming an extra-long title in the UI, as shown in Figure 2. The Windows 8 app’s MainPage is shown in Blend. You could achieve the same result in the Visual Studio designer. You can also use this technique in all XAML-based frameworks, such as in Windows Phone. Using the Device tab of Blend, you can even simulate the device’s orientation or (for Windows 8) the Snapped view.
Figure 2 Visualizing the MainPage’s UI in Blend
Using Other Design-Time Techniques
The technique just described works great when the DataContext is bound to a View in XAML directly. Blend will create and set the DataContext when the XAML is parsed, and everything will be wired as planned. However, the DataContext is often set in code-behind. Blend doesn’t execute code-behind: the DataContext will remain empty and the data bindings won’t return any data. In this case, all the dynamic content of a page would remain blank.
There is a way to request Blend to use a different DataContext object in design mode, however: the d:DataContext attribute. Let’s take a closer look at XAML to understand how this works. As you know, XAML is short for eXtensible Application Markup Language. The “eXtensible” part means that it is possible to extend the default XML schema with custom attributes. To use the d:DataContext attribute, you first need to tell the XAML parser where this attribute comes from. To do that, you add a new XMLNS (XML NameSpace) attribute, named“xmlns:d,” to the Page’s XAML. You point it to the URI “https://schemas.microsoft.com/expression/blend/2008.” This URI can’t be entered into a Web browser—it’s a unique identifier for the XAML parser.
Because the attributes from the “d” namespace are for design time only, the runtime XAML parser should ignore them. Blend will look for these attributes and modify the design view accordingly. To instruct the XAML parser to ignore a namespace, you use the mc:Ignorable attribute from the “https://schemas.openxmlformats.org/markup-compatibility/2006” namespace. The code below shows a Page element with the attributes mentioned.
<Page x:Class="MvvmLight1.BlankPage1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MvvmLight1"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
</Page>
Note In Windows 8, the MVVM Light project template uses a class deriving from the standard Page class in Windows 8. To add a LayoutAwarePage to your application, right-click on the project (or folder) in which this item should be added. Then select Add|New Item from the context menu. In the Add New Item dialog box, select Basic Page (as opposed to the Blank Page item, which is the default empty Page element). Adding a new LayoutAwarePage to the project will cause Visual Studio to include a few helper files in the Common folder. When you create a new MVVM Light Windows 8 application, however, these files are already included. The LayoutAwarePage is more flexible, especially when it comes to visual states such as the Snapped, Portrait and Landscape views.
In the RssReader application, the DetailsPage gets its DataContext set in the code-behind (in DetailsPage.xaml.cs), in the OnNavigatedTo method. To visually design this page, you can use the d:DataContext attribute as shown in in the code snippet below: set to use the first element in the Items collection in the MainViewModel and accessed through the ViewModelLocator resource. These items are created by the DesignRssService class as explained earlier in this article.
<common:LayoutAwarePage
x:Name="pageRoot"
x:Class="RssReader.DetailsPage"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:common="using:RssReader.Common"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{Binding Main.Items[0], Source={StaticResourceLocator}}">
<!--...-->
</common:LayoutAwarePage>
Another technique that yields the same result is to create a different instance of an RssArticle at design time and to use this instance as d:DataContext. To do this, you modify the markup as shown below. Notice the usage of the d:DesignInstance element, which forces the creation of a new RssArticle used only at design time. Also notice that the “vm” namespace has been added with a corresponding xmlns attribute.
<common:LayoutAwarePage
x:Name="pageRoot"
x:Class="RssReader.DetailsPage"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:common="using:RssReader.Common"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:RssReader.Model"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:RssArticle,IsDesignTimeCreatable=True}">
<!--...-->
</common:LayoutAwarePage>
Because the XAML parser creates the visual tree and all its elements by deserializing the XAML markup, the parser will use the default constructor of the RssArticle for this purpose. If the RssArticle class had multiple constructors, you would need to create the design-time data in the default constructor. The code below shows how you can add an explicit constructor to the RssArticle class.
public RssArticle()
{
#if DEBUG
if (ViewModelBase.IsInDesignModeStatic)
{
Title = "[insert design time title here]";
Summary = "[insert design time summary here]";
}
#endif
}
Using the same values for Title and Summary as shown earlier (to test the UI with a very long title), you can get the design-time experience shown in Figure 3 for Windows 8 and for Windows Phone.
Figure 3 Design-time view for the RssReader’s DetailsPage
Excluding Design-Time Data from Production Builds
When handled carefully, the design-time code isn’t harmful to production applications (because it isn’t executed at run time), but excluding this code and all the corresponding assets (such as images) from the production build is still preferable.
To do that, you need to modify the CSPROJ file manually with the following steps. There’s no other way to do this in the current version of Visual Studio 2012.
- Right-click on the project you want to edit in Solution Explorer.
- From the context menu, select Unload project.
- Right-click on the same project, and select Edit [project name].
- Locate the files you want to exclude, and for each file, add a Condition to the XML element in the CSPROJ file. For example, the DesignFriendsService can be excluded in Release mode with the code shown below.
- Close the CSPROJ file.
- Right-click on the unloaded project in Solution Explorer, and select Reload Project.
<Compile Include="Design\DesignRssService.cs"
Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "/>
This condition can be set on all the design-time files, including pictures and other assets. The excluded items will still be visible in Solution Explorer, but they won’t be included in the final binary. In fact, if you build the solution after excluding the DesignRssService, you’ll get a compilation error in the ViewModelLocator. You must enclose the lines of code that use the DesignRssService in precompiler directives as shown here:
if (ViewModelBase.IsInDesignModeStatic)
{
#if DEBUG
SimpleIoc.Default.Register<IRssService,
Design.DesignRssService>();
#endif
}
else
{
SimpleIoc.Default.Register<IRssService,
RssService>();
}
Wrapping Up
The value of design-time data is tremendous when you’re trying to reach maximum fidelity toward the design composition of your application. Even though creating and maintaining the design-time data takes some time and effort, the integrator who converts the design in XAML gets a huge gain in terms of productivity. As with every programming technique, you must be pragmatic: no application is ever 100 percent designable in Blend. But the techniques described in this article will help you get the most out of using Blend and the Visual Studio designer.
References
The MVVM Light Toolkit is available at mvvmlight.codeplex.com.
Laurent Bugnion is senior director for IdentityMine Inc., a Microsoft partner working with technologies such as Windows Presentation Foundation, Silverlight, Pixelsense, Kinect, Windows 8, Windows Phone and UX. He’s based in Zurich, Switzerland. He is also a Microsoft MVP and a Microsoft Regional Director.
Thanks to the following technical experts for reviewing this article:
Karl Erickson has been involved in XAML developer education for the past six years, working with WPF, Silverlight, Windows Phone and Windows 8. His most recent project is the Reversi XAML/C# sample board game app.