1

I am trying to bind DataGrid column header to its own ContextMenu like this:

 <DataGrid x:Name="AllLogs">
     <DataGrid.ContextMenu>
         <ContextMenu>
              <MenuItem Header="Show/Hide Columns" 
                        ItemsSource="{Binding ElementName=AllLogs, Path=Columns}">
                    <MenuItem.ItemTemplate>
                         <DataTemplate>
                             <TextBlock Text="{Binding Header}"></TextBlock>
                         </DataTemplate>
                    </MenuItem.ItemTemplate>
              </MenuItem>
           </ContextMenu>
       </DataGrid.ContextMenu>
   </DataGrid>

Its always sends the following error in output:

Cannot find source for binding with reference 'ElementName=AllLogs'. BindingExpression:Path=Columns; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')


EDIT: Binding with a ComboBox works as expected

 <ComboBox ItemsSource="{Binding ElementName=AllLogs, Path=Columns}">
        <ComboBox.ItemTemplate>
              <DataTemplate>
                  <CheckBox Content="{Binding Header}"/>
              </DataTemplate>
         </ComboBox.ItemTemplate>
 </ComboBox>

5 Answers 5

3

You should set first the DataContext of ContextMenu so that ItemsSource bind to Menu Item can inherit the same DataContext.

<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show/Hide Columns" 
    ItemsSource="{Binding Columns}">
    <MenuItem.ItemTemplate>
         <DataTemplate>
              <TextBlock Text="{Binding Header}"></TextBlock>
         </DataTemplate>
    </MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
Sign up to request clarification or add additional context in comments.

Comments

2
+50

The reason that ContextMenu didn't work but ComboBox did is that ContextMenu is a Popup, that means it's not part of DataGrid's visual tree, so ElementName would not work as ComboBox did. In fact, @user1672994 was vary close to the answer.

<DataGrid x:Name="AllLogs">
    <DataGrid.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Show/Hide Columns" 
                      ItemsSource="{Binding PlacementTarget.Columns, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
                <MenuItem.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Header}"/>
                    </DataTemplate>
                </MenuItem.ItemTemplate>
            </MenuItem>
        </ContextMenu>
    </DataGrid.ContextMenu>
</DataGrid>

Comments

1

The problem/misunderstanding is that ContextMenu isn't a part of a visual tree.

So ContextMenu.PlacementTarget is your "connection" to the UIElement, which is in the visual tree, therefore you have to go via PlacementTarget to access the elements from visual tree.

MSDN about PlacementTarget:

When the ContextMenu is assigned to the FrameworkElement.ContextMenu or FrameworkContentElement.ContextMenu property, the ContextMenuService changes this value of this property to the owning FrameworkElement or FrameworkContentElement when the ContextMenu opens

In answer below you don't have to traverse searching an ancestor type, but do use an UIElement as DataContext for ContextMenu:

<DataGrid.ContextMenu>
    <ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
        <MenuItem Header="Show/Hide Columns" ItemsSource="{Binding Columns}">
            <MenuItem.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Header}"/>
                </DataTemplate>
            </MenuItem.ItemTemplate>
        </MenuItem>
    </ContextMenu>
</DataGrid.ContextMenu>

Comments

0

I don't know the RadGrivView control per say, but the error means that it cannot find an IEnumerable property called "Columns" on your element. Are you sure it is a publicly accessible collection for the control?

1 Comment

RadGridView is a control from telerik, which is derived from DataGrid. and yes it has publicly accessible getter and a private setter !
0

ElementName uses VisualTree to find out desired element, it its not part of the current visual tree - up or down as it is with context menu you will get exception.

You could use Binding Source={x:Reference AllLogs} that does not use VisualTree, but unfortunately in your use case you would get exception of circular reference if you use it directly without style.

What you need to use is RelativeSource binding.

<Window.Resources>
    <ContextMenu x:Key="headerMenu">
        <ContextMenu.Items>
            <MenuItem
                Header="Show/Hide Columns"
                ItemsSource="{Binding Columns, RelativeSource={RelativeSource AncestorType=DataGrid}}">
                <MenuItem.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Header}"/>
                    </DataTemplate>
                </MenuItem.ItemTemplate>
            </MenuItem>
        </ContextMenu.Items>
    </ContextMenu>

    <Style TargetType="{x:Type DataGrid}">
        <Setter Property="ContextMenu" Value="{StaticResource headerMenu}" />
    </Style>
</Window.Resources>
<Grid>
    <DataGrid x:Name="AllLogs">
        <DataGrid.Columns>
            <DataGridTextColumn Header="ID"></DataGridTextColumn>
            <DataGridTextColumn Header="Name"></DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

This would work as well - with Reference:

<Window.Resources>
    <ContextMenu x:Key="headerMenu">
        <ContextMenu.Items>
            <MenuItem
                Header="Show/Hide Columns"
                ItemsSource="{Binding Source={x:Reference AllLogs}, Path=Columns}">
                <MenuItem.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Header}"/>
                    </DataTemplate>
                </MenuItem.ItemTemplate>
            </MenuItem>
        </ContextMenu.Items>
    </ContextMenu>

    <Style TargetType="{x:Type DataGrid}">
        <Setter Property="ContextMenu" Value="{StaticResource headerMenu}" />
    </Style>
</Window.Resources>
<Grid>
    <DataGrid x:Name="AllLogs">
        <DataGrid.Columns>
            <DataGridTextColumn Header="ID"></DataGridTextColumn>
            <DataGridTextColumn Header="Name"></DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.