Search code examples
wpfmvvmbindingicommand

binding a command inside a listbox item to a property on the viewmodel parent


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.


Solution

  • 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>