1

I am trying to bind some values to ToolTipService.ShowDuration and some other ToolTip's properties in a CellStyle of DataGridTextColumn.

Normaly, I am doing something like this:

<UserControl
    ...namespace declarations...>
    <UserControl.Resources>
        <mycontrols:BindingProxy x:Key="proxy" Data="{Binding MySettings}"/>
    </UserControl.Resources>
    <DataGrid>
        <DataGridTextColumn
            Binding="{Binding SomeBinding}">
            <DataGridTextColumn.CellStyle>
                <Style 
                    TargetType="DataGridCell" 
                    BasedOn="{StaticResource ResourceKey={x:Type DataGridCell}}">
                    <Setter 
                        Property="ToolTipService.ShowDuration" 
                        Value="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"/>
                    <Setter Property="ToolTip">
                        <Setter.Value>
                            <TextBlock 
                                Text="{Binding SomeBinding}" 
                                MaxWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}" 
                                TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
                        </Setter.Value>
                    </Setter>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
    </DataGrid>
</UserControl>

Since ToolTip has it's own visual tree, I am using binding proxy like this:

public class BindingProxy : Freezable
    {
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }

        public object Data
        {
            get { return (object)GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }

Up to this point, all works as expected. But I wanted to re-use this DataGridTextColumn, so I created new file like this:

<DataGridTextColumn
    x:Class="Test.MyControls.DataGridLargeTextColumn"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Test.MyControls">
    <DataGridTextColumn.CellStyle>
        <Style 
            TargetType="DataGridCell" 
            BasedOn="{StaticResource ResourceKey={x:Type DataGridCell}}">
            <Setter 
                Property="ToolTipService.ShowDuration" 
                Value="{Binding ToolTipDuration}"/>
            <Setter Property="ToolTip">
                <Setter.Value>
                    <TextBlock 
                        Text="{Binding SomeBinding}" 
                        MaxWidth="{Binding ToolTipWidth}" 
                        TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
                </Setter.Value>
            </Setter>
        </Style>
    </DataGridTextColumn.CellStyle>
</DataGridTextColumn>

With code behinde:

public partial class DataGridLargeTextColumn : DataGridTextColumn
{
    public int ToolTipDuration
    {
        get { return (int)GetValue(ToolTipDurationProperty); }
        set { SetValue(ToolTipDurationProperty, value); }
    }
    public static readonly DependencyProperty ToolTipDurationProperty =
        DependencyProperty.Register("ToolTipDuration", typeof(int), typeof(DataGridLargeTextColumn), new UIPropertyMetadata(default(int)));

    public string SomeBinding
    {
        get { return (string)GetValue(SomeBindingProperty); }
        set { SetValue(SomeBindingProperty, value); }
    }
    public static readonly DependencyProperty SomeBindingProperty =
        DependencyProperty.Register("SomeBinding", typeof(string), typeof(DataGridLargeTextColumn), new UIPropertyMetadata(default(string)));

    public int ToolTipWidth
    {
        get { return (int)GetValue(ToolTipWidthProperty); }
        set { SetValue(ToolTipWidthProperty, value); }
    }
    public static readonly DependencyProperty ToolTipWidthProperty =
        DependencyProperty.Register("ToolTipWidth", typeof(int), typeof(DataGridLargeTextColumn), new UIPropertyMetadata(default(int)));

    public DataGridLargeTextColumn()
    {
        InitializeComponent();
    }
}

This does't work because ToolTip has it's own visual tree, but since I have nowhere to put proxy, I don't know how to make it work or if it is even possible. I found this answer, and it seems to be on the right track, however, I tried to implement it like this with no luck:

<Setter 
    Property="ToolTipService.ShowDuration"
    Value="{Binding Path=PlacementTarget.(local:DataGridLargeTextColumn.ToolTipDuration), RelativeSource={RelativeSource Self}}"/>
<Setter Property="ToolTip">
    <Setter.Value>
        <TextBlock 
            Text="{Binding Path=PlacementTarget.(local:DataGridLargeTextColumn.SomeBinding), RelativeSource={RelativeSource Self}}" 
            MaxWidth="{Binding Path=PlacementTarget.(local:DataGridLargeTextColumn.ToolTipWidth), RelativeSource={RelativeSource Self}}" 
            TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
    </Setter.Value>
</Setter>

Am I using PlacementTarget wrong? If not, why is it not working, and is there another solution?

UPDATE:

As per Mark's answer, I've modified the DataGridLargeTextColumn's Style:

<Style
    TargetType="DataGridCell" 
    BasedOn="{StaticResource {x:Type DataGridCell}}">
    <Setter 
        Property="ToolTipService.ShowDuration" Value="{Binding Path=PlacementTarget.ToolTipShowDuration, RelativeSource={x:Static RelativeSource.Self}}"/>
    <Setter Property="ToolTip">
        <Setter.Value>
            <ToolTip DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
                <TextBlock 
                    Text="{Binding DataContext.SomeBinding}"
                    MaxWidth="{Binding Column.ToolTipWidth}"
                    TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
            </ToolTip>
        </Setter.Value>
    </Setter>
</Style>

And I'm using that control like this:

<UserControl
    ...namespace declarations...>
    <UserControl.Resources>
        <mycontrols:BindingProxy x:Key="proxy" Data="{Binding MySettings}"/>
    </UserControl.Resources>
    <DataGrid>
        <DataGrid.Columns>
            <mycontrols:DataGridLargeTextColumn
                Binding="{Binding SomeBinding}"
                ToolTipShowDuration="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"
                ToolTipWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}"/>
        </DataGrid.Columns>
    </DataGrid>
