10

We have been tasked with designing an enterprise application in WPF that will replace a battleship grey Winforms application with a modern look and feel.

We like the look and feel that Microsoft web applications currently possess:

enter image description here

We could create these menus in WPF in the usual way:

<DockPanel>
    <Menu DockPanel.Dock="Top">
        <MenuItem Header="_File">
            <MenuItem Header="_New" />
            <MenuItem Header="_Open" />
            <MenuItem Header="_Save" />
            <Separator />
            <MenuItem Header="_Exit" />
        </MenuItem>
    </Menu>
    <TextBox AcceptsReturn="True" />
</DockPanel>

But we'd get something that looks like a Winforms menu.

I've seen some rather impressive styling efforts like this one, but they all seem to have the same familiar Winforms shape. I've also seen menus in libraries like MahApps Metro, but these strike us as too spartan.

Is the WPF Menu control flexible enough to be styled as shown in the picture above, or should we go another route like building a custom menu control from Stack Panels and Lists? What are the tradeoffs?

Bonus points (i.e. a bounty) will be awarded for xaml/code that does exactly this.

An example of this menu style in action:
https://www.visualstudio.com/

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
  • Have you looked at something like this? https://davidowens.wordpress.com/2009/02/07/menus-with-style/ – Alex Dec 12 '17 at 18:57
  • 5
    As you'll know everything in WPF can be overridden with your own `ControlTemplate`, the base is [available on MSDN](https://msdn.microsoft.com/en-gb/library/ms747082(v=vs.100).aspx) and you can customise it to suit your application as much as you need. – Rudi Visser Dec 12 '17 at 19:03
  • @RudiVisser: Thanks. I'll look that over and see if I can have a go at it with Blend. – Robert Harvey Dec 12 '17 at 19:26
  • 1
    Besides rolling your own as Rudi suggested, you should look at taking a dependency on MahApps and then customizing on top of it which should be less work over all than completely starting from scratch. Also pop into our [WPF channel](https://chat.stackoverflow.com/rooms/18165/wpf) (all of the above comments are from our residents) to discuss and share ideas. – Maverik Dec 12 '17 at 19:36
  • 1
    So what part of that menu style makes you doubt it can be implemented with regular Menu control? Looks like pretty standard menu. – Evk Dec 12 '17 at 19:39
  • @evk: I have no doubt it can be done. It's just a matter of how much work it is. The style is not a deal-breaker, so if we decide it's going to take too much time and effort, we'll just do something else. – Robert Harvey Dec 12 '17 at 21:15
  • Wondering if you can rip the Application Menu out of the Ribbon https://msdn.microsoft.com/en-us/library/ff799534(v=vs.110).aspx – Jeremy Thompson Dec 12 '17 at 23:23

5 Answers5

18

WPF was really designed with the possibility to change the look and behavior of every out-of-the-box mechanisms / controls (buttons, menus, treeview, etc.). So, in general, it's better to do this rather than to rewrite everything. For example, if you redesign your custom menu, you'll have to think about keyboard, UI automation, etc...

So, I gave a shot at your example and tried to build a minimum working sample - pure XAML - that mimics VS online menu (I've also added the hover background color change that's not by default in WPF's menu).

Here is the result and, as you see, it looks very similar:

enter image description here

Here is the XAML. I've chosen to use a custom control template per each MenuItem. I think it's quite practical when you need to really customize every item.

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="550" Width="525">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="btv" />

        <!-- style a top level menu item -->
        <ControlTemplate x:Key="VsMenuTop" TargetType="MenuItem">
            <StackPanel TextBlock.FontSize="15px" Height="40">
                <!-- label, icons, etc. -->
                <Label Content="{TemplateBinding Header}" Margin="5" Foreground="White" />

                <!-- sub items -->
                <Popup IsOpen="{TemplateBinding IsSubmenuOpen}" AllowsTransparency="True" Focusable="False">
                    <Border BorderThickness="1" Background="White" BorderBrush="#E0E0E0">
                        <StackPanel IsItemsHost="True" />
                    </Border>
                </Popup>
                <StackPanel.Style>
                    <Style TargetType="StackPanel">
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver"  Value="True">
                                <Setter Property="Background" Value="#106EBE" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </StackPanel.Style>
            </StackPanel>
        </ControlTemplate>

        <!-- style a non-top level menu item -->
        <ControlTemplate x:Key="VsMenuSub" TargetType="MenuItem">
            <DockPanel TextBlock.FontSize="15px" x:Name="panel">
                <!-- label, icons, etc. -->
                <Image Source="{Binding Icon, RelativeSource={RelativeSource TemplatedParent}}" Width="20" Margin="5,0" />
                <Label Content="{TemplateBinding Header}" Foreground="Black" Margin="0,5,5,5" />

                <!-- draw the right arrow only if this menu item has sub items -->
                <Image Source="icon_right.png" Visibility="{Binding HasItems, Converter={StaticResource btv}, RelativeSource={RelativeSource TemplatedParent}}" />

                <!-- sub items -->
                <Popup IsOpen="{TemplateBinding IsSubmenuOpen}" AllowsTransparency="True" Focusable="False" Placement="Right" >
                    <Border BorderThickness="1" Background="White" BorderBrush="#E0E0E0">
                        <StackPanel IsItemsHost="True" />
                    </Border>
                </Popup>
            </DockPanel>
            <ControlTemplate.Triggers>
                <Trigger Property="IsHighlighted" Value="True">
                    <Setter Property="Background" TargetName="panel" Value="#EFF6FC" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!-- style the separator -->
        <ControlTemplate x:Key="VsMenuSep" TargetType="Separator">
            <Border Height="1" Background="#E0E0E0" />
        </ControlTemplate>

        <!-- style the VSOnline -->
        <ControlTemplate x:Key="VsOnline" TargetType="MenuItem">
            <StackPanel TextBlock.FontSize="15px" Height="40" Orientation="Horizontal" Background="#005A9E">
                <Label Content="{TemplateBinding Header}" Margin="5" Foreground="White" />
                <Image Source="icon_down.png" Height="20" Margin="0,0,5,0" />

                <!-- sub items -->
                <Popup IsOpen="{TemplateBinding IsSubmenuOpen}" AllowsTransparency="True" Focusable="False">
                    <Border BorderThickness="1" Background="White" BorderBrush="#E0E0E0">
                        <StackPanel IsItemsHost="True" />
                    </Border>
                </Popup>
            </StackPanel>
        </ControlTemplate>

        <!-- some base stuff -->
        <Style TargetType="Menu">
            <Setter Property="Background" Value="#0078D7" />
            <Setter Property="Height" Value="40px" />
        </Style>

    </Window.Resources>

    <!-- the real app and real menu -->
    <StackPanel>
        <Menu IsMainMenu="True">
            <MenuItem Header="_VSOnline" Template="{StaticResource VsOnline}" >
                <MenuItem Header="_Whatever" Template="{StaticResource VsMenuSub}" />
            </MenuItem>
            <MenuItem Header="_Dashboards" Template="{StaticResource VsMenuTop}">
                <MenuItem Header="_Overview" Template="{StaticResource VsMenuSub}" />
            </MenuItem>
            <MenuItem Header="_Code" Template="{StaticResource VsMenuTop}">
                <MenuItem Header="_Files" Template="{StaticResource VsMenuSub}" />
                <MenuItem Header="_Commits" Template="{StaticResource VsMenuSub}" />
                <MenuItem Header="_Pushes" Template="{StaticResource VsMenuSub}" />
            </MenuItem>
            <MenuItem Header="_Work" Template="{StaticResource VsMenuTop}">
                <MenuItem Header="_Backlogs" Template="{StaticResource VsMenuSub}" Icon="icon_backlogs.png" />
                <MenuItem Header="_Queries" Template="{StaticResource VsMenuSub}" Icon="icon_queries.png" />
                <Separator Template="{StaticResource VsMenuSep}" />
                <MenuItem Header="_New Work Item" Template="{StaticResource VsMenuSub}">
                    <MenuItem Header="_Epic" Template="{StaticResource VsMenuSub}" Icon="icon_epic.png" />
                    <MenuItem Header="_Feature" Template="{StaticResource VsMenuSub}" Icon="icon_feature.png" />
                    <MenuItem Header="_Issue" Template="{StaticResource VsMenuSub}" Icon="icon_issue.png" />
                    <MenuItem Header="_Task" Template="{StaticResource VsMenuSub}" Icon="icon_task.png" />
                    <MenuItem Header="_Test Case" Template="{StaticResource VsMenuSub}" Icon="icon_testcase.png" />
                    <MenuItem Header="_User Story" Template="{StaticResource VsMenuSub}" Icon="icon_userstory.png" />
                </MenuItem>
                <MenuItem Header="_Bug" Template="{StaticResource VsMenuSub}" Icon="icon_bug.png" />
            </MenuItem>
        </Menu>
    </StackPanel>
</Window>

The project is available here on github.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
3

You are probably looking for some Wpf Theme to start with.

Such as http://brianlagunas.com/free-metro-light-and-dark-themes-for-wpf-and-silverlight-microsoft-controls/ or have a look at that SO answer: Any free WPF themes?

Then you probably would want to customize it even more with look and feel you are looking for.

Arek Bal
  • 712
  • 8
  • 13
3

You could use something like: http://materialdesigninxaml.net

Wouter
  • 2,540
  • 19
  • 31
  • I'm familiar with all of the usual suspects. Material Design doesn't have what I've described in my question (out of the box). – Robert Harvey Dec 14 '17 at 16:00
  • Hi, @Wouter. We actually wound up adopting this. The menus were pretty straightforward, the look was acceptable, and there are tons of benefits that extend beyond mere menus. – Robert Harvey May 28 '20 at 15:42
2

This is the default menu, but white, you can change the background color, text font, height, and everything else you want just by editing this style.

Here your MainWindow.xaml

<Menu IsMainMenu="True" HorizontalContentAlignment="Left" Background="Transparent"   >
    <MenuItem Header="File" HorizontalContentAlignment="Left"  >
        <MenuItem Header="Menu Item 01" Command="{Binding MenuCommand}" CommandParameter="Param01"  >
            <MenuItem.Icon>
                <Image Source="Images/settings.png" Height="20"/>
            </MenuItem.Icon>
        </MenuItem>
        <MenuItem Header="Menu Item 02" Command="{Binding MenuCommand}" CommandParameter="Param02"  >
            <MenuItem.Icon>
                <Image Source="Images/settings.png" Height="20"/>
            </MenuItem.Icon>
        </MenuItem>
        <Separator />
        <MenuItem Header="LogOff"   Command="{Binding MenuCommand}" CommandParameter="LogOff" >
            <MenuItem.Icon>
                <Image Source="Images/logoff.png" Height="20"/>
            </MenuItem.Icon>
        </MenuItem>
    </MenuItem>
</Menu>

style in app.xaml file

<!-- Menu Begin -->
<!-- Menu --> 
<Style  TargetType="{x:Type Menu}">
    <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.MenuBarBrushKey}}"/>
    <Setter Property="FontFamily" Value="{DynamicResource {x:Static SystemFonts.MenuFontFamilyKey}}"/>
    <Setter Property="FontSize" Value="{DynamicResource {x:Static SystemFonts.MenuFontSizeKey}}"/>
    <Setter Property="FontStyle" Value="{DynamicResource {x:Static SystemFonts.MenuFontStyleKey}}"/>
    <Setter Property="FontWeight" Value="{DynamicResource {x:Static SystemFonts.MenuFontWeightKey}}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.MenuTextBrushKey}}"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Menu}">
                <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                    <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<!-- Level 0 -->
