来自不同来源的上下文菜单:为不同的菜单项设置不同的数据绑定

Contextmenu from different sources: Set different Databinding for different menuitems

我想实现上下文菜单行为,例如visual studio 具有工具栏、可检查项目列表和命令列表。 上下文菜单项应来自视图模型中的某些可观察集合。

VS ContextMenu for Toolboxes

因为这些来自不同的来源。我想到了使用复合集合来实现这一点。一个集合的绑定应该绑定到 Command,其他的绑定到 IsChecked/IsChecked。我也想使用分隔符。

我遇到的问题是关于绑定的。我不能对完整的菜单项使用数据模板,因为这不包括 IsChecked 属性。因此,我为此使用了 ItemContainerStyle(请参阅 )。 只要我只使用 1 个收集容器并有 1 个来源,一切都很好。 但是,从另一个来源(或分隔符)插入项目会将 "style" 绑定应用到所有菜单项,这不是预期的,如果 'Separator' 将导致异常。

<ContextMenu>
    <ContextMenu.Resources>
        <CollectionViewSource x:Key="ContextMenuColCollection" Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path= DataContext.HeaderContextMenu}"/>
    </ContextMenu.Resources>
    <ContextMenu.ItemTemplate>
        <DataTemplate DataType="{x:Type vm:Collection1VM}" >
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>
    </ContextMenu.ItemTemplate>
    <ContextMenu.ItemsSource>
        <CompositeCollection>
            <MenuItem Header="Settings"/>
            <Separator />
            <CollectionContainer Collection="{Binding Source={StaticResource ContextMenuColCollection}}"/>
        </CompositeCollection>
    </ContextMenu.ItemsSource>
    <ContextMenu.ItemContainerStyle>
        <Style TargetType="{x:Type MenuItem}">
            <Setter Property="IsCheckable" Value="True"/>
            <Setter Property="IsChecked" Value="{Binding IsSelected}"/>
        </Style>
    </ContextMenu.ItemContainerStyle>
</ContextMenu>

DataTemplate 移动到 <ContextMenu.Resources> 并删除 ItemTemplate:

<ContextMenu>
    <ContextMenu.Resources>
        <CollectionViewSource x:Key="ContextMenuColCollection" Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path= DataContext.HeaderContextMenu}"/>
        <DataTemplate DataType="{x:Type vm:Collection1VM}" >
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>
        <local:Converter x:Key="conv" />
    </ContextMenu.Resources>
    <ContextMenu.ItemsSource>
        <CompositeCollection>
            <MenuItem Header="Settings"/>
            <Separator />
            <CollectionContainer Collection="{Binding Source={StaticResource ContextMenuColCollection}}"/>
        </CompositeCollection>
    </ContextMenu.ItemsSource>
    <ContextMenu.ItemContainerStyle>
        <Style TargetType="{x:Type MenuItem}">
            <Setter Property="IsCheckable" Value="True"/>
            <Setter Property="IsChecked" Value="{Binding Path=., Converter={StaticResource conv}}"/>
        </Style>
    </ContextMenu.ItemContainerStyle>
</ContextMenu>

那么 DataTemplate 应该只应用于 Collection1VM 个对象。

当涉及到 IsChecked 属性 时,您可以忽略任何绑定警告或实施转换器,例如:

public class Converter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Collection1VM vm = value as Collection1VM;
        return vm != null && vm.IsChecked;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}

经过多次尝试,我终于找到了适合我的解决方案。不幸的是,它包含许多针对不同内容的解决方法,并不是一个直接的解决方案。我无法相信创建一个简单的上下文菜单如此困难。

如问题所述,我无法使用数据模板,因为这会导致由分隔符引起的异常,分隔符没有实现某些属性,例如IsCheckable。 将样式从 ContextMenu.ItemContainerStyle 移动到 ContextMenu.Resources 仅适用于真正的 MenuItems (请参阅 H.B。此处的回答 )

<ContextMenu>
    <ContextMenu.Resources>
        <Style TargetType="MenuItem">
            <Setter Property="Header" Value="{Binding Name}"/>
            <Setter Property="IsCheckable" Value="{Binding IsCheckable}"/>
            <Setter Property="IsChecked" Value="{Binding IsChecked}"/>
            <Setter Property="Command" Value="{Binding Cmd}"/>
            <!-- this is necessary to avoid binding error, see explanation below-->
            <Setter Property="HorizontalContentAlignment" Value="Left"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
        </Style>
        <!-- collectionViewSource necessary for behavior described here
        https://social.msdn.microsoft.com/Forums/vstudio/en-US/b15cbd9d-95aa-47c6-8068-7ae9f7dca88a/collectioncontainer-does-not-support-relativesource?forum=wpf
        -->
        <CollectionViewSource x:Key="MenuCmds" Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path= DataContext.CmdObsColl}"/>
        <CollectionViewSource x:Key="MenuCheckable" Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path= DataContext.CheckableObsCol}"/>
    </ContextMenu.Resources>
    <ContextMenu.ItemsSource>
        <CompositeCollection>
            <CollectionContainer Collection="{Binding Source={StaticResource MenuCmds}}"/>
            <Separator />
            <CollectionContainer Collection="{Binding Source={StaticResource MenuCheckable}}"/>
        </CompositeCollection>
    </ContextMenu.ItemsSource>
</ContextMenu>

如果使用多个Collection容器,仍然会出现一些奇怪的绑定错误,可以通过在Application.Resources中添加以下内容来处理。有关详细信息,请参阅来自 msdn 论坛的以下 link。 https://social.msdn.microsoft.com/Forums/vstudio/en-US/42cd1554-de7a-473b-b977-ddbd6298b3d0/binding-error-when-using-compositecollection-for-menuitems?forum=wpf

我仍然不明白的是,如果我只在 Application.Resources 或 Context.Resources 中设置 ContentAlignment,为什么仍然会出现绑定错误。由于某种原因,有必要同时设置两者。如果有人能给我解释一下,我会很高兴。

<Application.Resources>
    <Style TargetType="MenuItem">
        <Setter Property="HorizontalContentAlignment" Value="Left" />
        <Setter Property="VerticalContentAlignment" Value="Center" />
    </Style>
</Application.Resources>

对于绑定,我使用了一些 MenuItemVM class,它或多或少像这样,我可以在其中设置属性,具体取决于菜单项应该是可检查的还是命令。

class ContextMenuItemVM
{

    public string Name { get; }
    public bool IsCheckable { get; }
    public bool IsChecked { get; set; }
    public ICommand Cmd { get; }
}