</UserControl>

Width binding now works like a charm, but there are two problems I still cannot solve:

  1. I cannot get tool tip's duration to bind, I have tried few different approches, but since it's abstract, it cannot be declared explicitly
  2. ToolTip's Text property is set to SomeBinding, which is OK in this particular case, but I want to be able to set it to whatever, so I tried to declare it using DependencProperty from above like this:

Text="{Binding Column.ToolTipText}"

This works OK if i use it with string literal:

<myControls:DataGridLargeTextColumn
    Binding="{Binding SomeBinding}"
    ToolTipText="12345"
    ToolTipShowDuration="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"
    ToolTipWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}"/>

But it doesn't work when I try to bind it, which is what I am trying to achieve:

<myControls:DataGridLargeTextColumn
    Binding="{Binding SomeBinding}"
    ToolTipText="{Binding SomeOtherPropertyBinding}" 
    ToolTipShowDuration="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"
    ToolTipWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}"/>
Miljac
  • 135
  • 1
  • 12

1 Answers1

1

By default your ToolTip's DataContext gets set to the DataContext of the cell. You, however, are trying to bind to dependency properties in the cell's column instead, so you're going to have to change the DataContext to point to the cell itself and then reference DataContext explicitly when you want to access the data and Column when you want to access the DPs in your DataGridLargeTextColumn.

In other words, declare the ToolTip explicitly in addition to its content and set its DataContext, like this:

<Setter Property="ToolTip">
    <Setter.Value>
        <ToolTip DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
            <TextBlock 
                Text="{Binding DataContext.SomeBinding}"
                Width="{Binding Column.ToolTipWidth}" />
        </ToolTip>
    </Setter.Value>
</Setter>

...where Text in this case is binding to the data and Width is binding to the custom column DP.

Alternatively you could also just leave the DataContext as is and instead use the ToolTip's Tag property as a binding proxy to the DataGridLargeTextColumn:

<Setter Property="ToolTip">
    <Setter.Value>
        <ToolTip Tag="{Binding Path=PlacementTarget.Column, RelativeSource={x:Static RelativeSource.Self}}">
            <TextBlock 
                Text="{Binding SomeBinding}"
                Width="{Binding Tag.ToolTipWidth, RelativeSource={RelativeSource AncestorType=ToolTip}}" />
        </ToolTip>
    </Setter.Value>
</Setter>
Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • I've managed to get it working the way you wrote, but I still have two questions. 1. Why can't I bind `Text` to `Column.ToolTipText`? I tried it, but it works in a really weird way. It works when I set `ToolTipText` to string literal, but doesn't work when I bind it to the same value column's own `Binding` is assigned to; 2. How would that translate to `ToolTipService.ShowDuration`? Obviously I still need to access `Column` for DP, but I tried it in a few different ways without success. And since it's abstract, I cannot declare it explicitly – Miljac Mar 07 '19 at 08:13
  • What do you mean when you say the `Text` binding works "in a really weird way"? I know my solution code didn't match your question code 100%, if you post an update of what you've implemented so far I'll be happy to have another look. Might just be a problem with mode and/or update flags. – Mark Feldman Mar 08 '19 at 10:10
  • Try setting the mode on those broken bindings, I think you'll find that WPF is getting confused as to which direction you're trying to bind in. String literal is obvious because it can only go one way, other bindings not so much. I'd help more, but the code is all far to complex now with too many missing pieces. If you remain stuck I'll be happy to look at an mcve, it's a pretty straightforward thing you're trying to do. – Mark Feldman Mar 12 '19 at 00:46