How to bind from a ContentTemplate to the surrounding custom Control? - wpf

I've got the following user control:
<TabItem
x:Name="Self"
x:Class="App.MyTabItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:App"
>
<TabItem.Header>
<!-- This works -->
<TextBlock Text="{Binding ElementName=Self, Path=ShortLabel, UpdateSourceTrigger=PropertyChanged}"/>
</TabItem.Header>
<TabItem.ContentTemplate>
<DataTemplate>
<!-- This binds to "Self" in the surrounding window's namespace -->
<TextBlock Text="{Binding ElementName=Self, Path=ShortLabel, UpdateSourceTrigger=PropertyChanged}"/>
This custom TabItem defines a DependencyProperty 'ShortLabel' to implement an interface. I would like to bind to this and other properties from within the TabItem's DataTemplate. But due to strange interactions, the TextBlock within the DataTemplate gets bound to the parent container of the TabItem, which also is called "Self", but defined in another Xaml file.
Question
Why does the Binding work in the TabItem.Header, but not from within TabItem.ContentTemplate, and how should I proceed to get to the user control's properties from within the DataTemplate?
What I already tried
TemplateBinding: Tries to bind to the ContentPresenter within the guts of the TabItem.
FindAncestor, AncestorType={x:Type TabItem}: Doesn't find the TabItem parent. This doesn't work either, when I specify the MyTabItem type.
ElementName=Self: Tries to bind to a control with that name in the wrong scope (parent container, not TabItem). I think that gives a hint, why this isn't working: the DataTemplate is not created at the point where it is defined in XAML, but apparently by the parent container.
I assume I could replace the whole ControlTemplate to achieve the effect I'm looking for, but since I want to preserve the default look and feel of the TabItem without having to maintain the whole ControlTemplate, I'm very reluctant to do so.
Edit
Meanwhile I have found out that the problem is: TabControls can't have (any) ItemsTemplate (that includes the DisplayMemberPath) if the ItemsSource contains Visuals. There a thread on MSDN Forum explaining why.
Since this seems to be a fundamental issue with WPF's TabControl, I'm closing the question. Thanks for all your help!

What appears to be the problem is that you are using a ContentTemplate without actualy using the content property. The default DataContext for the ContentTemplate's DataTemplate is the Content property of TabItem. However, none of what I said actually explains why the binding doesn't work. Unfortunately I can't give you a definitive answer, but my best guess is that it is due to the fact that the TabControl reuses a ContentPresenter to display the content property for all tab items.
So, in your case I would change the code to look something like this:
<TabItem
x:Class="App.MyTabItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:App"
Header="{Binding ShortLabel, RelativeSource={RelativeSource Self}}"
Content="{Binding ShortLabel, RelativeSource={RelativeSource Self}}" />
If ShortLabel is a more complex object and not just a string then you would want to indroduce a ContentTemplate:
<TabItem
x:Class="App.MyTabItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:App"
Header="{Binding ShortLabel, RelativeSource={RelativeSource Self}}"
Content="{Binding ComplexShortLabel, RelativeSource={RelativeSource Self}}">
<TabItem.ContentTemplate>
<DataTemplate TargetType="{x:Type ComplexType}">
<TextBlock Text="{Binding Property}" />
</DataTemplate>
</TabItem.ContentTemplate>
</TabItem>

Try this. I'm not sure if it will work or not, but
<TabItem
x:Name="Self"
x:Class="App.MyTabItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:App"
>
<TabItem.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ShortLabel}"/>
</DataTemplate>
</TabItem.ContentTemplate>
</TabItem>
If it doesn't work, try sticking this attribute in the <TabItem/>:
DataContext="{Binding RelativeSource={RelativeSource self}}"

Related

DataTemplate Binding depending on property type and with working property Binding

