How is the DataContext of a ControlTemplate set? - wpf

I am setting the ControlTemplate in a Tile control (in the Telerik TileList). It looks something like this:
<ControlTemplate TargetType="{x:Type telerik:Tile}">
<Border>
<!-- Some Content that binds to DP on the view models -->
<ContentPresenter Content="{Binding}" />
</Border>
</ControlTemplate>
Elsewhere:
<telerik:RadTileList ItemsSource="{Binding ComponentViewModels}">
And I have DataTemplates defined for the items that would be presented within the Tile's ContentPresenter. The trouble I have is that, when a ComponentViewModel is added to the target of the ItemsSource (ComponentViewModel ObservableCollection) a new Tile appears but it's DataContext is the RadTileList's ViewModel and not the individual component's ViewModel.
Am I missing something regarding the setting of the DataContext in a ControlTemplate?

To bind to a property on the Presenter or ViewModel attached to the DataContext on the parent view or control from inside a DataTemplate you have to use the RelativeSource property with the value “FindAncestor” and the type of the control with the DataContext you are looking for.
The most common mistake I’ve seen using this, is people forgetting to use the {x:Type yourControlType} markup extension for the AcestorType property and using “AncestorType=yourControlType” instead.
Here is an example:
Width="{Binding DataContext.SomeProperty, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
where “SomeProperty” is a property on the Presenter or ViewModel that follows the INotifyPropertyChanged pattern.
Width is the property of the control inside the the ControlTemplate

This seemed to do it. I needed to do TemplateBinding for the ContentTemplate and Content properties.
<ControlTemplate TargetType="{x:Type telerik:Tile}">
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}"/>
</ControlTemplate>

Related

Triggers, commands not firing in custom ListBox's ItemTemplate

I've got a custom ListBox control with a style set up in my Themes/Generic.xaml. I then have a button in the ListBox's ItemTemplate, and it's Click event isn't firing and I've got no idea why. Same goes for the button's Commands (I'm confident the Command issue isn't DataContext related) and interaction triggers. While attempting to debug, I noticed that using the default ListBox instead of my own stopped the problem, but I need to use the custom control.
This is essentially what I've got (fluff removed for brevity). The button:
<controls:CustomListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Click=MyHandler/>
</DataTemplate>
</ListBox.ItemTemplate>
</controls:CustomListBox>
And the custom control's style in Themes/Generic:
<Style TargetType="{x:Type controls:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:CustomListBox}">
<Border>
<ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How can I get this event to fire?
I think this should probably be a usercontrol rather than a custom control.
Are you really going to change the template out of this for something else?
If you use an event handler like that then how are you planning on using the delegate? It's a very inflexible way of working you're headed in.
You mentioned command, which is probably rather more like it.
If you use a button in an item template with a command bound like
<Button Command="{Binding RowCommand}"
Then the datacontext of that Button is the content of the row.
If you bind ItemsSource to a collection Items of ItemVM then it's looking in the ItemVM that is presented to that row.

UserControl within a ControlTemplate

I have a ControlTemplate for a Telerik Tile and I am overriding like below:
<ControlTemplate TargetType="{x:Type ctrl:Tile}">
<Border>
<local:UserControl>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</local:UserControl>
</Border>
</ControlTemplate>
My user control looks like:
<DockPanel>
<!-- some content -->
<ContentPresenter/>
</DockPanel>
The ControlTemplate does not display the content of the UserControl.
If I change my control template to:
<ControlTemplate TargetType="{x:Type ctrl:Tile}">
<Border>
<StackPanel>
<local:UserControl/>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</StackPanel>
</Border>
</ControlTemplate>
It will find the content and place it appropriately. It seems like the ControlTemplate cannot find the content once it's nested inside my UserControl. Is there anything I could be doing wrong?
Note that these ControlTemplate Items are appearing in an ItemsPresenter.
You're treating the UserControl as if it is a basic ContentControl (like a Button) which is a little different than what it actually is. Using Button as an example, when you add a child (i.e. a TextBlock) into a Button element that's actually setting that TextBlock as the Button's Content property. The way it gets rendered is through the Button's ControlTemplate, which includes a ContentPresenter to inject Content into. The Visual Tree ends up like this:
<Button>
-start Template
<Border>
<ContentPresenter>
-start Content
<TextBlock>
So far that's basically the model your code is following. The problem is that you're using a (still ContentControl derived) UserControl instead, which rather than using a ControlTemplate is most often defined with a XAML+code-behind model, where the XAML defines the Content of the UserControl. (It is possible to switch these models and template a UserControl or make a Button derived class with XAML+code-behind but not common)
If you want to both define the look of your UserControl in XAML as normal and still be able to inject other content you can add another DependencyProperty that mirrors the setup of the Content property and set your content to that. This approach is used with HeaderedContentControl derivatives (i.e. Expander) which essentially has 2 content properties, Content and Header. Using the new property would look like this:
<Border>
<local:UserControl>
<local:UserControl.OtherContent>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</local:UserControl.OtherContent>
</local:UserControl>
</Border>
And then inside the UserControl's XAML you need to explicitly set up the ContentPresenter Bindings (you only get them for free inside templates of ContentControls):
<DockPanel>
<!-- some content -->
<ContentPresenter Content="{Binding Path=OtherContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"/>
</DockPanel>
If you want a ContentTemplate, ContentTemplateSelector, or ContentStringFormat you'll also need to add properties and bindings for those.

How to use template binding inside data template in custom control (Silverlight)

