I just reproduced the problem by making a similar new project.
I'm having an ItemsControl that contains buttons representing projects.
Now I want to add a context menu with options: view, edit, delete when the player right clicks on the project. I did this as follows:
- Create Models folder and Project model inside it:
namespace BindingFailureReproduce.Models
{
public class Project
{
private int _id;
private string _name = "";
private string _description = "";
public int ID
{
get { return _id; }
set { _id = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Description
{
get { return _description; }
set { _description = value; }
}
public Project()
{
}
public Project(int id, string name, string description)
{
_id = id;
_name = name;
_description = description;
}
}
}
- Create ViewModels folder and ViewModelBase inside it:
using System;
using System.ComponentModel;
namespace BindingFailureReproduce.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public virtual void Dispose() { }
}
}
using System;
using System.Windows.Input;
namespace BindingFailureReproduce
{
public class RelayCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
}
- Create MainWindowViewModel inherited ViewModelBase in ViewModels folder:
using BindingFailureReproduce.Models;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
namespace BindingFailureReproduce.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private List<Project> _projectList;
public List<Project> ProjectList
{
get
{
return _projectList;
}
set
{
_projectList = value;
OnPropertyChanged(nameof(_projectList));
}
}
private void ViewProject()
{
MessageBox.Show("View project");
}
private void EditProject()
{
MessageBox.Show("Edit project");
}
private void DeleteProject()
{
MessageBox.Show("Delete project");
}
private ICommand? _cmdViewProject;
public ICommand CmdViewProject
{
get
{
_cmdViewProject ??= new RelayCommand(
p => true,
p => ViewProject());
return _cmdViewProject;
}
}
private ICommand? _cmdEditProject;
public ICommand CmdEditProject
{
get
{
_cmdEditProject ??= new RelayCommand(
p => true,
p => EditProject());
return _cmdEditProject;
}
}
private ICommand? _cmdDeleteProject;
public ICommand CmdDeleteProject
{
get
{
_cmdDeleteProject ??= new RelayCommand(
p => true,
p => DeleteProject());
return _cmdDeleteProject;
}
}
public MainWindowViewModel()
{
_projectList = new List<Project>{new Project(1, "Web project", "This is web project"),
new Project(2, "AI project", "This is AI project"),
new Project(3, "Android project", "This is android project"),
new Project(4, "Embedded project", "This is embedded project"),
new Project(5, "Game project", "This is game project")};
}
}
}
<Window x:Class="BindingFailureReproduce.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BindingFailureReproduce"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ScrollViewer Width="700" Height="350" x:Name="MyScrollViewer" VerticalScrollBarVisibility="Visible">
<ItemsControl ItemsSource="{Binding ProjectList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="700"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="10 20 10 20" Width="200" Height="100">
<Button.ContextMenu>
<ContextMenu Name="cm" StaysOpen="true">
<MenuItem Command="{Binding CmdViewProject}" Header="View"/>
<MenuItem Command="{Binding CmdEditProject}" Header="Edit"/>
<MenuItem Command="{Binding CmdDeleteProject}" Header="Delete"/>
</ContextMenu>
</Button.ContextMenu>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" Margin="5,0"></TextBlock>
<TextBlock Text="{Binding Description}" Margin="5,0"></TextBlock>
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>
- I set the DataContext of MainWindow to the MainWindowViewModel() in MainWindow.xaml.cs:
using BindingFailureReproduce.ViewModels;
using System.Windows;
namespace BindingFailureReproduce
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
}
I used Button.ContextMenu to achieve what I want and it look good. But when I click on MenuItem to View, Edit or Delete project, nothing happened. My app not crash but the XAML Binding Failures Window show the error:
Count: 1
DataContext: Project
Binding Path: CmdViewProject
Target: MenuItem.Command
Target Type: ICommand
Description: CmdViewProject property not found on object of type Project
File: mainwindow.xaml
Line: 22
Project: BindingFailureReproduced
I also have 2 more similar errors with the remaining 2 commands at line 23 and 24 in MainWindow.xaml
I was expecting the MenuItem's DataContext to be the MainWindowViewModel but in reality it's taking the Project model as the DataContext
Please show me how to properly bind command in MainWindowViewModel to MenuItem. Thank you very much.