WPF Binding to parent DataContext - c#

We have a WPF application with a standard MVVM pattern, leveraging Cinch (and therefore MefedMVVM) for View -> ViewModel resolution. This works well, and I can bind the relevant controls to properties on the ViewModel.
Within a particular View, we have an Infragistics XamGrid. This grid is bound to an ObservableCollection on the ViewModel, and displays the appropriate rows. However, I then have a specific column on this grid which I am trying to bind a TextBox text value to a property on the parent DataContext, rather than the ObservableCollection. This binding is failing.
We've gone through several options here including:
Using AncestorType to track up the tree and bind to the DataContext of the parent UserControl like so (from the great answer to this question, as well as this one)...
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
Specifying the ElementName and trying to target the top level control directly. Have a look here if you'd like to read about using ElementName.
Using a 'proxy' FrameorkElement defined in the resources for the UserControl to try and 'pass in' the context as required. We define the element as below, then reference as a static resource...
<FrameworkElement x:Key="ProxyContext" DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}"></FrameworkElement>
In this case the binding finds the FrameworkElement, but can not access anything beyond that (when specifying a Path).
Having read around, it looks quite likely that this is caused by the Infragistics XamGrid building columns outside of the tree. However, even if this is the case, at least options 2 or 3 should work.
Our last thoughts are that it is related to the V - VM binding, but even using Snoop we've yet to find what the exact issue is. I'm by no means an expert with WPF binding so any pointers would be appreciated.
EDIT: I have found some templating examples from Infragistics here that I will try.
EDIT 2: As pointed out by #Dtex, templates are the way to go. Here is the relevant snippet for use with a XamGrid:
<ig:GroupColumn Key="CurrentDate">
<ig:GroupColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.CurrentDateTest, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</DataTemplate>
</ig:GroupColumn.HeaderTemplate>
<ig:GroupColumn.Columns>
I've left the XML open... you'd simply add the columns you wanted, then close off the relevant tags.

I dont know about XamGrid but that's what i'll do with a standard wpf DataGrid:
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.MyProperty, RelativeSource={RelativeSource AncestorType=MyUserControl}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding DataContext.MyProperty, RelativeSource={RelativeSource AncestorType=MyUserControl}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Since the TextBlock and the TextBox specified in the cell templates will be part of the visual tree, you can walk up and find whatever control you need.

Because of things like this, as a general rule of thumb, I try to avoid as much XAML "trickery" as possible and keep the XAML as dumb and simple as possible and do the rest in the ViewModel (or attached properties or IValueConverters etc. if really necessary).
If possible I would give the ViewModel of the current DataContext a reference (i.e. property) to the relevant parent ViewModel
public class ThisViewModel : ViewModelBase
{
TypeOfAncestorViewModel Parent { get; set; }
}
and bind against that directly instead.
<TextBox Text="{Binding Parent}" />

Related

Cannot get Two Way Template Binding (WPF) to work

I need help trying to understand why this is not working. According to MSDN, TemplateBinding is what should be used when binding the property of a control in a template to a property of the control implementing the template.
Except that Template Binding is not two-way. For two-way you need to use binding and then specify the relative source as TemplatedParent.
So I have the following XAML:
template
<ItemContainerTemplate x:Key="colHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
<ToggleButton Style="{StaticResource ToggleButtonStyle}" IsChecked="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay, Path=(props:VisibilityHelper.IsGroupCollapsed)}"/>
</StackPanel>
</ItemContainerTemplate>
which is used here
<dxg:GridColumn x:Name="Total" Header="Total" FieldName="field1" Width="Auto" HorizontalHeaderContentAlignment="Center" props:VisibilityHelper.IsGroupCollapsed="False" HeaderTemplate="{StaticResource colHeaderTemplate}">
<dxg:GridColumn.EditSettings>
<dx:TextEditSettings HorizontalContentAlignment="Center"/>
</dxg:GridColumn.EditSettings>
</dxg:GridColumn>
The toggle button in the template must set a dependency property on the grid column. This works fine when the template is binding to a parent ie. the controls are nested,
I just can't figure out what I am doing wrong.
MSDN ref - http://msdn.microsoft.com/en-us/library/ms742882.aspx
One of the many SO posts about this - In WPF, why doesn't TemplateBinding work where Binding does?
Thank you
Right so I have found the solution. Firstly DataTemplate does work. As #Quercus, it is all in the binding to the correct control.
In my case not the GridColumn but the GridColumnHeader. So this
IsChecked="{Binding RelativeSource={RelativeSource AncestorType=dxg:GridColumnHeader}, Path=DataContext.(props:VisibilityHelper.IsGroupCollapsed)}"
works perfectly...when bound to the correct parent.
Also as #Quercus stated, the template is actually nested and that is why this works. I used a tool called Snoop which actually shows you the visual tree of the application and then the datacontext of the selected element. Using this I solved this issue as well as 2 others I was having.
I really hope this helps someone somewhere before everyone goes to MAUI or WinUI 3.

