Home > Back-end >  WPF How to load Items inside ContextMenu without open it?
WPF How to load Items inside ContextMenu without open it?

Time:09-18

I try to get MenuItem from ContextMenu. If I open context menu at least once it works fine, otherwise I get error ItemCollection has no inner collection. As I understand this behavior is caused by ItemSource binding. How can I load Items without manual open context menu?

XAML:

<StackPanel>
    <Rectangle Fill="Red" Height="100" Width="100">
        <Rectangle.ContextMenu>
            <ContextMenu x:Name="MainContextMenu" ItemsSource="{Binding Items}">
                <ContextMenu.ItemContainerStyle>
                    <Style TargetType="MenuItem">
                        <Setter Property="Header" Value="{Binding}"/>
                    </Style>
                </ContextMenu.ItemContainerStyle>
            </ContextMenu>
        </Rectangle.ContextMenu>
    </Rectangle>
    <Button Content="Click" Command="{Binding GetMenuItemCommand}"
            CommandParameter="{Binding ElementName=MainContextMenu}"
            Height="30" Width="100"/>
</StackPanel>

ViewModel:

public class ViewModel
{
    public List<string> Items { get; }
    public MyCommand GetMenuItemCommand { get; }

    public ViewModel()
    {
        Items = new List<string>()
        {
            "a0", "a1", "a2", "a3", "a4", "a5"
        };

        GetMenuItemCommand = new MyCommand(GetMenuItem);
    }

    public void GetMenuItem(object obj)
    {
        var contextMenu = (ContextMenu)obj;

        // Here I get exception
        var item = contextMenu.Items[0] as MenuItem;
        var header = item.Header
    }
}

MyCommand

public class MyCommand : ICommand
{
    private Action<object> execute;

    public MyCommand(Action<object> execute)
    {
        this.execute = execute;
    }

    public event EventHandler? CanExecuteChanged;


    public bool CanExecute(object? parameter) => true;

    public void Execute(object? parameter)
    {
        execute?.Invoke(parameter);
    }
}

NOTE: It is demo code, I'm not following MVVM approach here

EDIT: Full exception message: System.InvalidOperationException: 'Operation is not valid while ItemCollection has no inner collection. ItemCollection is uninitialized or binding on ItemsControl.ItemSource supplied null for collection.'

EDIT 2: As people mentioned in the comments, probably purpose of getting MenuItem is not clear, so I introduce more details.

I'm trying to write helper class, that will merge child context menu with parent menu if needed. I do not know anything about context menus I will merge. I am currently trying to copy elements from parent menu and add them to child menu, when child menu is opening. For that reason I need to create new MenuItem, copy properties from parent's MenuItem and add this new MenuItem to child's context menu. My solution works fine, but only when parent menu was opened at least once.

I already asked straight question about merging context menus, but got no appropriate answers, so there I tried to ask something different.

CodePudding user response:

There is no MenuItem until the ContextMenu has been opened. And passing a ContextMenu to a view model breaks the MVVM pattern.

You should get the underlying data value from the source collection instead of trying to access the eventually created visual element. The MVVM version of your code looks something like this:

public void GetMenuItem(object _)
{
    var item = Items[0];
    ...
}

Obviously a view model cannot create additional MenuItem elements. It may add more strings to the source collection though.

  • Related