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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5063308/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 22:36:19  来源:igfitidea点击:

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

wpfmvvmbindingicommand

提问by gideon

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

我已经为此工作了大约一个小时,并查看了所有相关的 SO 问题。

My problem is very simple:

我的问题很简单:

I have HomePageVieModel:

我有 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 .....
System.Windows.Data 错误:4:无法找到与引用绑定的源.....

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

更新我正在像这样设置我的 ViewModel?没想到这会很重要:

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

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

我使用了 MVVMLight 工具包中的 ViewModelLocator 类,它具有魔力。

采纳答案by Cameron MacFarland

There's two issue working against you here.

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

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

DataContextDataTemplate被设置为模板显示的项目。这意味着您不能只使用绑定而不设置源。

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

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

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

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

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

回答by Darren

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:

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

回答by Jobi Joy

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.

所以看起来您正在尝试为 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>

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

AncestorType 仅检查 Visual-Type,而不检查 ViewModel 类型。

回答by xtds

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

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

回答by throbi

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.

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

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

回答by Andrew B

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:

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

  • DataGridwith DataGridTemplateColumn
  • .NET 4
  • Windows XP
  • 数据网格DataGridTemplateColumn
  • .NET 4
  • 视窗 XP

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

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

In the following fictional example, we are trying to bind to an ICommandcalled ShowThingCommandon the UserControl.DataContext(which is the ViewModel):

在以下虚构示例中,我们尝试绑定到UserControl.DataContext(即 ViewModel)上名为ShowThingCommandICommand

<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 DataTemplatenot being in the same VisualTree as the main control, it's not possible to reference back up to the control by ElementName.

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

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

为了解决这个问题,可以使用鲜为人知的 .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>

For reference, see the following stackoverflow posts:

作为参考,请参阅以下 stackoverflow 帖子:

Question 19244111

问题 19244111

Question 5834336

问题 5834336

...and for an explanation of why ElementNamedoesn't work in this circumstance, see this blog postwhich contains a pre-.NET 4 workaround.

...要解释为什么ElementName在这种情况下不起作用,请参阅此博客文章,其中包含 .NET 4 之前的解决方法。