Control not inheriting datacontext from parent?

Is there any reason for a control not to inherit the datacontext from its parent?
I've inherited a rather ugly piece of WPF code, which I've been tasked with updating. I want to change some text blocks from being hard coded strings to data bound values.
The basic layout I've got is
<Grid>
<TextBlock Text="The Caption" />
</Grid>
which I've changed to
<Grid>
<TextBlock Text="{Binding BoundCaption}" />
</Grid>
However when I run the application, the textblock is blank.
Using WPF Snoop I can see that the grid has the expected datacontext (the view's viewmodel), but the datacontext of the textblock is null. If it makes any difference, the grid is actually used in a C1TabItem header (Component One library), so the visual tree is a mass of borders, content presenters, layout rounders etc. So far as I can tell there are no styles defined in the application for textblock.
I know I can get around this by directly setting the datacontext of the textblock explictly using
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}"
but this ugly hack shouldn't be necessary.

Is it possible to bind to a property on the container of the Adorned Element?

In my WPF app we are using an adorner for displaying validation messages, in the particular case there is a single row grid that has multiple controls some of which have validation. The problem I'm having is that I want to force the width of the error message control to be the same as the grid but can't seem to find a way to reference that grid from the adorner template. Here is a sample of what I tried:
<ControlTemplate x:Key="Local_TopAdornedTemplateWide">
<StackPanel>
<AdornedElementPlaceholder x:Name="adornedElement"/>
<TextBlock MaxWidth="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType=Grid}, ElementName=adornedElement}"
TextWrapping="Wrap"
Text="{Binding Converter={StaticResource Local_ValidationErrorMessageConverter}}"
Style="{DynamicResource Error_Text}"
Padding="2 1 0 0"
Visibility="{Binding ElementName=adornedElement, Mode=OneWay, Path=AdornedElement.IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</StackPanel>
</ControlTemplate>
This causes the application to crash with an XamlParseException.
Ideally the solution would not be specific to a grid so that it would get the width of any container type, but for now grid is the only use case.
Edit:
Here is an example of another template we use in the application; this template would not work for my case as it would limit the error to be the width of a single column of the aforementioned grid:
<ControlTemplate x:Key="Local_TopAdornedErrorTemplate">
<StackPanel>
<AdornedElementPlaceholder x:Name="adornedElement"/>
<TextBlock MaxWidth="{Binding ElementName=adornedElement, Path=ActualWidth}"
TextWrapping="Wrap"
Text="{Binding Converter={StaticResource Local_ValidationErrorMessageConverter}}"
Style="{DynamicResource Error_Text}"
Padding="2 1 0 0"
Visibility="{Binding ElementName=adornedElement, Mode=OneWay, Path=AdornedElement.IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</StackPanel>
</ControlTemplate>
Using snoop I captured the following two screenshots (I could not take one of the full stack to prevent posting anything proprietary)
This shot shows the grid I mentioned previously, within this it is the FinancialTextBox item that is being adorned
This shot shows two things, the item selected in blue is the highest ancestor of the grid in the previous shot, the yellow highlight is the Textbox from the content template
With those two it seems to be apparent that (based on information from Contango's answer) the two items aren't not in the same visual tree which would lead me to believe my question is not possible. However the second template I added (which does work) points that at least some visual information from the adorned element lives on in the place holder.
So now my question boils down to a) does this information include the parent of the adorned element and b) how can this be accessed via a binding on a different element?
This ended up being a lot simpler than the path I was trying to go down.
I was doing some reading on the AdornedElementPlaceholder class and came across this entry on MSDN and noticed that the class actually has a property called parent, with that I tried the following binding and it works perfectly:
MaxWidth="{Binding ElementName=adornedElement,
Mode=OneWay,
Path=AdornedElement.Parent.ActualWidth}"
WPF is quite powerful and flexible.
You can bind any property in any XAML tag to any property in any other XAML tag.
For example, you could write a test app that binds the Text property of an input box to the Text property of a label, so as you type something into the text box, the label would change automatically (assuming you use UpdateSourceTrigger=PropertyChanged). This is a direct XAML to XAML binding, with no C# in sight.
Similarly, you could bind the width of your error box to the width of the parent control, whatever that may be.
Google RelativeSource and AncestorType, this is a great link:
http://druss.co/2013/10/wpf-binding-examples/
See if you can grok how the Visual Tree and Logical Tree works in WPF, once you understand that, you will understand more of how binding works.
I'd also recommend using the free tool Snoop to look at the Visual Tree. XAML Spy is excellent, but not free.
Snoop can tell you if there is anything that has a bad binding at runtime (you set the filters up, and it will list all bad bindings).
You can use Snoop to get the full XAML path of your source (the XAML you wrote above), then get the full XAML path of the target (i.e. the ActualWidth of your Grid), then compare them: it may be quickly apparent that one is not the ancestor of the other, as they are on different branches of the visual tree, or that there is some other issue which is preventing a simple walk up the visual tree from working.
If you just want to get something working, as a proof of concept, try naming the target XAML grid using x:Name, and reference it by name instead of AncestorType.

