How can I access the root DataContext in a DataTemplate in WPF? - wpf

I have a grid of items which is populated using databinding.
In the grid I have a DataTemplate for certain cells. I need to access the DataContext of the root element (the one which is hosting the grid) so that I can access additional bindings to support my datatemplate.
So you have:
Window
Window.DataContext = TheDataSourceWithItemsAndSupports
DataGrid.ItemsSource = {Binding Items}
DataTemplate
ListBox.ItemsSource = {Binding Supports}
I want the {Binding Supports} on TheDataSourceWithItemsAndSupports, but I don't see how to do that. I tried specifying {Binding} but that always returns null. I also tried using RelativeSource FindAncestor, but that yields null too.
Any clues?

Maybe try
Window Name="TheWindow"
...
ListBox.ItemsSource = {Binding DataContext.Supports, ElementName=TheWindow}

Another little trick to bind to your root context
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}, AncestorLevel=1}, Path=DataContext.Supports}"/>

My solution was to expose whole DataContext class by implementing This field
get
{
return this;
}
and then binding to it.

It should work the way you describe. Only thing I see your DataTemplate is not ItemTemplate. You should also look at the output window to see where bindings fail.

Related

Binding inside ContentControl not working

I'm building a graphical designer, based upon an article by Sukram in CodeProject. I'm now trying to extend it so that each item on the canvas binds to a different ViewModel object - i.e. I'm setting the DataContext for each item.
Every item on the designer is actually a ContentControl, into which is placed a different template (based upon which toolbox item was dragged onto the canvas). So I have a template containing a TextBox, and I have a ViewModel object containing a Name property, and I bind the Text property of the TextBox to the Name property of the ViewModel, and ... nothing. I've checked the visual tree with Snoop, and it confirms that the DataContext of the TextBox is the ViewModel object. Yet the TextBox remains empty. And if I modify the (empty) Text in the TextBox, the Name property in the ViewModel does not change. So it looks like the binding is not being applied (or has been removed somehow).
I've found a few posts which talk about the ContentControl messing around with the DataContext and Content properties, but I'm not sure how applicable they all are. The code sets the ContentControl.Content as follows:
newItem = new ContentControl();
ControlTemplate template = toolbox.GetTemplate();
UIElement element = template.LoadContent() as UIElement;
ViewModelItem viewModel = new ViewModelItem() { Name = "Bob" };
newItem.Content = element;
newItem.DataContext = viewModel;
and the XAML for the template is:
<ControlTemplate>
<Border BorderBrush="Black" BorderThickness="1" Width="100">
<TextBox Text={Binding Name}/>
</Border>
</ControlTemplate>
Snoop shows that the TextBox has a DataContext, and if I Delve that DataContext I can see that it has a Name property whose value is "Bob". So why does the TextBox remain empty? Snoop allows me to change that Name property, but the TextBox remains empty.
What am I doing wrong?
A few more details. I've set the VS2010 Debug DataBinding option for the OutputWindow to Verbose, which seems to show that the binding is all being attempted before I set the DataContext. Is it possible that the change to the DataContext is not being recognised?
I've just found this post DataTemplate.LoadContent does not preserve bindings - apparently DataTemplate.LoadContent does not preserve bindings. So it looks like I have to write my own version of LoadContent().
I've realised that the template has come through a XamlWriter, which apparently strips all bindings. This wouldn't be helping.
I've not been able to fix the DataTemplate.LoadContent(), but I realised that I didn't actually need a DataTemplate, since the XamlWriter / XamlReader was already instantiating the UI element that I was after. I found a fix to make the XamlWriter write all the bindings here, and after that it all works.
Thanks for your help.
Maybe you need to tell the binding in the ControlTemplate to look at the TemplatedParent, as is mentioned in this thread?
<TextBox Text="{Binding Path=Name, RelativeSource={RelativeSource TemplatedParent}}"/>
Either that, or try to use a DataTemplate instead.
I can't test this at the moment, so I might just be guessing here.
I would use a DataTemplate, as bde suggests.
You are trying to put some UI on your own data (ViewModel), and this is what Data-Templates are meant for (ControlTemplate is usually what you use if you want to change how e.g. a Button looks).
Change your code to use ContentControl.ContentTemplate with a DataTemplate:
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Width="100">
<TextBox Text={Binding Name}/>
</Border>
</DataTemplate>
Code-behind:
newItem = new ContentControl();
//NOTE: .GetTemplate() needs to return a DataTemplate, and not a ControlTemplate:
newItem.ContentTemplate = toolbox.GetTemplate();
ViewModelItem viewModel = new ViewModelItem() { Name = "Bob" };
newItem.Content = viewModel;
newItem.DataContext = viewModel;

Setting RelativeSource correctly

I have a combo box on a xaml form (MainWindow).
I set the Items source to an ObservableCollection in the code behind. To populate the Combo box I used Relative Source (it sits inside an ItemsControl), which worked great (without it, if did not populate):
ItemsSource="{Binding SelectableItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
I have now factored out the ObservableCollection into a seperate View Model Class, named 'MainWindowViewModel', the combo box does not populate.
I have set the DataContext of the MainWindow to my ViewModel and have checked that it populates other controls as expected, which it does.
How should I construct the RelativeSource so the combo box populates?
Thanks
Joe
I needed to add the Path at the end, thus:
ItemsSource="{Binding SelectableItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.SelectableItems}"
You do not want to use a RelativeSource any longer. If you don't specify a RelativeSource (or Source, or ElementName), then the binding will resolve against the current DataContext. Since the DataContext is inherited, your ItemsControl obtains its DataContext from the parent Window. Thus, this binding will resolve against your view model.
ItemsSource="{Binding SelectableItems}"

How do I databind to the control's property rather than the datacontext?

I have a sub control embedded inside my main control, it allows the user to edit an address. Because this is reused all over the place (sometimes in multiple places on one control) I bind it like so
<Controls:EditAddressUserControl DataContext="{Binding Path=HomeAddress}"/>
<Controls:EditAddressUserControl DataContext="{Binding Path=WorkAddress}"/>
But the EditAddressUserControl needs access to the main control's list of CountrySummary objects so it can choose which country the address belongs to.
I have added a Countries DependencyProperty to EditAddressUserControl and added
Countries="{Binding Countries}"
So far all is going well, the EditAddressUserControl.Countries property has the correct countries in it. However, how do I databind my Combobox.ItemsSource to that in XAML?
I still want everything on my EditAddressUserControl to bind to its DataContext, but the ComboBoxCountries.ItemsSource needs to bind to "this.Countries".
How do I do that?
I've tried this
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Controls:EditAddressUserControl}}, Path=Countries}" />
I saw no binding errors in the output box, but I also saw no items in the combobox.
You can accomplish this by using a RelativeSource for the binding source, instead of the DataContext.
This would most likely look something like:
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Controls:EditAddressUserControl}}, Path=Countries}" />
The way to do it was to stop using DataContext completely. Instead I added a DependencyProperty to my control
public static DependencyProperty AddressProperty = DependencyProperty.Register("Address", typeof(EditPostalAddressDto), typeof(EditPostalAddressControl));
Then in the parent control instead of setting DataContext="..." I set Address="..." - The XAML for the control is then changed to include an ElementName on the binding
<UserControl ..... x:Name="MainControl">
<TextBox Text="{Binding ElementName=MainControl,Path=Address.Region}"/>
Now I can specifically bind to the Address property, but also bind to properties on the main data context.

WPF : Binding Order

I have a usercontrol (ItemsView) that I use in one of my other view. Since I need to access its ViewModel, the ItemsViewViewModel is contained by the ViewModel of the view that contained the control. I use this control at many times and I find it useful to bind a collection on the ItemsSource of my ItemsView control (differently, depending on the view that used it). It works, but not all the time.
Here some code :
<local:ItemsView DataContext="{Binding Path=ItemsViewModel}" ItemsSource="{Binding Path=DataContext.CurrentItem.Children, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
The problem is that the DataContext of the ItemsView is sometimes set before the ItemsSource, and sometimes after. This is problematic because the ItemsSource is a dependency property linked to the ItemsViewViewModel.
Is there a way to have the DataContext set before the ItemsSource everytime?
After InitializeComponent, set SelectedIndex to -1 - this worked for me.

WPF won't let me put a binding on the path of a binding -- is there another way?

I have a DataTemplate that I'm using as the CellTemplate for a GridViewColumn.
I want to write something like this for the DataTemplate:
<DataTemplate
x:Key="_myTemplate">
<TextBlock
Text="{Binding Path={Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumn}}, Path=Header}}" />
</DataTemplate>
My GridView is bound to a DataTable, and I want to bind to the column of the DataTable whose name is equal to the Header of the GridViewColumn the DataTemplate is attached to. [I hope that made sense!]
Unfortunately, this doesn't work. I get a XamlParseException: "A 'Binding' cannot be set on the 'Path' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependenceyObject."
How can I set this up?
Edit (elevating comment by DanM to the question)
I basically need a DataTemplate whose binding is determined by the DataContext and which column the DataTemplate is attached to. Is there an alternative?
You cannot assign a Binding to just any property. The property must either of the type Binding or be implemented as a Dependency Property on the object.
The Path property of the Binding class is of type PropertyPath and Binding does not implement the Path property as a dependency property. Hence you cannot dynamically bind the Path in the way you are attempting to.
Edit
You basically want to embed metadata in your bound data which drives the configuration of the DataTemplate. This can't be done in XAML alone. You would need at least some support from code.
It would seem to me that the best approach would be to use a ViewModel. That makes the binding in the XAML straight-forward and pushes this "what to bind with what" decision down into the code of the ViewModel.
Don't you just want this?
{Binding Path=Header, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumn}}}

Resources