I want to add a Custom Property (via a Dependency Property) to my custom ToggleButton Template.
Now i want to have the value of the containing Label (as a placeholder for future implementation) to be a concatenated value of, say "Hello " and the actual Property Value.
Without the concatenation it works fine (displaying "Warrior" on the Label)
But when i try to set the label as a concatenation the xaml doesnt compile anymore.
<Label Content="Hello {TemplateBinding local:HeroClassCheckbox.HeroClass}"/>
How can i achieve this?
The Rest of the Code:
My .xaml
<Window.Resources>
<ResourceDictionary>
<Style x:Key="HeroClassCheckbox" TargetType="ToggleButton">
<Setter Property="Content" Value="Green" />
<Setter Property="local:HeroClassCheckbox.HeroClass" Value="NoClass"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Label Content="{TemplateBinding local:HeroClassCheckbox.HeroClass}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ToggleButton Width="150" Height="50" local:HeroClassCheckbox.HeroClass="Warrior" Style="{DynamicResource HeroClassCheckbox}"/>
</Grid>
My .xaml.cs
public static class HeroClassCheckbox
{
public static readonly DependencyProperty HeroClassProperty = DependencyProperty.RegisterAttached("HeroClass",
typeof(string), typeof(HeroClassCheckbox), new FrameworkPropertyMetadata(null));
public static string GetHeroClass(UIElement element)
{
if (element == null)
throw new ArgumentNullException("element");
return (string)element.GetValue(HeroClassProperty);
}
public static void SetHeroClass(UIElement element, string value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(HeroClassProperty, value);
}
}
You should use a converter as an approach to your goal. Here is an example..
public class HelloLabelConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
char[] removeThis = "Hello ".ToCharArray();
return value.ToString().TrimStart(removeThis);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return string.Format("Hello {0}", value);
}
}
<Window.Resources>
<local:HelloLabelConverter x:Key="HelloLabelConverter" />
</Window.Resources>
<Grid>
<Label Content="{Binding ElementName= lblPropertyToBind, Path=Text, Converter={StaticResource HelloLabelConverter}}"></Label>
</Grid>
Related
I have my own combobox (autocompleteCombobox) where I would like to see only 35 characters of the selectedItem, but with a tooltip that show the full name.
The user control code:
<UserControl.Resources>
<Style x:Key="ComboboxStyle" TargetType="{x:Type ComboBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding ShownName}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<ComboBox x:Name="Combobox" IsSynchronizedWithCurrentItem="True"
IsEditable="True" TextSearch.Text="ShownName"
IsTextSearchEnabled="True" DisplayMemberPath="ShownName"
ToolTip="{Binding SelectedItem.ShownName,ElementName=autoComplete}"
ItemsSource="{Binding ItemsSource,ElementName=autoComplete}"
SelectedItem="{Binding SelectedItem, ElementName=autoComplete}"
Style="{StaticResource ComboboxStyle}">
<ComboBox.InputBindings>
<KeyBinding Key="Enter"
Command="{Binding Path=SelectItemCommand, ElementName=autoComplete}"
CommandParameter="ShownName"/>
</ComboBox.InputBindings>
</ComboBox>
</Grid>
And inside the cs file of the autocompletecombobox:
public static readonly DependencyProperty MaxTextLengthProperty =
DependencyProperty.Register(
"MaxTextLength",
typeof(int),
typeof(ComboBoxAutoComplete),
new UIPropertyMetadata(35));
public int MaxTextLength
{
get { return (int)GetValue(MaxTextLengthProperty); }
set
{
SetValue(MaxTextLengthProperty, value);
LimitTextInCombobox();
}
}
private void LimitTextInCombobox()
{
Combobox.Text = Combobox.Text.Substring(0, MaxTextLength);
}
But it doesn't work...
Rather than trimming the text to a certain number of characters you could let WPF trim it for you with respect to the visual width of the text, which probably would look better. If that is an option for you, you could look into the TextBlock.TextTrimming-property.
you can use converter
[ValueConversion(typeof(object), typeof(string))]
public class StringFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
string str = value.ToString();
if (string.IsNullOrEmpty(str))
{
return "";
}
else
{
if (str.Length >= 35)
{
return str.Substring(0, 35);
}
else
{
return str;
}
}
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
and in Xaml
<Windows.Resources>
<dict:StringFormatConverter x:Key="StringFormatConverter"/>
</Windows.Resources>
<UserControl.Resources>
<Style x:Key="ComboboxStyle" TargetType="{x:Type ComboBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding ShowName, Converter={StaticResource StringFormatConverter}}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The setter of a CLR wrapper for a dependency property should always only call the SetValue method to set the value of the dependency property. Nothing else:
public int MaxTextLength
{
get { return (int)GetValue(MaxTextLengthProperty); }
set
{
SetValue(MaxTextLengthProperty, value);
}
}
Also, you want to keep the original value to be able to display it in the tooltip anyway.
Using a converter as suggested by #Alematt seems like a good option. Just modify the ItemTemplate slightly:
<UserControl.Resources>
<local:Converter x:Key="converter" />
<Style x:Key="ComboboxStyle" TargetType="{x:Type ComboBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding ShownName, Converter={StaticResource converter}}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
And create a converter class:
public class Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string shownName = value as string;
if (!string.IsNullOrEmpty(shownName) && shownName.Length > 35)
return shownName.Substring(0, 35) + "...";
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
And keep the Tooltip as it is:
ToolTip="{Binding SelectedItem.ShownName, ElementName=autoComplete}"
Instead of that just pass the SelectedItem to your converter like so:
<ComboBox x:Name="cmb">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource multi}">
<Binding Path="."/>
<Binding Path="SelectedItem" ElementName="cmb"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
<ComboBox.Items>
<sys:String>Very long string 1</sys:String>
<sys:String>Very long string 2</sys:String>
</ComboBox.Items>
</ComboBox>
Then use your converter like so:
class MultiValConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] != null && values[1] != null)
{
if (values[0].ToString() == values[1].ToString())
{
return "...";//put your logic here i.e. substring(0,30);
}
}
return values[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and you would reference the converter like this:
<locals:MultiValConverter x:Key="multi"/>
I have a style declared in a ResourceDictionary like so.
<Style x:Key="MapMarkerLabelStyle" TargetType="{x:Type TextBlock}">
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="{Binding ActualWidth,
Converter={StaticResource DoubleMultiplierConverter},
ConverterParameter=-0.5}"
Y="-62"></TranslateTransform>
</Setter.Value>
</Setter>
</Style>
And the textblock
<TextBlock Style="{StaticResource MapMarkerLabelStyle}" />
And the converter:
public class DoubleMultiplierConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var originalValue = (double) value;
var format = new NumberFormatInfo {NumberDecimalSeparator = "."};
var multiplier = System.Convert.ToDouble(parameter, format);
return originalValue * multiplier;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
In the translate transform i need to access the textblock ActualWidth property but cant figure out how the binding should look.
The Binding is missing an appropriate source object. To bind to the TextBlock's ActualWidth property, you should set the RelativeSource like this:
<Style x:Key="MapMarkerLabelStyle" TargetType="TextBlock">
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform
X="{Binding Path=ActualWidth,
RelativeSource={RelativeSource AncestorType=TextBlock}
Converter={StaticResource DoubleMultiplierConverter},
ConverterParameter=-0.5}"
Y="-62"/>
</Setter.Value>
</Setter>
</Style>
Try this.
<TranslateTransform X="{Binding RelativeSource={RelativeSource Self},
Path=ActualWidth,
Converter={StaticResource DoubleMultiplierConverter},
ConverterParameter=-0.5}"
Y="-62">
</TranslateTransform>
I am making a WPF application using the MVVM design pattern. Part of the app is a signal strength bar. We created it with just a rectangular user control and created a 4 column grid, so all we need to do is change either the background or foreground color of the control.
My idea on how to do this is a simply store Boolean values for each of the 4 sections and use a value converter. However, there are 3 instances of this control, each with a different color. How can I pass the needed color into the converter? I know converters have a parameter argument, but I haven't been able to find any examples using it, so I'm not even sure if the parameter argument is what I'm looking for.
Your case may not be best addressed by the method you've chosen (it makes it hard to parameterize the colors of the segments), but your specific question is a good one, so I'll answer it.
As you've found, it's tough to pass anything but a string to ConverterParameter. but you don't have to. If you derive a converter from MarkupExtension, you can assign named and typed properties when you use it, and also not have to create it as a resource (indeed, creating it as a resource would break the thing, since that would be a shared instance and the properties are initialized when it's created). Since the XAML parser knows the types of the properties declared on the class, it will apply the default TypeConverter for Brush, and you'll get the exact same behavior as if you were assigning "PapayaWhip" to "Border.Background" or anything else.
This works with any type, of course, not just Brush.
namespace HollowEarth.Converters
{
public class BoolBrushConverter : MarkupExtension, IValueConverter
{
public Brush TrueBrush { get; set; }
public Brush FalseBrush { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return System.Convert.ToBoolean(value) ? TrueBrush : FalseBrush;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
}
Usage:
<TextBox
xmlns:hec="clr-namespace:HollowEarth.Converters"
Foreground="{Binding MyFlagProp, Converter={hec:BoolBrushConverter TrueBrush=YellowGreen, FalseBrush=DodgerBlue}}"
/>
You could give BoolBrushConverter a constructor that takes parameters, too.
public BoolBrushConverter(Brush tb, Brush fb)
{
TrueBrush = tb;
FalseBrush = fb;
}
And in XAML...
<TextBox
xmlns:hec="clr-namespace:HollowEarth.Converters"
Foreground="{Binding MyFlagProp, Converter={hec:BoolBrushConverter YellowGreen, DodgerBlue}}"
/>
I don't think that's a good fit for this case. But sometimes the semantics are so clear, the property name is unnecessary. {hec:GreaterThan 4.5}, for example.
UPDATE
Here's a complete implementation of a SignalBars control. This has five segments to your four, but you can easily remove one; that's only in the template, and the Value property is a double that could be subdivided any way you like (again, in the template).
SignalBars.cs
using System;
using System.ComponentModel;
using System.Windows.Media;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
namespace HollowEarth
{
public class SignalBars : ContentControl
{
static SignalBars()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SignalBars), new FrameworkPropertyMetadata(typeof(SignalBars)));
}
#region Value Property
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(SignalBars),
new PropertyMetadata(0d));
#endregion Value Property
#region InactiveBarFillBrush Property
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("White")]
public Brush InactiveBarFillBrush
{
get { return (Brush)GetValue(InactiveBarFillBrushProperty); }
set { SetValue(InactiveBarFillBrushProperty, value); }
}
public static readonly DependencyProperty InactiveBarFillBrushProperty =
DependencyProperty.Register("InactiveBarFillBrush", typeof(Brush), typeof(SignalBars),
new FrameworkPropertyMetadata(Brushes.White));
#endregion InactiveBarFillBrush Property
}
public class ComparisonConverter : MarkupExtension, IMultiValueConverter
{
public virtual object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length != 2)
{
throw new ArgumentException("Exactly two values are expected");
}
var d1 = GetDoubleValue(values[0]);
var d2 = GetDoubleValue(values[1]);
return Compare(d1, d2);
}
/// <summary>
/// Overload in subclasses to create LesserThan, EqualTo, whatever.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
protected virtual bool Compare(double a, double b)
{
throw new NotImplementedException();
}
protected static double GetDoubleValue(Object o)
{
if (o == null || o == DependencyProperty.UnsetValue)
{
return 0;
}
else
{
try
{
return System.Convert.ToDouble(o);
}
catch (Exception)
{
return 0;
}
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
public class GreaterThan : ComparisonConverter
{
protected override bool Compare(double a, double b)
{
return a > b;
}
}
}
Themes\Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Style
xmlns:he="clr-namespace:HollowEarth"
TargetType="{x:Type he:SignalBars}"
>
<!-- Foreground is the bar borders and the fill for "active" bars -->
<Setter Property="Foreground" Value="Black" />
<Setter Property="InactiveBarFillBrush" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<ControlTemplate.Resources>
<Style TargetType="Rectangle">
<Setter Property="Width" Value="4" />
<Setter Property="VerticalAlignment" Value="Bottom" />
<Setter Property="Stroke" Value="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" />
<Setter Property="StrokeThickness" Value="1" />
<Setter Property="Fill" Value="{Binding InactiveBarFillBrush, RelativeSource={RelativeSource TemplatedParent}}" />
<Setter Property="Margin" Value="0,0,1,0" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{he:GreaterThan}">
<MultiBinding.Bindings>
<Binding
Path="Value"
RelativeSource="{RelativeSource TemplatedParent}"
/>
<Binding
Path="Tag"
RelativeSource="{RelativeSource Self}"
/>
</MultiBinding.Bindings>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Fill" Value="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ControlTemplate.Resources>
<ContentControl
ContentTemplate="{Binding ContentTemplate, RelativeSource={RelativeSource TemplatedParent}}">
<StackPanel
Orientation="Horizontal"
SnapsToDevicePixels="True"
UseLayoutRounding="True"
>
<!-- Set Tags to the minimum threshold value for turning the segment "on" -->
<!-- Remove one of these to make it four segments. To make them all equal height, remove Height here
and set a fixed height in the Rectangle Style above. -->
<Rectangle Height="4" Tag="0" />
<Rectangle Height="6" Tag="2" />
<Rectangle Height="8" Tag="4" />
<Rectangle Height="10" Tag="6" />
<Rectangle Height="12" Tag="8" />
</StackPanel>
</ContentControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Example XAML:
<StackPanel
xmlns:he="clr-namespace:HollowEarth"
Orientation="Vertical"
HorizontalAlignment="Left"
>
<Slider
Minimum="0"
Maximum="10"
x:Name="SignalSlider"
Width="200"
SmallChange="1"
LargeChange="4"
TickFrequency="1"
IsSnapToTickEnabled="True"
/>
<he:SignalBars
HorizontalAlignment="Left"
Value="{Binding Value, ElementName=SignalSlider}"
InactiveBarFillBrush="White"
Foreground="DarkRed"
/>
</StackPanel>
Usually you may need a ColorToBrushConverter, but not a BooleanToColor.
I would simply create different styles with triggers for each bar, like
<Style.Triggers>
<DataTrigger Binding="{Binding IsOffline}" Value="True">
<Setter Property="Background" Value="Salmon" />
</DataTrigger>
<DataTrigger Binding="{Binding IsPrinting}" Value="True">
<!--<Setter Property="Background" Value="Honeydew" />-->
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
I have created a UserControl and have quite a few bindings to several custom DependencyProperties in the control. For some reason though, one of these is failing with the error:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference 'ElementName=YearSelectorControl'.
BindingExpression:Path=SelectedBorderColor; DataItem=null; target
element is 'SolidColorBrush' (HashCode=45568629); target property is
'Color' (type 'Color')
The control includes a ToggleButton and Popup. The error occurs when the Popup is opened by clicking on the ToggleButton.
Here is the xaml:
<UserControl x:Class="MyProject.Views.YearSelector"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyProject.Views"
mc:Ignorable="d"
d:DesignHeight="23" d:DesignWidth="181"
x:Name="YearSelectorControl">
<UserControl.Resources>
<SolidColorBrush x:Key="HoverColorBrush" Color="{Binding ElementName=YearSelectorControl, Path=HoverColor}"/> <!--This binding is virtually identical, but works fine-->
<SolidColorBrush x:Key="SelectedBorderColorBrush" Color="{Binding ElementName=YearSelectorControl, Path=SelectedBorderColor}"/> <!--This is the binding that fails-->
<local:YearToBackColorConverter x:Key="YearToBackColorConverter"/>
<local:YearToBorderThicknessConverter x:Key="YearToBorderThicknessConverter"/>
<Style x:Key="ForwardBackButtons" TargetType="{x:Type Button}" >
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="FontFamily" Value="Arial" />
</Style>
<ControlTemplate x:Key="YearButton" TargetType="{x:Type Button}">
<Grid Width="55" Height="30" Margin="2">
<Rectangle x:Name="ButtonRectangle" Stroke="{StaticResource SelectedBorderColorBrush}"> <!--Use of the failing brush-->
<!--Removed code that determines StrokeThickness. Works correctly if Stroke set to a specific color rather than a Static Resource-->
</Rectangle>
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonRectangle" Property="Fill" Value="{StaticResource HoverColorBrush}"/> <!--Use of the properly binding brush-->
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Resources>
<Grid>
<!--Layout here-->
</Grid>
</UserControl>
Here is an excerpt from the code-behind for the two DependencyProperties that are bound to SolidColorBrush resources:
public Color HoverColor
{
get { return (Color)GetValue(HoverColorProperty); }
set { SetValue(HoverColorProperty, value); }
}
public static readonly DependencyProperty HoverColorProperty =
DependencyProperty.Register("HoverColor", typeof(Color), typeof(YearSelector), new PropertyMetadata(Color.FromArgb(255,190,230,253)));
public Color SelectedBorderColor
{
get { return (Color)GetValue(SelectedBorderColorProperty); }
set { SetValue(SelectedBorderColorProperty, value); }
}
public static readonly DependencyProperty SelectedBorderColorProperty =
DependencyProperty.Register("SelectedBorderColor", typeof(Color), typeof(YearSelector), new PropertyMetadata(Color.FromArgb(255,126,0,234)));
I went away from this for a few months to focus on more important elements of my application and have finally come back to it. I discovered the solution, though I'm not sure why it was a problem in the first place.
For whatever reason, binding a SolidColorBrush resource to the Stroke property doesn't work. What I did instead, is I bound the Color DependencyProperty directly to Stroke and used an IValueConverter to convert it to a SolidColorBrush.
XAML:
<Rectangle x:Name="ButtonRectangle" Stroke="{Binding ElementName=YearSelectorControl, Path=SelectedBorderColor, Converter={StaticResource ColorToSolidColorBrushConverter}}">
Code-behind:
public class ColorToSolidColorBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
Color c = (Color)value;
return new SolidColorBrush(c);
}
catch
{
return new SolidColorBrush(Color.FromRgb(0,0,0));
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have a viewmodel implementing the IDataErrorInfo interface for validation, for an error it produces a string which is a key in the resource dictionary for a localized string describing the error. However when trying to apply the following style and template to the textbox I get the red border but no tooltip, however removing my converter and using the default one gives me the tooltip but obviously not the localized string.
Can you see what I am doing wrong and/or if there is a better way of doing this?
class MessageCodeToMessageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
string messageCode = (string)value;
try
{
return (string)App.Current.Resources[messageCode];
}
catch (Exception)
{
return messageCode;
}
}
else
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return "";
}
}
<local:MessageCodeToMessageConverter x:Key="Converter"></local:MessageCodeToMessageConverter>
<Style x:Key="TextBox" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent, Converter={StaticResource Converter}}"/>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="ErrorTemplate">
<DockPanel LastChildFill="True">
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
In my opinion if your resource key is defined in the App.xaml then it should work. But your resource key is probably in resource in some user control. (string)App.Current.Resources[messageCode]; search only in the App.xaml resource.
Solution for you can be use multivalueconverter
class MessageCodeToMessageConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetObject = values[0] as FrameworkElement;
if (targetObject == null)
{
return DependencyProperty.UnsetValue;
}
if (value != null)
{
string messageCode = (string)values[1];
try
{
return targetObject.FindResource(messageCode);
}
catch (Exception)
{
return messageCode;
}
}
else
{
return null;
}
}
public object ConvertBack(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return "";
}
}
and
<Style x:Key="TextBox" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip">
<Setter.Value>
<MultiBinding Converter="{StaticResource Converter}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{x:Static RelativeSource.Self}" Path="(Validation.Errors)[0].ErrorContent" />
</MultiBinding.Bindings>
</MultiBinding>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>