I check those articles about doing DataTemplate :
WPF DataTemplate Binding
WPF DataTemplate and Binding
WPF DataTemplate Textblock binding
and thoses about DataTemplate depending on property type :
WPF DataTemplate Binding depending on the type of a property
Dynamically display a control depending on bound property using WPF
I'm trying to display a property with different controls depending of the property value. I have this Xaml that is partialy working. I have 2 problems :
The property is displaying with the right control, but when I set the value it doesn't go back to the property. Means the "set" of My property is not call (but was before I creates the DataTemplate). I detect that the problem about setting the property is about the ="{Binding Path=.}" but I cannot find the solution to set it otherwise.
Also To be able to make it work, I had to "isolate" the Value into a single ViewModel so that the DataTemplate doesn't affect all the other control.
Can you help me find betters solutions to resolves those 2 problems?
Here is the xaml code of my View linked with MyContainerViewModel that has a "ChangingDataType" :
<UserControl >
<UserControl.Resources>
<!-- DataTemplate for strings -->
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding Path=.}" HorizontalAlignment="Stretch"/>
</DataTemplate>
<!-- DataTemplate for bool -->
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Path=.}" />
</DataTemplate>
<!-- DataTemplate for Int32 -->
<DataTemplate DataType="{x:Type sys:Int32}">
<dxe:TextEdit Text="{Binding Path=.}" MinWidth="50" Mask="d" MaskType="Numeric" HorizontalAlignment="Stretch"/>
<!--<Slider Maximum="100" Minimum="0" Value="{Binding Path=.}" Width="100" />-->
</DataTemplate>
<!-- DataTemplate for decimals -->
<DataTemplate DataType="{x:Type sys:Decimal}">
<!-- <TextBox Text="{Binding Path=.}" MinWidth="50" HorizontalAlignment="Stretch" />-->
<dxe:TextEdit Text="{Binding Path=.}" MinWidth="50" Mask="f" MaskType="Numeric" HorizontalAlignment="Stretch" />
</DataTemplate>
<!-- DataTemplate for DateTimes -->
<DataTemplate DataType="{x:Type sys:DateTime}">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
</DataTemplate.Resources>
<DatePicker SelectedDate="{Binding Path=.}" HorizontalAlignment="Stretch"/>
</DataTemplate>
</UserControl.Resources>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</UserControl>
More informations about 2 :
I wanted to have in a view a label and a property that changes depending of the object. Something like this :
<UserControl>
<UserControl.Resources>
<!-- ...DataTemplate here... -->
</UserControl.Resources>
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</StackPanel>
</UserControl>
But if I put the DataTemplate on this UserControl resources, it will also affect the Label "allo". So I had to create another view that contains the DataTemplate and MyChangingProperty so that the label Allo would not be affected. But the extra View created just for one property is kind of ugly to me, I'm sure there is a better way to isolate the DataTemplate so it can apply only to one UIControl.
<UserControl >
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyContainerViewModel}"/>
</StackPanel>
</UserControl>
Note : MyContainerViewModel here is linked with the first view described.
Thanks in advance!
One possible solution would be to use a DataTemplateSelector. You cannot bind primitive types using two way bindings because that would have to be somehow by reference via the DataTemplate which I think is not supported by WPF.
The DataTemplateSelector now selects the right DataTemplate based on the property type and searches for the right DataTemplate in the resources by name. This also solves your problem that your DataTemplates interacted with the Label.
So first you need to define a DataTemplateSelector that changes the DataTemplate based on the type of the property:
public class MyDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var fe = (FrameworkElement)container;
var prop = (item as MyViewModelType)?.MyChangingProperty;
if (prop is string)
return fe.FindResource("MyStringDT") as DataTemplate;
else if (prop is bool)
return fe.FindResource("MyBoolDT") as DataTemplate;
// More types...
return base.SelectTemplate(item, container);
}
}
Then you need to change the UserControl like this:
<UserControl>
<UserControl.Resources>
<local:MyDataTemplateSelector x:Key="MyDTSelector" />
<!-- DataTemplate for strings -->
<DataTemplate x:Key="MyStringDT">
<TextBox Text="{Binding MyChangingProperty, Mode=TwoWay}"
HorizontalAlignment="Stretch"/>
</DataTemplate>
<!-- DataTemplate for bool -->
<DataTemplate x:Key="MyBoolDT">
<CheckBox IsChecked="{Binding MyChangingProperty, Mode=TwoWay}" />
<!-- More DataTemplates... -->
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyContainerViewModel}"
ContentTemplateSelector="{StaticResource MyDTSelector}" />
</StackPanel>
</UserControl>
You can find a bit more information regarding the DataTemplateSelector here.
You can of course also set a DataType on this new DataTemplates but it isn't required because the x:Key makes them unique anyway. But if you want then it has to look like this:
<DataTemplate x:Key="MyStringDT" DataType="{x:Type local:MyViewModelType}">
In my opinion, the previously posted answer is overkill. While a DateTemplateSelector is a useful thing to know about, it seems unnecessary to me in this scenario.
But if I put the DataTemplate on this UserControl resources, it will also affect the Label "allo".
The reason it affects the Label object is that the Label object is a ContentControl, and so does the same template-matching behavior for content types as your own ContentPresenter element does. And you've set the content of the Label object to a string value. But you can put anything you want as the content for it.
The way to fix the undesired effect is to intercept that behavior by changing the content from a string object to an explicit TextBlock (the control in the template that a string object normally gets assigned). For example:
<UserControl>
<UserControl.Resources>
<!-- ...DataTemplate here... -->
</UserControl.Resources>
<StackPanel>
<Label>
<TextBlock Text="Allo"/>
</Label>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</StackPanel>
</UserControl>
In that way, you bypass the template-finding behavior (since TextBlock doesn't map to any template and can be used directly), and the content for the Label will just be the TextBlock with the text you want.
This seems like a lot simpler way to fix the issue, than either to create a whole new view or to add a DataTemplateSelector.

How to find Button's DataContext in this sample code?

I was going through the excellent blog written by Rachel. Here is the link.
She mentions in "The View" section that " As Button’s DataContext is the PageViewModel, she used a RelativeSource binding to find the ChangePageCommand".
Could any one explain me, how is that Button's DataContext is PageViewModel?
She has written another blog explaining about DataContext here. From this article it seemed to me that DataContext of the Button would be "ApplicationViewModel", because if the element's DataContext is not specified it will inherit DataContext of it's Parent. And as none of the elements specify any DataContext, it seems like DataContext of Button should be of Window element DataContext (which is "ApplicationViewModel" as defined in App.xaml.cs).
Obviously I am wrong here, but what is that I am not thinking correctly?
Other Code snippets can be found in the article, below is the XAML code.
<Window x:Class="SimpleMVVMExample.ApplicationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SimpleMVVMExample"
Title="Simple MVVM Example" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:HomeViewModel}">
<local:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ProductsViewModel}">
<local:ProductsView />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }"
Margin="2,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</DockPanel>
Because you're inside of an ItemsControl's ItemTemplate. The DataContext is implicitly defined as the binding of each object provided by the ItemsSource binding collection.
The ItemsControl creates an ItemTemplate for each item in the ItemsSource collection. The DataContext of each ItemTemplate will be bound to the individual object that is being iterated in the collection. You can read more about datatemplate behavior here. (See Remarks)
So, in order to get to the ChangePageCommand provided by the window's DataContext , you have to provide a relative source lookup.

