Home > Enterprise >  Access Data Context properties from Context Menu
Access Data Context properties from Context Menu

Time:12-23

I try to learn a little bit about how a listboxview combined with context menu works, so I've made the following XAML code:

<UserControl>
   ...
     <ListView
                x:Name="level1Lister"
                Grid.Row="1"
                behaviours:AutoScrollListViewBehaviour.ScrollOnNewItem="True"
                ItemsSource="{Binding LogBuffer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem">
                        <Setter Property="Padding" Value="1" />
                        <Setter Property="Margin" Value="2,0" />
                        <Setter Property="BorderThickness" Value="0" />
                    </Style>
                </ListView.ItemContainerStyle>
                <ListView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Vertical" />
                    </ItemsPanelTemplate>
                </ListView.ItemsPanel>
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <StackPanel.ContextMenu>
                                <ContextMenu>
                                    <MenuItem Command="{Binding Path=DataContext.ValidateAllCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, FallbackValue=9999999999}" Header="Copy" />
                                </ContextMenu>
                            </StackPanel.ContextMenu>
                            <TextBlock Foreground="{Binding Color, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
</UserControl>

My main problem is that I'm not able to access my "ValidateAllCommand" function, for some reason... I think it has to do with the "RelativeSource={RelativeSource FindAncestor ... " part but I can't figure out how.

CodePudding user response:

The "problem" with the ContextMenu is that it is not part of the visual tree. The reason is that the MenuItem uses a Popup to host the content. While the Popup itself is part of the visual tree, its Child content is disconnected as it is rendered in a new Window instance. We know there can can only be a single Window in the tree and this Window must be the root.
Since Binding.RelativeSource traverses the visual tree to find the binding source, the Bindig does not resolve.

The Popup.Child content inherits the DataContext of the Popup. In case of the ContextMenu it means that the MenuItem inherits the DataContext from the ContextMenu or from the parent of the ContextMenu, to be more precise. In your scenario the DataContext is the data model of the ListBoxItem i.e. the DataContext of the DataTemplate.

This means, one solution could be to implement the command in the item model.

The second solution is to make use of routed commands. This solution might be more reasonable in your scenario. The routed command/event can cross the boundary between the two trees.

In the following example, I use the ApplicationCommands.Copy command, one of the predefined routed commands. The MenuItem.Parameter is bound to the DataContext, which is the item data model (inherited from the ContextMenu, as mentioned before). This way the command handler can know the data source.
The event handler can be attached to any parent element using the UIElement.CommandBindings property. In the example the handler is attached to the ListBox element:

MainWindow.xaml

<ListBox>
  <ListBox.CommandBindings>
    <CommandBinding Command="{x:Static ApplicationCommands.Copy}"
                    Executed="CopyCommand_Executed" />
  </ListBox.CommandBindings>
  <ListBox.ItemTemplate>
    <DataTemplate DataType="{x:Type local:DataItem}">
      <StackPanel>
        <StackPanel.ContextMenu>
          <ContextMenu>
      
            <!-- 
              Here we are in a detached visual tree. 
              The DataContext is inherited from the ContextMenu element. 
              The framework allows routed events to cross the boundaries between the trees.
            -->
            <MenuItem Command="{x:Static ApplicationCommands.Copy}"
                      CommandParameter="{Binding}"
                      Header="Copy" />
          </ContextMenu>
        </StackPanel.ContextMenu>

        <TextBlock />
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

MainWindow.xaml.cs

private void CopyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
  var listBoxItemModel = e.Parameter as LogBufferItem;

  // TODO::Handle Copy command. For example:
  var commandTarget = this.DataContext as ICommandModel;
  commandTarget.ValidateAllCommand.Execute(listBoxItemModel);
}

A third, but not recommended solution is to bind the DataContext of the ContextMenu parent to the context of interest:

  <ListBox.ItemTemplate>
    <DataTemplate DataType="{x:Type local:DataItem}">
      <StackPanel>
        <!-- DataTemplate DataContext -->

        <Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext}">

          <!-- ListBox DataContext -->
          <Grid.ContextMenu>
            <ContextMenu>
      
              <!-- 
                Here we are in a detached visual tree. 
                The DataContext is inherited from the ContextMenu/Grid element. 
              -->
              <MenuItem Command="{Binding ValidateAllCommand}"
                        Header="Copy" />
            </ContextMenu>
          </Grid.ContextMenu>
        </Grid>

        <!-- DataTemplate DataContext -->
        <TextBlock />
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox
  • Related