21

I've been working on this for about an hour and looked at all related SO questions.

My problem is very simple:

I have HomePageVieModel:

HomePageVieModel
+IList<NewsItem> AllNewsItems
+ICommand OpenNews

My markup:

<Window DataContext="{Binding HomePageViewModel../>
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
 <ListBox.ItemTemplate>
   <DataTemplate>
       <StackPanel>
        <TextBlock>
           <Hyperlink Command="{Binding Path=OpenNews}">
               <TextBlock Text="{Binding Path=NewsContent}" />
           </Hyperlink>
        </TextBlock>
      </StackPanel>
    </DataTemplate>
</ListBox.ItemTemplate>

The list shows fine with all the items, but for the life of me whatever I try for the Command won't work:

<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel, AncestorLevel=1}}">
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel,**Mode=FindAncestor}**}">
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel,**Mode=TemplatedParent}**}">

I just always get :

System.Windows.Data Error: 4 : Cannot find source for binding with reference .....

Update I am setting my ViewModel like this? Didn't think this would matter:

 <Window.DataContext>
        <Binding Path="HomePage" Source="{StaticResource Locator}"/>
    </Window.DataContext>

I use the ViewModelLocator class from the MVVMLight toolkit which does the magic.

gideon
  • 19,329
  • 11
  • 72
  • 113

7 Answers7

30

Slightly different example but, I found that by referencing the parent container (using ElementName) in the binding you can get to it's DataContext and its subsequent properties using the Path syntax. As shown below:

<ItemsControl x:Name="lstDevices" ItemsSource="{Binding DeviceMappings}">
 <ItemsControl.ItemTemplate>
  <DataTemplate>
   <Grid>
    <ComboBox Text="{Binding Device}" ItemsSource="{Binding ElementName=lstDevices, Path=DataContext.AvailableDevices}" />
    ...
   </Grid>
  </DataTemplate>
 </ItemsControl.ItemTemplate>
</ItemsControl>
Darren
  • 309
  • 1
  • 3
  • 2
  • This solution worked for me when the control is a **UserControl**. Other solutions merely linked to the root view model! –  Feb 06 '14 at 02:06
14

There's two issue working against you here.

The DataContext for the DataTemplate is set to the item the template is displaying. This means you can't just use binding without setting a source.

The other issue is that the template means the item is not technically part of the logical tree, so you can't search for ancestors beyond the DataTemplate node.

To solve this you need to have the binding reach outside the logical tree. You can use a DataContextSpy defined here.

<ListBox ItemsSource="{Binding Path=AllNewsItems}">
    <ListBox.Resources>
        <l:DataContextSpy x:Key="dataContextSpy" />
    </ListBox.Resources>

    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock>
                   <Hyperlink Command="{Binding Source={StaticResource dataContextSpy}, Path=DataContext.OpenNews}" CommandParameter="{Binding}">
                       <TextBlock Text="{Binding Path=NewsContent}" />
                   </Hyperlink>
               </TextBlock>
           </StackPanel>
       </DataTemplate>
   </ListBox.ItemTemplate>
</ListBox>
Cameron MacFarland
  • 70,676
  • 20
  • 104
  • 133
  • 2
    +1 Thanks! =) Little annoying one needs a workaround for this? Isn't this kinda thing common in WPF where you want the Child item in a list bound to some parent thing!? – gideon Feb 21 '11 at 08:30
  • I'm thinking of just adding the OpenNews Command to a NewsItemViewModel and then binding the list to that, that would make setting the command easy right? I need a VM for the NewsItem anyway for some other stuff too. Or would this be a **mem/per issue**? – gideon Feb 21 '11 at 08:31
  • yes it is quite common, but do you think you can avoid the action(Command) right inside of the DataTemplate. Which might be a better User experience also. – Jobi Joy Feb 21 '11 at 08:38
  • @jobi How? This list is supposed to be like the news list you see in the start page on view studio. – gideon Feb 21 '11 at 08:43
  • 1
    In that case the Command should be part of your NewsItem Viewmodel – Jobi Joy Feb 21 '11 at 08:49
  • For Windows Phone or other light frameworks you can just put Border instead of DataContextSpy – Alex Sorokoletov Apr 12 '13 at 06:57
9

try something like this

<Button Command="{Binding DataContext.YourCommand,RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"

he can't find your command binding inside the listbox because you set a diffrent datacontext than the viewmodel for that listbox

xtds
  • 2,453
  • 2
  • 19
  • 12
  • Works beautifully and keeps it simple. Just ignore th VS warning about datacontext being na object and VS can't look inside - although there should be a way to tell the object type too and clear the warning – 537mfb Sep 10 '13 at 11:52
8

So looks like you are trying to give the proper DataContext to the HyperLink so as to trigger ICommand. I think a simple element name binding can solve this.