UserControl's DataContext

I'm creating a UserControl I want to use something like this:
<controls:ColorWithText Color="Red" Text="Red color" />
So far, I've implemented similar controls like this:
<UserControl x:Class="Namespace.ColorWithText" Name="ThisControl">
<StackPanel Orientation="Horizontal" >
<Border Width="15" Height="15" Background="{Binding Color, ElementName=ThisControl}" />
<TextBlock Text="{Binding Text, ElementName=ThisControl}" />
</StackPanel>
</UserControl>
where Color and Text are dependency properties of the control defined in code. This works, but specifying ElementName every time seems unnecessary.
Another option that works is using
<UserControl x:Class=… DataContext="{Binding ElementName=ThisControl}" Name="ThisControl">
and not specifying ElementNames, but that doesn't seem like a clean solution to me either.
I have two questions:
Why doesn't <UserControl DataContext="{RelativeSource Self}"> work?
What is the best way to do something like this?
For first one, try :
<UserControl DataContext="{Binding RelativeSource={RelativeSource Self}}">
And for second question, I think using ElementName or AncestorBinding is best way to bind to UserControl's properties.
Why can't you use <UserControl DataContext="{RelativeSource Self}">?
This is how you would use the control
<Grid DataContext="{StaticResource ViewModel}">
<!-- Here we'd expect this control to be bound to -->
<!-- ColorToUse on our ViewModel resource -->
<controls:ColorWithText Color="{Binding ColorToUse}" />
</Grid>
Now because we've hardcoded our data-context in the control it will instead attempt to lookup ColorToUse property on the ColorWithText object not your ViewModel, which will obviously fail.
This is why you can't set the DataContext on the user control. Thanks to Brandur for making me understand that.
What is the best way to do something like this?
Instead you should set the DataContext in the first child UI element in your control.
In your case you want
<StackPanel
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
Orientation="Horizontal" >
Now you have a DataContext which refers to your control so you can access any properties of that control using relative bindings.
I know this has been answered but none of the explanations give an Understanding of DataContext and how it works. This link does a great job for that.
EVERYTHING YOU WANTED TO KNOW ABOUT DATABINDING IN WPF, SILVERLIGHT AND WP7 (PART TWO)
In answer to your question #1
Why doesn't <UserControl DataContext="{RelativeSource Self}"> work?
This is a summary of the above link.
DataContext should not be set to Self at UserControl Element level. This is because it breaks the Inheritance of the DataContext. If you do set it to self and you place this control on a Window or another control, it will not inherit the Windows DataContext.
DataContext is inherited to all lower Elements of the XAML and to all the XAML of UserControls unless it is overwritten somewhere. By setting the UserControl DataContext to itself, this overwrites the DataContext and breaks Inheritance. Instead, nest it one Element deep in the XAML, in your case, the StackPanel. Put the DataContext binding here and bind it to the UserControl. This preserves the Inheritance.
See also this link below for a detailed explanation of this.
A SIMPLE PATTERN FOR CREATING RE-USEABLE USERCONTROLS IN WPF / SILVERLIGHT
In answer to your question #2
What is the best way to do something like this?
See code example below.
<UserControl x:Class="Namespace.ColorWithText" Name="ThisControl">
<StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=ThisControl}">
<Border Width="15" Height="15" Background="{Binding Color" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</UserControl>
Note that once you do this, you will not need the ElementName on each binding.
You should be using
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=Color}
for Databinding Related doubts always refer this sheet.
http://www.nbdtech.com/Blog/archive/2009/02/02/wpf-xaml-data-binding-cheat-sheet.aspx
You can set the datacontext to self at the constructor itself.
public ColorWithText()
{
InitializeComponent();
DataContext = this;
}
Now you can simply say
<UserControl x:Class="Namespace.ColorWithText" Name="ThisControl">
<StackPanel Orientation="Horizontal" >
<Border Width="15" Height="15" Background="{Binding Color}" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</UserControl>
For the desperate souls, who are trying to make pdross's answer work and can't:
It's missing an essential detail - Path=DataContext.
The lower code segment starts working when you add it there with this being the result:
<StackPanel DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext}">

