I get System.Windows.Style as text of the tooltip of the header in a datagrid

ComptonAlvaro 166 Reputation points
2022-03-01T16:53:58.367+00:00

I want to define a style for the datagrid columns that get the text of the tooltip through an attached property. But I get the text System.Windows.Style instead of the text.

The code is this.

XML resource file that defines the style:

<Style TargetType="{x:Type DataGridColumnHeader}" x:Key="DataGridColumnHeaderConTooltip">
    <Setter Property="ToolTip">
        <Setter.Value>
            <Style TargetType="ToolTip">
                <Setter Property="ToolTipService.ShowOnDisabled" Value="true"/>
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <StackPanel>
                                <!--Para poder utilizar el attached propery, se tiene que utilizar PlacementTarget, y además indicar que el source
                                es el control padre, que es el tooltip, porque el TextBlck no pertenece al mismo visual tree.-->
                                <TextBlock Text="{Binding PlacementTarget.(ap:CabeceraDatagridAttachedProperty.Tooltip), RelativeSource={RelativeSource AncestorType=ToolTip}}"  MaxWidth="400" TextWrapping='Wrap' />
                            </StackPanel>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Setter.Value>
    </Setter>
</Style>

The code in the axml:

    <DataGridTextColumn Header="Cantidad Para Descontar" Binding="{Binding CantidadParaDescontar, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnDataErrors=True}" Width="AUTO" IsReadOnly="false"
                        ap:CabeceraDatagridAttachedProperty.Tooltip="tooltip cabecera por attached property"
                        HeaderStyle="{StaticResource DataGridColumnHeaderConTooltip}">

The attached property:

namespace GTS.CMMS.Client.AttachedProperties
{
    public static class CabeceraDatagridAttachedProperty
    {
        public static readonly DependencyProperty TooltipProperty =
            DependencyProperty.RegisterAttached(
            "Tooltip",
            typeof(string),
            typeof(CabeceraDatagridAttachedProperty));

        public static string GetTooltip(DependencyObject obj)
        {
            return (string)obj.GetValue(TooltipProperty);
        }

        public static void SetTooltip(DependencyObject obj, string value)
        {
            obj.SetValue(TooltipProperty, value);
        }
    }
}

Thanks.

EDIT: I will add a complite simple example to simplify the code.

the view:

    <Window x:Class="TestStyleWithAttachedProperties.MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:TestStyleWithAttachedProperties.ViewModels"
        xmlns:ap="clr-namespace:TestStyleWithAttachedProperties.AttachedProperties"
        xmlns:local="clr-namespace:TestStyleWithAttachedProperties"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>


    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="GUIResources.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>


    <Grid>

        <!--Las columnas no pertenecen al tree view, por lo que no pueden acceder directamente al DataContext.
        Para que pueda acceder al DataContext del padre (ventana o control de usuario), se utilizar un un FrameworkElement dummy,
        el cual hereda el DataContext del control padre. De este modo se puede acceder al view model y todas sus propiedades.-->
        <FrameworkElement x:Name="dummyElement" Visibility="Collapsed"/>
        <DataGrid Name="dgdTest" Grid.Column="0" Margin="5,5,5,5"
                  ItemsSource="{Binding Items}"
                  AutoGenerateColumns="false">

            <DataGrid.Columns>
                <DataGridTextColumn Header="Price" Binding="{Binding Price}" Width="150"
                                    ap:HeaderAttachedProperty.Tooltip="{Binding Path=DataContext.PriceTooltip, Source={x:Reference dummyElement}}"
                                    HeaderStyle="{StaticResource DataGridColumnHeaderConTooltip}"
                                    ap:CellWithErrorsAttachedProperty.TextoTooltip01="{Binding Path=Tooltip, Source={x:Reference dummyElement}}"
                                    ap:CellWithErrorsAttachedProperty.TextoTooltip02="{Binding Path=ErrorDescription, Source={x:Reference dummyElement}}"
                                    ap:CellWithErrorsAttachedProperty.EsDatoCorrecto="{Binding Path=IsDataCorrect}"
                                    CellStyle="{StaticResource DataGridCellWithErrorsStyle}">
                </DataGridTextColumn>

                <DataGridTextColumn Header="Discount" Binding="{Binding Discount}" Width="150"
                                    ap:HeaderAttachedProperty.Tooltip="Tooltip cabecera 2"
                                    HeaderStyle="{StaticResource DataGridColumnHeaderConTooltip}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

The view model:

   using System.Collections.ObjectModel;



