When Binding.RelativeSource
doesn't resolve, you can always be sure that the Binding.Target
is not part of the visual tree.
In your first example you are explicitly defining the tree structure of the ToolTip
. You are explicitly creating the content e.g. by adding the TextBlock
. The content of the ToolTip
is not part of the visual tree and therefore the Binding.RelativeSource
can't be resolved.
In your second example, you let the FrameworkElement
implicitly create the ToolTip
content.
Now FrameWorkElement
will first resolve the Binding
, which resolves, as the FrameworkElement
is still part of the visual tree. The resolved value is taken, ToString
invoked, a TextBlock
created and the string value assigned to TextBlock.Text
.
Solution
To solve the binding problem, when implementing the ToolTip
explicitly, you can implement a Binding Proxy as suggested in a comment by @Mark Feldman which makes use of the StaticResource
markup to provide a Binding.Source
to elements that are not part of the visual tree.
It's basically a bindable ObjectDataProvider
.
A similar solution to the binding proxy is to define the content as a resource of the Grid
and then reference it via DynamicResource
using a ContentPresnter
:
<UserControl>
<Grid>
<Grid.Resources>
<!-- The proxy -->
<TextBlock x:Key="ToolTipText"
Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=InfoTT}" />
<Grid.ToolTip>
<ToolTip>
<ContentPresenter Content="{DynamicResource ToolTipText}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>
But you could also make use of the fact that the DataContext
is still inherited. Bindings to the DataContext
will still resolve.
In your scenario, where you want to bind the content of the ToolTip
to a property of the parent UserControl
, you could bind this property to a property of the view model, which is the current DataContext
of Grid
(and therefore for its ToolTip
). I only recommend this, when binding to business data and not layout data:
<UserControl InfoTT="{Binding ViewModelInfoTT}">
<UserControl.DataContext>
<ViewModel />
</UserControl.DataContext>
<Grid>
<Grid.ToolTip>
<ToolTip>
<TextBlock Text="{Binding ViewModelInfoTT}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>
If you don't use view models and host the data directly in the control, you may like to set the DataContext
to the control itself. This way you simplify all bindings and of course can now bind to the UserControl
from within the ToolTip
:
// Constructor
public MyUserControl()
{
InitializeComponent();
// Set the UserControl's DataContext to the control itself
this.DataContext = this;
}
<UserControl>
<Grid>
<Grid.ToolTip>
<ToolTip>
<TextBlock Text="{Binding InfoTT}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>
Alternatively override the DataContext
. Of course you'll lose access to the current context:
<UserControl>
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestoType=UserControl}>
<Grid.ToolTip>
<ToolTip>
<TextBlock Text="{Binding InfoTT}" />
</ToolTip>
</Grid.ToolTip>
</Grid>
</UserControl>