Have you ever wanted to databind a tooltip in Silverlight (or WPF for that matter),
and found that the DataContext isn’t available for tooltips (the datacontext
is null)? It’s very annoying. Tooltips, unfortunately,
aren’t connected to their parents in anyway when they’re created, so
they loose the ability to connect to the proper data context that is being provided
to their parent. Listboxes, textboxes, all suffer from this problem.
In order to combat that problem, I’ve come up with a simple workaround for
this data context problem.
I’ve created a simple attached property which augments the standard tooltip
property and intervenes before it is created so that the proper data context can be
established. This code hasn’t been tested thoroughly, but it works in my
limited testing. It could be extended to handle more edge cases, but if you’re
creating advanced tooltips outside of XAML, there’s little reason to use this
code.
public class DataBindingTooltip {
public static DependencyProperty TooltipProperty;
static DataBindingTooltip()
{
TooltipProperty = DependencyProperty.RegisterAttached
("Tooltip", typeof(object),
typeof(DataBindingTooltip),
new PropertyMetadata(TooltipChanged));
}
public static void SetTooltip(DependencyObject d, object value)
{
d.SetValue(DataBindingTooltip.TooltipProperty, value);
}
public static object GetToolTip(DependencyObject d)
{
return d.GetValue(DataBindingTooltip.TooltipProperty);
}
private static void TooltipChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement)
{
FrameworkElement owner = sender as FrameworkElement;
// wait for it to be in the visual tree so that // context can be established owner.Loaded += new RoutedEventHandler(owner_Loaded);
}
}
static void owner_Loaded(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement)
{
FrameworkElement owner = sender as FrameworkElement;
// remove the event handler owner.Loaded -= new RoutedEventHandler(owner_Loaded);
DependencyObject tooltip =
owner.GetValue(DataBindingTooltip.TooltipProperty) as DependencyObject;
if (tooltip != null)
{
// assign the data context of the current owner control to the tooltip's datacontext tooltip.SetValue(FrameworkElement.DataContextProperty,
owner.GetValue(FrameworkElement.DataContextProperty));
}
ToolTipService.SetToolTip(owner, tooltip);
}
}
}
Here’s how to use it:
<Grid x:Name="LayoutRoot" Background="White"> <ListBox x:Name="lb" ItemsSource="{Binding Mode=OneWay}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <local:DataBindingTooltip.Tooltip> <StackPanel Orientation="Vertical" > <TextBlock Text="Tooltip!" FontWeight="Bold" /> <TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding Age}" /> </StackPanel> </local:DataBindingTooltip.Tooltip> <TextBlock Text="{Binding Name}" Margin="4" /> <TextBlock Text="{Binding Age}" Margin="4"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid>
And, a small test sample:
public partial class Page : UserControl {
public Page()
{
InitializeComponent();
lb.DataContext = new List<Person>() {
new Person(){Name="John", Age=23},
new Person(){Name="Hank", Age=37},
new Person(){Name="Sally", Age=31}};
}
}
public class Person {
public string Name { get; set; }
public int Age { get; set; }
}
Which, when you put it all together results in:
Hopefully, you can spot the trick — as soon as the host control has been
created and assigned a datacontext (in the Loaded event for the element), I set the
datacontext onto the tooltip and assign it to the REAL tooltip property (via the
TooltipService.Tooltip attached property).
Hope you find this useful.