namespace TestStyleWithAttachedProperties.ViewModels
{
    public class MainWindowViewModel : BaseViewModel
    {
        public MainWindowViewModel()
        {
            Items = new ObservableCollection<Data>();

            Data miData01 = new Data()
            {
                Price = 1m,
                Discount = 0m,
                Tooltip = "Tooltip Data01",
                ErrorDescription = "No errors",
                IsDataCorrect = true,
            };

            Items.Add(miData01);


            Data miData02 = new Data()
            {
                Price = 2m,
                Discount = 10m,
                Tooltip = "Tooltip Data02",
                ErrorDescription = "No errors",
                IsDataCorrect = true,
            };

            Items.Add(miData02);


            Data miData03 = new Data()
            {
                Price = -1m,
                Discount = 0m,
                Tooltip = "Tooltip Data03",
                ErrorDescription = "Price has to be greater than 0.",
                IsDataCorrect = false,
            };

            Items.Add(miData03);
        }


        private ObservableCollection<Data> _items;

        public ObservableCollection<Data> Items
        {
            get { return _items; }
            set
            {
                _items = value;
                base.RaisePropertyChangedEvent(nameof(Items));
            }
        }




        private string _priceTooltip = "Tooltip for Price column";

        public string PriceTooltip
        {
            get { return _priceTooltip; }
            set { _priceTooltip = value; }
        }
    }
}

The view model base, for notify when a property is changed:

using System.ComponentModel;

namespace TestStyleWithAttachedProperties.ViewModels
{
    public abstract class BaseViewModel : INotifyPropertyChanging, INotifyPropertyChanged
    {
        #region INotifyPropertyChanging Members

        public event PropertyChangingEventHandler PropertyChanging;

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Administrative Properties

        /// <summary>
        /// Whether the view model should ignore property-change events.
        /// </summary>
        public virtual bool IgnorePropertyChangeEvents { get; set; }

        #endregion

        #region Public Methods
        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The name of the changed property.</param>
        public virtual void RaisePropertyChangedEvent(string propertyName)
        {
            // Exit if changes ignored
            if (IgnorePropertyChangeEvents) return;

            // Exit if no subscribers
            if (PropertyChanged == null) return;

            // Raise event
            var e = new PropertyChangedEventArgs(propertyName);
            PropertyChanged(this, e);
        }

        /// <summary>
        /// Raises the PropertyChanging event.
        /// </summary>
        /// <param name="propertyName">The name of the changing property.</param>
        public virtual void RaisePropertyChangingEvent(string propertyName)
        {
            // Exit if changes ignored
            if (IgnorePropertyChangeEvents) return;

            // Exit if no subscribers
            if (PropertyChanging == null) return;

            // Raise event
            var e = new PropertyChangingEventArgs(propertyName);
            PropertyChanging(this, e);
        }

        #endregion
    }
}

The class Data:

using TestStyleWithAttachedProperties.ViewModels;

namespace TestStyleWithAttachedProperties
{
    public class Data : BaseViewModel
    {
        private decimal _price;

        public decimal Price
        {
            get { return _price; }
            set
            {
                _price = value;
                base.RaisePropertyChangedEvent(nameof(Price));
            }
        }

        private decimal _disocunt;

        public decimal Discount
        {
            get { return _disocunt; }
            set
            {
                _disocunt = value;
                base.RaisePropertyChangedEvent(nameof(Discount));
            }
        }


        private string _tooltip;

        public string Tooltip
        {
            get { return _tooltip; }
            set
            {
                _tooltip = value;
                base.RaisePropertyChangedEvent(nameof(Tooltip));
            }
        }



        private string _errorDescription;

        public string ErrorDescription
        {
            get { return _errorDescription; }
            set
            {
                _errorDescription = value;
                base.RaisePropertyChangedEvent(nameof(ErrorDescription));
            }
        }



        private bool _isDataCorrect;

        public bool IsDataCorrect
        {
            get { return _isDataCorrect; }
            set
            {
                _isDataCorrect = value;
                base.RaisePropertyChangedEvent(nameof(IsDataCorrect));
            }
        }
    }
}

