When you open a *.xaml file, VisualStudio automatically opens a Design view that’s powered by Expression Blend. The idea is to be able to preview/design your UI with XAML without ever having to run the app (or at least not run it as frequently). It’s a huge time saver, but unless your statically assigning values to your XAML components instead of binding to them, it’s not super intuitive to make Blend pull in sample data values at design time. The best documentation I could find for it was for Silverlight.
So let’s fill those GridViews with data! There are a few ways to go about it. If you were to create a new Windows Store app based on the Grid App template (Boot VS -> New Project… -> Visual C# -> Windows Store -> Grid App (XAML)), you’d see one way of it working:
<CollectionViewSource x:Name="groupedItemsViewSource" Source="{Binding Groups}" IsSourceGrouped="true" ItemsPath="Items" d:Source="{Binding Groups, Source={d:DesignData Source=/DataModel/SampleData.json, Type=data:SampleDataSource}}"/>
The d namespace qualifier is at the heart of this — above you’ll see it defined as:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
The “blend” in the name tips you off that this is the design namespace. It does not impact running code. So when they said
d:Source="{Binding Groups, Source={d:DesignData Source=/DataModel/SampleData.json, Type=data:SampleDataSource}}"
this is the mechanism for supplying the design data. Go ahead, misspell the path to SampleData.json. The app runs fine, but the Design tab no longer shows data in the GridView.
d:DesignData
DesignData is mode #1. Given:
- A data file (both XAML and JSON files seem to work)
- /DataModel/SampleData.json
- A Type
- SampleDataSource
Blend is smart enough to populate the GridView with data at this point. XAML data files are strongly typed, but as long as the JSON values have the same name/type as your ViewModel object, it’ll parse in just fine.
This method is OK, but I don’t really fancy it. It requires your sample data file to be directly mapped to Properties on your ViewModel. Unless your service happens to be outputting JSON that’s just perfect for you, your ViewModel will probably differ. Also, if you’re into MVVM, your data is probably coming from a ServiceProvider, and you probably have an interface defining your service calls. This lines itself up perfectly for run-time mocking with sample data, so why not use the same technique for design time?
d:DesignInstance
DesignInstance is mode #2. Essentially, it’s a way of telling Blend, “Hey, I’ve got an object here that you can call the default constructor on when the app’s not running and you can use it to bind”. For the same CollectionViewSource as above:
d:Source="{Binding Groups, Source={d:DesignInstance Type=data:SampleDataDesigner, IsDesignTimeCreatable=True}}"
This corresponds to the following class (a refactored verson of the SampleDataSource class in the Grid app template):
public class SampleDataDesigner { public ObservableCollection<SampleDataGroup> Groups { get; private set; } public SampleDataDesigner() { Groups = new ObservableCollection<SampleDataGroup>(); Task.Run(async () => { await GetGroupsAsync(); }).Wait(); } private async Task<IEnumerable<SampleDataGroup>> GetGroupsAsync() { await GetSampleDataAsync(); return Groups; } private async Task GetSampleDataAsync() { if (Groups.Count != 0) return; Uri dataUri = new Uri("ms-appx:///DataModel/SampleData.json"); StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(dataUri); string jsonText = await FileIO.ReadTextAsync(file); JsonObject jsonObject = JsonObject.Parse(jsonText); JsonArray jsonArray = jsonObject["Groups"].GetArray(); foreach (JsonValue groupValue in jsonArray) { JsonObject groupObject = groupValue.GetObject(); SampleDataGroup group = new SampleDataGroup(groupObject["UniqueId"].GetString(), groupObject["Title"].GetString(), groupObject["Subtitle"].GetString(), groupObject["ImagePath"].GetString(), groupObject["Description"].GetString()); foreach (JsonValue itemValue in groupObject["Items"].GetArray()) { JsonObject itemObject = itemValue.GetObject(); group.Items.Add(new SampleDataItem(itemObject["UniqueId"].GetString(), itemObject["Title"].GetString(), itemObject["Subtitle"].GetString(), itemObject["ImagePath"].GetString(), itemObject["Description"].GetString(), itemObject["Content"].GetString())); } this.Groups.Add(group); } } }
At first, this doesn’t seem like a very large improvement, but remember, you should be using a ServiceProvider:
public class SampleDataDesigner { public ObservableCollection<SampleDataGroup> Groups { get; private set; } public SampleDataDesigner() { Task.Run(async () => { IDataService service = ServiceProvider.Current.GetService(typeof(IDataService)) as IDataService; Groups = await service.GetGroupsAsync(); }).Wait(); } }
Much, much simpler. You get to share code that populates your Design view, populates your app in a sample-data mode, and probably populates your unit tests.
Oh, and one more thing. If you need to tell (in code) whether the instance has been started by the Blend Designer or not, use DesignModeEnabled.