【问题标题】:How to avoid repeating blocks of XAML in a menu如何避免在菜单中重复 XAML 块
【发布时间】:2020-12-03 22:04:36
【问题描述】:

我必须创建一个菜单。它有 10 个条目,它们之间只有一个参数。

条目 1:

<MenuItem Visibility="{Binding MenuSelected.Type, Converter={StaticResource TypeToVisibilityConverter}, ConverterParameter='PAZ', Mode=OneWay}">
   <i:Interaction.Triggers>
      <i:EventTrigger EventName="Click">
         <i:InvokeCommandAction Command="{Binding CmdContextMenu}" CommandParameter="PAZ" />
      </i:EventTrigger>
   </i:Interaction.Triggers>
   <MenuItem.Header>
      <Grid>
         <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
         </Grid.ColumnDefinitions>
         <TextBlock
                                        Grid.Column="0"
                                        HorizontalAlignment="Center"
                                        VerticalAlignment="Center"
                                        FontFamily="Segoe MDL2 Assets"
                                        Foreground="{Binding MenuSelected.Type, Converter={StaticResource TypeToColorConverter}, ConverterParameter='PAZ', Mode=OneWay}"
                                        Text="{Binding MenuSelected.Type, Converter={StaticResource TypeToIconConverter}, ConverterParameter='PAZ', Mode=OneWay}" />
         <TextBlock
                                        Grid.Column="1"
                                        Margin="{StaticResource XSmallLeftMargin}"
                                        HorizontalAlignment="Left"
                                        VerticalAlignment="Center"
                                        Text="PAZ" />
      </Grid>
   </MenuItem.Header>
</MenuItem>

条目 2:

<MenuItem Visibility="{Binding MenuSelected.Type, Converter={StaticResource TypeToVisibilityConverter}, ConverterParameter='APP', Mode=OneWay}">
   <i:Interaction.Triggers>
      <i:EventTrigger EventName="Click">
         <i:InvokeCommandAction Command="{Binding CmdContextMenu}" CommandParameter="APP" />
      </i:EventTrigger>
   </i:Interaction.Triggers>
   <MenuItem.Header>
      <Grid>
         <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
         </Grid.ColumnDefinitions>
         <TextBlock
                                        Grid.Column="0"
                                        HorizontalAlignment="Center"
                                        VerticalAlignment="Center"
                                        FontFamily="Segoe MDL2 Assets"
                                        Foreground="{Binding MenuSelected.Type, Converter={StaticResource TypeToColorConverter}, ConverterParameter='APP', Mode=OneWay}"
                                        Text="{Binding MenuSelected.Type, Converter={StaticResource TypeToIconConverter}, ConverterParameter='APP', Mode=OneWay}" />
         <TextBlock
                                        Grid.Column="1"
                                        Margin="{StaticResource XSmallLeftMargin}"
                                        HorizontalAlignment="Left"
                                        VerticalAlignment="Center"
                                        Text="APP" />
      </Grid>
   </MenuItem.Header>
</MenuItem>

如您所见,PAZAPP 之间的唯一区别在于不同点......所有其他点也是如此。 有什么方法可以避免重复 10 次,只需更改 3 个字母?

我不想使用代码隐藏来动态创建菜单...而是从 XAML 处理它。