<ControlTemplate x:Key="{x:Static MenuItem.TopLevelItemTemplateKey}" TargetType="{x:Type MenuItem}">
    <Border x:Name="templateRoot" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
        <Grid x:Name="Grid" VerticalAlignment="Center">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <ContentPresenter x:Name="Icon" Content="{TemplateBinding Icon}" ContentSource="Icon" HorizontalAlignment="Center" Height="16" Margin="3" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center" Width="16" Visibility="Collapsed"/>
            <Path x:Name="GlyphPanel" Data="F1M10,1.2L4.7,9.1 4.5,9.1 0,5.2 1.3,3.5 4.3,6.1 8.3,0 10,1.2z" Fill="{TemplateBinding Foreground}" FlowDirection="LeftToRight" Margin="3" Visibility="Collapsed" VerticalAlignment="Center"/>
            <ContentPresenter x:Name="ContentPresenter" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" Grid.Column="1" ContentStringFormat="{TemplateBinding HeaderStringFormat}" ContentSource="Header" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            <Popup x:Name="PART_Popup" AllowsTransparency="True" Focusable="False" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" Placement="Bottom" >
                <Border x:Name="SubMenuBorder" BorderBrush="#FF999999" BorderThickness="1" Background="White" Padding="2">
                    <ScrollViewer x:Name="SubMenuScrollViewer" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
                        <Grid x:Name="Grid1" RenderOptions.ClearTypeHint="Enabled">
                            <Canvas x:Name="Canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                <Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=SubMenuBorder}" Height="{Binding ActualHeight, ElementName=SubMenuBorder}" Width="{Binding ActualWidth, ElementName=SubMenuBorder}"/>
                            </Canvas>
                            <Rectangle x:Name="Rectangle" Fill="Transparent" HorizontalAlignment="Left" Margin="29,2,0,2" Width="1"/>
                            <ItemsPresenter x:Name="ItemsPresenter"  KeyboardNavigation.DirectionalNavigation="Cycle" Grid.IsSharedSizeScope="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" KeyboardNavigation.TabNavigation="Cycle"/>
                        </Grid>
                    </ScrollViewer>
                </Border>
            </Popup>
        </Grid>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsSuspendingPopupAnimation" Value="True">
            <Setter Property="PopupAnimation" TargetName="PART_Popup" Value="None"/>
        </Trigger>
        <Trigger Property="Icon" Value="{x:Null}">
            <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="IsChecked" Value="True">
            <Setter Property="Visibility" TargetName="GlyphPanel" Value="Visible"/>
            <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="IsHighlighted" Value="True">
            <Setter Property="Background" TargetName="templateRoot" Value="#3D26A0DA"/>
            <Setter Property="BorderBrush" TargetName="templateRoot" Value="#FF26A0DA"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="TextElement.Foreground" TargetName="templateRoot" Value="#FF707070"/>
            <Setter Property="Fill" TargetName="GlyphPanel" Value="#FF707070"/>
        </Trigger>
        <Trigger Property="CanContentScroll" SourceName="SubMenuScrollViewer" Value="False">
            <Setter Property="Canvas.Top" TargetName="OpaqueRect" Value="{Binding VerticalOffset, ElementName=SubMenuScrollViewer}"/>
            <Setter Property="Canvas.Left" TargetName="OpaqueRect" Value="{Binding HorizontalOffset, ElementName=SubMenuScrollViewer}"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

