将listbox项中的命令绑定到viewmodel parent上的属性

时间:2011-02-21 07:22:38

标签: wpf mvvm binding icommand

我一直在研究这个问题大约一个小时,并查看了所有相关的SO问题。

我的问题非常简单:

我有HomePageVieModel:

HomePageVieModel
+IList<NewsItem> AllNewsItems
+ICommand OpenNews

我的标记:

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

列表显示所有项目都很好,但对于我的生活,无论我尝试使用命令都行不通:

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

我总是得到:

System.Windows.Data错误:4:无法找到带引用的绑定源.....

更新 我正在设置我的ViewModel吗?不认为这很重要:

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

我使用MVVMLight工具包中的ViewModelLocator类来实现神奇。

7 个答案:

答案 0 :(得分:31)

略有不同的例子但是, 我发现通过在绑定中引用父容器(使用ElementName),您可以使用Path语法获取它的DataContext及其后续属性。如下图所示:

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

答案 1 :(得分:13)

这里有两个问题对你不利。

DataContext的{​​{1}}设置为模板显示的项目。这意味着您不能仅在不设置源的情况下使用绑定。

另一个问题是模板意味着该项在技术上不是逻辑树的一部分,因此您无法搜索DataTemplate节点之外的祖先。

要解决此问题,您需要将绑定范围扩展到逻辑树之外。您可以使用已定义的DataContextSpy here

DataTemplate

答案 2 :(得分:8)

看起来您正在尝试将适当的DataContext提供给HyperLink以触发ICommand。 我认为一个简单的元素名称绑定可以解决这个问题。

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

AncestorType仅检查Visual-Types而不是ViewModel类型。

答案 3 :(得分:6)

尝试这样的事情

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

他在列表框中找不到你的命令绑定,因为你为该列表框设置了一个不同于该模板的视图模板的不同的数据文本

答案 4 :(得分:2)

嗯,这有点晚了,我知道。但我最近才遇到同样的问题。由于架构原因,我决定使用静态viewmodel定位器而不是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>

静态视图模型定位器实例化视图模型:

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

使用此变通方法是从数据模板绑定到viewmodel中的命令的另一种方法。

答案 5 :(得分:1)

@Darren的答案在大多数情况下效果很好,如果可能的话应该是首选的方法。但是,如果出现以下(利基)条件,则不是一个有效的解决方案:

  • DataGrid DataGridTemplateColumn
  • .NET 4
  • Windows XP

......也可能在其他情况下。理论上它应该在所有版本的Windows上失败;但根据我的经验, ElementName 方法适用于Windows 7上的 DataGrid ,但不适用于XP。

在下面的虚构示例中,我们尝试绑定到 UserControl.DataContext (即ViewModel)上名为 ShowThingCommand ICommand ):

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

由于 DataTemplate 与主控件不在同一个VisualTree中,因此无法通过 ElementName 引用备份到控件。

要解决此问题,可以使用鲜为人知的.NET 4及以上 {x:Reference} 。修改上面的例子:

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

供参考,请参阅以下stackoverflow帖子:

Question 19244111

Question 5834336

...并且为了解释为什么 ElementName 在这种情况下不起作用,请参阅包含pre-.NET 4解决方法的this blog post

答案 6 :(得分: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>