I am trying to create control which will take ItemsSource and InnerTemplate and will show all the items wrapped in CheckBoxes.
The control has 2 dependency properties:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CheckBoxWrapperList), null);
public static readonly DependencyProperty InnerTemplateProperty = DependencyProperty.Register("InnerTemplate", typeof(DataTemplate), typeof(CheckBoxWrapperList), null);
and here is the template:
<ControlTemplate TargetType="local:CheckBoxWrapperList">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="wrapper">
<CheckBox>
<ContentPresenter ContentTemplate="{TemplateBinding InnerTemplate}" Content="{Binding}" />
</CheckBox>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemTemplate="{StaticResource wrapper}" ItemsSource="{TemplateBinding ItemsSource}" />
</Grid>
</ControlTemplate>
However, this approach does not work.
Binding in the ControlPresenter.ContentTemplate using TemplateBinding does not work.
However, when I don't use template binding and reference the template as static resource, then it works as expected.
Why cannot I use the template binding inside the content presenter in datatemplate?
What am I missing here? Any special markup required?
Is there a way to achieve the expected behavior?
Thanks in advance.
Silverlight and WPF
You can get around this with a relative source binding:
Instead of:
{TemplateBinding InnerTemplate}
You would use:
{Binding RelativeSource={RelativeSource AncestorType=local:CheckBoxWrapperList}, Path=InnerTemplate}
It's a bit messier but it works.
WinRT
WinRT doesn't have AncestorType. I've got something that works but it's kind of horrifying.
You can use an attached property to store a TemplateBinding value and then access it using ElementName...
<ControlTemplate TargetType="local:CheckBoxWrapperList">
<Grid x:Name="TemplateGrid" magic:Magic.MagicAttachedProperty="{TemplateBinding InnerTemplate}">
<Grid.Resources>
<DataTemplate x:Key="wrapper">
<CheckBox>
<ContentPresenter ContentTemplate="{Binding ElementName=TemplateGrid, Path=(magic:Magic.MagicAttachedProperty)}" Content="{Binding}" />
</CheckBox>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemTemplate="{StaticResource wrapper}" ItemsSource="{TemplateBinding ItemsSource}" />
</Grid>
</ControlTemplate>
I don't know if there's a better way for WinRT.
TemplateBinding can only be used within a ControlTemplate, you're using it within a DataTemplate. (The fact that the DataTemplate is within a ControlTemplate doesn't matter)

WPF Binding parent property in HierarchicalDataTemplate

I have a WPF TreeView with 2 levels of data and 2 HierarchicalDataTemplate to format each level. From the HierarchicalDataTemplate at the second level, I need to bind a property in the class of the first level. I have tried in this way, but it dosn't work:
Text="{Binding Path=Ori, RelativeSource={RelativeSource TemplatedParent}}"
with Ori as the name of the propery
Even in this way it dosn't works:
Text="{Binding Path=tOri, RelativeSource={RelativeSource TemplatedParent}}"
with tOri as the name of the TextBlock in the fisrt HierarchicalDataTemplate that bind the Ori propery.
Can you help me?
TemplatedParent only refers to the parent Control inside a ControlTemplate and so doesn't work with DataTemplates. You can use FindAncestor instead to locate the parent TreeViewItem and then access its DataContext.
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}, AncestorLevel=2}, Path=DataContext.Ori}"
You have misunderstood the TemplatedParent binding in WPF. TemplatedParent refers to the inherited control that you are extending. Example: if I wrote a ControlTemplate that targeted a Button.
<ControlTemplate TargetType="{x:Type Button}" x:Key="MyButtonTemplate">
<Border BorderBrush="{TemplateBinding Property=Background}" BorderThickness="3" >
<ContentPresenter Margin="10"/>
</Border>
</ControlTemplate>
This is binding the BorderBrush to the the base Button.Background property.
To achieve what you want, you need to walk the visual tree using the RelativeSource FindAncestor to find the parent and then perform the binding. To help try using either Mole WPF or Snoop.
Note: the copy of Snoop available above has some serious issues, i.e., cannot go more than 256-levels deep. I have a patched and feature extended version that is awesome. A interchange between using Mole and Snoop2 to debug/visualise during development.

Exposing a sub-control in a UserControl for use in XAML

I have a UserControl that contains a TreeView. I want the user to be able to set the properties of the inner TreeView control via XAML and I'm not sure how to do that.
I've tried creating a public property on the UserControl to the TreeView, but that only allows me to set a SelectedItemChanged trigger.
I'd like to do something like:
<ExampleUserControl>
<ExampleUserControl.TreeView.ItemTemplate>
...
</ExampleUserControl.TreeView.ItemTemplate>
</ExampleUserControl>
Or:
<ExampleUserControl TreeView.ItemsSource="{Binding Foo}" />
I would prefer not to create properties in the UserControl for each TreeView property, and I don't want to force the user to define the control in C#.
As for passing multiple properties to the child control in your user control, you can always expose a Style property.
ie ChildStyle
For the ItemsSource unless you use [Josh Smith's Element Spy / Data Context Spy / Freezable][1] trick, you will have a disconnect on DataContexts.
So either you employ those tricks or simply have 2 properties.
1) the ItemsSource
2) the ChildStyle
The xaml ends up...
<ChildTreeAnswer:MyControl ItemsSource="{Binding Items}">
<ChildTreeAnswer:MyControl.ChildStyle>
<Style>
<Setter Property="ItemsControl.ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black"
BorderThickness="1"
Margin="5">
<TextBlock Text="{Binding }" />
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ChildTreeAnswer:MyControl.ChildStyle>
</ChildTreeAnswer:MyControl>
Then in your user control do... (I used a listbox for simplicity sake)
<ListBox ItemsSource="{Binding ItemsSource}"
Style="{Binding ChildStyle}" />

Resources