4
votes

I am using the mvvm pattern and I am having a hard time figuring out how to set the DataContext on a ContextMenu from inside a ListView’s ItemContainerStyle.

I also do not understand why the ListView.ContextMenu and the ListView’s GridView.ColumnHeaderContextMenu can see the properties and commands from my view model, but the ContextMenu inside the ListView.ItemContainerStyle cannot.

Error

System.Windows.Data Error: 40 : BindingExpression path error: 'AddMenuItem' property not found on 'object' ''Currency' (HashCode=43406546)'. BindingExpression:Path=AddMenuItem; DataItem='Currency' (HashCode=43406546); target element is 'ContextMenu' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')

View

<!-- Removed styles for clarity. -->
<UserControl>

<!-- Add ElementSpy to the UserControl’s rsources -->
<UserControl.Resources>
    <framework:ElementSpy x:Key="spy" />
</UserControl.Resources>

<ListView ItemsSource="{Binding Currency}">

    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <!-- 'AddMenuItem' property not found on 'object' 'Currency' -->
                    <!-- ContextMenu ItemsSource="{Binding AddMenuItem}" / -->

                    <!-- Use the ElementSpy resource -->
                    <ContextMenu ItemsSource="{Binding Source={StaticResource spy}, Path=Element.DataContext.AddMenuItem}" />
                </Setter.Value>
            </Setter>
        </Style>
    </ListView.ItemContainerStyle>

    <ListView.ContextMenu>
        <!-- Works -->
        <ContextMenu ItemsSource="{Binding EditMenuItem}" />
    </ListView.ContextMenu>

    <ListView.View>
        <GridView>
            <GridView.ColumnHeaderContextMenu>
                <!-- Works -->
                <ContextMenu ItemsSource="{Binding SortMenuItem}" />
            </GridView.ColumnHeaderContextMenu>

            <GridViewColumn Header="Code"
                            DisplayMemberBinding="{Binding Path=Code}" />

            <GridViewColumn Header="Description"
                            DisplayMemberBinding="{Binding Path=Description}" />

            <GridViewColumn Header="Exchange Rate"
                            DisplayMemberBinding="{Binding Path=ExchangeRate}" />
        </GridView>

    </ListView.View>

</ListView>
</UserControl>

Code Behind

[Export(ViewNames.CurrencyMasterView, typeof(IMasterView))]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class CurrencyMasterView
    : UserControl, IMasterView
{
    public CurrencyMasterView()
    {
        InitializeComponent();
    }

    [Import]
    private MasterViewModel ViewModel
    {
        set
        {
            this.DataContext = value;
        }
    }
}

ViewModel

[Export(typeof(MasterViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class MasterViewModel
    : ViewModelBase
{
    [ImportingConstructor]
    public MasterViewModel(IGeneralController generalController, IRegionManager regionManager)
    {
    }

    public ObservableCollection<Currency> Currency
    {
        get
        {
            return this.currency;
        }

        set
        {
            if (this.currency != value)
            {
                this.currency = value;
                this.RaisePropertyChanged(() => this.Currency);
            }
        }
    }

    public List<MenuItemMvvm> SortMenuItem
    {
        get
        {
            return this.CreateSortMenuItem();
        }
    }

    public List<MenuItemMvvm> EditMenuItem
    {
        get
        {
            return this.CreateEditMenuItem();
        }
    }

    public List<MenuItemMvvm> AddMenuItem
    {
        get
        {
            return this.CreateAddMenuItem();
        }
    }

    private List<MenuItemMvvm> CreateEditMenuItem()
    {
        var menu = new List<MenuItemMvvm>();

        menu.Add(new MenuItemMvvm("_Edit")
        {
            Command = this.EditCommand,
            Icon = new Image
            {
                Source = new BitmapImage(new Uri("pack://application:,,,/POS.Modules.Core;component/Resources/Images/16X16/Edit.png"))
            }
        });

        menu.Add(new MenuItemMvvm("_Duplicate")
        {
            Command = this.DuplicateCommand,
            Icon = new Image
            {
                Source = new BitmapImage(new Uri("pack://application:,,,/POS.Modules.Core;component/Resources/Images/16X16/Copy.png"))
            }
        });

        menu.Add(new MenuItemMvvm("_Delete")
        {
            Command = this.DeleteCommand,
            Icon = new Image
            {
                Source = new BitmapImage(new Uri("pack://application:,,,/POS.Modules.Core;component/Resources/Images/16X16/Delete.png"))
            }
        });


        return menu;
    }

    // Other methods removed for clarity
}
2
kevin: See stackoverflow.com/questions/504533 and stackoverflow.com/questions/2306386 - you can bind via ContextMenu.PlacementTarget.Matt Hamilton

2 Answers

2
votes

This question helped me figure it out. WPF MenuItem.Command binding to ElementName results to System.Windows.Data Error: 4 : Cannot find source for binding with reference

I updated the source for anyone else that is having this issue.

For quick reference this is what I did.

Add Josh Smith's ElementSpy class. Enable ElementName Bindings with ElementSpy

Add ElementSpy to the UserControl’s rsources.

<UserControl.Resources>
    <framework:ElementSpy x:Key="spy" />
</UserControl.Resources>

Then bind to the resource and use the Element property to bind to the DataContext and the property that you choose.

<ContextMenu ItemsSource="{Binding Source={StaticResource spy}, Path=Element.DataContext.AddMenuItem}" />
0
votes

you have to use RelativeSource and PlacementTarget in your binding. i use the following xaml to add a contextmenu to set visibility of datagrid columns.

  <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Spalten ein-/ausblenden">                           
                        <StackPanel>
                            <ItemsControl 
                            ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.Columns, Mode=OneWay}"
                            ItemTemplate="{StaticResource Visibility4DataGridColumns}"
                            ></ItemsControl>
                        </StackPanel>
                    </MenuItem>
                </ContextMenu>
            </DataGrid.ContextMenu>