<Window x:Name="window" DataContext="{Binding HomePageViewModel../>
 <ListBox ItemsSource="{Binding Path=AllNewsItems}">
 <ListBox.ItemTemplate>
  <DataTemplate>
   <StackPanel>
    <TextBlock>
       <Hyperlink DataContext="{Binding DataContext ,ElementName=window}" Command="{Binding Path=OpenNews}">
           <TextBlock Text="{Binding Path=NewsContent}" />
       </Hyperlink>
    </TextBlock>
  </StackPanel>
</DataTemplate>

The AncestorType checks only for Visual-Types not for ViewModel types.

Jobi Joy
  • 49,102
  • 20
  • 108
  • 119
  • Resetting the DataContext breaks the binding on the TextBlock – Cameron MacFarland Feb 21 '11 at 08:11
  • Sure it will, You can do the same idea and set the dataContext of the textbox using the StackPanel as the elementname binding reference. – Jobi Joy Feb 21 '11 at 08:19
  • +1 @Cam @Jobi unfortunately the hyperlink itself doesn't bind! =( I am setting my Window DataContext from the ViewModelLocater as a static resource. Updated the question? – gideon Feb 21 '11 at 08:26
2

Well, it's a little bit late, I know. But I have only recently faced the same problem. Due to architectural reasons I decided to use a static viewmodel locator instead of the dataContextSpy.

<UserControl x:Class="MyView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:locator="clr-namespace: MyNamespace"
             DataContext="{Binding Source={x:Static locator:ViewModelLocator.MyViewModel}}" >
    <ListBox ItemsSource="{Binding Path=AllNewsItems}">        

        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock>
                        <Hyperlink Command="{Binding Source={x:Static locator:ViewModelLocator.MyViewModel}, 
                                                     Path=OpenNews}" 
                                   CommandParameter="{Binding}">
                            <TextBlock Text="{Binding Path=NewsContent}" />
                        </Hyperlink>
                    </TextBlock>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</UserControl>

The static viewmodel locator instantiates the view model:

namespace MyNamespace
{
    public static class ViewModelLocator
    {
        private static MyViewModelType myViewModel = new MyViewModelType();
        public static MyViewModelType MyViewModel 
        {
            get
            {
                return myViewModel ;
            }
        }
    }
}

Using this workaround is another way to bind from a data template to a command that is in the viewmodel.

throbi
  • 127
  • 1
  • 3
1

The answer from @Darren works well in most cases, and should be the preferred method if possible. However, it is not a working solution where the following (niche) conditions all occur:

  • DataGrid with DataGridTemplateColumn
  • .NET 4
  • Windows XP

...and possibly in other circumstances too. In theory it should fail on all versions of Windows; but in my experience the ElementName approach works in a DataGrid on Windows 7 upwards but not XP.

In the following fictional example, we are trying to bind to an ICommand called ShowThingCommand on the UserControl.DataContext (which is the ViewModel):

<UserControl x:Name="ThisUserControl" DataContext="whatever...">
    <DataGrid ItemsSource="{Binding Path=ListOfThings}">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Thing">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Button
                            Command="{Binding ElementName=ThisUserControl, Path=ShowThingCommand}"
                            CommandParameter="{Binding Path=ThingId}"
                            Content="{Binding Path=ThingId}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</UserControl>

Due to the DataTemplate not being in the same VisualTree as the main control, it's not possible to reference back up to the control by ElementName.

To solve this, the little known .NET 4 and above {x:Reference} can be used. Modifying the above example:

<UserControl x:Name="ThisUserControl" DataContext="whatever...">
    <DataGrid ItemsSource="{Binding Path=ListOfThings}">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Thing">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Button
                            Command="{Binding Source={x:Reference ThisUserControl}, Path=ShowThingCommand}"
                            CommandParameter="{Binding Path=ThingId}"
                            Content="{Binding Path=ThingId}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</UserControl>

For reference, see the following stackoverflow posts:

Question 19244111

Question 5834336

...and for an explanation of why ElementName doesn't work in this circumstance, see this blog post which contains a pre-.NET 4 workaround.

Community
  • 1
  • 1
Andrew B
  • 364
  • 5
  • 6
  • Nice solution, although it would be nicer if we could omit the `x:Name="ThisUserControl"`. For now I'll be using this though :-). – Stefan May 12 '16 at 10:10
1
<ListBox xmlns:model="clr-namespace:My.Space;assembly=My.Assembly"
         ItemsSource="{Binding Path=AllNewsItems, Mode=OneWay}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock>
                    <Hyperlink Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.(model:MyNewsModel.OpenNews), Mode=OneWay}"
                               CommandParameter="{Binding Path=., Mode=OneWay}">
                        <TextBlock Text="{Binding Path=NewsContent, Mode=OneWay}" />
                    </Hyperlink>
                </TextBlock>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
Alexey
  • 1,826
  • 3
  • 17
  • 20
  • This one is quite underrated. Very simple binding without any extra elements or names. Directly bound to parent's VARIABLENAME. `Binding="{Binding DataContext.VARIABLENAME, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}"` – Nomad Developer Oct 06 '20 at 05:40