<!-- Level 0 -->
<ControlTemplate x:Key="{x:Static MenuItem.TopLevelHeaderTemplateKey}" TargetType="{x:Type MenuItem}">
    <Border x:Name="templateRoot" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
        <Grid x:Name="Grid" VerticalAlignment="Center">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <ContentPresenter x:Name="Icon" Content="{TemplateBinding Icon}" ContentSource="Icon" HorizontalAlignment="Center" Height="16" Margin="3" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center" Width="16" Visibility="Collapsed"/>
            <Path x:Name="GlyphPanel" Data="F1M10,1.2L4.7,9.1 4.5,9.1 0,5.2 1.3,3.5 4.3,6.1 8.3,0 10,1.2z" Fill="{TemplateBinding Foreground}" FlowDirection="LeftToRight" Margin="3" Visibility="Collapsed" VerticalAlignment="Center"/>
            <ContentPresenter x:Name="ContentPresenter" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" Grid.Column="1" ContentStringFormat="{TemplateBinding HeaderStringFormat}" ContentSource="Header" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            <Popup x:Name="PART_Popup" AllowsTransparency="True" Focusable="False" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" Placement="Bottom" >
                <Border x:Name="SubMenuBorder" BorderBrush="#FF999999" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.MenuColorKey}}" Padding="2">
                    <ScrollViewer x:Name="SubMenuScrollViewer" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
                        <Grid x:Name="Grid1" RenderOptions.ClearTypeHint="Enabled">
                            <Canvas x:Name="Canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                <Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=SubMenuBorder}" Height="{Binding ActualHeight, ElementName=SubMenuBorder}" Width="{Binding ActualWidth, ElementName=SubMenuBorder}"/>
                            </Canvas>
                            <Rectangle x:Name="Rectangle" Fill="Transparent" HorizontalAlignment="Left" Margin="29,2,0,2" Width="1"/>
                            <ItemsPresenter x:Name="ItemsPresenter"  KeyboardNavigation.DirectionalNavigation="Cycle" Grid.IsSharedSizeScope="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" KeyboardNavigation.TabNavigation="Cycle"/>
                        </Grid>
                    </ScrollViewer>
                </Border>
            </Popup>
        </Grid>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsSuspendingPopupAnimation" Value="True">
            <Setter Property="PopupAnimation" TargetName="PART_Popup" Value="None"/>
        </Trigger>
        <Trigger Property="Icon" Value="{x:Null}">
            <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="IsChecked" Value="True">
            <Setter Property="Visibility" TargetName="GlyphPanel" Value="Visible"/>
            <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="IsHighlighted" Value="True">
            <Setter Property="Background" TargetName="templateRoot" Value="#3D26A0DA"/>
            <Setter Property="BorderBrush" TargetName="templateRoot" Value="#FF26A0DA"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="TextElement.Foreground" TargetName="templateRoot" Value="#FF707070"/>
            <Setter Property="Fill" TargetName="GlyphPanel" Value="#FF707070"/>
        </Trigger>
        <Trigger Property="CanContentScroll" SourceName="SubMenuScrollViewer" Value="False">
            <Setter Property="Canvas.Top" TargetName="OpaqueRect" Value="{Binding VerticalOffset, ElementName=SubMenuScrollViewer}"/>
            <Setter Property="Canvas.Left" TargetName="OpaqueRect" Value="{Binding HorizontalOffset, ElementName=SubMenuScrollViewer}"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