WPF Binding with RelativeSource and AncestorType

I have a WPF application. It has a grid that is split into 4 rows. In the 2 row I have a datagrid that has its datacontext set to an object of OrderBlock. This all works fine. However I would like one of the column header text value of the datagrid be bound to a property in my view model.
Below is an example of what I have tried unsuccessfully.
<DataGridTextColumn Header="{Binding RelativeSource={RelativeSource
AncestorType={x:Type Window}}, Path=ColumnHeadInfo}"
Binding="{Binding RejectReason}" IsReadOnly="True"/>
Window does not have a property called ColumnHeadInfo, but I assume your ViewModel is your Window's DataContext, and this probably has this property?!
If so, please try this instead:
Path=DataContext.ColumnHeadInfo
EDIT:
Since this alone did not solve your problem: The reason could be that the DataGridColumn is not part of the visual tree and thus cannot find any parent elements because it does not have any. Therefore, the RelativeSource AncestorType binding does not yield any result. This should be indicated by a warning in your output window. Probably, this link may help you.
instead of controls i use "marker interfaces" to find the right datacontext.
"marker interfaces" just empty interfaces:
public interface IDataContextColumnHeadInfoMarker{}
the view wich i know has the right datacontext is marked with the interface
public partial class MyViewWiththeDatagrid : UserControl, IDataContextMarkerLadeSolldatenCommand
and now i can use the interface with relativesource binding instead of controls or ancestorlevel, that makes thing easier for some cases :)
<DataGridTextColumn Header="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:IDataContextColumnHeadInfoMarker}}, Path=DataContext.ColumnHeadInfo}"

Is there a cleaner way to Bind property to owner's DataContext?

I have some code that looks like this:
<Expander Header="{Binding SelectedSlot.Name}"
Visibility="{Binding ShowGroupSlot, Converter={StaticResource BooleanToVisibility}}">
<Controls:GroupPrototypeSlotControl Slot="{Binding DataContext.SelectedSlot,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Expander}}}" />
</Expander>
This works, but the ugliness of the Slot Binding bothers me. This is required because the GroupPrototypeSlotControl has a GroupPrototypeViewModel as its DataContext. If I simply use {Binding SelectedSlot}, it attempts to resolve it on the 'child' ViewModel, which fails. I get around this by explicitly looking at the DataContext of my parent control. Is there a cleaner way to do this type of binding?
EDIT: I found a cleaner way of resolving my problem, though it still feels like a hack. I modified the GroupPrototypeSlotControl so that it has a top-level LayoutRoot (a StackPanel, in this case) and then set the DataContext of LayoutRoot to the ViewModel rather than setting the DataContext of the entire control. This allows me to use the {Binding SelectedSlot} syntax where I use the control (since the control still has the parent DataContext), at the cost of slightly increasing the complexity of the control. In general, this is probably the better pattern for a custom control, since the consumer of the control expects a {Binding} to resolve to their parent DataContext if one isn't explicitly specified.
A slightly cleaner (shorter) way is to use ElementName in your Binding like this:
<Expander Header="{Binding SelectedSlot.Name}"
x:Name="expander"
Visibility="{Binding ShowGroupSlot, Converter={StaticResource BooleanToVisibility}}">
<Controls:GroupPrototypeSlotControl Slot="{Binding DataContext.SelectedSlot, ElementName=expander}" />
</Expander>

Categories