0

I'm trying to make Context Menu, which will have items depending on some data in code. So, i have simple class, determining single item of menu

class ContextMenuItem
{
    public string ItemHeader {get; set;}
    public Command ItemAction {get; set;
}

where Command is implementation of ICommand, and stores action, which will be fired once this item is selected. Then i have class, serving as DataContext

class SomeClass
{
    public List<ContextMenuItem> ContextMenuItems {get; set;}
    public string SomeProperty {get; set;}
    public string SomeAnotherProperty {get; set;}
}

So, ContextMenuItems is list of actions I need in my context menu, which can be generated using different approaches.

And I'm creating dynamic context menu, using this approach.

<ContextMenu ItemsSource="{Binding ContextMenuItems}">
    <ContextMenu.ItemContainerStyle>
        <Style TargetType="{x:Type MenuItem}">
            <Setter Property="Command" Value="{Binding ItemAction}"/>
            <Setter Property="Header" Value="{Binding ItemHeader}"/>
        </Style>
    </ContextMenu.ItemContainerStyle>
</ContextMenu>

So, i was suspecting this to work well. But, for some reason, binding works not the way I want it to.

<Setter Property="Command" Value="{Binding ItemAction}"/>
<Setter Property="Header" Value="{Binding ItemHeader}"/>

Somehow, data context for this lines is not ContextMenuItem, but SomeClass itself. So, i can bind SomeProperty and SomeAnotherProperty here, but not ItemHeader or ItemAction. And this ruins whole idea of dynamicaly created context menu.

So, how can i make this template recognize ContextMenuItem as its DataContext?

What i want to do can be accomplished using DataTemplate, but it gives us MenuItem inside MenuItem, and this is not good.

Update

Full xaml code involving ListBox

<ListBox Margin="5, 5" Background="White" ItemsSource="{Binding SwitchAgents, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Stretch">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid Margin="3,1">
                <Grid.ContextMenu>
                    <ContextMenu ItemsSource="{Binding ContextMenuItems}">
                        <ContextMenu.ItemContainerStyle>
                            <Style TargetType="MenuItem">
                                <Setter Property="Command" Value="{Binding ItemAction}"/>
                                <Setter Property="Header" Value="{Binding ItemHeader}"/>
                            </Style>
                        </ContextMenu.ItemContainerStyle>
                    </ContextMenu>

                </Grid.ContextMenu>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="1*"/>
                    <ColumnDefinition Width="7*"/>
                </Grid.ColumnDefinitions>
                <CheckBox IsChecked="{Binding Enabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,3"/>
                <TextBlock Text="{Binding ObjectName}" Grid.Column="1" Margin="0,2"/>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
Community
  • 1
  • 1
lentinant
  • 792
  • 1
  • 10
  • 36
  • I've set up a test app with your code and I can't duplicate your problems. It's all working as expected here. What's your ContextMenu attached to? Is it in any kind of DataTemplate? – goobering Apr 09 '15 at 13:40
  • Well, it is. I have ListBox, with ItemsSource binded to list of SomeClass, and template to show its data correctly. Is Context Menu (or some template) inside DataTemplate something what we shouldn't do? – lentinant Apr 09 '15 at 13:43
  • It's not a problem but it might change the binding code a little. Can you update the question with the XAML that shows the ContextMenu as you've placed it in your ListBox? – goobering Apr 09 '15 at 13:56
  • Done. Now there is all code i'm using to create ListBox and its items – lentinant Apr 09 '15 at 14:08

2 Answers2

2

There is a sneaky trick to making this work. Normally I'd just use a RelativeSource in the binding to have it tunnel up to something with a DataContext. The problem is that ContextMenu doesn't sit in the visual tree hierarchy, so RelativeSource has nothing to find.

The solution is outlined here: http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited

Copy/paste this class into your project somewhere:

public class BindingProxy : Freezable
{
    #region Overrides of Freezable

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    #endregion

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

Then reference the namespace of the BindingProxy at the top of your Window/UserControl/whatever:

xmlns:local="clr-namespace:INSERTYOURNAMESPACEHERE"

Add the BindingProxy as a resource to your ListBox:

<ListBox.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</ListBox.Resources>

And finally set the Source of your ContextMenu ItemsSource binding to the proxy:

<ContextMenu ItemsSource="{Binding Data.ContextMenuItems, Source={StaticResource proxy}}" >
goobering
  • 1,547
  • 2
  • 10
  • 24
  • Well, it just can't recognize ContextMenuItems in Data (since last one is object). – lentinant Apr 09 '15 at 15:09
  • It seems, it's not a problem, since although it says, that it can't recognize this properties, it works well. Problem was in my other code. – lentinant Apr 10 '15 at 08:52
0

Refer below code. it is working fine for me.

<Window x:Class="BindingListBox_Learning.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <ListBox Margin="5, 5" Background="White"  ItemsSource="{Binding SwitchAgents, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Stretch">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid Margin="3,1">
                    <Grid.ContextMenu>
                        <ContextMenu ItemsSource="{Binding ContextMenuItems}">
                            <ContextMenu.ItemContainerStyle>
                                <Style TargetType="MenuItem">
                                    <Setter Property="Command" Value="{Binding ItemAction}"/>
                                    <Setter Property="Header" Value="{Binding ItemHeader}"/>
                                </Style>
                            </ContextMenu.ItemContainerStyle>
                        </ContextMenu>
                    </Grid.ContextMenu>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="7*"/>
                    </Grid.ColumnDefinitions>
                    <CheckBox IsChecked="{Binding Enabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,3"/>
                    <TextBlock Text="{Binding SomeProperty}" Grid.Column="1" Margin="0,2"/>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainViewModel();           
    }
}

class MainViewModel
{
    public List<SomeClass> SwitchAgents { get; set; }
    public MainViewModel()
    {
        SwitchAgents = new List<SomeClass>();
        SomeClass obj = new SomeClass();
        obj.SomeProperty = "Test";
        List<ContextMenuItem> lst = new List<ContextMenuItem>();
        lst.Add(new ContextMenuItem() { ItemHeader = "Hi", ItemAction = new BaseCommand(MenuClick) });
        obj.ContextMenuItems = lst;
        SwitchAgents.Add(obj);
    }

    void MenuClick(object obj)
    {
        // Do Menu Click Stuff
    }
}

class ContextMenuItem
{
    public string ItemHeader { get; set; }
    public ICommand ItemAction { get; set; }
}

class SomeClass
{
    public List<ContextMenuItem> ContextMenuItems { get; set; }
    public string SomeProperty { get; set; }
    public string SomeAnotherProperty { get; set; }
}

public class BaseCommand : ICommand
{
    private Predicate<object> _canExecute;
    private Action<object> _method;
    public event EventHandler CanExecuteChanged;

    public BaseCommand(Action<object> method)
        : this(method, null)
    {
    }

    public BaseCommand(Action<object> method, Predicate<object> canExecute)
    {
        _method = method;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute == null)
        {
            return true;
        }

        return _canExecute(parameter);
    }

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

Instead of BaseCommand you use RelayCommand from MVVMLight OR DelegateCommand from PRISM.

Ayyappan Subramanian
  • 5,348
  • 1
  • 22
  • 44
  • Thanks, i will check difference between my and your code, to see, what of my code might be wrong. – lentinant Apr 09 '15 at 15:19
  • Thanks to your code, I know, that this approach works, and it actually gets DataContext right, although xaml editor marks it with warning. Problem was with my definion of Context Menu items. – lentinant Apr 10 '15 at 08:51