Reputation: 1719
I am using c#, prism, wpf. I want to create a context list dynamically within a listview, such as the following picture:
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()
{
}
Upvotes: 0
Views: 1057
Reputation: 1719
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)
{
}
Upvotes: 0
Reputation: 2065
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}}">
Upvotes: 2