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

你说的曾经没有我的故事 提交于 2019-12-08 23:18:02

问题


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.


回答1:


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>



回答2:


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>



回答3:


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.




回答4:


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




回答5:


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.




回答6:


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.



来源:https://stackoverflow.com/questions/5063308/binding-a-command-inside-a-listbox-item-to-a-property-on-the-viewmodel-parent

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!