65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF:ContextMenu再次出击。DataContext未更新

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (16投票s)

2011年2月27日

Apache

2分钟阅读

viewsIcon

83021

downloadIcon

1330

当父级的 DataContext 更改时,ContextMenu 的 DataContext 未更新

简而言之

你可能已经知道,ContextMenu 类并非 WPF 视觉树的一部分(参见 这里这里,了解由此引起的一些问题)。

尽管如此,ContextMenu 会获取其父控件的 DataContext(自 .NET 3.5 起?参见 这里)。问题在于,这种获取只发生一次。如果父控件的数据上下文随后发生变化,ContextMenu 的数据上下文将不会更新。如果包含上下文菜单的视图绑定到可变内容(例如 listbox 的选定项目),这将导致问题。

解决方法

解决方法是显式地将菜单的数据上下文绑定到父控件的 datacontext,如下所示

<ContextMenu DataContext="{Binding PlacementTarget.DataContext, 
	RelativeSource={RelativeSource Self}}" >

这个神奇的咒语告诉 WPF 在菜单的数据上下文和其“放置目标”(即父控件)的数据上下文之间创建永久绑定,即使父控件的数据上下文发生变化,该绑定仍然有效。只有当你预计父控件的数据上下文在其生命周期内会发生变化时,才需要这个咒语。

示例

Sample screen shot

我创建了一个简单的示例来演示这个问题。它包含一个带有国家/地区名称的列表框和两个用户控件:“好”和“坏”。两个控件都显示国家/地区的首都。它们还具有右键单击菜单,以显示国家/地区的语言。“好”控件显示所选国家/地区的正确语言。“坏”控件在第一次调用菜单时显示正确的语言,然后即使所选国家/地区发生变化,也继续显示该语言。

这是因为菜单首先在右键单击时创建,然后从父控件获取(正确的)数据上下文。在后续的右键单击中,将重用相同的菜单对象(由“Same menu?”命令证明),并且其数据上下文不会改变,除非我们为其创建显式绑定。

以下是一些关键代码片段(为了清晰起见,省略了某些细节,包括“Same menu”命令)

class Country
{
    public string Name { get; set; }
    public string Capital { get; set; }
    public string Language { get; set; }
}

static class Countries
{
    public static readonly Country[] List = new[]
    {
        new Country { Name = "USA", Capital="Washington", Language="English"},
        new Country { Name = "Spain", Capital="Madrid", Language="Spanish"},
        new Country { Name = "France", Capital="Paris", Language="French"},
        new Country { Name = "Brazil", Capital="Brasilia", Language="Portuguese"},
        new Country { Name = "Thailand", Capital="Bangkok", Language="Thai"},
    };
 } 

class LanguageCommand : ICommand
{
    public void Execute(object parameter)
    {
        object safeParameter = parameter ?? "null";
        MessageBox.Show(safeParameter.ToString());
    }
}
<!-- MainWindow.xaml -->
<Window Title="MainWindow" Height="350" Width="525">
    <DockPanel LastChildFill="True">
        <ListBox Name="CountryList" ItemsSource="{x:Static local:Countries.List}" />
        <UniformGrid Rows="2" Columns="1" DataContext="{Binding SelectedItem, 
		ElementName=CountryList}">
            <local:GoodControl />
            <local:BadControl />
        </UniformGrid>
    </DockPanel>
</Window>
        
<!-- GoodControl.xaml -->
<UserControla x:Class="ContextMenuDataContext.GoodControl">
    <UserControl.Resources>
        <local:LanguageCommand x:Key="LanguageCommand" />
    </UserControl.Resources>
    <UserControl.ContextMenu>
        <ContextMenu DataContext="{Binding PlacementTarget.DataContext, 
		RelativeSource={RelativeSource Self}}" >
            <MenuItem Header="Language" Command="{StaticResource LanguageCommand}" 
		CommandParameter="{Binding Language}" />
        </ContextMenu>
    </UserControl.ContextMenu>
    <TextBlock Text="{Binding  Capital}" />
</UserControl>

<!-- BadControl.xaml -->
<UserControl x:Class="ContextMenuDataContext.BadControl">
    <UserControl.Resources>
        <local:LanguageCommand x:Key="LanguageCommand" />
    </UserControl.Resources>
    <UserControl.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Language" Command="{StaticResource LanguageCommand}" 
		CommandParameter="{Binding Language}" />
        </ContextMenu>
    </UserControl.ContextMenu>
    <TextBlock Text="{Binding Capital}" />
</UserControl />

结论

默认的数据上下文绑定机制在大多数情况下都能很好地工作,因为大多数视图在其生命周期内都不会改变数据上下文。问题出现在数据上下文发生变化时。最令人恼火的问题是,即使数据上下文发生变化,第一次调用菜单时一切都会正常工作。但是,后续调用将返回陈旧的数据。这可能会被忽略一段时间。显式地将上下文菜单的数据上下文绑定到 PlacementTarget.DataContext 以避免此错误。

历史

  • 2011 年 2 月 27 日:初始发布
© . All rights reserved.