How to have custom SubMenus in a MenuItem where an ItemSource and ItemContainer are being used?

81 Views Asked by At

I am writing a C# application using WFP and the MVVM pattern.

In my view, I have a TreeView and each Item in that Tree View has its own Context Menu displayed when the user right clicks. The behavior I want a SubMenu within the Context Menu's Menu item that also allows for an Item Source to be supported.

Here is a text version of what I'd like: ContextMenu (items sourced from an Item Source) MenuItem_A (item sourced from an Item Source) SubMenuItem_A MenuItem_B (might not have any sub-items)

Here's what I tried:

<TreeView x:Name="MyTreeView" Tag={Binding ElementName=MyTreeView, Path=DataContext}>
    <TreeView.ItemTemplate> 
        <HeriarachicalDataTemplate DataType={x:Type namespace:ItemsViewModel} 
            <Grid> 
                <Grid.ContextMenu>
                    <ContextMenu 
    DataContext={Binding Path=PlacementTarget.Tag, RelativeSouce={Self}} ItemSource ={Binding HostedMenuOptions}>
                    <ContextMenu.ItemContainerStyle>
                        <Style TargetType="MenuItem"> 
                            <Setter Property="Header" Value={Binding HostedMenuItemName}> 
                            <Setter Property="ItemsSource" Value={Binding RelativeSource={RelativeSource FindAcestor, AncestorType={x:TYpe ContextMenu}}, Path=DataContext.HostedMenuItemSubMenus}/>
                        </Style> 
                    </ContextMenu.ItemContainerStyle> 
                </Grid.ContextMenu> 
            </Grid> 
        </HerarchicalDataTemplate> 
    </TreeView.ItemTemplate>
</TreeView>

The error that I'm seeing is an xaml binding error: HostedMenuItemName property not found on object of RunTimeType

Is there another way to achieve this? Basically I want the contextmenu, menus and submenus to to be determined by lists of objects that are stored in the ViewModel.

1

There are 1 best solutions below

3
Andy On

Here's some experimental markup I tried.

I have a treeview which is bound to a collection of family which itself has family members.

As a quick and dirty experiment I added a class MenuThing

public class MenuThing : baseVM
{
    public string Name { get; set; }

    public List<MenuThing> MenuChildren { get; set; }
}

In my treeview resources I define a contextmenu as a resource:

            <ContextMenu ItemsSource="{Binding MenuThings}" x:Key="ContextMenuAsResource">
                <ContextMenu.Resources>
                    <HierarchicalDataTemplate DataType="{x:Type local:MenuThing}" ItemsSource="{Binding MenuChildren}">
                        <MenuItem Header="{Binding Name}"/>
                    </HierarchicalDataTemplate>
                </ContextMenu.Resources>
            </ContextMenu>
        </TreeView.Resources>

Note that this will just be looking for a collection called MenuThings in the viewmodel a treeviewitem is bound to.

And I have a hierarchicaldatatemplate for family:

        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource="{Binding Members}">
               <Border>
                    <StackPanel Orientation="Horizontal"
                                Height="32"
                                ContextMenu="{DynamicResource ContextMenuAsResource}"
                                >
                        <Label VerticalAlignment="Center" FontFamily="WingDings" Content="1"/>
                        <TextBlock Text="{Binding Name}" />
                    </StackPanel>
                </Border>
            </HierarchicalDataTemplate>

Note that this template references the contextmenu resource.

This gets round some of that placementtarget stuff.

My contextmenu doesn't have any commands of course but it does display.

enter image description here

If some of the treeviewitems won't have commands then you'll get a sort of empty little box. You'd probably want to suppress that. But maybe every one of your treeviewitems will have commands.

My data setup.

    Families = new ObservableCollection<Family>();

    Family family1 = new Family() { Name = "The Doe's" };

    family1.MenuThings = new List<MenuThing>
    {
        new MenuThing{ Name="AA", MenuChildren=new List<MenuThing>{new MenuThing{Name ="AAA" }, new MenuThing { Name = "AAB" } } },
        new MenuThing{ Name="BB", MenuChildren=new List<MenuThing>{new MenuThing{Name ="BBB" }, new MenuThing { Name = "BBBB" } } }
    };

    family1.Members.Add(new FamilyMember() { Name = "John Doe", Age = 42 });
                    family1.Members.Add(new FamilyMember() { Name = "Jane Doe", Age = 39 });
                    family1.Members.Add(new FamilyMember() { Name = "Sammy Doe", Age = 13 });
                    Families.Add(family1);

                    Family family2 = new Family() { Name = "The Moe's" };
    family2.Members.Add(new FamilyMember() { Name = "Mark Moe", Age = 31 });
                    family2.Members.Add(new FamilyMember() { Name = "Norma Moe", Age = 28 });
                    Families.Add(family2);
    }