tags:

views:

37

answers:

1

I'm sure that most of us will agree that it is possible to run out of declarative DataContext sources from parent or self contexts. We then might resort to Binding to a parent Tag and then use Element Binding. So the source might look like this:

<Grid.Tag>
    <Binding Path="MyProperty" Source="{StaticResource MySource}" />
</Grid.Tag>

What happens when we can't even do this? My next sloppy trick is to use a collapsed UserControl element:

<UserControl
    x:Name="MySloppyControl"
    DataContext="{StaticResource YetAnotherSourceInThisCrazyGrid}"
    Foreground={Binding CrazyForegroundColor}
    Visibility="Collapsed" />

Now I can do this:

<Grid.Resources>
    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Foreground" Value="{Binding CrazyForegroundColor, ElementName=MySloppyControl}" />
    </Style>
</Grid.Resources>

Assuming that I explained this adequately, is this collapsed UserControl pattern misguided?

+3  A: 

Not that I haven't done it myself, but I would have to say "yes" -- that pattern is misguided.

In those few cases where I chose to do this my code looked like this:

<Control x:Name="Whatever" DataContext="..." />
<Control x:Name="SomethingElse" DataContext="..." />

Since a control is invisible by default this acheives the same effect with less code.

Having said that, let me explain why I think this approach is misguided:

WPF is all about binding to data. If you have some data to bind to, it is either part of your model (or viewmodel) or it is external. If it is part of your model (or viewmodel) and your model's structure is well-defined you should be able to access it through the current DataContext. On the other hand, if it is static you should be able to access it directly from the target.

For example, let's say you want to populate a ComboBox with all the possible WidgetTypes from the model. If your model is well-constructed, the binding can be as simple as:

<ComboBox ItemsSource="{Binding DataSet.AllWidgetTypes}" />

This assumes that your ComboBox's DataContext is a "Widget" and that "Widget" has a "DataSet" property that gives access to other related data. Alternatively if the list of available types might change based on the other details of the Widget, the binding might be simply {Binding AppliableWidgetTypes}.

The other situation in which you might want a "separate DataContext" as you call it is when referencing static objects, such as through x:Static or StaticResource. In these cases there is a better way to do it: Set Binding.Source. It looks like this:

Text="{Binding DefaultFontSize,Source={x:Static ApplicationProperties.Instance}}"

or this

Text="{Binding PropertyName,Source={StaticResource ResourceContainingProperty}}"

If your motivation is to avoid creating a viewmodel to combine multiple model objects, use C# anonymoust types to construct an poor-man's viewmodel. For example:

DataContext = new { something = 123, whatever = "Test" };

Another common pattern is to create properties on the control and use a ControlTemplate and TemplateBinding to bind to them.

This leaves one final situation, which is when you really want a shared value and you want no code-behind at all. In this case I have actually gone ahead and used the invisible <Control> as shown previously. So there are rare situations where it may apply.

Ray Burns