views:

772

answers:

1

I have a DataGridTextColumn in a Silverlight 4 DataGrid and I want to set a ToolTip value on the column that is different from the bound value.

I know I can do this with a templated column quite easily - but it adds a huge amount of extra XAML and makes it cumbersome to read and maintain.

This works, but is a lot of extra code - especially if ever need to change the template

    <data:DataGridTemplateColumn Header="Name" Width="*">
        <data:DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <TextBlock TextTrimming="WordEllipsis" Text="{Binding FullName}"
                 ToolTipService.ToolTip="{Binding Email}" Width="Auto" Margin="5" />
            </DataTemplate>
        </data:DataGridTemplateColumn.CellTemplate>
    </data:DataGridTemplateColumn>

I'd like to find a nice way to do this with either a Style or inherited class. Like I said my main goal is to reduce bloat in the XAML for something so trivial as a tooltip in the best possible way.

There are a few similar stackoverflow questions with solutions like this and this, but they both show the same tooltip value as the contents of the cell (for instance when it overflows). While this is often what you want - I'm trying to show a different tooltip to the cell's contents.

I did find some sample code for an inherited class (scroll to end), which i tried to modify but got stuck becasue my XAML knowledge isn't up to par and I don't want to spend all night on this! This particular example appears like it works, but it looks like quite a hack and I think trying to modify it to work with two dependency properties is going to be an even bigger one.

PS. I would expect that a well written subclass would make it easy for me to bind other properties such as TextTrimming also.

A: 

Try to put this style in your resource dictionary:

<Style TargetType="{x:Type data:DataGridCell}">
    <Setter Property="ToolTip" Value="{Binding EMail}"/>
</Style>

To enable binding to any property, as you requested in the comment, you need to get creative. What I did was create two attached properties ToolTipBinding and IsToolTipBindingEnabled. The ToolTipBinding is set on the column to determine the tooltip, similar to the Binding property that determines the content of the cell, and the IsToolTipBindingEnabled is set to true on the DataGridCell object using a style similar to the one I mentioned above.

Then, I wrote code so that when the cell is loaded, the binding from its parent column is applied to its ToolTip property.

Here's the extension class:

public class DGExtensions
{
    public static object GetToolTipBinding(DependencyObject obj)
    {
        return obj.GetValue(ToolTipBindingProperty);
    }

    public static void SetToolTipBinding(DependencyObject obj, object value)
    {
        obj.SetValue(ToolTipBindingProperty, value);
    }

    public static readonly DependencyProperty ToolTipBindingProperty =
        DependencyProperty.RegisterAttached(
            "ToolTipBinding",
            typeof(object),
            typeof(DGExtensions),
            new FrameworkPropertyMetadata(null));

    public static bool GetIsToolTipBindingEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsToolTipBindingEnabled);
    }

    public static void SetIsToolTipBindingEnabled(
        DependencyObject obj, 
        bool value)
    {
        obj.SetValue(IsToolTipBindingEnabled, value);
    }

    public static readonly DependencyProperty IsToolTipBindingEnabled =
        DependencyProperty.RegisterAttached(
            "IsToolTipBindingEnabled",
            typeof(bool),
            typeof(DGExtensions),
            new UIPropertyMetadata(false, OnIsToolTipBindingEnabledChanged));

    public static void OnIsToolTipBindingEnabledChanged(
        DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
    {
        DataGridCell cell = d as DataGridCell;
        if (cell == null) return;

        bool nv = (bool)e.NewValue, ov = (bool)e.OldValue;

        // Act only on an actual change of property value.
        if (nv == ov) return; 

        if (nv)
            cell.Loaded += new RoutedEventHandler(cell_Loaded);
        else
            cell.Loaded -= cell_Loaded;
    }

    static void cell_Loaded(object sender, RoutedEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        if (cell == null) return;
        var binding = BindingOperations.GetBinding(
                        cell.Column, ToolTipBindingProperty);
        if (binding == null) return;

        cell.SetBinding(DataGridCell.ToolTipProperty, binding);

        // This only gets called once, so remove the strong reference.
        cell.Loaded -= cell_Loaded;
    }
}

Example XAML usage:

<Window.Resources>
    <Style TargetType="{x:Type tk:DataGridCell}">
        <Setter 
            Property="dge:DGExtensions.IsToolTipBindingEnabled" 
            Value="True"/>
    </Style>
</Window.Resources>
<Grid>
    <tk:DataGrid ItemsSource="{Binding TheList}" AutoGenerateColumns="False">
        <tk:DataGrid.Columns>
            <tk:DataGridTextColumn 
                Header="PropA" 
                Binding="{Binding PropA}" 
                dge:DGExtensions.ToolTipBinding="{Binding PropB}"/>
            <tk:DataGridTextColumn 
                Header="PropB" 
                Binding="{Binding PropB}"/>
        </tk:DataGrid.Columns>
    </tk:DataGrid>
</Grid>
Aviad P.
thanks, but what if i want to bind to something other than email
Simon_Weaver
There you go, it took some creativity to get this right, might not be optimal, but it's good :-)
Aviad P.
Just a quick comment, but I didn't think that Silverlight supported the `x:Type` in the `TargetType`?
Mark Glorie
@mark ya whats with that. i keep seeing all these examples with x:Type and i keep having to strip them all out!
Simon_Weaver