Binding parent container props to content child control in Silverlight

Example:
<UserControl x:Name="userControl"
<StackPanel x:Name="container" Margin="0">
<TextBox Text="{Binding Path=SettingValue, RelativeSource={RelativeSource Mode=Self}}"/>
</StackPanel>
</UserControl>
UserControl contains SettingValue dependency property, TextBox doesn't,
so this example won't work.
I could've done this if I had AncestorType, like in WPF:
RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControlType}
Is there any possibility to bind to UserControl.SettingValue property?
Did you try the following? Use the ElementName source (the syntax might be a bit off).
<TextBox Text="{Binding Path=SettingValue, ElementName=userControl"/>
The answer I've found here:
Binding Silverlight UserControl custom properties to its' elements

WPF - Databind to a StackPanel using DataTemplates

I've modified my question since it has changed focus when trying things out.
I narrowed the problem down to the following...
I try to bind the selected Item of a TreeView to a StackPanel (or some other container that can hold User Controls). This container will then display a UserControl, depending on the type of the selected item.
Here is the xaml of the StackPanel (both treeview and stackpanel are in the same window ==> different grid column)
<StackPanel Grid.Column="2" MinWidth="500" DataContext="{Binding ElementName=myTree, Path=SelectedItem, Mode=OneWay}">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type mvTypes:MyTypeA}">
<controls:UserControlA DataContext="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type mvTypes:MyTypeB}">
<controls:UserControlB DataContext="{Binding}" />
</DataTemplate>
</StackPanel.Resources>
</StackPanel>
When I place a user control directly under the stackpanel (not in the resources), it displays it with the selected object as their datacontext.
Idem if I place a TextBox in it, it will show the correct type of the selected item.
<TextBox Name="textBox1" Text="{Binding}" />
For some reason, placing it within a DataTemplate (even without setting the DataType) results in nothing to display.
Any sugestions. I'm thinking that maybe a StackPanel is not the right control for this, though I can't seem to find other controls that look suitable as containers like this.
Thanks in advance.
Replace the StackPanel in your example with ContentPresenter and instead of DataContext set the Content property. That should work.
Although you have set the Binding on the second custom control, are you setting the DataContext, as the binding is the route to the information and the DataContext is the information it applies this binding information to.
Andrew
You can create a UserControl to display the TreeView and the selection info on the right, all in one. It saves you from creating any custom control. A custom control is basically unnecessary since you do not create anything which didn't exist before.
<UserControl x:Class="NameSpace.SelectionView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="namespace.Controls"
Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TreeView Name="customTree">
<!--Items go here-->
</TreeView>
<StackPanel Grid.Column="1" MinWidth="50" DataContext="{Binding ElementName=customTree, Path=SelectedItem, Mode=OneWay}">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type StylingTest:CustomViewModelA}">
<controls:CustomADetailsControl />
</DataTemplate>
<DataTemplate DataType="{x:Type StylingTest:CustomViewModelB}">
<controls:CustomBDetailsControl />
</DataTemplate>
</StackPanel.Resources>
<TextBlock Text="{Binding}"/>
</StackPanel>
</Grid>
</UserControl>
Any other custom behaviour, I'm sure you could create or set in styles/templates here.
Also, you might find one of my other answers useful.
Good luck with wpf, cheers.

Resources