Search code examples
c#wpfmegamenu

How to implement "Mega Menus" in WPF?


I'm trying to implement "Mega Menu" style menus using WPF. To see examples of mega menus in web design, see here.

So far, I've tried creating a similar interface by using TextBlocks as the highest level of the menu, and then using the mouse hover event to display an additional window that appears positioned below the text block. This is cumbersome and inflexible, future changes would require adding/removing TextBlocks dynamically.

I have considered using the WPF Menu control, because I know the styles can be dramatically modified, but I haven't seen any way to produce multi-column layouts with the hierarchical model that the Menu control uses.

Is there a better way to do this? Am I going to have to stick with custom windows and relative positioning? Can someone point me to an example of this that has already been implemented?


Solution

  • Instead of using custom Windows and positioning, you could use a Popup control. Your can use the StaysOpen=false setting to have it close when the user clicks off-screen.

    If you can settle for clicking a menu item instead of hovering, the following custom control will work:

    [TemplatePart(Name="PART_HoverArea", Type=typeof(FrameworkElement))]
    [TemplatePart(Name="PART_Popup", Type=typeof(Popup))]
    public class MegaMenuItem : HeaderedContentControl
    {
        private FrameworkElement hoverArea;
        private Popup popup;
    
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
    
            // Unhook old template
            if (hoverArea != null)
            {
                hoverArea.PreviewMouseUp -= ShowPopupOnMouseDown;
            }
    
            hoverArea = null;
            popup = null;
    
            if (Template == null)
                return;
    
            // Hook up new template
            hoverArea = (FrameworkElement)Template.FindName("PART_HoverArea", this);
            popup = (Popup)Template.FindName("PART_Popup", this);
            if (hoverArea == null || popup == null)
                return;
    
            hoverArea.PreviewMouseUp += ShowPopupOnMouseDown;
        }
    
        private void ShowPopupOnMouseDown(object sender, MouseEventArgs e)
        {
            popup.PlacementTarget = hoverArea;
            popup.Placement = PlacementMode.Bottom;
            popup.StaysOpen = false;
            popup.IsOpen = true;
        }
    }
    

    You would need a style to display it - something like this. Note the PART_ template part names:

    <Style TargetType="WpfApplication14:MegaMenuItem">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="WpfApplication14:MegaMenuItem">
                    <Grid>
                        <Border Name="PART_HoverArea" Background="#fb9c3b" BorderBrush="White" BorderThickness="0,0,1,0">
                            <ContentPresenter Content="{TemplateBinding Header}" />
                        </Border>
    
                        <Popup 
                            Name="PART_Popup" 
                            PlacementTarget="{Binding ElementName=HoverArea}"
                            >
                            <Border MinWidth="100" MaxWidth="400" MinHeight="40" MaxHeight="200" Background="#0d81c3">
                                <ContentPresenter Content="{TemplateBinding Content}" />
                            </Border>
                        </Popup>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>    
    </Style>
    

    The XAML for your menu would then be:

    <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
        <WpfApplication14:MegaMenuItem Header="Parent 1">
            <WrapPanel Margin="5">
                <TextBlock Text="Put any content you want here" Margin="5" />
                <TextBlock Text="Put any content you want here" Margin="5" />
                <TextBlock Text="Put any content you want here" Margin="5" />
            </WrapPanel>
        </WpfApplication14:MegaMenuItem>
        <WpfApplication14:MegaMenuItem Header="Parent 2">
            <WrapPanel Margin="5">
                <TextBlock Text="Put any content you want here" Margin="5" />
                <TextBlock Text="Put any content you want here" Margin="5" />
                <TextBlock Text="Put any content you want here" Margin="5" />
            </WrapPanel>
        </WpfApplication14:MegaMenuItem>
    </StackPanel>
    

    Making the menu appear on hover is much harder, because of the way Popups steal focus (you can show the menu, but you can't easily hide it if they mouse over another menu). For that a custom window might work better.