<!-- Level 1,2,3,4,... -->
<ControlTemplate x:Key="{x:Static MenuItem.SubmenuHeaderTemplateKey}" TargetType="{x:Type MenuItem}">
    <Border x:Name="templateRoot" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
        <Grid Margin="-1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition MinWidth="22" SharedSizeGroup="MenuItemIconColumnGroup" Width="Auto"/>
                <ColumnDefinition Width="13"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="30"/>
                <ColumnDefinition SharedSizeGroup="MenuItemIGTColumnGroup" Width="Auto"/>
                <ColumnDefinition Width="20"/>
            </Grid.ColumnDefinitions>
            <ContentPresenter x:Name="Icon" Content="{TemplateBinding Icon}" ContentSource="Icon" HorizontalAlignment="Center" Height="16" Margin="3" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center" Width="16"/>
            <Border x:Name="GlyphPanel" BorderBrush="#FF26A0DA" BorderThickness="1" Background="#3D26A0DA" Height="22" Margin="-1,0,0,0" Visibility="Hidden" VerticalAlignment="Center" Width="22" >
                <Path x:Name="Glyph" Data="{DynamicResource ŧ}" Fill="#FF212121" FlowDirection="LeftToRight" Height="11" Width="9"/>
            </Border>
            <ContentPresenter ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" Grid.Column="2" ContentStringFormat="{TemplateBinding HeaderStringFormat}" ContentSource="Header" HorizontalAlignment="Left" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
            <TextBlock Grid.Column="4" Margin="{TemplateBinding Padding}" Opacity="0.7" Text="{TemplateBinding InputGestureText}" VerticalAlignment="Center"/>
            <Path x:Name="RightArrow" Grid.Column="5" Data="M0,0L4,3.5 0,7z" Fill="#FF212121" HorizontalAlignment="Left" Margin="10,0,0,0" VerticalAlignment="Center"/>
            <Popup x:Name="PART_Popup" AllowsTransparency="True" Focusable="False" HorizontalOffset="-2" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" Placement="Right" VerticalOffset="-3">
                <Border x:Name="SubMenuBorder" BorderBrush="#FF999999" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.MenuColorKey}}" Padding="2">
                    <ScrollViewer x:Name="SubMenuScrollViewer" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
                        <Grid RenderOptions.ClearTypeHint="Enabled">
                            <Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                <!--Fill="{Binding Background, ElementName=SubMenuBorder}" White  #FFF0F0F0-->
                                <Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=SubMenuBorder}" Height="{Binding ActualHeight, ElementName=SubMenuBorder}" Width="{Binding ActualWidth, ElementName=SubMenuBorder}"/>
                            </Canvas>
                            <Rectangle Fill="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}" HorizontalAlignment="Left" Margin="29,2,0,2" Width="1"/>
                            <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Cycle" Grid.IsSharedSizeScope="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" KeyboardNavigation.TabNavigation="Cycle"/>
                        </Grid>
                    </ScrollViewer>
                </Border>
            </Popup>
        </Grid>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsSuspendingPopupAnimation" Value="True">
            <Setter Property="PopupAnimation" TargetName="PART_Popup" Value="None"/>
        </Trigger>
        <Trigger Property="Icon" Value="{x:Null}">
            <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="IsChecked" Value="True">
            <Setter Property="Visibility" TargetName="GlyphPanel" Value="Visible"/>
            <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="IsHighlighted" Value="True">
            <Setter Property="Background" TargetName="templateRoot" Value="Transparent"/>
            <Setter Property="BorderBrush" TargetName="templateRoot" Value="#FF26A0DA"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="TextElement.Foreground" TargetName="templateRoot" Value="#FF707070"/>
            <Setter Property="Fill" TargetName="Glyph" Value="#FF707070"/>
            <Setter Property="Fill" TargetName="RightArrow" Value="#FF707070"/>
        </Trigger>
        <Trigger Property="CanContentScroll" SourceName="SubMenuScrollViewer" Value="False">
            <Setter Property="Canvas.Top" TargetName="OpaqueRect" Value="{Binding VerticalOffset, ElementName=SubMenuScrollViewer}"/>
            <Setter Property="Canvas.Left" TargetName="OpaqueRect" Value="{Binding HorizontalOffset, ElementName=SubMenuScrollViewer}"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>
