来自不同来源的上下文菜单:为不同的菜单项设置不同的数据绑定
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; }
}
我想实现上下文菜单行为,例如visual studio 具有工具栏、可检查项目列表和命令列表。 上下文菜单项应来自视图模型中的某些可观察集合。
VS ContextMenu for Toolboxes
因为这些来自不同的来源。我想到了使用复合集合来实现这一点。一个集合的绑定应该绑定到 Command,其他的绑定到 IsChecked/IsChecked。我也想使用分隔符。
我遇到的问题是关于绑定的。我不能对完整的菜单项使用数据模板,因为这不包括 IsChecked 属性。因此,我为此使用了 ItemContainerStyle(请参阅
<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; }
}