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.