C# WPF Context Menu Data Binding

I'm trying to create a dynamic Context Menu in the WPF DataGrid. The following are the issues that I need help:

1) Root Menu Item Header are not bind with ViewModel while the submenu works fine.

2) The submenu always pop up on the left side instead of the right. How can I fix this with style?

<DataGrid.ContextMenu>
<ContextMenu ItemsSource="{Binding PackageCM.Members}" HasDropShadow="True" Placement="Right">
    <ContextMenu.ItemContainerStyle>
        <Style TargetType="MenuItem">
            <Setter Property="Header" Value="{Binding CategoryName}" />
        </Style>
    </ContextMenu.ItemContainerStyle>
    <ContextMenu.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Items}">
            <MenuItem Header="{Binding DisplayName}" Command="{Binding AllPackagesVM.OpenCOBAPackageCommand, Source={StaticResource Locator}}"></MenuItem>
        </HierarchicalDataTemplate>
    </ContextMenu.ItemTemplate>
</ContextMenu>

Root Menu Item Header are not being bind.

Basically, Context Menu is binding to the PackageCM.Members with has a list of Category object and I want to display the CategoryName on the Context Menu root. Following that, Each Category contains a list of Items which will be showed as submenu.

Thanks in advance for help.

1 answer

  • answered 2017-10-11 21:59 Redouane

    First, your ContextMenu.ItemTemplate definition is incorrect, when you set an ItemSource for ContextMenu you do not define MenuItems yourself because actually ContextMenu will wrap this content inside another MenuItem. So you need to change your template to something like this:

    <ContextMenu ItemsSource="{Binding PackageCM.Members}" ...>
        <ContextMenu.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Items}">
                <TextBlock Text="{Binding DisplayName}"></TextBlock >
            </HierarchicalDataTemplate>
        </ContextMenu.ItemTemplate>
    </ContextMenu>
    

    You need to put a TextBlock instead of MenuItem because you want to display a text in your ContextMenu menus and bind its Text property to a propety in your model. But so this to work, the model used for root menus and model used for sub-menus must have a property that is named equally, in your case it is DisplayName for sub-menus so in your root menus model must also have a property named DisplayName, This property is bound to Text property of the TextBlock.

    You need to do some renaming in your models or introduce an new propety named DisplayName in Category model. So your models would have a common propety something like in this snippet:

    // for root menu
    public class Category
    {
        public string CategoryName { get; }
        public string DisplayName => CategoryName;
        ...
    }
    
    // for submenus
    public class Item
    {
        public string DisplayName { get; }
        ...
    }
    

    Hopefully this explanation helps you understand the problem for missing header values.

  • Render all WP Menu/Submenus as siblings

    I need to render a wordpress custom menu (registered via register_nav_menus()) and its submenus upto depth n as siblings.

    The current menu is like this:

    <ul id="menu-primary-menu" class="menu">
        <li id="menu-item-1" class="WP_DEFAULT_CLASSES">
           <a href="URL">MENU ITEM 1</a>
           <ul class="sub-menu">
               <li id="menu-item-2" class="WP_DEFAULT_CLASSES">
                   <a href="URL">MENU ITEM 2</a>
               </li>
           </ul>
        </li>
        <li id="menu-item-3" class="WP_DEFAULT_CLASSES">
            <a href="URL">MENU ITEM 3</a>
            <ul class="sub-menu">
                <li id="menu-item-4" class="WP_DEFAULT_CLASSES>
                    <a href="URL">MENU ITEM 4</a>
                </li>
                <li id="menu-item-5" class="WP_DEFAULT_CLASSES>
                    <a href="URL">MENU ITEM 5</a>
                    <ul class="sub-menu">
                        <li id="menu-item-6" class="WP_DEFAULT_CLASSES">
                            <a href="URL">MENU ITEM 6</a>
                        </li>
                    </ul>
                </li>
            </ul>
        </li>
    </ul>
    

    I want it to be like this:

    <ul id="menu-primary-menu" class="menu" data-level="0">
        <li id="menu-item-1" class="WP_DEFAULT_CLASSES">
           <a href="URL">MENU ITEM 1</a>
        </li>
        <li id="menu-item-3" class="WP_DEFAULT_CLASSES">
            <a href="URL">MENU ITEM 3</a>
        </li>
    </ul>
    
    <!-- with data-child-of and data-level attribute -->
    <ul class="sub-menu" data-child-of="menu-item-1" data-level="1"> 
       <li id="menu-item-2" class="WP_DEFAULT_CLASSES">
           <a href="URL">MENU ITEM 2</a>
       </li>
    </ul>
    
    <ul class="sub-menu" data-child-of="menu-item-3" data-level="1">
        <li id="menu-item-4" class="WP_DEFAULT_CLASSES>
            <a href="URL">MENU ITEM 4</a>
        </li>
        <li id="menu-item-5" class="WP_DEFAULT_CLASSES>
            <a href="URL">MENU ITEM 5</a>
        </li>
    </ul>
    
    <ul class="sub-menu" data-child-of="menu-item-5" data-level="2">
        <li id="menu-item-6" class="WP_DEFAULT_CLASSES">
            <a href="URL">MENU ITEM 6</a>
        </li>
    </ul>
    

    I've tried creating custom markup by getting all menu items using wp_get_nav_menu_items() and then arranging them by their menu_item_parent key. In this case, I do get the desired markup but the classes on menu items are lost. Which includes class for current-menu-item.

    I've tried checking for Walker class but I'm not sure if its possible to get this result using Walker class.

    I know this can be done using JS but I don't want to use JS for this if there's a server end solution available.

    Thanks

    EDIT:

    I've read the Walker class in detail and it looks like we can find a solution by extending the Walker class and using custom walker for menu. I'm still struggling to correctly traverse the items and make the desired markup, keeping the default WP functionality intact.