How to identify the DataContext of different items in .xaml? - wpf

I have a FilterUserControl with FilterViewModel to be its DataContext.
In FilterControl.xaml:
<Button x:Name="FilterButton">
<Button.ContextMenu PlacementTarget="{x:Reference FilterButton}" ItemsSource="{Binding FilterConditions}" Style="{StaticResource ButtonContextMenu}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<MenuItem Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu, Mode=FindAncestor}, Path=DataContext.ChangeFilterCondition}"
CommandParameter="{Binding}">
...
I searched on Web and knew that
CommandParameter="{Binding}"
is the same as
CommandParameter="{Binding DataContext,RelativeSource={RelativeSource Self}}"
I originally thought that DataContext would be FilterViewModel But after debugging I found that DataContext was actually "each item of FilterConditions"
I finally got the evidence here ItemsSource vs DataContext in binding case
Now I would like to know in .xaml how do we identify what the DataContext is? What are the typical/common cases? Thanks.

Long story short: In an ItemsControl with an assigned ItemsSource you can be sure that each item has a different DataContext, this means ItemTemplate and ItemContainerStyle. Not the ItemsPanel.
DataContext is the root of binding path, and it remains the same throughout XAML hierarchy unless you change it.
You can change the DataContext explicitly or by changing the ItemsSource. Having an ItemsSource changes the DataContext of each element, so you don't have to take care of indexes.
This is not true when you assign to Items because it implicitly adds them to the ItemCollection and clears ItemsSource. Using Items is similar to when you add items to any other control. i.e. the contents of the DataContext in this case:
<ItemsControl>
<Button Content="{Binding A}"/>
</ItemsControl>
is just like this case:
<StackPanel>
<Button Content="{Binding A}"/>
</StackPanel>
or even:
<Button>
<Button Content="{Binding A}"/>
</Button>
However using ItemsSource means that you're asking the ItemsControl to enumerate through the given collection, take each element, set their DataContext and render them. Therefore the DataContext is changed there.
RelativeSource Self resolves to the current XAML element, so these two are equal:
<... Prop="{Binding Path=Width, RelativeSource={RelativeSource Self}}"/>
<... Prop="{Binding Path=Width, ElementName=name}" x:Name="name"/>
DataContext is always the root object of the binding ({Binding} or {Binding Path=.}), so these three are equal:
<... Prop="{Binding Path=A}"/>
<... Prop="{Binding Path=DataContext.A, RelativeSource={RelativeSource Self}}"/>
<... Prop="{Binding Path=DataContext.A, ElementName=name}" x:Name="name"/>
Default Binding Path of all the objects in the object tree always resolves to the same object (unless they are changed). e.g. If grid.DataContext=A then A is the Binding root for all the objects inside grid object tree hierarchically.
Note that, you can either change DataContext in the code (preferably in the view's constructor), or you can "bind" the DataContext to have different scopes, so that this view:
<Grid DataContext="{Binding}"> // this is redundant and points to VM
<Grid DataContext="{Binding Child1}">
<Button Command="{Binding Action11}"/>
<Button Command="{Binding Action12}"/>
</Grid>
<Grid DataContext="{Binding Child2}">
<Button Command="{Binding Action21}"/>
<Button Command="{Binding Action22}"/>
</Grid>
<ItemsControl ItemsSource="{Binding Collection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
DataContext="{Binding}" // this is redundant and points to an item
Command="{Binding ElementAction}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
perfectly represents this VM:
VM
Child1
Action11
Action12
Child2
Action21
Action22
Collection
Item1
ElementAction
Item2
ElementAction
Item3
ElementAction
...

There's only one case for an itemscontrol ( or things inherit from itemscontrol ).
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.itemscontrol?view=netcore-3.1
When you bind itemssource to a collection.
What happens is each item in that collection is presented.
Datatemplating then gives an instance of whatever UI you specified in the template.
That row UI appears in your itemscontrol itemspanel.
The row UI has a datacontext of the item.
You can use a datatemplate selector or datatemplates associated with datatype so that you get different UI.
You can change the itemspanel that presents these so it's say a canvas instead of the default stackpanel.
But whatever you do, the datacontext of each will be one of those items in the collection you bound to itemssource.

Related

Binding a button nested in DataTemplate

I need to bind a button's command inside a datatemplate like below:
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="-" Cursor="Hand" Width="50"
Background="Red" x:Name="removeButton"
Command="{Binding Remove}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
Unfortunately it does not work. How can I bind a command in a button insade a datatemplate?
I found that thread in the forum:
Bindings in nested WPF DataTemplates
but the method given by person, who answered this question, does not work as well. I think, that something has changed in WPF since this time, I would you grateful for your help.
If Remove is defined in the view model of the parent ListView, you could bind to it using a RelativeSource:
Command="{Binding DataContext.Remove,
RelativeSource={RelativeSource AncestorType=ListView}}"
You could also set the AncestorType to Window or UserControl depending on where the command property is defined and where the DataTemplate is applied.

Binding ToolTip in UserControl