The dictionery with the resources: the styles for the hearder and the cells:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ap="clr-namespace:TestStyleWithAttachedProperties.AttachedProperties"
                    xmlns:conv="clr-namespace:TestStyleWithAttachedProperties.Converters"
                    xmlns:sys="clr-namespace:System;assembly=System.Runtime">




    <conv:CellTooltipMultiValueConverter x:Key="CellTooltipMultiValueConverter"/>



    <Style TargetType="{x:Type DataGridColumnHeader}" x:Key="DataGridColumnHeaderConTooltip">
        <Setter Property="Background" Value="Yellow"/>
        <Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
        <Setter Property="ToolTip" Value="{Binding Column.(ap:HeaderAttachedProperty.Tooltip), RelativeSource={RelativeSource Self}}"/>
    </Style>



    <Style TargetType="{x:Type DataGridCell}" x:Key="DataGridCellWithErrorsStyle">
        <Setter Property="Background" Value="Red"/>
        <Setter Property="ToolTip">
            <Setter.Value>
                <MultiBinding Converter="{StaticResource ResourceKey=CellTooltipMultiValueConverter}">
                    <MultiBinding.Bindings>
                        <Binding Path="(ap:CellWithErrorsAttachedProperty.TextoTooltip01)" RelativeSource="{RelativeSource AncestorType=ToolTip}"/>
                        <Binding Path="(ap:CellWithErrorsAttachedProperty.TextoTooltip02)" RelativeSource="{RelativeSource AncestorType=ToolTip}"/>
                    </MultiBinding.Bindings>
                </MultiBinding>
            </Setter.Value>
        </Setter>

        <Style.Triggers>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding Path=(ap:CellWithErrorsAttachedProperty.EsDatoCorrecto), RelativeSource={RelativeSource AncestorType=DataGridCell}}" Value="false"/>
                </MultiDataTrigger.Conditions>
                <Setter Property="Background" Value="Orange"/>
            </MultiDataTrigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>

The value converter for the tooltip of the cells:

using System;
using System.Globalization;
using System.Windows.Data;



namespace TestStyleWithAttachedProperties.Converters
{
    public class CellTooltipMultiValueConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            //here values are unset values.
            //If I return a string, it is shown in the datagrid. But I can't do any logic because the values are null.
            return "Hola";
        }



        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Then attached properties for the cells, to can communicate the view with the cell style in the resource dictionary:

using System.Windows;




namespace TestStyleWithAttachedProperties.AttachedProperties
{
    public static class CellWithErrorsAttachedProperty
    {
        //TextoTooltip01
        //Primer texto del tooltip que se mostrará.
        public static readonly DependencyProperty TextoTooltip01Property =
            DependencyProperty.RegisterAttached(
            "TextoTooltip01",
            typeof(string),
            typeof(CellWithErrorsAttachedProperty));

        public static string GetTextoTooltip01(DependencyObject obj)
        {
            return (string)obj.GetValue(TextoTooltip01Property);
        }

        public static void SetTextoTooltip01(DependencyObject obj, string value)
        {
            obj.SetValue(TextoTooltip01Property, value);
        }



        //TextoTooltip02
        //Primer texto del tooltip que se mostrará.
        public static readonly DependencyProperty TextoTooltip02Property =
            DependencyProperty.RegisterAttached(
            "TextoTooltip02",
            typeof(string),
            typeof(CellWithErrorsAttachedProperty));

        public static string GetTextoTooltip02(DependencyObject obj)
        {
            return (string)obj.GetValue(TextoTooltip01Property);
        }

        public static void SetTextoTooltip02(DependencyObject obj, string value)
        {
            obj.SetValue(TextoTooltip01Property, value);
        }





        //ES DATO CORRECTO
        //Indica si el dato de la celda es correcto o no.
        public static readonly DependencyProperty EsDatoCorrectoProperty =
            DependencyProperty.RegisterAttached(
            "EsDatoCorrecto",
            typeof(bool),
            typeof(CellWithErrorsAttachedProperty));

        public static bool GetEsDatoCorrecto(DependencyObject obj)
        {
            return (bool)obj.GetValue(EsDatoCorrectoProperty);
        }

        public static void SetEsDatoCorrecto(DependencyObject obj, bool value)
        {
            obj.SetValue(EsDatoCorrectoProperty, value);
        }
    }
}

The attached property to use with headers:

using System.Windows;




namespace TestStyleWithAttachedProperties.AttachedProperties
{
    public static class HeaderAttachedProperty
    {
        //TextoTooltip01
        //Primer texto del tooltip que se mostrará.
        public static readonly DependencyProperty TooltipProperty =
            DependencyProperty.RegisterAttached(
            "Tooltip",
            typeof(string),
            typeof(HeaderAttachedProperty));

        public static string GetTooltip(DependencyObject obj)
        {
            return (string)obj.GetValue(TooltipProperty);
        }

