如何在不弄乱DataContext的情况下为WPF工具提示设置PlacementTarget?

我有一个典型的Listbox设置和vm + DataTemplate和项目vm。 数据模板具有工具提示,其中包含绑定到项目vm的元素。 一切都很棒。

现在,我想将工具提示相对于列表框本身放置。 它相当大,当随便鼠标hover在列表框上时会妨碍它。 所以我想我会在DataTemplate中做这样的事情:

   ... 

…使用静态资源……

     ...   

这是我的问题。 当我使用该PlacementTarget时,我得到一个绑定错误,DisplayName.Name和Details不绑定。 它尝试绑定的对象不是项目vm而是整个Listbox vm。

所以我的问题是:如何为工具提示设置ToolTipService.PlacementTarget,同时保持DataContextinheritance自其所有者?

好吧,工作中的朋友大多数都是为我找到的。 这种方式超级干净,不觉得hacky。

这是基本问题:如user164184所述,工具提示是弹出窗口,因此不属于可视化树。 所以WPF有一些神奇之处。 弹出窗口的DataContext来自PlacementTarget,这是绑定大部分时间的工作方式,尽管弹出窗口不是树的一部分。 但是当你更改PlacementTarget时会覆盖默认值,现在DataContext来自新的PlacementTarget,无论它是什么。

完全不直观。 如果MSDN有,而不是花费数小时构建所有那些不同工具提示出现的漂亮图表,那将是很好的,用一句话说一下DataContext会发生什么。

无论如何,对解决方案! 与所有有趣的WPF技巧一样,附加属性也可以解决。 我们将添加两个附加属性,以便我们可以在生成时直接设置工具提示的DataContext。

 public static class BindableToolTip { public static readonly DependencyProperty ToolTipProperty = DependencyProperty.RegisterAttached( "ToolTip", typeof(FrameworkElement), typeof(BindableToolTip), new PropertyMetadata(null, OnToolTipChanged)); public static void SetToolTip(DependencyObject element, FrameworkElement value) { element.SetValue(ToolTipProperty, value); } public static FrameworkElement GetToolTip(DependencyObject element) { return (FrameworkElement)element.GetValue(ToolTipProperty); } static void OnToolTipChanged(DependencyObject element, DependencyPropertyChangedEventArgs e) { ToolTipService.SetToolTip(element, e.NewValue); if (e.NewValue != null) { ((ToolTip)e.NewValue).DataContext = GetDataContext(element); } } public static readonly DependencyProperty DataContextProperty = DependencyProperty.RegisterAttached( "DataContext", typeof(object), typeof(BindableToolTip), new PropertyMetadata(null, OnDataContextChanged)); public static void SetDataContext(DependencyObject element, object value) { element.SetValue(DataContextProperty, value); } public static object GetDataContext(DependencyObject element) { return element.GetValue(DataContextProperty); } static void OnDataContextChanged(DependencyObject element, DependencyPropertyChangedEventArgs e) { var toolTip = GetToolTip(element); if (toolTip != null) { toolTip.DataContext = e.NewValue; } } } 

然后在XAML中:

        ...     ... 

只需将ToolTip切换到BindableToolTip.ToolTip ,然后添加一个新的BindableToolTip.DataContext ,指向您想要的任何内容。 我只是将它设置为当前的DataContext,因此它最终inheritance了绑定到DataTemplate的viewmodel。

请注意,我嵌入了ToolTip而不是使用StaticResource。 这是我原来问题中的一个错误。 显然必须为每个项目生成唯一。 另一种选择是使用ControlTemplate Style触发器。

一个改进可能是让BindableToolTip.DataContext注册ToolTip上的通知更改,然后我可以摆脱BindableToolTip.ToolTip。 另一天的任务!

工具提示不是可视树的一部分,因为它们是基于弹出窗口的。 因此,您的放置目标biding(使用Visual Tree搜索)来获得相对祖先不会工作。 为什么不使用ContentHacking呢? 这样就可以从ContextMenu,Popups,ToolTip等逻辑元素中攻击可视树。

  1. 声明一个StaticResource,它是任何FrameworkElement(我们需要支持数据上下文)。

        
  2. 在Visual Tree中提供内容控件,并将此静态资源“ProxyElement”设置为其内容。

         

上面的步骤是什么,“ProxyElement”已连接到ItemsControl(用作DataContext),它可作为SaticResource在任何地方使用。

  1. 现在使用此StaticResource作为工具提示中失败的任何绑定的源…

        

      ...   

如果这有帮助,请告诉我……

据我所知[但我可能错了(尝试没有害处)],你可以参考祖先DataContext中使用的对象初始化你的项目,即

 public class ItemsVM : VMBase { public T parentElement; public ItemsVM (T _parentElement) { this.parentElement = _parentElement; } ... }