Search code examples
c#wpfmvvmwpfdatagrid

Binding to ElementName located far in the VisualTree from a MenuItem


Here is the layout sketch of my application:

<UserControl>
    <Grid>
        <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions>
        <Grid.RowDefinitions>...</Grid.RowDefinitions>
        <Border>...</Border>
        <Grid>...</Grid>
        <DataGrid Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="2" AutoGenerateColumns="False"
                  HorizontalAlignment="Stretch" Name="myDataGrid" VerticalAlignment="Stretch"
                  ItemsSource="{Binding Path=MyModel.Files}"
                  SelectedItem="{Binding SelectedFile}"                     
                  Margin="0,0,10,3" CanUserReorderColumns="False" CanUserResizeRows="False" Style="{StaticResource DataGridStyle_Generic}"
                  IsReadOnly="True">...</DataGrid.ContextMenu>
        </DataGrid>
        <Border>...</Border>
        <Border BorderBrush="Gray" BorderThickness="1" Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,0,10,0">
            <Grid Margin="0,0,10,0">
                <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions>
                <Grid.RowDefinitions>...</Grid.RowDefinitions>
                <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,10,2">
                    <Button>...</Button>
                    <Button Margin="0" Name="m_DropDownButton" Padding="0, 4"
                            HorizontalAlignment="Center" VerticalAlignment="Center"                            
                            ContextMenuService.IsEnabled="False" Click="m_DropDownButton_Click">
                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
                            <Path x:Name="BtnArrow" Margin="4" VerticalAlignment="Center" Width="6" Fill="#FF000000" Stretch="Uniform" HorizontalAlignment="Right" Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z "/>
                        </StackPanel>
                        <Button.ContextMenu>
                            <ContextMenu>
                                <MenuItem Header="All files" Command="{Binding AllFilesCommand}" />
                                <MenuItem Header="Selected files only" 
                                          Command="{Binding SelectedFilesOnlyCommand}"
                                          CommandParameter="{Binding Path=SelectedItems, ElementName=myDataGrid}"/>          
                            </ContextMenu>
                        </Button.ContextMenu>
                    </Button>
                </StackPanel>
                <StackPanel Grid.Row="0" Orientation="Horizontal" VerticalAlignment="Stretch" Margin="0,0,10,0">...</StackPanel>
            </Grid>
        </Border>
    </Grid>
</UserControl>

I'd like to pass the SelectedItems property of myDataGrid as a CommandParameter in myMenuItem's Command. How can I achieve this?

Note #1: These two controls are located far in the Visual Tree and their first common parent is myGrid. Couldn't find any working solution for this because I'm always getting the "Cannot find source for binding" error message in the output window.

Note #2: The SelectedItems property of DataGrid is readonly therefore it is not allowed to bind it to a ViewModel property.

Note #3: I'd like to avoid any code behind code such as creating an event handler for DataGrid's SelectionChanged event.


Solution

  • I did not try the Sheridan's way, and it's worth a read IMHO.

    Anyway, you may also try the below hack:

                    <Button Margin="0" Name="m_DropDownButton" Padding="0, 4"
                            HorizontalAlignment="Center" VerticalAlignment="Center"                            
                            ContextMenuService.IsEnabled="False" Click="m_DropDownButton_Click"
                            Tag="{Binding ElementName=myDataGrid}"
                            >
                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
                            <Path x:Name="BtnArrow" Margin="4" VerticalAlignment="Center" Width="6" Fill="#FF000000" Stretch="Uniform" HorizontalAlignment="Right" Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z "/>
                        </StackPanel>
                        <Button.ContextMenu>
                            <ContextMenu>
                                <MenuItem Header="All files" Command="{Binding AllFilesCommand}" />
                                <MenuItem Header="Selected files only" 
                                          Command="{Binding SelectedFilesOnlyCommand}"
                                          CommandParameter="{Binding Path=PlacementTarget.Tag.SelectedItems, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
                            </ContextMenu>
                        </Button.ContextMenu>
                    </Button>
    

    Note the "Tag" attribute on the button and the CommandParameter binding.

    There's also a little code-behind, but I believe you have it as well:

        private void m_DropDownButton_Click(object sender, RoutedEventArgs e)
        {
            var button = (Button)sender;
            button.ContextMenu.PlacementTarget = button;
            button.ContextMenu.IsOpen = true;
        }
    

    Let me know.