0
votes

I'm customizing the DataGridCells to have a small triangle in the corner with a tooltip. This triangle should only show if the tooltip has content and the content of the tooltip will come from the ItemsSource of the DataGrid.

DataGrid with the triangle in the corner:

DataGrid with the triangle in the corner

Right now I created an attached property called Hint which will bind to the property of the ItemsSource that would have the content of the tooltip.

public class HintAttachedProperty
{

        public static void SetHint(DependencyObject obj, string text)
        {
            obj.SetValue(HintProperty, text);
        }

        public static string GetHint(DependencyObject obj)
        {
            return obj.GetValue(HintProperty)?.ToString() ?? string.Empty;
        }

        public static readonly DependencyProperty HintProperty =
            DependencyProperty.RegisterAttached("Hint", typeof(string), typeof(HintAttachedProperty), new FrameworkPropertyMetadata(null));

}

The xaml of the DataGrid, now the attached property hint is set with the value "A test":

        <local:CustomDataGrid x:Name="datagrid"
              AutoGenerateColumns="False"
              ItemsSource="{Binding ItemsCollection}"
              ClipboardCopyMode="IncludeHeader"
              EnableRowsMove="True"
              AlternatingRowBackground="LightBlue"
              AlternationCount="2">
        <DataGrid.Columns>
           
            <DataGridCheckBoxColumn Header="IsChecked" Binding="{Binding IsChecked}"/>
            <DataGridTextColumn x:Name="Test" 
                                local:IsHighLightedAttachedProperty.IsHighLighted="True" 
                                local:HintAttachedProperty.Hint="A test" 
                                Header="ExperienceInMonth" 
                                Binding="{Binding ExperienceInMonth}">
            </DataGridTextColumn>
            <DataGridTemplateColumn Header="References">
                ...
            </DataGridTemplateColumn>
            <DataGridTextColumn Header="EmployeeName" Binding="{Binding EmployeeName}"/>
            <DataGridTextColumn Header="EmployeeAge" Binding="{Binding EmployeeAge}" />
        </DataGrid.Columns>
    </local:CustomDataGrid>

And here is the style for the DataGridCell which controls the visibility of the triangle and should bind the attached property to the content of the tooltip:

 <Style TargetType="{x:Type DataGridCell}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGridCell}">
                <Border x:Name="borderSides" BorderThickness="2,0,2,0" Background="Transparent" BorderBrush="Transparent" SnapsToDevicePixels="True">
                    <Border x:Name="borderTopBottom" BorderThickness="0,2,0,2" Background="Transparent" BorderBrush="Transparent" SnapsToDevicePixels="True">
                        <Grid>
                            <Grid x:Name="grid">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <ContentPresenter Grid.Column="0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                                <Polygon x:Name="pol" ToolTip="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource HintConverter}}" Grid.Column="1" HorizontalAlignment="Right" Points="5,0 10,0, 10,5" Stroke="Black" Fill="Black" >
                                
                                </Polygon>
                            </Grid>
                        </Grid>
                    </Border>
                </Border>

                <ControlTemplate.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource HintConverter}}" Value="{x:Static sys:String.Empty}">
                        <Setter TargetName="pol" Property="Visibility" Value="Collapsed"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource HintConverter}}" Value="{x:Null}">
                        <Setter TargetName="pol" Property="Visibility" Value="Collapsed"/>
                    </DataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>                
        </Setter.Value>
    </Setter>
</Style>

In the binding {Binding RelativeSource={RelativeSource Self}, Converter={StaticResource HintConverter}} I'm using a converter of the content of the DataGridCell to get the value of the attached property, because when I was doing {Binding path=(local:HintAttachedProperty.Hint)} It always returned null.

Code of the converter:

public class HintConverters : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
         DataGridCell dgc = value as DataGridCell;
         if (dgc != null)
         {
             DataGridColumn col = dgc.Column;
             if (col != null)
             {
                 var hint = HintAttachedProperty.GetHint(col);
                 return hint;
             }
         }
         return "";
     }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

