17

I'm trying to bind a command to a menuitem in WPF. I'm using the same method that's been working for all my other command bindings, but I can't figure out why it doesn't work here.

I'm currently binding my commands like this:

Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.MyCommand}"

This is where it goes wrong (this is inside a UserControl)

<Button Height="40" Margin="0,2,0,0" CommandParameter="{Binding Name}" 
                        Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ConnectCommand}">

     <Button.ContextMenu>
         <ContextMenu>
             <MenuItem Header="Remove" CommandParameter="{Binding Name}"
                                      Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.RemoveCommand}"/>
         </ContextMenu>
     </Button.ContextMenu>
     ...

The first command binding works like it should, but the second one refuses to do anything. I've tried changing the ancestor level and naming my Control to access it through ElementName instead of RelativeSource, but still no change. It keeps saying "Cannot find source for binding with reference..."

What am I missing?

Valyrion
  • 2,342
  • 9
  • 29
  • 60
  • 1
    I'd have to check, but the MenuItem may be in a different tree, so it can't find the UserControl since technically it's not an ancestor (Snoop could confirm whether I remember this right or not). For the other command bindings (such as the command for the Button control), why can't you just do Command="{Binding Path=ConnectCommand}" ? The Button should be inheriting the DataContext from the UserControl and therefore not require the whole RelativeSource/FindAncestor syntax. – MetalMikester Apr 03 '12 at 13:31

4 Answers4

32

(Edit) Since you mentioned this is in an ItemsControl's template, things are different:

1) Get the BindingProxy class from this blog (and read the blog, as this is interesting information): How to bind to data when the DataContext is not inherited.

Basically the elements in the ItemsControl (or ContextMenu) are not part of the visual or logical tree, and therefore cannot find the DataContext of your UserControl. My apologies for not writing more on this here, but the author has done a good job explaining it step by step, so there's no way I could give a complete explanation in just a few lines.

2) Do something like this: (you may have to adapt it a bit to make it work in your control):

a. This will give you access to the UserControl DataContext using a StaticResource:

<UserControl.Resources>
<BindingProxy
  x:Key="DataContextProxy"
  Data="{Binding}" />
</UserControl.Resources>

b. This uses the DataContextProxy defined in (a):

<Button.ContextMenu>
 <ContextMenu>
     <MenuItem Header="Remove" CommandParameter="{Binding Name}"
         Command="{Binding Path=Data.RemoveCommand, Source={StaticResource DataContextProxy}}"/>
 </ContextMenu>

This has worked for us in things like trees and datagrids.

karfus
  • 869
  • 1
  • 13
  • 20
MetalMikester
  • 1,067
  • 1
  • 13
  • 15
  • The problem is this button is part of an ItemsControl ItemTemplate. I have a collection of models that's used as the binding for the ItemsControl `` That's why the simple way of binding the commands didn't work because they're not in those models (I think, it's still mostly magic to me) – Valyrion Apr 03 '12 at 13:49
  • 1
    Oh, that's different then, but I had to go through the same thing with a XamDataTree (Infragistics tree control). Let me find the information for you (if no one else posts the solution before I do) :) – MetalMikester Apr 03 '12 at 13:54
  • @Baboon care to explain? – SZT Jun 06 '14 at 16:52
  • awesome,it works. – Farb Jul 26 '23 at 04:40
14

ContextMenu is in different logical tree, that's why RelativeSource doesnt work. But context menu inherit DataContext from its "container", in this case it is Button. It is enough in common case but in your case you need two "data contexts", of ItemsControl item and of ItemsControl itself. I think you have no other choice but combine your view models into one, implement custom class to be used as ItemsControl item data context and contain both "Name" and "Remove command" or your item's view model can define RemoveCommand "proxy", that would call parent command internally

EDIT: I slightly changed Baboon's code, it must work this way:

<Button Height="40" Margin="0,2,0,0" CommandParameter="{Binding Name}" 
    Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
    Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ConnectCommand}">
            <Button.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Remove" 
                   CommandParameter="{Binding Name}"
                   Command="{Binding Path=PlacementTarget.Tag.DataContext.RemoveCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
                </ContextMenu>
            </Button.ContextMenu>
koshdim
  • 196
  • 4
  • That works. I was using "Data" instead of "DataContext" when I tried the original code. I'll stick to the BindingProxy approach though - the syntax is a little less heavy, but above all we sometimes use the Tag on some objects to carry some additional information and I don't want to come to a point where we're using this approach and run into a scenario where the Tag is needed for two different purpose. Nice to have options though! – MetalMikester Apr 03 '12 at 17:51
5

koshdim is spot on, it works like a charm!! Thanks Koshdim

I modified his code to fit in my context menu

    <DataGrid 
        AutoGenerateColumns="False" 
        HeadersVisibility="Column"
        Name="dgLosses"
        SelectedItem="{Binding SelectedItem, Mode= TwoWay}"
        AllowDrop="True"
        ItemsSource="{Binding Losses}"
        Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}">


        <DataGrid.ContextMenu >
            <ContextMenu >
                <MenuItem Header="Move to Top     "   Command="{Binding PlacementTarget.Tag.MoveToTopCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>
                <MenuItem Header="Move to Period 1"   Command="{Binding PlacementTarget.Tag.MoveToPeriod1Command,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>
                <MenuItem Header="Move to Period 2"   Command="{Binding PlacementTarget.Tag.MoveToPeriod2Command,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>
                <MenuItem Header="Move to Period 3"   Command="{Binding PlacementTarget.Tag.MoveToPeriod3Command,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>                    
            </ContextMenu>
        </DataGrid.ContextMenu>
Tarik.J
  • 121
  • 1
  • 3
3

That's a tricky issue, sure marginally you will find a quick workaround, but here is a no-magic-solution:

<Button Height="40" Margin="0,2,0,0" CommandParameter="{Binding Name}" 
        Tag={Binding}
        Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ConnectCommand}">    
     <Button.ContextMenu>
         <ContextMenu>
             <MenuItem Header="Remove" 
                       CommandParameter="{Binding Path=PlacementTarget.Tag.Name, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
                       Command="{Binding Path=PlacementTarget.Tag.RemoveCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
         </ContextMenu>
     </Button.ContextMenu>
...

It boils down to using the Tag of the PlacementTarget (the Button here).

Louis Kottmann
  • 16,268
  • 4
  • 64
  • 88
  • Interesting. I'll have to try that on our own stuff to see if it can replace the BindingProxy approach. – MetalMikester Apr 03 '12 at 14:13
  • Actually I believe I originally deduced that from an MSDN example somewhere... can't remember which though. – Louis Kottmann Apr 03 '12 at 14:15
  • I gave this a quick try in our XamDataTree ItemTemplate and no go - it can't find the command. Maybe it'll work in Slyder's case - hard to say without the surrounding code. – MetalMikester Apr 03 '12 at 14:21
  • You're probably going to have to adjust the bindings to fit the DataContext you need. – Louis Kottmann Apr 03 '12 at 14:24
  • I did - or at least I think I did. :) I'll play with this a bit more when I have some time, since what we have in place works fine. Got other fires to put out. :) – MetalMikester Apr 03 '12 at 14:33