Using Design-time Databinding While Developing a WPF User Control






4.40/5 (4 votes)
A trick that allows populating a user control with sample data while you are designing it in the Visual Studio designer
Introduction
When one designs WPF UI elements in Microsoft Visual Studio or Blend, it is very beneficial to see them populated with sample data. Using sample data ensures proper layout and allows one to see data-specific effects (e.g., effects of very long stings in bound properties) without running the application.
For example, if one designs a simple progress report user control that has a progress bar with an overlaid message and a progress value, he might not discover problems with the design until he runs the application.
On the other hand, as soon as the control is data bound at design time, one can easily see that the current design has problems:
There are a fair amount of articles on the net that describe how to use the design-time data binding while working with WPF/Silverlight Windows and Pages. However, those methods do not directly apply when one designs a user control. This tip describes a trick to make design-time data binding working even for user controls.
Background
Visual Studio 2010 introduced support for design-time data binding in its Designer view. To use it, all one needs is to include into a Window, a Page, or a User Control XAML file a couple of additional namespaces...
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
...and a number of new design-time attributes become available for use. The following articles describe design-time data binding in detail:
- Design-Time Attributes in the Silverlight Designer
- Walkthrough: Using Sample Data in the Silverlight Designer
- Sample Data in the WPF and Silverlight Designer
The most important of the design-time attiributes is d:DataContext
. It can be set for any FrameworkElement
and specifies the design-time DataContext
for a control and its children. The designer then uses the context to populate the control binding in the Design view and to display sample data in the designer. For example:
xmlns:dvm="clr-namespace:Dima.Controls.DesignViewModel"
d:DataContext ="{d:DesignInstance {x:Type dvm:ProgressReportSample1}, IsDesignTimeCreatable=True}"
This works well for the content of WPF/Silverlight Windows and Pages. However, user controls in many cases ignore the DataContext
and instead expose dependency properties that their host needs to bind to the data. This makes direct use of the d:DataContext
attribute in user controls impossible and one needs to resolve to a trick.
User Control Design
As an example, let's consider the progress report user control shown in figures 1 and 2. It defines the Percentage
, Message
and CancelCommand
dependency properties:
public partial class ProgressReportControl : UserControl
{
public ProgressReportControl()
{
InitializeComponent();
this.root.DataContext = this;
}
public static readonly DependencyProperty PercentageProperty =
DependencyProperty.Register("Percentage", typeof(int), typeof(ProgressReportControl));
public int Percentage
{
get { return (int)GetValue(PercentageProperty); }
set { SetValue(PercentageProperty, value); }
}
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof(string), typeof(ProgressReportControl));
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty CancelCommandProperty =
DependencyProperty.Register("CancelCommand", typeof(ICommand), typeof(ProgressReportControl));
public ICommand CancelCommand
{
get { return (ICommand)GetValue(CancelCommandProperty); }
set { SetValue(CancelCommandProperty, value); }
}
}
and binds its elements to those properties:
<DockPanel x:Name="root" Margin="4">
<Button Content="Cancel" Command="{Binding CancelCommand, Mode=OneWay}"
Width="60" Margin="4,0,4,0"
DockPanel.Dock="Right" VerticalAlignment="Center" />
<Grid Margin="4,0,4,0" Height="32">
<ProgressBar Value="{Binding Percentage,
Mode=OneWay}" Minimum="0" Maximum="100" />
<TextBlock Text="{Binding ElementName=progressBar, Path=Value, StringFormat={}{0:0}%}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Text="{Binding Message, Mode=OneWay}"
Margin="4,0,4,0" VerticalAlignment="Center" DockPanel.Dock="Top" />
</Grid>
</DockPanel>
At runtime, when the control is loaded, we need to ensure that its elements are bound to the dependency properties and not to the arbitrary DataContext
that the control inherits from its host. So, in the control’s constructor, we set DataContext
of its child root element to the control itself.
this.root.DataContext = this;
Any window that hosts the progress report control will need to bind the control properties to the data. The DataContext
that it passes to the control is ignored within the control.
<Controls:ProgressReportControl Grid.Row="1"
HorizontalAlignment="Stretch" VerticalAlignment="Top"
Message="{Binding Report.Message}" Percentage="{Binding Report.Percentage}"
CancelCommand="{Binding ComputationCancel}" />
At first glance, this completely eliminates the possibility to use the design-time data passed as d:DataContext
.
Trick Description
However, we should recall that when a user control is designed in the Design view, the designer does not execute its constructor (though it will execute constructors of all its child elements). Thus, if we create a design-time view model which shape matches control's dependency properties...
public class ProgressReportSample1
{
public int Percentage { get; private set; }
public string Message { get; private set; }
public ICommand CancelCommand { get; private set; }
public ProgressReportSample1()
{
this.Percentage = 60;
this.Message = "Computation progress";
this.CancelCommand = null;
}
}
...and pass it as design-time sample data via d:DataContext
to the designed user control, the control child elements will see it:
<UserControl x:Class="Dima.Controls.ProgressReportControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignHeight="80" d:DesignWidth="300"
xmlns:dvm="clr-namespace:Dima.Controls.DesignViewModel"
d:DataContext ="{d:DesignInstance {x:Type dvm:ProgressReportSample1},
IsDesignTimeCreatable=True}">
Due to the matching shape, the designer will successfully bind the user control elements to the properties of the design-time view model and we will get the control view shown in figure 2.
At the same time, when we design the window hosting our user control, the window constructor again will not be executed, but the control constructor will. Thus, when the host window is designed, the control will ignore the window's design-time view model passed to it as DataContext
and will properly bind to the control’s dependency properties:
Conclusion
The described above usage of design-time data binding is just a trick, not an all-encompassing solution, but it should work for most of the user controls. The attached UseControlDesignTimeDataBinding.zip file contains the full source code for the tip.
History
- 2/27/2015 - Initial release