0

Good afternoon,

I have what appear to be a very common issue. I have a user control which has a View Model as its data context. Elements within this user control then use this ViewModel for Binding purposes ect.

The ViewModel

public class TicketDetailsViewModel : ViewModelBase
{
    public DelegateCommand<object> HideSelectedText { get; private set; }

    private Ticket _ticket;
    public Ticket Ticket
    {
        get { return _ticket; }
        set
        {
            _ticket = value;
            this.RaisePropertyChanged(p => p.Ticket);
        }
    }
}

My ViewModel contains a single Ticket object. This ticket object has a collection of comments attached to it and these are rendered to the Ticket Display user control using an ItemsControl

 <ItemsControl ItemsSource="{Binding Path=Ticket.Comments}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Border CornerRadius="15" Background="{Binding Path=CommentType, ConverterParameter=CommentType, Converter={StaticResource ResourceKey=commentColorConverter}}" Padding="10" Margin="40,10,40,0">
                                <TextBox x:Name="tbComment" Text="{Binding CommentText}" IsReadOnly="True">
                                <TextBox.ContextMenu>
                                    <ContextMenu>
                                        <MenuItem Header="Spam" Command="{Binding Path=DataContext.HideSelectedText,RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type UserControl} }}">

                                        </MenuItem>
                                    </ContextMenu>
                                </TextBox.ContextMenu>
                            </TextBox>
                        </Border>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>  
                            <StackPanel Orientation="Vertical"/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                </ItemsControl>  

You will notice each TextBox rendered by this ItemsControl has a ContextMenu attached to it. What I am trying to do is bind the command of this ContextMenuItem to the DelegateCommand in my ViewModel. Of course, simply using;

<MenuItem Header="Spam" Command="{Binding HideSelectedText}">

We dont get anything useful as 'Binding' in this context equals a Ticket.Comment and thus has no idea what the HideSelectedText actually is.

There seem to be many questions similar to this one and all answers seem to veer towards the RelativeSource solution. As you can see in my original XAML code, I have tried this as well as many other versions of it (with and without AncestorLevel set, with AncestorType={x:Type ItemsControl}, AncestorType={x:Type Window}, AncestorType={x:Type DataTemplate} ect) and ALL produce an output error similar to;

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.UserControl', AncestorLevel='1''. BindingExpression:Path=DataContext.HideSelectedText; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')

or

System.Windows.Data Error: 40 : BindingExpression path error: 'HideSelectedText' property not found on 'object' ''TicketComment' (HashCode=49290260)'. BindingExpression:Path=DataContext.HideSelectedText; DataItem='ContextMenu' (Name=''); target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')

So why does this solution seem to work for so many people and yet for me, it makes no difference what so ever from simply typing {Binding HideSelectedText} ?

4

3 回答 3

3

ContextMenus 实际上并不是 WPF 的 VisualTree 的一部分,因此绑定不会像预期的那样工作。作为替代方案,试试这个绑定:

<MenuItem Header="Spam" Command="{Binding PlacementTarget.DataContext.HideSelectedText,
    RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" />
于 2011-03-08T18:28:02.730 回答
1

最好的解决方案是通过资源来完成。如果将在资源中创建 ContextMenu,它将自动从父级继承 DataContext。然后只需创建一个具有静态资源传递值的样式即可。

例子:

<DataGrid.Resources>
    <ContextMenu x:Key="test_ContextMenu" DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
        <MenuItem Header="Test Header" Command="{Binding Path=TestCommand, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
    </ContextMenu>
</DataGrid.Resources>

<DataGrid.ItemContainerStyle>
    <Style TargetType="DataGridRow">
        <Setter Property="ContextMenu" Value="{StaticResource test_ContextMenu}"/>
    </Style>
</DataGrid.ItemContainerStyle>

此代码在 .NET 4.5 上进行了测试,但我认为它也适用于早期版本。

于 2014-04-28T11:10:40.137 回答
0

由于上下文菜单不是可视化树的一部分,因此默认情况下它不继承 DataContext。您必须将上下文菜单的 DataContext 设置为 PlacementTarget,如下所示 -

<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext}">
   <ContextMenuItem Header="Item1" Command="{Binding YourCommand}" />
</ContextMenu>

希望这可以帮助。

-VJ

于 2013-04-05T16:32:49.443 回答