What exactly does WPF Data Binding's "RelativeSource FindAncestor" do? - wpf

I am currently working within a WPF user control (the root element of my XAML file is "UserControl"), which I know is being hosted inside a Window. How can I access a property of the Window using data binding?
Does anyone know why simply
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}" Path="..." />
does not work? The error message I get is:
System.Windows.Data Warning: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''.
Edit: I ended up using a variation on ArsenMkrt's approach, so have accepted his answer. However, I am still interested in finding out why FindAncestor does not "just work".

The best way is to give a name to UserControl
Create dependency property MyProperty in UserControl with two way binding and bind it in main Window, than bind in UserControl like this
<UserControl x:Name = "myControl">
<Label Content={Binding ElementName= myControl, Path=MyProperty}/>
</UserControl>

If you're trying to 'escape' from an ItemsControl or DataGridView to get to a Window you may be finding that AncestorType of x:Type Window doesn't work. Or at least doesn't seem to...
If this is the case you're probably running Blend or Visual Studio and expecting the data to be visible at design time - which it won't because VS + Blend both create their own instances that aren't really Windows. It will work at runtime just fine, but not during design mode.
There's a couple things you can do:
Wrap in a UserControl
Here's an alternative solution I've come up with. It has one advantage in that you're not referencing a UserControl or Window directly, so if you change the parent container your code won't break.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:MyWPFApplication.Views"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyWPFApplication.Views.UPCLabelPrinterWindow"
mc:Ignorable="d"
x:Name="LayoutRoot"
Title="UPCLabelPrinterWindow">
<views:DataContextWrapper>
<DockPanel>
...
</DockPanel>
</views:DataContextWrapper>
Where DataContextWrapper is just a Grid
namespace MyWPFApplication.Views {
public class DataContextWrapper : Grid
{
}
}
Then when you bind you do this :
<TextBlock Text="{Binding="{Binding DataContext.SomeText,
RelativeSource={RelativeSource AncestorType={x:Type views:DataContextWrapper},
Mode=FindAncestor}}" />
Note: if you want to bind to a property ON Window itself it's trickier and you should probably bind via a dependency property or something like that. But if you are using MVVM then this is one solution I found.

I Think You Should SET Mode="OneWayToSource" Like this:
<TextBox Text="{Binding RelativeSource={RelativeSource FindAncestor ,AncestorType={x:Type Grid}},Path=BackGround , Mode=OneWayToSource , UpdateSourceTrigger = PropertyChanged}" />

If you are using a view model as your Window's DataContext and the property you need to bind to is from that view model then you should prefix the path with DataContext.MyPropertyPath, something like this:
<TextBox Text="{Binding DataContext.MyProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}"/>
this translates as "Find me an ancestor window and then look in it's data context for MyProperty"

Related

Using TemplateBinding inside DataTemplates

I'm creating a custom control, and I'm having problems binding UI elements inside a DataTemplate to the custom control's dependency properties.
There's a content control in my control that should change its content and content template according to a certain property, so I bound it this way -
<ContentControl Content="{TemplateBinding ControlMode}" ContentTemplateSelector="{StaticResource TemplateSelector}"/>
The Content Template selector is defined this way -
<ns:TemplateSelector x:Key="TemplateSelector">
<ns:TemplateSelector.Template1>
<DataTemplate>
<TreeView ItemsSource="{TemplateBinding TreeSource}"/>
</DataTemplate>
</ns:TemplateSelector.Template1>
<ns:TemplateSelector.Template2>
<DataTemplate>
<ListView ItemsSource="{TemplateBinding ListSource}"/>
</DataTemplate>
</ns:TemplateSelector.Template2>
</ns:TemplateSelector>
The problem is that the TreeView and the ListView can't be bound to their itemssource with TemplateBinding due to this error for example -
"Cannot find TreeSourceProperty on the type ContentPresenter"
I've been looking around for an answer and I found this answer that simple states that this is impossible.
How to use template binding inside data template in custom control (Silverlight)
So if this really is impossible, how else could I bind the elements inside my template to the DependencyProperties of the CustomControl?
Thanks!
In WPF you can use a binding with RelativeSource targeting the "templated" control.
e.g.
{Binding TreeSource,
RelativeSource={RelativeSource AncestorType=MyCustomControl}}
Edit: If you have a break in a tree you could possibly work around that by passing that control around, e.g.
<ControlThatOwnsPopup
Tag="{Binding RelativeSource={RelativeSource AncestorType=MyCustomControl}}">
<Popup>...
<TreeView ItemsSource="{Binding PlacementTarget.Tag.TreeSource,
RelativeSource={RelativeSource AncestorType=Popup}}"/>

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}">