I tried to bind a ToolTip text in a UserControl this way:
<Grid.ToolTip>
<TextBlock
Text="{
Binding Path=InfoTT,
RelativeSource={
RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}
}
}" />
</Grid.ToolTip>
And it doesn't work, the Tooltip was empty and in logs, I saw:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.UserControl', AncestorLevel='1''. BindingExpression:Path=InfoTT; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')*
But when I did:
<Grid
ToolTip="{
Binding Path=InfoTT,
RelativeSource={
RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}
}
}">
</Grid>
It worked. Can anyone explain why the first way doesn't work?
When Binding.RelativeSource doesn't resolve, you can always be sure that the Binding.Target is not part of the visual tree.
In your first example you are explicitly defining the tree structure of the ToolTip. You are explicitly creating the content e.g. by adding the TextBlock. The content of the ToolTip is not part of the visual tree and therefore the Binding.RelativeSource can't be resolved.
In your second example, you let the FrameworkElement implicitly create the ToolTip content.
Now FrameWorkElement will first resolve the Binding, which resolves, as the FrameworkElement is still part of the visual tree. The resolved value is taken, ToString invoked, a TextBlock created and the string value assigned to TextBlock.Text.
Solution
To solve the binding problem, when implementing the ToolTip explicitly, you can implement a Binding Proxy as suggested in a comment by #Mark Feldman which makes use of the StaticResource markup to provide a Binding.Source to elements that are not part of the visual tree.
It's basically a bindable ObjectDataProvider.
A similar solution to the binding proxy is to define the content as a resource of the Grid and then reference it via DynamicResource using a ContentPresnter:
<UserControl>
<Grid>
<Grid.Resources>
<!-- The proxy -->
<TextBlock x:Key="ToolTipText"
Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=InfoTT}" />
<Grid.ToolTip>
<ToolTip>
<ContentPresenter Content="{DynamicResource ToolTipText}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>
But you could also make use of the fact that the DataContext is still inherited. Bindings to the DataContext will still resolve.
In your scenario, where you want to bind the content of the ToolTip to a property of the parent UserControl, you could bind this property to a property of the view model, which is the current DataContext of Grid (and therefore for its ToolTip). I only recommend this, when binding to business data and not layout data:
<UserControl InfoTT="{Binding ViewModelInfoTT}">
<UserControl.DataContext>
<ViewModel />
</UserControl.DataContext>
<Grid>
<Grid.ToolTip>
<ToolTip>
<TextBlock Text="{Binding ViewModelInfoTT}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>
If you don't use view models and host the data directly in the control, you may like to set the DataContext to the control itself. This way you simplify all bindings and of course can now bind to the UserControl from within the ToolTip:
// Constructor
public MyUserControl()
{
InitializeComponent();
// Set the UserControl's DataContext to the control itself
this.DataContext = this;
}
<UserControl>
<Grid>
<Grid.ToolTip>
<ToolTip>
<TextBlock Text="{Binding InfoTT}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>
Alternatively override the DataContext. Of course you'll lose access to the current context:
<UserControl>
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestoType=UserControl}>
<Grid.ToolTip>
<ToolTip>
<TextBlock Text="{Binding InfoTT}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>

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.

Binding to two different DataContexts in a ContextMenu

I'm trying to bind to a property of a container from inside a DataTemplate. A simplified version of my markup looks like:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type myCustomItem}">
<!--Visual stuff-->
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Item"
Command="{Binding myCustomItemsICommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type CustomContainerType}}, Path=ContainerProperty}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<CustomContainerType/>
</Grid>
My approach is based on this post but it doesn't seem to be working. The issue seems to arise from the placement of the ContextMenu within the visual tree. Basically I am trying to bind the Command to the DataContext of the DataTemplate but bind the CommandParameter to a DataContext outside the DataTemplate.
ContextMenus are not in the same visual tree as the rest of the controls, there are a few questions regarding how to do bindings accross that boundary but this might be somewhat difficult without specifying names.
ElementName fails as well because of the lacking tree connection, but you could use x:Reference in the Binding.Source instead.

How to bind to Tabcontrol.Items

I have a WPF application that I'm trying to dynamically add items to a tabcontrol. I have a list of menu items that should be databound to the tabcontrol's items. The only problem is that TabControl.Items does not notify others that items have been added. I've tested this by binding instead to TabControl.Items.Count and get calls to the converter (but the value passed in is the count and not something useful). Here's the relevent code that doesn't get databound properly because Items doesn't call out updates:
<MenuItem ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=Items, Converter={StaticResource TabControlItemConverter}}">
This MenuItem XAML is inside a ControlTemplate for a TabControl. With static items, i.e., items that are already defined in a TabControl, this code works perfectly. But I have a TabControl that gets items added at runtime and can't seem to update this binding. Has anyone added some sort of attached property to a TabControl that can bind to the Items collection?
Edit for background info
The TabControl that has items added to it is a region (this is a Prism application). Here is the relevent XAML
<TabControl cal:RegionManager.RegionName="{x:Static local:LocalRegionNames.SelectedItemRegion}" >
<TabControl.Resources>
<Style TargetType="TabItem" BasedOn="{StaticResource TabItemStyle}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Style="{StaticResource tabItemImage}" Height="20" />
<TextBlock Text="{Binding Content.DataContext.TabHeader, RelativeSource={RelativeSource AncestorType=TabItem}}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The relevent code for adding a view to the region is here:
ProjectDetailView view = new ProjectDetailView();
ProjectDetailViewModel viewModel = new ProjectDetailViewModel();
viewModel.CurrentProject = project;
view.DataContext = viewModel;
IRegionManager retManager = RegionManager.Regions[LocalRegionNames.SelectedItemRegion].Add(view, null, true);
RegionManager.Regions[LocalRegionNames.SelectedItemRegion].Activate(view);
All this works fine...views get added, the tab control adds items, and views appear. But the Items property on the tabcontrol never broadcasts the changes to its collection.
You do the same thing for TabControls, you bind the ItemsSource, the only thing you need to take into account is that the source collection should implement INotifyCollectionChanged if you want it updated if items are added. ObservableCollection<T> already implements the interface and is often used as source for such bindings.

Resources