When debugging I can see in the converter how the value is "A test" and consequently is not hidding the triangle, but when I put the cursor over the tooltip is empty:

Value of hint:

Value of hint

Tooltip is empty:

Tooltip is empty

On the other hand, if I directly bind to a property of the ItemsSource in the style, for example the Employee Name,ToolTip="{Binding EmployeeName}" it works perfectly:

Tooltip working

How can I bind the value of and attached property to a property? Is this possible? Maybe I should go in another direction?

Thanks for your attention

1
<Trigger Property = local.IsHighLightedAttachedProperty.IsHighLighted value = true> your setters </Trigger>Athul Raj

1 Answers

1
votes

The converter is redundant, you just need to correct your bindings to make it work.

In the Polygon you need to find the DataGridCell ancestor, not Self, because that is the polygon. From there you need to access the Column, because that is what Hint is attached to.

<Polygon x:Name="pol" ToolTip="{Binding Column.(local:HintAttachedProperty.Hint), RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}}" Grid.Column="1" HorizontalAlignment="Right" Points="5,0 10,0, 10,5" Stroke="Black" Fill="Black"/>

In your triggers, the relative source is right, because Self is the DataGridCell in this case, but as above you need to access the Column where Hint is attached.

<DataTrigger Binding="{Binding Column.(local:HintAttachedProperty.Hint), RelativeSource={RelativeSource Self}}" Value="{x:Static system:String.Empty}">
   <Setter TargetName="pol" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding Column.(local:HintAttachedProperty.Hint), RelativeSource={RelativeSource Self}}" Value="{x:Null}">
   <Setter TargetName="pol" Property="Visibility" Value="Collapsed"/>
</DataTrigger>

Upadate for your comments. Binding a property on the current item's data context or the data grid data context does not work this way, as the column is not a part of the visual tree, because the column is only the definition of how cells in this column should be constructed.

I will show you a workaround for the data context of the current item. Binding to the data context of the parent DataGrid works differently. You can attach the Hint property to the element that is actually displayed inside a cell via a Style. In the style you have access to the current item's data context.

If you have a read-only column you only need to create an ElementStyle. If you have an editbale column where you want to display the polygon and tooltip, you also need an EditingElementStyle.

<DataGridTextColumn Header="EmployeeName" Binding="{Binding EmployeeName}">
   <DataGridTextColumn.ElementStyle>
      <Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
        <Setter Property="local:HintAttachedProperty.Hint" Value="{Binding EmployeeName}"/>
      </Style>
   </DataGridTextColumn.ElementStyle>
   <DataGridTextColumn.EditingElementStyle>
      <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
         <Setter Property="local:HintAttachedProperty.Hint" Value="{Binding EmployeeName}"/>
      </Style>
   </DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>

Adapt the bindings to use the Content property instead of Column to access the top level element in the cell template that you have attached the Hint property to, e.g. TextBlock.

<Polygon x:Name="pol" ToolTip="{Binding Content.(local:HintAttachedProperty.Hint), RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}}" Grid.Column="1" HorizontalAlignment="Right" Points="5,0 10,0, 10,5" Stroke="Black" Fill="Black" >
<DataTrigger Binding="{Binding Content.(local:HintAttachedProperty.Hint), RelativeSource={RelativeSource Self}}" Value="{x:Static system:String.Empty}">
   <Setter TargetName="pol" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding Content.(local:HintAttachedProperty.Hint), RelativeSource={RelativeSource Self}}" Value="{x:Null}">
   <Setter TargetName="pol" Property="Visibility" Value="Collapsed"/>
</DataTrigger>

Just to recap, the main issue here is that you cannot access data contexts from a column.

An alternative to your approach of overwriting the cell ControlTemplate would be to create DataGridTemplateColumns where you create a custom DataTemplate for each column with your arrow polygon and tooltip in it.

Then you could easily attach and bind your Hint property, as you can access all controls and the data context of the current item. The drawback of this solution is that you would have to create custom data templates for each column and copy your arrow and tooltip XAML everywhere.