开发者

ContextMenu.PlacementTarget is not getting set, no idea why

开发者 https://www.devze.com 2023-02-05 07:56 出处:网络
<DataTemplate x:Key=\"_ItemTemplateA\"> <Grid Tag=\"{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}\">
<DataTemplate x:Key="_ItemTemplateA">
  <Grid Tag="{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <ContentControl Content="{Binding}" ContentTemplate="{StaticResource ContentTemplateB}" Grid.Row="0" />
    <ContentControl Name="uiContentPresenter" Content="{Binding ContentView}" Grid.Row="1" Height="0" />
    <ContentControl DataContext="{Binding IsContentDisplayed}" DataContextChanged="IsDisplayed_Changed" Visibility="Collapsed" />
    <Grid.ContextMenu>
      <ContextMenu>
        <MenuItem Header="Text" 
              Command="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
              CommandParameter="{Binding}" />
      </ContextMenu>
    </Grid.ContextMenu>
  </Grid>
</DataTemplate>

The above data template is applied to an ItemsControl. The issue is that for the ContextMenu that is specified for the Grid, the PlacementTarget property is never actually getting set to anything so I cannot get to the Tag property of the Grid which i开发者_JS百科s necessary for passing the Command that should execute on the parent UserControl down to the context menu. I've based this approach off of similar examples such as this: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/0244fbb0-fd5f-4a03-bd7b-978d7cbe1be3/

I've not been able to identify any other good way to pass this command down. This is setup this way because we are using an MVVM approach so the command we have to execute lives in the View Model of the user control this template is applied in. I've tried explicitly setting the PlacementTarget in a few different ways but it still always shows up as not set.


I realise that this is old and answered, but it doesn't seem properly answered. I came across a similar post and left a full answer. You might like to take a look as you can get it working with just a few adjustments to your code.

First, name your view UserControl... I generally name all of mine This for simplicity. Then remembering that our view model is bound to the DataContext of the UserControl, we can bind to the view model using {Binding DataContext, ElementName=This}.

So now we can bind to the view model, we have to connect that with the ContextMenu.DataContext. I use the Tag property of the object with the ContextMenu (the PlacementTarget) as that connection, in this example, a Grid:

<DataTemplate x:Key="YourTemplate" DataType="{x:Type DataTypes:YourDataType}">
    <Grid ContextMenu="{StaticResource Menu}" Tag="{Binding DataContext, 
        ElementName=This}">
        ...
    </Grid>
</DataTemplate>

We can then access the view model properties and commands in the ContextMenu by binding the ContextMenu.DataContext property to the PlacementTarget.Tag property (of the Grid in our example):

<ContextMenu x:Key="Menu" DataContext="{Binding PlacementTarget.Tag, RelativeSource=
    {RelativeSource Self}}">
    <MenuItem Header="Delete" Command="{Binding DeleteFile}" CommandParameter=
        "{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource 
        AncestorType=ContextMenu}}" CommandTarget="{Binding PlacementTarget, 
        RelativeSource={RelativeSource Self}}" />
</ContextMenu>

Note the binding on the MenuItem.CommandTarget property. Setting this ensures that the target element on which the specified command is raised is the PlacementTarget, or the Grid in this case.

Also note the CommandParameter binding. This binds to the DataContext of the PlacementTarget, or the Grid in this case. The DataContext of the Grid will be inherited from the DataTemplate and so your data item is now bound to the object parameter in your Command if you're using some implementation of the ICommand interface:

public bool CanExecuteDeleteFileCommand(object parameter)
{
    return ((YourDataType)parameter).IsInvalid;
}

public void ExecuteDeleteFileCommand(object parameter)
{
    Delete((YourDataType)parameter);
}

Or if you are using some kind of RelayCommand delegates directly in your view model:

public ICommand Remove
{
    get 
    {
        return new ActionCommand(execute => Delete((YourDataType)execute), 
            canExecute => return ((YourDataType)canExecute).IsInvalid); 
    }
}


We have the same problem, but it works randomly. A contextmenu inside the controltemplate in a style for a listbox. We have tried to move the contextmenu to different levels inside the template but the same error occurs.

We think it might be connected to the refreshing of our ICollectionView that is the itemssource of the ListBox.

It seems that when the view refreshes the relative source binding inside the contextmenu is being evaluated before the PlacementTarget is being set.

It feels like a bug in either collectionviewsource or the ContextMenu of WPF...


Here is a working standalone XAML-only example based on your test case: a ContextMenu that retrieves a Command from the DataContext of its PlacementTarget using a Tag. You can reintroduce portions of your code until it stops working to try to find where the problem is:

<Grid>
    <Grid.Resources>
        <PointCollection x:Key="sampleData">
            <Point X="10" Y="20"/>
            <Point X="30" Y="40"/>
        </PointCollection>
        <DataTemplate x:Key="_ItemTemplateA">
            <Grid Tag="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}}">
                <TextBlock Text="{Binding X}"/>
                <Grid.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding}"/>
                    </ContextMenu>
                </Grid.ContextMenu>
            </Grid>
        </DataTemplate>
    </Grid.Resources>
    <DockPanel DataContext="{x:Static ApplicationCommands.Open}">
        <ListBox ItemTemplate="{StaticResource _ItemTemplateA}" ItemsSource="{StaticResource sampleData}"/>
    </DockPanel>
</Grid>


In this post,

ContextMenu.PlacementTarget is filled up from ContextMenuService.PlacementTarget when you do right click with mouse on button.

It means ContextMenu.PlacementTarget is filled up when the menu is shown up. You can check that by snoop.

EDIT 1 This code works fine.

<DataGrid.RowStyle>
    <Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowBaseStyle}">
        <Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=DataContext}"/>
        <Setter Property="ContextMenu">
            <Setter.Value>
                <ContextMenu Style="{StaticResource ContextMenuStyle}" 
                             ItemContainerStyle="{StaticResource MenuItemStyle}">
                    <MenuItem Header="Enable" Command="{Binding Path=PlacementTarget.Tag.(viewModels:PrinterListPageViewModel.EnableCommand), RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
                </ContextMenu>
            </Setter.Value>
        </Setter>
    </Style>
</DataGrid.RowStyle>
0

精彩评论

暂无评论...
验证码 换一张
取 消