        public static void SetTooltip(DependencyObject obj, string value)
        {
            obj.SetValue(TooltipProperty, value);
        }
    }
}

This example shows the text in the tooltip. The key line is in the view and is this:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" Width="150"
                    ap:HeaderAttachedProperty.Tooltip="This text is shown"
                    HeaderStyle="{StaticResource DataGridColumnHeaderConTooltip}"
                    ap:CellWithErrorsAttachedProperty.TextoTooltip01="{Binding Path=Tooltip, Source={x:Reference dummyElement}}"
                    ap:CellWithErrorsAttachedProperty.TextoTooltip02="{Binding Path=ErrorDescription}"
                    ap:CellWithErrorsAttachedProperty.EsDatoCorrecto="{Binding Path=IsDataCorrect}"
                    CellStyle="{StaticResource DataGridCellWithErrorsStyle}">

But if I try to use a binding then it doesn't work. The code that I am trying to use to bind a property in the object that is data source of the row is this:

ap:HeaderAttachedProperty.Tooltip="{Binding Price, Source={x:Reference dummyElement}}"

So I could advance a bit in the solution, but I would like to can bind a property of the source object of tupe Data.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,685 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
768 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,231 Reputation points
    2022-03-03T09:02:11.197+00:00

    Hi ComptonAlvaro,
    I check your post and it works fine in my demo:

    XAML:

    <Window x:Class="WpfApp1.Window111"  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
            xmlns:local="clr-namespace:WpfApp111"  
            xmlns:ap="clr-namespace:WpfApp111"  
            mc:Ignorable="d"  
            Title="ComptonAlvaro_220301" Height="450" Width="800">  
      <Window.DataContext>  
        <local:ViewModel/>  
      </Window.DataContext>  
      <Window.Resources>  
        <ToolTip x:Key="ToolTipStyle" ToolTipService.ShowOnDisabled="true">  
          <StackPanel>  
            <TextBlock Text="{Binding Type, RelativeSource={RelativeSource AncestorType=ToolTip}}" MaxWidth="400" TextWrapping='Wrap' />  
          </StackPanel>  
        </ToolTip>  
        <Style TargetType="{x:Type DataGridColumnHeader}" x:Key="DataGridColumnHeaderConTooltip">  
          <Setter Property="ToolTip" Value="{Binding Column.(ap:HeaderAttachedProperty.Tooltip), RelativeSource={RelativeSource Self}}"/>  
        </Style>  
      </Window.Resources>  
      <Grid>  
        <FrameworkElement x:Name="dummyElement" Visibility="Collapsed"/>  
        <DataGrid ItemsSource="{Binding View}" AutoGenerateColumns="False">  
          <DataGrid.Columns>  
            <DataGridTextColumn Header="ID" Binding="{Binding ID}"/>  
            <DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="false"  
                                ap:HeaderAttachedProperty.Tooltip="{Binding Path=DataContext.PriceTooltip, Source={x:Reference dummyElement}}"  
                                HeaderStyle="{StaticResource DataGridColumnHeaderConTooltip}"/>  
          </DataGrid.Columns>  
        </DataGrid>  
      </Grid>  
    </Window>  
    

    and code:

    using System;  
    using System.Collections.ObjectModel;  
    using System.ComponentModel;  
    using System.Windows;  
    using System.Windows.Data;  
      
    namespace WpfApp111  
    {  
      public class ViewModel  
      {  
        public ViewModel()  
        {  
          for (int i = 1; i < 10; i++) col.Add(new Data() { ID = i, Price = i*111 });  
          cvs.Source = col;  
        }  
        private CollectionViewSource cvs = new CollectionViewSource();  
        private ObservableCollection<Data> col = new ObservableCollection<Data>();  
        private Random rnd = new Random();  
        public ICollectionView View { get => cvs.View; }  
        public string PriceTooltip { get; set; } = "Tooltip for Price column";  
      }  
      
      public class Data  
      {  
        public int ID { get; set; }  
        public decimal Price { get; set; }  
      }  
      
      public static class HeaderAttachedProperty  
      {  
        public static readonly DependencyProperty TooltipProperty =  
            DependencyProperty.RegisterAttached(  
            "Tooltip",  
            typeof(string),  
            typeof(HeaderAttachedProperty));  
        public static string GetTooltip(DependencyObject obj) => (string)obj.GetValue(TooltipProperty);  
        public static void SetTooltip(DependencyObject obj, string value) => obj.SetValue(TooltipProperty, value);  
      }  
    }  
    

    Result:

    179529-x.png