Correct way to do DataBinding in a WP7 UserControl

I'm building a Windows Phone 7 App. So, I'm using SilverLight 3(.7) and I'm having a bit of a problem with the UserControls that I've built. Here's how I'm doing it now:
UserControl has a DependencyProperty named Number
<UserControl x:Class="MyUserControl" x:Name="myUserControl">
<TextBlock Text="{Binding ElementName=myUserControl, Path=Number}"/>
</UserControl>
To use it I'm simply doing:
<MyUserControl Number="{Binding ElementName=MyPage, Path=SomeNumber}">
<MyUserControl Number="{Binding ElementName=MyPage, Path=SomeOtherNumber}">
This all works great, but if I add a name to one of the two instances of my control, things go wacky. For example:
<MyUserControl x:Name="SomeNumberControl"
Number="{Binding ElementName=MyPage, Path=SomeNumber}">
<MyUserControl Number="{Binding ElementName=MyPage, Path=SomeOtherNumber}">`
Then the data doesn't show up. It appears that the name given overrides the name specified in the UserControl and the bindings don't work.
So, I tried to do binding through the datacontext. And setting the DataContext to the UserControl.
so, my control became:
<UserControl x:Class="MyUserControl"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding Number}"/>
</UserControl>
With this nothing gets bound. It's like the datacontext is never set and even weirder when I put this control in another user control that I created, It appears that the parent control is now somehow bound to the MyUserControl instance.
So when I do the following:
<MyPage>
<MyUserControl Number={Binding SomeNumber}"/>
I get the error,
SomeNumber not found in type MyUserControl.
It's as if the binding for the parent control is now the instance of MyUserControl. Am I just doing something fundamentally wrong in how I'm doing bindings in my user control, or is this something strange with SilverLight 4 and WP7.
Any help on this is greatly appreciated.
If you have a DependecyProperty in your codebehind called Number you can set
DataContext = this;
That should allow you to bind directly to your Number property from TextBlock.
<TextBlock Text="{Binding Number}"/>

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

ElementName not working when assigning DataTemplate from code behind?

I am trying to access Control using ElementName from DataTemplate that is used in different UserControl (Resources) than defined (in xaml).
Imagine this situation:
MyUserControl.xaml with following DataTemplate in resources:
<UserControl.Resources>
<DataTemplate x:Key="SomeTemplate">
<TextBlock Text="{Binding Text, ElementName=TextElement}"/>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<TextBlock x:Name="TextElement" Text="IT WORKS! (not...)"/>
</Grid>
</UserControl>
MyUserControlWrapper.xaml
<ContentPresenter x:Name="ContentPresenter" Content="{Binding SomeContent}"/>
and in code behind of MyUserControlWrapper.xaml i set ContentTemplate of ContentPresenter from MyUserControl.xaml:
something like:
ContentPresenter.ContentTemplate = (DataTemplate)childView.Resources["SomeTemplate"];
Is it possible to use ElementName from resources that are defined outside UserControl?
How DataTemplate searches for ElementName in same UserControl then? Maybe its possible to set something like DataContext for DataTemplate itself for ElementName to work, without messing with DataContext that is sent to controls used inside Template?
You need to review the concepts related to Namescopes.
Briefly names are scoped at the point where a Xaml resources are loaded. For example each UserControl will each load their own Xaml and therefore have their own namescope. In your case you asking MyUserControlWrapper to find a name that its LoadComponent has not seen.
Maybe you can just walk up the VisualTree using RelativeSource and FindAncestor?
There is a nice presentation of different binding variants here:
http://www.wpfwiki.com/Default.aspx?Page=WPF%20Q5.3&AspxAutoDetectCookieSupport=1

Resources