How to avoid repeating blocks of XAML in a menu

烈酒焚心 提交于 2021-01-07 03:23:16

问题


I have to create a menu. It has 10 entries and they differ by one parameter.

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

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

As you can see, the only difference is between PAZ and APP in different points... and same goes on for all the others. Is there a way I can avoid to repeat it 10 times just changing the 3 letters?

I do not want to use code-behind to create the menu dynamically... but to process it from XAML.


回答1:


From your question I assume that CmdContextMenu and MenuSelected are properties on your main view model and not in a separate menu item type. If this is different, you have to adapt the code accordingly.

In order to remove the redundant code, create a collection for your menu items in your view model.

public class MyMainViewModel : INotifyPropertyChanged
{
   public IEnumerable<string> MyTypes { get; } = new List<string>
   {
      "PAZ",
      "APP"
   };

   // ...other properties and methods (like "CmdContextMenu" and "MenuSelected" I Assume).
}

Next, you have to change the value converters, because the ConverterParameter is not a dependency property and cannot be bound. Instead, you can use IMultiValueConverters that can bind multiple values. Adapt all of your converters to implement IMultiValueConverter. Here is an example of how a converter could look like. Of course, it depends on your implementation. In essence, use the values array to access bound 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();
   }
}

Next, create a data template for the header of your menu items. As you can see, the bindings are replaced with MultiBindings that use an IMultiValueConverter as Converter.

The first binding in each block is a RelativeSource binding to access the data context of the parent Menu, because I assume that the MenuSelected property is defined on your main view model. The other empty bindings will bind to the data context of the current menu item, which is an item from the MyTypes collection.

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

Create a new header item style that uses this data template. The Visibility also uses a multi-value converter as above. Instead of using an event trigger, you can simply assign the command to the menu item directly and pass a CommandParameter, which is bound to the data context of the current menu item.

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

Finally, create a Menu and bind the MyTypes collection, as well as the style.

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



回答2:


  1. ConverParameter property is not a DependencyProperty - so it cannot be Bound to. You can use a MultiValue converter instead.

  2. Instead of creating 10 menu items in xaml manually, you should be able to bind an ItemsCollection and define a DataTemplate for MenuItem



来源:https://stackoverflow.com/questions/65134843/how-to-avoid-repeating-blocks-of-xaml-in-a-menu

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!