【问题标题】:Contextmenu from different sources: Set different Databinding for different menuitems来自不同来源的上下文菜单:为不同的菜单项设置不同的数据绑定
【发布时间】:2017-09-18 13:47:33
【问题描述】:

我想实现上下文菜单行为,例如Visual Studio 具有用于工具栏、可检查项目列表和命令列表。 上下文菜单项应该来自视图模型中的一些 observablecollection。

VS ContextMenu for Toolboxes

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

我遇到的问题是关于绑定。我不能将数据模板用于完整的菜单项,因为它不包括 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>

【问题讨论】:

  • 如果我的方法有点愚蠢或者有更好的方法来实现这种类型的上下文菜单,这也会对我有很大帮助。

标签: c# wpf mvvm data-binding contextmenu


【解决方案1】:

在尝试了很多之后,我终于找到了适合我的解决方案。不幸的是,它包含许多针对不同内容的解决方法,并不是一个直接的解决方案。我不敢相信创建一个简单的上下文菜单有这么难。

如问题所述,我无法使用数据模板,因为这会导致由分隔符引起的异常,该分隔符未实现某些属性,例如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; }
}

【讨论】:

    【解决方案2】:

    DataTemplate 移动到&lt;ContextMenu.Resources&gt; 并删除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;
        }
    }
    

    【讨论】:

    • 我没有想到ValueConverter。使用 ValueConverter,我应该能够对来自不同来源的不同 元素做出反应。所以这部分我会得到解决。但是, 仍然会抛出异常,因为它没有任何属性 'IsChecked' 或 'IsCheckable'。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-26
    • 1970-01-01
    • 2014-09-25
    • 2012-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多