Search code examples
c#xamlcollectionsmenu-items

Create MenuItems from Collection


i tried to create MenuItems of collection items - and failed. In detail: I have a simple class ClassA that defines a string-property 'HeadText'. In my MainViewModel i defined an ObservableCollection property. The collection is filled with 3 items. Now in XAML i want to create MenuItems of these 3 items of type ClassA. I did the following:

<Window.Resources>
    <CompositeCollection x:Key="CollA">
        <ItemsControl ItemsSource="{Binding Path=MItems}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <MenuItem Header="{Binding HeadText}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </CompositeCollection>
</Window.Resources>

<Grid>
    <Menu DockPanel.Dock="Top" ItemsSource="{Binding Source={StaticResource CollA}}"/>
</Grid>

But all i get is an empty menu bar. Any ideas how i can do this?

The viewmodel and the class ClassA:

public class MainVM
{
    public MainVM() {
        _mItems.Add(new ClassA() { HeadText = "A" });
        _mItems.Add(new ClassA() { HeadText = "B" });
        _mItems.Add(new ClassA() { HeadText = "C" });
    }

    private ObservableCollection<ClassA> _mItems = new ObservableCollection<ClassA>();
    public ObservableCollection<ClassA> MItems{
        get { return _mItems; }
    }
}

public class ClassA
{
    public ClassA() { }
    public String HeadText { get; set; }
}

Thanks in advance.

Edit:

If i write this, it works:

<Menu DockPanel.Dock="Top" ItemsSource="{Binding MItems}">
    <Menu.ItemContainerStyle>
        <Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
            <Setter Property="Header" Value="{Binding HeadText}"/>
        </Style>
    </Menu.ItemContainerStyle>
</Menu>

But i want to do it the other way. And i'm interested why the other way does not work.


Solution

  • I found the answer here. My XAML first looked like this:

        <Window.DataContext>
            <local:MainVM/>
        </Window.DataContext>
    
        <Window.Resources>
            <CollectionViewSource Source="{Binding Path=MItems}" x:Key="source"/>
        </Window.Resources>
    
        <StackPanel>
            <Menu DockPanel.Dock="Top">
                <Menu.ItemContainerStyle>
                    <Style TargetType="MenuItem">
                        <Setter Property="Header" Value="{Binding HeadText}"/>
                    </Style>
                </Menu.ItemContainerStyle>
            <Menu.ItemsSource>
                <CompositeCollection>
                    <MenuItem Header="Static 1"/>
                    <MenuItem Header="Static 2"/>
                    <CollectionContainer Collection="{Binding Source={StaticResource source}}"/>
                    <MenuItem Header="Static 3"/>
                </CompositeCollection>
            </Menu.ItemsSource>
        </Menu>
    </StackPanel>
    

    And the MainVM.cs and ClassA:

    public class MainVM
    {
        public MainVM() {
            _mItems.Add(new ClassA() { HeadText = "Dyn1" });
            _mItems.Add(new ClassA() { HeadText = "Dyn2" });
            _mItems.Add(new ClassA() { HeadText = "Dyn3" });
        }
    
        private ObservableCollection<ClassA> _mItems = new ObservableCollection<ClassA>();
        public ObservableCollection<ClassA> MItems{
            get { return _mItems; }
        }
    }
    
    public class ClassA
    {
        public ClassA() { }
    
        private string _text = "";
        public String HeadText {
            get { return _text; }
            set { _text = value; }
        }
    }
    

    This works fine but you have to notice that Visual Studio will give you BindingExpression errors in the case you really mix static and dynamic menu items.

    The reason for this is that the XAML parser applies the specified ItemContainerStyle to the dynamic AND to the static menu items: it creates a new MenuItem and binds the HeadText property to the Header property of the newly created MenuItem. This is done for the dynamic menu items as well as for the static ones. Because there is no HeadText property in the MenuItem class, an error is displayed.

    The application will not crash, but it's not a good solution. A workaround for this is to split the MenuBar like this:

        <StackPanel>
            <Grid DockPanel.Dock="Top">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Menu Grid.Column="0">
                    <MenuItem Header="Static 1"/>
                    <MenuItem Header="Static 2"/>
                </Menu>
                <Menu Grid.Column="1">
                    <Menu.ItemContainerStyle>
                        <Style TargetType="MenuItem">
                            <Setter Property="Header" Value="{Binding HeadText}"/>
                        </Style>
                    </Menu.ItemContainerStyle>
                    <Menu.ItemsSource>
                        <CompositeCollection>
                            <CollectionContainer Collection="{Binding Source={StaticResource source}}"/>
                        </CompositeCollection>
                    </Menu.ItemsSource>
                </Menu>
                <Menu Grid.Column="2">
                    <MenuItem Header="Static 3"/>
                </Menu>
            </Grid>
        </StackPanel>
    

    Maybe there is a nicer solution. Let me know if this is so.