<!-- Level 1,2,3,4,... -->
<ControlTemplate x:Key="{x:Static MenuItem.SubmenuItemTemplateKey}" TargetType="{x:Type MenuItem}">
    <Border x:Name="templateRoot" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
        <Grid Margin="-1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition MinWidth="22" SharedSizeGroup="MenuItemIconColumnGroup" Width="Auto"/>
                <ColumnDefinition Width="13"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="30"/>
                <ColumnDefinition SharedSizeGroup="MenuItemIGTColumnGroup" Width="Auto"/>
                <ColumnDefinition Width="20"/>
            </Grid.ColumnDefinitions>
            <ContentPresenter x:Name="Icon" Content="{TemplateBinding Icon}" ContentSource="Icon" HorizontalAlignment="Center" Height="16" Margin="3" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center" Width="16"/>
            <Border x:Name="GlyphPanel" BorderBrush="#FF26A0DA" BorderThickness="1" Background="#3D26A0DA" ClipToBounds="False" HorizontalAlignment="Center" Height="22" Margin="-1,0,0,0" Visibility="Hidden" VerticalAlignment="Center" Width="22">
                <Path x:Name="Glyph" Data="F1M10,1.2L4.7,9.1 4.5,9.1 0,5.2 1.3,3.5 4.3,6.1 8.3,0 10,1.2z" Fill="#FF212121" FlowDirection="LeftToRight" Height="11" Width="10"/>
            </Border>
            <ContentPresenter x:Name="menuHeaderContainer" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" Grid.Column="2" ContentStringFormat="{TemplateBinding HeaderStringFormat}" ContentSource="Header" HorizontalAlignment="Left" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
            <TextBlock x:Name="menuGestureText" Grid.Column="4" Margin="{TemplateBinding Padding}" Opacity="0.7" Text="{TemplateBinding InputGestureText}" VerticalAlignment="Center"/>
        </Grid>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="Icon" Value="{x:Null}">
            <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="IsChecked" Value="True">
            <Setter Property="Visibility" TargetName="GlyphPanel" Value="Visible"/>
            <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="IsHighlighted" Value="True">
            <Setter Property="Background" TargetName="templateRoot" Value="#3D26A0DA"/>
            <Setter Property="BorderBrush" TargetName="templateRoot" Value="#FF26A0DA"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="TextElement.Foreground" TargetName="templateRoot" Value="#FF707070"/>
            <Setter Property="Fill" TargetName="Glyph" Value="#FF707070"/>
        </Trigger>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsHighlighted" Value="True"/>
                <Condition Property="IsEnabled" Value="False"/>
            </MultiTrigger.Conditions>
            <Setter Property="Background" TargetName="templateRoot" Value="#0A000000"/>
            <Setter Property="BorderBrush" TargetName="templateRoot" Value="#21000000"/>
        </MultiTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