【问题讨论】:

    标签: c# wpf xaml data-binding


    【解决方案1】:

    根据您的问题,我假设 CmdContextMenuMenuSelected 是您的主视图模型上的属性,而不是单独的菜单项类型。如果这不同,您必须相应地调整代码。

    为了删除冗余代码,请在视图模型中为菜单项创建一个集合。

    public class MyMainViewModel : INotifyPropertyChanged
    {
       public IEnumerable<string> MyTypes { get; } = new List<string>
       {
          "PAZ",
          "APP"
       };
    
       // ...other properties and methods (like "CmdContextMenu" and "MenuSelected" I Assume).
    }
    

    接下来,您必须更改值转换器,因为ConverterParameter 不是依赖属性,无法绑定。相反,您可以使用可以绑定多个值的IMultiValueConverters。调整所有转换器以实现IMultiValueConverter。以下是转换器的外观示例。当然,这取决于你的实现。本质上,使用values 数组来访问绑定值。

    public class TypeToVisibilityConverter : IMultiValueConverter
    {
       public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
       {
          return values[0].Equals(values[1]) ? Visibility.Visible : Visibility.Collapsed;
       }
    
       public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
       {
          throw new NotImplementedException();
       }
    }
    

    接下来,为菜单项的标题创建一个数据模板。如您所见,绑定被替换为MultiBindings,使用IMultiValueConverter 作为Converter

    每个块中的第一个绑定是RelativeSource 绑定,用于访问父Menu 的数据上下文,因为我假设MenuSelected 属性是在您的主视图模型上定义的。其他空绑定将绑定到当前菜单项的数据上下文,该菜单项来自MyTypes 集合。

    <DataTemplate x:Key="MyMenuItemHeaderTemplate" DataType="{x:Type system:String}">
       <Grid>
          <Grid.ColumnDefinitions>
             <ColumnDefinition Width="Auto" />
             <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <TextBlock Grid.Column="0"
                     HorizontalAlignment="Center"
                     VerticalAlignment="Center"
                     FontFamily="Segoe MDL2 Assets">
             <TextBlock.Foreground>
                <MultiBinding Converter="{StaticResource TypeToColorConverter}">
                   <Binding Path="DataContext.MenuSelected.Type" RelativeSource="{RelativeSource AncestorType={x:Type Menu}}" Mode="OneWay"/>
                   <Binding/>
                </MultiBinding>
             </TextBlock.Foreground>
             <TextBlock.Text>
                <MultiBinding Converter="{StaticResource TypeToIconConverter}">
                   <Binding Path="DataContext.MenuSelected.Type" RelativeSource="{RelativeSource AncestorType={x:Type Menu}}" Mode="OneWay"/>
                   <Binding/>
                </MultiBinding>
             </TextBlock.Text>
          </TextBlock>
          <TextBlock Grid.Column="1"
                     Margin="{StaticResource XSmallLeftMargin}"
                     HorizontalAlignment="Left"
                     VerticalAlignment="Center"
                     Text="{Binding}"/>
       </Grid>
    </DataTemplate>
    

    创建一个使用此数据模板的新标题项样式。 Visibility 也使用上述的多值转换器。您可以直接将命令分配给菜单项并传递CommandParameter,而不是使用事件触发器,该CommandParameter 绑定到当前菜单项的数据上下文。

    <Style x:Key="MyMenuItemStyle" TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
       <Setter Property="HeaderTemplate" Value="{StaticResource MyMenuItemHeaderTemplate}"/>
       <Setter Property="Visibility">
          <Setter.Value>
             <MultiBinding Converter="{StaticResource TypeToVisibilityConverter}">
                <Binding Path="DataContext.MenuSelected.Type" RelativeSource="{RelativeSource AncestorType={x:Type Menu}}" Mode="OneWay"/>
                <Binding/>
             </MultiBinding>
          </Setter.Value>
       </Setter>
       <Setter Property="Command" Value="{Binding DataContext.CmdContextMenu, RelativeSource={RelativeSource AncestorType={x:Type Menu}}}"/>
       <Setter Property="CommandParameter" Value="{Binding}"/>
    </Style>
    

    最后,创建Menu 并绑定MyTypes 集合以及样式。

    <Menu ItemsSource="{Binding MyTypes}" ItemContainerStyle="{StaticResource MyMenuItemStyle}"/>
    

    【讨论】:

    • 抱歉,我收到此错误:Windows Presentation Foundation (WPF) 项目中不支持 XDG0008 字符串 即使我尝试添加:xmlns:System="clr-namespace:System;assembly=mscorlib" 我使用的是 .Net Core 3.1
    • @MassimoSavazzi 该命名空间仅在 .NET Framework 中可用,对于 .NET Core,请改用 xmlns:system="clr-namespace:System;assembly=System.Runtime"
    • 现在还有一个小挑战要解决:stackoverflow.com/questions/65147745/…
    【解决方案2】:
    1. ConverParameter 属性不是 DependencyProperty - 所以它不能被绑定到。 您可以改用多值转换器。

    2. 您应该能够绑定一个 ItemsCollection 并为 MenuItem 定义一个 DataTemplate,而不是手动在 xaml 中创建 10 个菜单项

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-02
      • 2021-08-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多