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

sco*_*obi 4 c# wpf xaml

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

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

<Grid ...>
    <TextBlock x:Name="ObjectText"
        ToolTipService.Placement="Left"
        ToolTip="{StaticResource ItemToolTip}"
        ToolTipService.PlacementTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}">
    </TextBlock>
...
Run Code Online (Sandbox Code Playgroud)

...使用静态资源...

<ToolTip x:Key="ItemToolTip">
    <StackPanel>
        <TextBlock Text="{Binding DisplayName.Name}"/>
        <TextBlock Text="{Binding Details}" FontStyle="Italic"/>
        ...
    </StackPanel>
</ToolTip>
Run Code Online (Sandbox Code Playgroud)

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

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

sco*_*obi 7

好吧,工作的朋友大多数都是为我找的.这种方式超级干净,不觉得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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在XAML中:

<Grid ...>
    <TextBlock x:Name="ObjectText"
        ToolTipService.Placement="Left"
        ToolTipService.PlacementTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
        mystuff:BindableToolTip.DataContext="{Binding}">
        <mystuff:BindableToolTip.ToolTip>
            <ToolTip>
                <StackPanel>
                    <TextBlock Text="{Binding DisplayName.Name}"/>
                    <TextBlock Text="{Binding Details}" FontStyle="Italic"/>
                    ...
                </StackPanel>
            </ToolTip>
        </mystuff:BindableToolTip.ToolTip>
    </TextBlock>
...
Run Code Online (Sandbox Code Playgroud)

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

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

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


WPF*_*-it 5

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

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

    <UserControl.Resources ...>
            <TextBlock x:Key="ProxyElement" DataContext="{Binding}" />
    </UserControl.Resources>
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在Visual Tree中提供内容控件,并将此静态资源"ProxyElement"设置为其内容.

    <UserControl ...>
            <Grid ...>
                    <ItemsControl x:Name="MyItemsControl"
                                  ItemsTemplate="{StaticResource blahblah}" .../>
                    <ContentControl Content="{StaticResource ProxyElement}"
                                    DataContext="{Binding ElementName=MyItemsControl}" Visibility="Collapsed"/>
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    <Grid ...>
            <TextBlock x:Name="ObjectText"
                       ToolTipService.Placement="Left"
                       ToolTip="{StaticResource ItemToolTip}"
                       PlacementTarget="{Binding Source={StaticResource ProxyElement}, Path=DataContext}" ... /> <!-- This sets the target as the items control -->
    
    Run Code Online (Sandbox Code Playgroud)

    <ToolTip x:Key="ItemToolTip">
            <StackPanel DataContext="{Binding Source={StaticResource ProxyElement}, Path=DataContext.DataContext}"><!-- sets data context of the items control -->
                    <TextBlock Text="{Binding DisplayName.Name}"/>
                    <TextBlock Text="{Binding Details}" FontStyle="Italic"/> ...
            </StackPanel>
    </ToolTip>
Run Code Online (Sandbox Code Playgroud)

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