0

I am using c#, prism, wpf. I want to create a context list dynamically within a listview, such as the following picture:

enter image description here

When I click those menu item, that will callback to my custom function. In that function I can identify which menu item is clicked, for example, I can get the header of the menu item.

I tried to add a command tag and bind to a ICommand. But there is no response when I clicked it.

I have read different example from web, but they never shows the implementation of xaml and viewmodel at the same time. I would like to ask how to do it? Thank you very much.

In App.xaml.cs:

ViewModelLocationProvider.Register<MainWindowView, MainWindowViewModel>();

Following is my xaml:

<ListView Grid.Row="2"  ItemsSource="{Binding ServiceObjects}" ItemContainerStyle="{StaticResource LstBoxItemStyleNormal}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <Grid>
                <TextBlock Text="{Binding ServiceName}">
                    <TextBlock.ContextMenu>
                        <ContextMenu ItemsSource="{Binding ContextMenuList}">
                            <ContextMenu.ItemTemplate>
                                <HierarchicalDataTemplate>
                                    <MenuItem Header="{Binding MenuName}" Command="{Binding ConfirmButtonCommand}"/>
                                </HierarchicalDataTemplate>
                            </ContextMenu.ItemTemplate>
                        </ContextMenu>
                    </TextBlock.ContextMenu>
                </TextBlock>
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Following is my viewmodel:

class MainWindowViewModel : BindableBase
{
    public class MenuNode : BindableBase
    {
        private string _menuName;
        public string MenuName
        {
            get => _menuName;
            set => SetProperty(ref _menuName, value);
        }
    }
    public class ServiceNode : BindableBase
    {
        private string _serviceName;
        public string ServiceName
        {
            get => _serviceName;
            set => SetProperty(ref _serviceName, value);
        }
        public ObservableCollection<MenuNode> ContextMenuList { get; } = new ObservableCollection<MenuNode>();
    }
    public ObservableCollection<ServiceNode> ServiceObjects { get; } = new ObservableCollection<ServiceNode>();
    
    public MainWindowViewModel()
    {
        for (int i = 0; i < 10; i++)
        {
            ServiceNode tempNode = new ServiceNode { ServiceName = "AP", State = "Normal" };
            tempNode.ContextMenuList.Add(new MenuNode { MenuName = "A Item" });
            tempNode.ContextMenuList.Add(new MenuNode { MenuName = "B Item" });
            tempNode.ContextMenuList.Add(new MenuNode { MenuName = "C Item" });
            ServiceObjects.Add(tempNode);
        }
        ConfirmButtonCommand = new DelegateCommand(HandleConfirmButtonCommand);
    }
    public ICommand ConfirmButtonCommand { get; }

    private void HandleConfirmButtonCommand()
    {
        
    }

2 Answers 2

2

First of all binding to Command for MenuItem should be curly brackets: {Binding ConfirmButtonCommand}.
Second, your command is defined in MainWindowViewModel class, while DataContext for MenuItem is MenuNode, so the command can't be found.
The simplest fix is - give name to List and when binding to command, refer to its DataContext.
For example:

<ListView x:Name="list" ...>

and then

<MenuItem Header="{Binding MenuName}" Command="{Binding DataContext.ConfirmButtonCommand, ElementName=list}"/>

Also, probably for the command you'll need also to know on what ServiceNode it was clicked, and you can pass it via CommandParameter (fetching DataContext from ContextMenu that holds MenuItem):

<MenuItem ... CommandParameter="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}}">
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for your reply. I missed to mention that I am using ViewModelLocationProvider.Register<MainWindowView, MainWindowViewModel>(); Also, after I add the {}, there is no more exception throw, but also there is no callbacks to the function HandleConfirmButtonCommand. Also, what is the function declaration should be? If I want to get the information about the who is the caller ? Since I am new to C# wpf and prism, I really don't know where to start. Thanks
Have you changed binding to Command in MenuItem as I suggested? ViewModelLocationProvider has nothing to do with DataContext in wpf. To get caller - add CommandParameter as I suggested and change your command to DelegateCommand<ServiceMode>.
BTW, if your context menu is the same for all ServiceMode instances and is not changing in time, it may be better to move context menu to MainWindowViewModel or even to xaml.
0

Here is the answer I used at the end. Thanks

In App.xaml.cs:

ViewModelLocationProvider.Register<MainWindowView, MainWindowViewModel>();

Following is my xaml:

<ListView Grid.Row="2"  ItemsSource="{Binding ServiceObjects}" ItemContainerStyle="{StaticResource LstBoxItemStyleNormal}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <Grid>
                <TextBlock Text="{Binding ServiceName}" Tag="{Binding DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListView}}">
                    <TextBlock.ContextMenu>
                        <ContextMenu ItemsSource="{Binding ContextMenuList}">
                            <ContextMenu.ItemTemplate>
                                <HierarchicalDataTemplate>
                                    <MenuItem Header="{Binding MenuName}"
                                              Command="{Binding PlacementTarget.Tag.ConfirmButtonCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
                                              CommandParameter="Binding MenuName"/>
                                </HierarchicalDataTemplate>
                            </ContextMenu.ItemTemplate>
                        </ContextMenu>
                    </TextBlock.ContextMenu>
                </TextBlock>
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Following is my viewmodel:

class MainWindowViewModel : BindableBase
{
    public class MenuNode : BindableBase
    {
        private string _menuName;
        public string MenuName
        {
            get => _menuName;
            set => SetProperty(ref _menuName, value);
        }
    }
    public class ServiceNode : BindableBase
    {
        private string _serviceName;
        public string ServiceName
        {
            get => _serviceName;
            set => SetProperty(ref _serviceName, value);
        }
        public ObservableCollection<MenuNode> ContextMenuList { get; } = new ObservableCollection<MenuNode>();
    }
    public ObservableCollection<ServiceNode> ServiceObjects { get; } = new ObservableCollection<ServiceNode>();
    
    public MainWindowViewModel()
    {
        for (int i = 0; i < 10; i++)
        {
            ServiceNode tempNode = new ServiceNode { ServiceName = "AP", State = "Normal" };
            tempNode.ContextMenuList.Add(new MenuNode { MenuName = "A Item" });
            tempNode.ContextMenuList.Add(new MenuNode { MenuName = "B Item" });
            tempNode.ContextMenuList.Add(new MenuNode { MenuName = "C Item" });
            ServiceObjects.Add(tempNode);
        }
        ConfirmButtonCommand = new DelegateCommand<string>(HandleConfirmButtonCommand);
    }
    public ICommand ConfirmButtonCommand { get; }

    private void HandleConfirmButtonCommand(string parameter)
    {
        
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.