简体   繁体   中英

Bind CommandParameter from MenuItem to Parent DataGrid

How can I bind a command parameter from MenuItem to parent Grid DataContext?

I have a DataGrid with ContextMenu, binding the menu items to ViewModel commands, but the command parameter is always null.

I use Tag parameter into DataGrid to get access into DataContext and use the desired command but could figure it out to get the binding data from every row to use as a command parameter.

I had already looked into many answers here, but couldn't find anyone that works, the command parameter inside the ViewModel is called and command parameter is always null.

C#

public class People
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class PeopleWindowViewModel
{
    public List<People> Peoples { get; set; }

    public PeopleWindowViewModel()
    {
        // populate Peoples list...
    }

    public ICommand RemoveCommand
    {
        get
        {
            return RelayCommand.Create((m) =>
            {
                // m always null
            });
        }
    }
}

public class PeoplePage : Page
{
    public PeoplePage()
    {
        InitializeComponent();

        DataContext = new PeopleWindowViewModel();
    }
}

XAML:

  <DataGrid
      Margin="0 8 0 8"
      d:DataContext="{d:DesignInstance local:People}"
      IsReadOnly="True"
      ItemsSource="{Binding Peoples}"
      Tag="{Binding DataContext,
                      RelativeSource={RelativeSource AncestorType={x:Type Page}}}">
    <DataGrid.Columns>
      <DataGridTextColumn
          Binding="{Binding Id}"
          Header="Id" />
      <DataGridTextColumn
          Binding="{Binding Name}"
          Header="Name" />
    </DataGrid.Columns>

    <DataGrid.ContextMenu>
      <ContextMenu
          Tag="{Binding Path=PlacementTarget.Tag,
                              RelativeSource={RelativeSource Self}}">
        <MenuItem
            Command="{Binding PlacementTarget.Tag.RemoveCommand,
                                      RelativeSource={RelativeSource Mode=FindAncestor,
                                                                     AncestorType=ContextMenu}}"
            CommandParameter="{Binding Path=Id,
                                               RelativeSource={RelativeSource Mode=FindAncestor,
                                                                              AncestorType=DataGrid}}"
            Header="Remover" />
      </ContextMenu>
    </DataGrid.ContextMenu>
  </DataGrid>
</Page>

I found a solution, but I'm not sure if there isn't a better one. Anyway, you can do it like this:

<DataGrid ItemsSource="{Binding Peoples}">
    <DataGrid.Resources>
        <ContextMenu x:Key="ctx_menu">
            <ContextMenu.Resources>
                <Style TargetType="{x:Type MenuItem}">
                    <Setter Property="DataContext" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
                </Style>
            </ContextMenu.Resources>
            <MenuItem Command="{Binding DataContext.RemoveCommand}"
                      CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
                      Header="Remove" />
        </ContextMenu>
    </DataGrid.Resources>

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

</DataGrid>

Edit: this gives you the whole People object as CommandParameter. If you just want the Id, just change the CommandParameter to:

CommandParameter="{Binding PlacementTarget.DataContext.Id, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"

If you have to keep the ContextMenu in your DataGrid for the UI-LIB, you have to know, thats generally bad practice in your case. The context menu should go where you need the context of... and that are your rows. But anyway, there a some kind of ugly "solutions". Here is one:

In your Xaml just bind the Command and ignore the CommandParameter.

<DataGrid ItemsSource="{Binding Peoples}">
    <DataGrid.ContextMenu>
        <ContextMenu>
            <MenuItem Command="{Binding RemoveCommand}"
                      Header="Remove" />
        </ContextMenu>
    </DataGrid.ContextMenu>
</DataGrid>

In your ICommand Method, you do this:

private void Remove(object obj)
{
    obj = Mouse.DirectlyOver as FrameworkElement;
    if (obj != null)
        obj = ((FrameworkElement)obj).DataContext;

    // obj should be one People here
}

This should work for most scenarios, but really, in general you try to avoid something like this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM