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

时间:2017-09-18 13:47:34

标签: c# wpf mvvm data-binding contextmenu

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

VS ContextMenu for Toolboxes

因为这些来自不同的来源。我想过使用复合集合来实现这一目标。一个集合的绑定应该是Command,另一个是IsChecked / IsChecked。我也想使用分隔符。

我遇到的问题是绑定问题。我不能将datatemplate用于完整的menuitem,因为这不包括IsChecked属性。因此,我使用ItemContainerStyle(参见https://stackoverflow.com/a/29130774/5381620)。 只要我只使用1个收集容器并且有1个源,一切都很好。 但是,从其他来源(或分隔符)插入项目将应用"样式"绑定到所有菜单项不适用于'分隔符'会导致异常。

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

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

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

如上所述,我无法使用datatemplate,因为这会导致由分隔符引起的异常,该分隔符不会实现某些属性,例如: IsCheckable。 将样式从ContextMenu.ItemContainerStyle移动到ContextMenu.Resources仅适用于真实的MenuItems (见H.B。在这里回答https://stackoverflow.com/a/18948356/5381620

<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论坛中的以下链接。 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类,它或多或少是这样的,我可以根据menuitem是可检查的还是命令来设置属性。

class ContextMenuItemVM
{

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