Home > Software design >  How to set MenuItems to fit equal space in the Menu?
How to set MenuItems to fit equal space in the Menu?

Time:09-17

I have such a XAML

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFPlayground"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20*"/>
            <RowDefinition Height="80*"/>
        </Grid.RowDefinitions>

        <Menu Grid.Row="0">
            <MenuItem 
                Background="Green"
                Header="First"/>    
            <MenuItem 
                Background="Yellow"
                Header="Second"/>
        </Menu>
    </Grid>
</Window>

When I run it I got such a result

enter image description here

I need to set these items equally inside the Menu, like the First button takes 50% and the Secondone also takes 50%, horizontally and vertically respectively. And textFirstandSecond` also should take a place at the center of the button.

Whatever I have done nothing is helps, I can set fix height or weight, but there is no way to set something like

HorizontalAlignment="Stretch"
    VerticalAlignment="Stretch"

It is just doesn't work

P.S. I know there are other possible approaches something like using another view, but I need to use Menu which should contain 4 or 5 items... (Just for this example I build Menu example where it is 2)

UPD

Following @BionicCode answer there is what I got

<Window x:Class="PlaygroundWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PlaygroundWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>

        <Style TargetType="{x:Type Menu}">
            <Setter Property="OverridesDefaultStyle" Value="True" />
            <Setter Property="SnapsToDevicePixels" Value="True" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Menu}">
                        <Border BorderThickness="1">
                            <Border.BorderBrush>
                                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                    <LinearGradientBrush.GradientStops>
                                        <GradientStopCollection>
                                            <GradientStop Color="{DynamicResource BorderLightColor}" Offset="0.0" />
                                            <GradientStop Color="{DynamicResource BorderDarkColor}" Offset="1.0" />
                                        </GradientStopCollection>
                                    </LinearGradientBrush.GradientStops>
                                </LinearGradientBrush>

                            </Border.BorderBrush>
                            <Border.Background>
                                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                    <GradientStop Color="{DynamicResource ControlLightColor}" Offset="0" />
                                    <GradientStop Color="{DynamicResource ControlMediumColor}" Offset="1" />
                                </LinearGradientBrush>
                            </Border.Background>

                            <WrapPanel ItemWidth="200" 
                     ClipToBounds="True"
                     IsItemsHost="True" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20*"/>
            <RowDefinition Height="20*"/>
            <RowDefinition Height="60*"/>
        </Grid.RowDefinitions>

        <Menu Grid.Row="1">
            <MenuItem 
                Background="Green"
                Header="First"/>
            <MenuItem 
                Background="Yellow"
                Header="Second"/>
        </Menu>
    </Grid>
</Window>

enter image description here

There are three issues

  1. In the example I have two items and they should take equally entire space horizontally
  2. In the example I have two items and they should take equally entire space vertically (according to the grid 20%)
  3. Text on the items (first, second) should be centered, currently, it is aligned left

UPD2

Currently my XAML implementation is:

<Window x:Class="PlaygroundWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PlaygroundWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>

        <Style TargetType="{x:Type Menu}">
            <Setter Property="OverridesDefaultStyle" Value="True" />
            <Setter Property="SnapsToDevicePixels" Value="True" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Menu}">
                        <Border BorderThickness="1">
                            <Border.BorderBrush>
                                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                    <LinearGradientBrush.GradientStops>
                                        <GradientStopCollection>
                                            <GradientStop Color="{DynamicResource BorderLightColor}" Offset="0.0" />
                                            <GradientStop Color="{DynamicResource BorderDarkColor}" Offset="1.0" />
                                        </GradientStopCollection>
                                    </LinearGradientBrush.GradientStops>
                                </LinearGradientBrush>

                            </Border.BorderBrush>
                            <Border.Background>
                                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                    <GradientStop Color="{DynamicResource ControlLightColor}" Offset="0" />
                                    <GradientStop Color="{DynamicResource ControlMediumColor}" Offset="1" />
                                </LinearGradientBrush>
                            </Border.Background>

                            <UniformGrid  
                     ClipToBounds="True"
                     IsItemsHost="True" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20*"/>
            <RowDefinition Height="20*"/>
            <RowDefinition Height="60*"/>
        </Grid.RowDefinitions>

        <Menu Grid.Row="1">
            <MenuItem HorizontalContentAlignment="Center" 
                      Background="Green"
                      Header="Top Level Item" />

            <MenuItem HorizontalContentAlignment="Center" 
                      Background="Yellow"
                      Header="Top Level Header">
                <MenuItem HorizontalContentAlignment="Center" Header="Submenu Item" />

                <MenuItem HorizontalContentAlignment="Center" Header="Submenu Header">
                    <MenuItem HorizontalContentAlignment="Center" Header="Submenu Item" />
                </MenuItem>
            </MenuItem>
        </Menu>
        
    </Grid>
</Window>

and it looks like this

enter image description here

BUT if I add one more item

  • The desired result is: the item will be added to the same line like the third item in the sequence and take the space equally
  • Actual result (see screenshot below) the item moves on the next line

enter image description here

Actually, there are two issues: the first as I described that item moves on the next line and the second one is that text on the item itself is not centered (it alight on the left instead).

It is problems that I am trying to explain from the beginning of the question.

CodePudding user response:

You have to override the default template for the Menu and replace the items host.

The default host is a StackPanel which always assigns the mimimum required space to each child.
To automatically spreadout the available space evenly, you can use the UniformGrid or the WrapPanel instead

The following Style is taken from Microsoft Docs: Menu Styles and Templates. but you can easily extract the style using the XAML Designer or Blend.

<Style TargetType="{x:Type Menu}">
  <Setter Property="OverridesDefaultStyle" Value="True" />
  <Setter Property="SnapsToDevicePixels" Value="True" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type Menu}">
        <Border BorderThickness="1">
          <Border.BorderBrush>
            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource BorderLightColor}" Offset="0.0" />
                  <GradientStop Color="{DynamicResource BorderDarkColor}" Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>

          </Border.BorderBrush>
          <Border.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
              <GradientStop Color="{DynamicResource ControlLightColor}" Offset="0" />
              <GradientStop Color="{DynamicResource ControlMediumColor}" Offset="1" />
            </LinearGradientBrush>
          </Border.Background>
          
          <WrapPanel ItemWidth="200" 
                     ClipToBounds="True"
                     IsItemsHost="True" />
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

To center the content of the MenuItem.Header you have to override the default template of the MenuItem. Add the following template to a ResourceDictionary within the scopw of the targeted MenuItem (e.g., Window.Resources or App.xaml). Since the template overrides the resource ky, they will apply automatically.

Note that if your top level MenuItem does not contain child items, you must change the x:Key identifier of the template to {x:Static MenuItem.TopLevelItemTemplateKey}.

The pattern to center the MenuItem content is to find the hosting ContentPresenter in the corresponding item level template and modify it by bindng the ContentPresenter.HorizontalAlignment to the templated parent's MenuItem.HorizontalContentAlignment e.g. by using TemplateBinding.
After defining the template override you simply have to center the item's content using the MenuItem.HorizontalContentAlignment property.:

<ControlTemplate x:Key="{x:Static MenuItem.TopLevelHeaderTemplateKey}"
                 TargetType="{x:Type MenuItem}">
  <Border x:Name="Border" 
          Background="{TemplateBinding Background}">
    <Grid>
      <ContentPresenter Margin="6,3,6,3"
                        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                        ContentSource="Header"
                        RecognizesAccessKey="True" />
      <Popup x:Name="Popup"
            Placement="Bottom"
            IsOpen="{TemplateBinding IsSubmenuOpen}"
            AllowsTransparency="True"
            Focusable="False"
            PopupAnimation="Fade">
        <Border x:Name="SubmenuBorder"
                    SnapsToDevicePixels="True"
                    BorderThickness="1"
                    Background="{DynamicResource MenuPopupBrush}">
          <Border.BorderBrush>
            <SolidColorBrush Color="{DynamicResource BorderMediumColor}" />
          </Border.BorderBrush>
          <ScrollViewer CanContentScroll="True"
                                    >
            <StackPanel IsItemsHost="True"
                                    KeyboardNavigation.DirectionalNavigation="Cycle" />
          </ScrollViewer>
        </Border>
      </Popup>
    </Grid>
  </Border>
  <ControlTemplate.Triggers>
    <Trigger Property="IsSuspendingPopupAnimation"
                Value="true">
      <Setter TargetName="Popup"
                Property="PopupAnimation"
                Value="None" />
    </Trigger>
    <Trigger Property="IsHighlighted"
                Value="true">
      <Setter TargetName="Border"
                Property="BorderBrush"
                Value="Transparent" />
      <Setter Property="Background"
                TargetName="Border">
        <Setter.Value>
          <LinearGradientBrush StartPoint="0,0"
                                                    EndPoint="0,1">
            <LinearGradientBrush.GradientStops>
              <GradientStopCollection>
                <GradientStop Color="{StaticResource ControlLightColor}" />
                <GradientStop Color="{StaticResource ControlMouseOverColor}"
                                                Offset="1.0" />
              </GradientStopCollection>
            </LinearGradientBrush.GradientStops>
          </LinearGradientBrush>

        </Setter.Value>
      </Setter>
    </Trigger>
    <Trigger SourceName="Popup"
                Property="AllowsTransparency"
                Value="True">
      <Setter TargetName="SubmenuBorder"
                Property="CornerRadius"
                Value="0,0,4,4" />
      <Setter TargetName="SubmenuBorder"
                Property="Padding"
                Value="0,0,0,3" />
    </Trigger>
    <Trigger Property="IsEnabled"
                Value="False">
      <Setter Property="Foreground">
        <Setter.Value>
          <SolidColorBrush Color="{StaticResource DisabledForegroundColor}" />
        </Setter.Value>
      </Setter>
    </Trigger>
  </ControlTemplate.Triggers>
</ControlTemplate>

Example

The MenuItem.Header property value of the items is chosen to give you a hint which template/style key you have to chos. Visit Microsoft Docs: Menu and MenuItem ControlTemplate Example to find the required styles and templates and the required x:Key identifiers. You can modify them (like I did by replacing the StackPanel of the Menu style with a WrapPanel or UniformGrid).
Add the styles and templates to a ResourceDictionary within the scope of the Menu (e.g., App.xaml).

<Menu>
  <MenuItem HorizontalContentAlignment="Center" Header="Top Level Item" />

  <MenuItem HorizontalContentAlignment="Center" Header="Top Level Header">
    <MenuItem HorizontalContentAlignment="Center" Header="Submenu Item" />

    <MenuItem HorizontalContentAlignment="Center" Header="Submenu Header">
      <MenuItem HorizontalContentAlignment="Center" Header="Submenu Item" />
    </MenuItem>
  </MenuItem>
</Menu>
  • Related