<!-- Menu End -->
Paul Zahra
  • 9,522
  • 8
  • 54
  • 76
Celso Lívero
  • 716
  • 2
  • 14
  • 18
1

I would create the menu with XAML

XAML of Page something like this:

<Window.Resources>
    <Style TargetType="TextBlock">
        <Setter Property="Margin" Value="10" />
        <Setter Property="FontSize" Value="15" />
        <Setter Property="Foreground" Value="White" />
    </Style>
</Window.Resources>
<Grid x:Name="Content">
    <Grid >
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" Background="CornflowerBlue">
            <TextBlock Text="Menu1" MouseLeftButtonDown="OnMouseLeftButtonDown" />
            <TextBlock Text="Menu2" MouseLeftButtonDown="OnMouseLeftButtonDown" />
            <TextBlock Text="Menu3" MouseLeftButtonDown="OnMouseLeftButtonDown" />
        </StackPanel>

        <Border Grid.Row="1" Background="Bisque"></Border>
    </Grid>
</Grid>

And in Code:

    private void OnMouseLeftButtonDown(object sender, MouseEventArgs e) {
        var textBlock = sender as TextBlock;
        if (textBlock == null) return;
        var relativePoint = VisualTreeHelper.GetOffset(textBlock);//textBlock.TransformToAncestor(Content).Transform(new Point(0, 0));

        //Almost invisible Background - If the user clicks it the menu is hidden
        var back = new Border{Background = new SolidColorBrush(Color.FromArgb(1,255,255,255))};
        back.MouseLeftButtonDown += (o, args) => {
            Content.Children.Remove(back.Tag as UIElement);
            Content.Children.Remove(back);
        };
        Content.Children.Add(back);

        //Build menu
        var menu = new Border{Background = new SolidColorBrush(Colors.White), Width = 200, Height = 500, BorderBrush = new SolidColorBrush(Colors.Gray), BorderThickness = new Thickness(1), HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top};
        back.Tag = menu;
        menu.RenderTransform = new TranslateTransform(relativePoint.X, relativePoint.Y + textBlock.ActualHeight + 5);
        //Do some more styling
        //Fill Menu here according to the tag of the TextBlock (switch(textBlock.Tag){...} or use a predefined view
        Content.Children.Add(menu);

        //Hide the Menu on click - TODO Implement logic to hide menu if a MenuItem was clicked...
        menu.MouseLeftButtonDown += (o, args) => {
            Content.Children.Remove(back.Tag as UIElement);
            Content.Children.Remove(back);
        };
    }

OK, this is a very basic example and needs a lot of improvement but I think you can work on it...

Markus
  • 2,184
  • 2
  • 22
  • 32
  • That's a nice example of a code-behind solution. For our project, one of the goals is to use as little code-behind as possible, so that the styling can be decoupled from the controls. – Robert Harvey Dec 17 '17 at 17:03