0

Hi I have a question about how can I bind datagrid datacontext to context menu datacontext in the following situation:

         <DataGrid DataContext="{Binding someDataContext}" ItemsSource="{Binding items}">

            <DataGrid.Resources>
                    <ContextMenu  x:Key="LocalViewerMenu" DataContext="{path to DataGrid.DataContext}">
                                <MenuItem Header="Open" Command="{Binding OpenCommand}"/>
                    </ContextMenu>
            </DataGrid.Resources>

            <DataGrid.RowStyle>
                    <Style TargetType="{x:Type DataGridRow}">
                          <Setter Property="ContextMenu" Value="{StaticResource LocalViewerMenu}"/>
                    </Style>
            </DataGrid.RowStyle>

          </DataGrid>

P.S. I can't set context menu to DataGrid directly. It should be as Row context menu.

Thanks in advance

Vlad
  • 854
  • 1
  • 10
  • 29
  • A context menu is not part of the visual tree. Try using the solution selected as the correct answer on the following question: http://stackoverflow.com/questions/13390671/databinding-in-wpf-for-a-parent-object/13390810#13390810 – Lee O. Nov 19 '12 at 15:08
  • thanks, the answer provided there isn't helpfull in this problem, because in xaml above, as you can see, ContextMenu is bounded to DataGridRow, not DataGrid itself. So I can't get the whole DataGrid Datacontext through PlacementTarget. – Vlad Nov 19 '12 at 17:09

1 Answers1

2

You could try to use a DataContextProxy, described here. The DataContextProxy is used in Silverlight 4 to simplify DataBinding in nested controls, because Silverlight 4 does not support "RelativeSource", "AncestorType", like WPF. In your case, you don't have access to the ancestor neither, as the context menu is not part of the visual tree, so maybe it can help you.

EDIT

Yes, I tested it myself and indeed it does not work, because the DataContextProxy.Loaded event is never raised ( it seems we're not the only ones to have hit this problem). Nevertheless, we can use a similar approach. Take a look on the following behavior:

public class DataContextProxyBehavior : Behavior<FrameworkElement>
{
    public Object DataSource
    {
        get { return (Object)GetValue(DataSourceProperty); }
        set { SetValue(DataSourceProperty, value); }
    }
    public static readonly DependencyProperty DataSourceProperty =
        DependencyProperty.Register("DataSource", typeof(Object), typeof(DataContextProxy), null);

    protected override void OnAttached()
    {
        base.OnAttached();

        // Binds the target datacontext to the proxy,
        // so whenever it changes the proxy will be updated
        var binding = new Binding();
        binding.Source = this.AssociatedObject;
        binding.Path = new PropertyPath("DataContext");
        binding.Mode = BindingMode.OneWay;
        BindingOperations.SetBinding(this, DataContextProxyBehavior.DataSourceProperty, binding);

        // Add the proxy to the resource collection of the target
        // so it will be available to nested controls
        this.AssociatedObject.Resources.Add(
            "DataContextProxy",
            this
        );
    }
    protected override void OnDetaching()
    {
        base.OnDetaching();

        // Removes the proxy from the Resources
        this.AssociatedObject.Resources.Remove(
            "DataContextProxy"
        );
    }
}

Once you set it on a control, it will make the control's DataContext available in the control's resource dictionary. To test it, I created the following ViewModel:

public class ViewModel : INotifyPropertyChanged
{
    #region INotifyPropertyChanged values

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion

    public ActionCommand Command { get; set; }

    public List<string> Elements { get; set; }

    public ViewModel()
    {
        this.Elements = new List<string>(){
            "Element1",
            "Element2"
        };

        this.Command = new ActionCommand()
        {
            ExecuteDelegate = this.Execute
        };
    }

    public void Execute(object o)
    {
        MessageBox.Show(o.ToString());
    }
}

And the following Window:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="MainWindow"
    Width="525"
    Height="350">
<Window.DataContext>
    <local:ViewModel />
</Window.DataContext>
<Grid Name="LayoutRoot">
    <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Elements}">
        <i:Interaction.Behaviors>
            <local:DataContextProxyBehavior />
        </i:Interaction.Behaviors>
        <DataGrid.RowStyle>
            <Style TargetType="{x:Type DataGridRow}">
                <Setter Property="ContextMenu">
                    <Setter.Value>
                        <ContextMenu>
                            <MenuItem Command="{Binding DataSource.Command,
                                                        Source={StaticResource DataContextProxy}}"
                                      CommandParameter="{Binding}"
                                      Header="Open" />
                        </ContextMenu>
                    </Setter.Value>
                </Setter>
            </Style>
        </DataGrid.RowStyle>
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding}" Header="MyHeader" />
        </DataGrid.Columns>
    </DataGrid>
</Grid>

We attach the behavior to the DataGrid. The behavior will then bind the DataGrid.DataContext to it's own DataSource property. Next, it will add itself to the DataGrid's ResourceDictionary. We can then get it as a staticresource when defining the ContextMenu.

Community
  • 1
  • 1
Arthur Nunes
  • 6,718
  • 7
  • 33
  • 46