50
votes

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:

  1. 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}}}
    
  2. 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.

  3. 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.

2
"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". Actually i think that's incorrect, they are not in the visual tree, you can't reference anything, even using ElementName as you can see. You can work around it for example by defining a DataGridTemplateColumn and specify the binding in the cell templateDtex
@Dtex Sure, and this is where my limit of binding / tree knowledge is slowing me down. I will try a template column, but would appreciate an example if you have one.Nick
Just IMO, but this is why I don't think user controls/custom controls should set their DataContext. It breaks stuff for the consumer!Dan Puzey
Please do not put answers in questions. You can post a separate answer post to share your solution.BartoszKP

2 Answers

91
votes

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.

0
votes

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