WPF中ContextMenu多绑定传递PdfViewerCtrl返回UnsetValue问题
我太懂你现在的糟心感受了——明明写了两种绑定方式,结果ContextMenu里就是拿不到PdfViewerCtrl的实例,一直返回DependencyProperty.UnsetValue。这其实是WPF里ContextMenu的经典坑:ContextMenu并不属于当前UserControl的可视化树(Visual Tree),它是作为独立的Popup元素存在的,所以你用ElementName或者RelativeSource FindAncestor的方式,根本找不到你的UserControl,自然拿不到目标控件。
下面给你几个可行的解决方案,你可以根据项目情况选:
方案1:借助ContextMenu的PlacementTarget属性
ContextMenu的PlacementTarget指向的是它附着的控件(也就是你的ListBox),而这个ListBox肯定在UserControl的可视化树里,我们可以通过它间接找到UserControl和里面的PdfViewerCtrl:
<ContextMenu> <MenuItem Header="Löschen"/> <i:Interaction.Triggers> <i:EventTrigger EventName="PreviewMouseDown"> <i:InvokeCommandAction Command="{Binding DeleteAnnotationCmd}"> <i:InvokeCommandAction.CommandParameter> <MultiBinding Converter="{StaticResource MultiBindingConv}"> <!-- 通过PlacementTarget找到UserControl,再获取PdfViewerCtrl --> <Binding RelativeSource="{RelativeSource Mode=Self}" Path="PlacementTarget"> <Binding.RelativeSource> <RelativeSource Mode="FindAncestor" AncestorType="{x:Type UserControl}"/> </Binding.RelativeSource> <Binding.Path>PdfViewerCtrl</Binding.Path> </Binding> </MultiBinding> </i:InvokeCommandAction.CommandParameter> </i:InvokeCommandAction> </i:EventTrigger> </i:Interaction.Triggers> </ContextMenu>
如果你的UserControl的DataContext里已经暴露了PdfViewerCtrl属性,还可以简化成:
<Binding RelativeSource="{RelativeSource Mode=Self}" Path="PlacementTarget.DataContext.PdfViewerCtrl"/>
方案2:把UserControl实例绑定到ContextMenu的DataContext
你可以在ContextMenu加载时,把UserControl的实例绑定到它的DataContext上,后续绑定直接从DataContext取值:
<!-- 先给你的UserControl设置x:Name,比如x:Name="PdfUserCtrl" --> <UserControl x:Name="PdfUserCtrl" ...> <ListBox> <ListBox.ContextMenu> <ContextMenu DataContext="{Binding ElementName=PdfUserCtrl}"> <MenuItem Header="Löschen"/> <i:Interaction.Triggers> <i:EventTrigger EventName="PreviewMouseDown"> <!-- 注意Command要从UserControl的DataContext里取 --> <i:InvokeCommandAction Command="{Binding DataContext.DeleteAnnotationCmd}"> <i:InvokeCommandAction.CommandParameter> <Binding Path="PdfViewerCtrl"/> </i:InvokeCommandAction.CommandParameter> </i:InvokeCommandAction> </i:EventTrigger> </i:Interaction.Triggers> </ContextMenu> </ListBox.ContextMenu> </ListBox> </UserControl>
方案3:使用x:Reference(谨慎选用)
x:Reference可以直接引用XAML中命名的元素,不受可视化树限制,但要注意如果引用的元素还未加载完成可能会出问题,不过对于UserControl来说一般不会有问题:
<!-- 确保你的UserControl已设置x:Name="PdfUserCtrl" --> <ContextMenu> <MenuItem Header="Löschen"/> <i:Interaction.Triggers> <i:EventTrigger EventName="PreviewMouseDown"> <i:InvokeCommandAction Command="{Binding DeleteAnnotationCmd}"> <i:InvokeCommandAction.CommandParameter> <MultiBinding Converter="{StaticResource MultiBindingConv}"> <Binding Source="{x:Reference PdfUserCtrl}" Path="PdfViewerCtrl"/> </MultiBinding> </i:InvokeCommandAction.CommandParameter> </i:InvokeCommandAction> </i:EventTrigger> </i:Interaction.Triggers> </ContextMenu>
另外提一句,你原来的MultiBinding里放了两个相同的Binding,其实没必要,选一种能拿到PdfViewerCtrl的方式就行,多绑定可以留着以后需要传多个参数时再用。
内容的提问来源于stack exchange,提问作者Detlef D Soost

