One of the immediate benefits I saw in moving to WPF was the ability to design a small (or large) user control and reuse it, both multiple times in the same application and in some cases across multiple applications. I have now done both, and even though I'm a relative newcomer to WPF, I feel there are a few simple techniques worth documenting that make the process of reusing components easier and cleaner.
Who's Got the DataContext
The biggest hurdle for me was binding to the proper data context for the control. Even in the same application there is no guarantee that a user control will be nested in the same chain. Obviously across applications you can't rely on the same data context being present. This means in general you don't want the actual data context to be set anywhere in the view, i.e. not in the xaml and not in the code behind. This doesn't mean you can't bind to the properties by name. In my case I create a View/ViewModel pairing for every reusable control. The two go together everywhere just like an old married couple. The property names will never change. Not having a DataContext does mean that tools like Resharper will put a info icon under the property letting you know it couldn't find a DataContext to resolve the property against. Not that big a deal, and if you want, you can always set the design time DataContext to make sure you got the names right. You'll probably want to remove that design time context once you plan on reusing the control across multiple solutions. Here's how one tab in the final solution was structured.
A Real World Example
Working with the Reusable Control
The line item navigation box for this order entry system needs to be used on multiple tabs. On some tabs it will be nested directly within the the GroupBox that holds the line items on other tabs (like the one shown) it may be a level deeper inside a grouping of controls for the line items. The user control takes up only about 40 lines of xaml (included below) and does nothing more than allow the user to jump to a particular line item (there can be hundreds) and scroll that line item into view, or bring up a dialog allowing the user to search by the line item description and jump to that line item again bringing it into view. There is NOTHING in the code behind except the default call to InitializeComponent method call. All the logic for the control is in the LineItemNavViewModel. The question is where do I put a property that returns the LineItemNavViewModel such that the control can find it.
While the line item navigation box may be in one of several container controls, and those controls may themselves be nested in other controls, they all only make sense in the context of an actual order. Therefore I decided to add a property at the OrderEntryViewModel that holds my LineItemNavViewModel.
The question becomes: how do I reach up from inside a control that already has an assigned data context to a parent control an unknown number of levels above that has a different data context? It turns out it's not that hard. The syntax appears a little convoluted but the RelativeSource attribute is very flexible.
For the sake of completeness, let me specify how the parent container is used. The LineItemNavView sits in another control LineItemActionsView (the red outlined box in the image). The code for that row looks like this:
You can see the DataContext is set to LineItemActionVm and not to the parent OrderEntryViewModel. In the LineItemActionsView.xaml I specify the LineItemNavView control as follows:
Shared: and Order: are just xmlns paths to the clr-namespaces that hold my xaml code for the respective user controls. The RelativeSource is specifying that somewhere up the chain of controls holding the LineItemActionsView is a user control of type OrderTabLineItemsView (the View that holds the entire tab). The Path portion of the binding says that I want to reuse the DataContext of that view, and in that DataContext there will be a property named LineItemNavVm that holds my LineItemNavViewModel.
There were two things that took me a while to discover, one was just the rather strange double use of RelativeSource, and the second was discovering that I had to explicitly state in the path, that I wanted the DataContext. It seemed obvious once I discovered it but without a rather casual reference to it on StackOverflow I might still be struggling. The Ancestor I specified is the "View". The view doesn't have any of my properties since in MVVM I have nothing in the code behind. The data context associated with the view holds the View Model and it does have the properties I'm interested in. So explicitly prefixing the property with the DataContext makes it look for the property on my view model.
In my case everywhere I reuse this control I'll be able to use this same Binding but that's only because in this application I know there will always be an ancestor Order:OrderTabLineItemsView. If there weren't it wouldn't be a problem, it just means I need to specify a RelativeSource that makes sense for the application where I want to use the control. Remember the reusable control knows nothing about its DataContext, it just references the properties on its ViewModel. As long as you specify where that ViewModel can be found, the control will work. In fact if I saw this particular control as being useful across multiple applications I would move it into its own assembly, or at least into an assembly with other related reusable controls.
There are other ways to use the RelativeSource binding. You can specify another control explicitly using an x:Type of UserControl and the AncestorLevel attribute to specify the level where the UserControl of interest can be found. This struck me as a rather fragile way to do things but nonetheless might be prove to be a useful technique to know about. Below as promised is the entire XAML of the little user control LineItemNavView. Those three properties, MaxLineItem, CurrentLineItem and FindLineItemCommand are all in the LineItemNavViewModel class (they are shown in red because the xaml can't resolve them because it doesn't know the data context.).
For the insatiably curious here is the LineItemNavViewModel code: