UWP - Data Templates in Resource Dictionaries
Lately, I’ve found myself looking into UWP development. Tonight I want to share a problem I ran into and a workaround that I found. I’ve created a repo that shows the step by step progression, and this blog post is meant to provide additional context around that.
Pre-emptive nitpickers note: for the sake of problem reduction, the code in this repo is not overly concerned with patterns, best practices, error checking, and so on
Some Background⌗
In XAML, there’s the concept of a DataTemplate
. This allows you to define what UI components or user controls are used when displaying certain objects inside of other content controls. A canonical example is a ListView
.
Without Templates⌗
By default, a ListView
will just ToString()
the object and put it on the screen. You can run this commit, or look at the screenshot below.
Notice that the ListView says “DataTemplates.RedThing” and “DataTemplates.BlueThing” (i.e., the System.Type
of each item) instead of any meaningful representation of the instance properties.
DataTemplate, Binding⌗
In this commit we create a ResourceDictionary
1 with a couple of DataTemplate
definitions in it. We also merge that ResourceDictionary
into the app resources in App.xaml
, and create a ThingTemplateSelector
to use one or the other based on the type of underlying Thing
.
I’m going to be very careful not to say this looks good, but it proves the technique. We can now let our designers have all the fun polishing the DataTemplate
itself, and we’re good to go! Right?
What Are Tests? Baby, Don’t Hurt Me. Don’t Hurt Me. No More!⌗
I have to say that whatever positives there might be with UWP, the unit testing story is not one of them. The vast majority of issues we’ve seen so far have boiled down to testability.
The next commit highlights one such example where our app runs run fine, but our unit test application crashes at startup. The error isn’t what I’d call helpful.
… test discovery output, a la deployment of application, etc., omitted …
A user callback threw an exception. Check the exception stack and inner exception to determine the callback that failed.
========== Run test finished: 0 run (0:00:04.9484062) ==========
If you debug the test to check the stack and inner exception, both are null. You may now flip your tables.
Update 6/30: After some discussion about this post, I realized it could be even clearer. In the original commits, the unit test was a UITestMethod that tested the template selector itself, making it seem like the problem was limited to testing UI concerns. Now, our unit test does nothing other than Assert.IsTrue(true)
in a TestMethod. Let us all agree that one should always be able to Assert.IsTrue(true)
without one’s unit tests crashing.
The Workaround⌗
Of course, I neither force quit nor flipped tables in real life. I took a breath and decided to push through the pain. What I found was that our test project would not crash if I removed our equivalent of <local:ThingTemplates />
from the App.xaml.
In the final commit, you can see how our custom DataTemplateSelector
takes care of instantiating the dictionary it needs itself. One might argue this is actually cleaner anyway. I’m not currently going to argue that point either way. All I’m going to say is, “Now we can put DataTemplates in a ResourceDictionary and still keep our unit tests, too!” That makes me a much happier camper.
1Note: The ResourceDictionary needs a code behind because we’re using x:Bind
. This is a new alternative to Binding
that lets you take advantage of static types. Leaving aside any debates on the merit of static types themselves, I feel that you might as well use ’em if you got ’em. I have not confirmed if the problems in the unit test project would also exist when using {Binding}
and eliminating the code behind.