0

I wanted to bind a property in a MenuItem within an ItemsControl container. I have a hierarchical model ; Item class has a list of SubItem. The ViewModel itself has a list of Item (so there's two ItemsControl, one being in the ItemTemplate of the other).

I found several other questions on SO asking about that (this one for instance) and I learned that the Visual Tree of a ContextMenu is separated from the rest.

I managed to do it and it works (but it feels kind of hacky) by "transferring" the model's data via the Tag property.

Here's the two model classes:

public class SubItem
{
    public int Current { get; set; }

    public Subitem(int current)
    {
        Current = current;
    }
}

public class Item
{
    public ObservableCollection<SubItem> SubItems { get; set; }

    public string Parent { get; set; }

    public Item(string Parent)
    {
        Parent = Parent;
        SubItems = new ObservableCollection<SubItem>();
    }
}

Here's the view model:

public class ViewModel
{
    public ObservableCollection<Item> Items { get; set; } 

    public ViewModel()
    {
        Items = new ObservableCollection<Item>();
        FillData();
    }

    private void FillData()
    {
        //...
    }
}

And here's the ItemsControl at the root of the page (the page's DataContext is an instance of the ViewModel class):

<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <ItemsControl ItemsSource="{Binding SubItems}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Current}"
                                   Tag="{Binding DataContext.Parent, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}, Mode=FindAncestor}}">
                            <TextBlock.ContextMenu>
                                <ContextMenu>
                                    <MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}, Mode=FindAncestor}}"/>
                                </ContextMenu>
                            </TextBlock.ContextMenu>
                        </TextBlock>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

My question is: is this the right way to do it? I tried many other ways to avoid binding the property to the Tag but couldn't make it work.

The ugly part is specifically:

Tag="{Binding DataContext.Parent, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}, Mode=FindAncestor}}" 

Followed by:

Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}, Mode=FindAncestor}}"

I think it's kind of ugly and I'm sure there's a better way to do that. The solution must work with .NET 4.0.

Community
  • 1
  • 1
Max
  • 3,453
  • 3
  • 32
  • 50
  • You would usually do it [this way](http://stackoverflow.com/a/6914964/1136211). – Clemens Feb 25 '15 at 11:21
  • @Clemens thanks for the link ; however the `DataContext` referenced that way would allow me to bind `SubItem`'s properties. I'm interested in `Item`'s properties. I don't see how I could reference the right `DataContext` that way but that may be a start. – Max Feb 25 '15 at 11:25

1 Answers1

1

You need to add a tag to the menu's container and bind to it using placement target.

View this example:

<StackPanel x:Key="ConfigurationListItem" x:Shared="False" Tag="{Binding ElementName=UserControl}">
        <StackPanel Orientation="Horizontal">
            <Button>
                <Button.InputBindings>
                    <MouseBinding Gesture="LeftDoubleClick" Command="{Binding ElementName=UserControl, Path=LaunchCommand}" CommandParameter="{Binding}" />
                    <MouseBinding Gesture="LeftClick" Command="{Binding ElementName=UserControl, Path=SelectCommand}" CommandParameter="{Binding}" />
                </Button.InputBindings>
        </StackPanel>

        <StackPanel.ContextMenu>
            <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}" Tag="{Binding}">
                <MenuItem Header="Sync Environment Dependencies" 
                        Command="{Binding Parent.PlacementTarget.Tag.SyncEnvironmentCommand, RelativeSource={RelativeSource Self}}"
                        CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.DataContext}" />
            </ContextMenu>
        </StackPanel.ContextMenu>
    </StackPanel>
Scott Nimrod
  • 11,206
  • 11
  • 54
  • 118
  • Well yes, you don't even need to go through a `ContextMenu` property, you can skip it and bind to the `Tag` property of the parent directly (that is what I did actually). I wanted to avoid referencing other `UIElement` properties in the binding and solely use the `DataContext`... – Max Mar 01 '15 at 09:38