将列表框项内的命令绑定到视图模型父级上的属性

Posted

技术标签:

【中文标题】将列表框项内的命令绑定到视图模型父级上的属性【英文标题】:binding a command inside a listbox item to a property on the viewmodel parent 【发布时间】:2011-07-01 02:27:59 【问题描述】:

我已经为此工作了大约一个小时,并查看了所有相关的 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 类,它可以发挥作用。

【问题讨论】:

【参考方案1】:

示例略有不同,但是, 我发现通过在绑定中引用父容器(使用 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>

【讨论】:

当控件是 UserControl 时,此解决方案对我有用。其他解决方案仅链接到根视图模型!【参考方案2】:

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

DataTemplateDataContext 设置为模板正在显示的项目。这意味着你不能只使用绑定而不设置源。

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

要解决这个问题,您需要让绑定到达逻辑树之外。您可以使用定义的 DataContextSpy 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>

【讨论】:

+1 谢谢! =) 小烦人需要解决这个问题?这在 WPF 中不是很常见,您希望列表中的子项绑定到某个父项!? 我正在考虑将 OpenNews 命令添加到 NewsItemViewModel,然后将列表绑定到该列表,这样可以轻松设置命令对吗?无论如何,我也需要一个用于 NewsItem 的 VM 来处理其他一些东西。或者这会是一个mem/per issue吗? 是的,这很常见,但是您认为您可以避免在 DataTemplate 内部执行操作(命令)吗?这也可能是更好的用户体验。 @jobi 怎么样?该列表应该类似于您在 view studio 的起始页中看到的新闻列表。 在这种情况下,命令应该是您的 NewsItem 视图模型的一部分【参考方案3】:

看起来您正试图为 HyperLink 提供正确的 DataContext 以触发 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 类型。

【讨论】:

重置 DataContext 会破坏 TextBlock 上的绑定 当然可以,你可以做同样的想法,使用 StackPanel 作为元素名绑定参考来设置文本框的 dataContext。 +1 @Cam @Jobi 不幸的是,超链接本身没有绑定! =(我正在将 ViewModelLocater 中的 Window DataContext 设置为静态资源。更新了问题?【参考方案4】:

试试这样的

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

他在列表框中找不到您的命令绑定,因为您设置的数据上下文与该列表框的视图模型不同

【讨论】:

工作精美并保持简单。只需忽略 VS 关于 datacontext 是 na 对象的警告,VS 无法查看内部 - 尽管应该有一种方法也可以告诉对象类型并清除警告【参考方案5】:

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

使用此解决方法是从数据模板绑定到视图模型中的命令的另一种方法。

【讨论】:

【参考方案6】:

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

DataGridDataGridTemplateColumn .NET 4 Windows XP

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

在下面的虚构示例中,我们试图绑定到 UserControl.DataContext 上名为 ShowThingCommandICommand(即 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>

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

有关参考,请参阅以下 *** 帖子:

Question 19244111

Question 5834336

...关于为什么 ElementName 在这种情况下不起作用的解释,请参阅 this blog post,其中包含 .NET 4 之前的解决方法。

【讨论】:

不错的解决方案,尽管如果我们可以省略 x:Name="ThisUserControl" 会更好。不过现在我将使用它:-)。【参考方案7】:
<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>

【讨论】:

这个被低估了。非常简单的绑定,没有任何额外的元素或名称。直接绑定到父级的 VARIABLENAME。 Binding="Binding DataContext.VARIABLENAME, RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type ListBox"

以上是关于将列表框项内的命令绑定到视图模型父级上的属性的主要内容,如果未能解决你的问题,请参考以下文章

MVC 绑定到具有列表属性的模型忽略其他属性

从视图模型绑定到 ListView 项目的点击属性

如何绑定到列表作为 MVC 3 Razor 中模型上的属性?

MVC - 两个模型绑定到同一视图的转换问题

将视图模型绑定到列表视图

绑定到基类型上的阴影属性