WPF Value Converter to replace error code with localized string - c#

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>

Related

Converter to Bool

I have a normal Checkbox, where I want to set the IsChecked property to a Binding resource.
The resource is a self written class myClass, which can be null or referenced (means not null).
The Checkbox should be NOT checked, if the assigned object myObject (out of myClass) is null
and checked, if it is not null.
What do I have to write in the IsChecked="..." property in my xaml file?
You can create a style with a DataTrigger that sets the IsChecked property.
<CheckBox>
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="IsChecked" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MyObject}" Value="{x:Null}">
<Setter Property="IsChecked" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
An alternative is to create a reusable value converter.
public class NotNullToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
Create an instance of the converter in any resource dictionary, e.g. application resources.
<local:NotNullToBooleanConverter x:Key="NotNullToBooleanConverter"/>
This converter can be used directly in the binding.
<CheckBox IsChecked="{Binding MyObject, Converter={StaticResource NotNullToBooleanConverter}}"/>

DataTrigger is not setting the opacity

I have a datagrid with data trigger.
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource {x:Type DataGridRow}}">
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<DataTrigger x:Uid="DataTrigger_1" x:Name="RowMenuDataTrigger" Binding="{Binding IsModified, diag:PresentationTraceSources.TraceLevel=High}" Value="True">
<Setter x:Uid="Setter_1" Property="Opacity" Value="0.5" />
<Setter x:Uid="Setter_2" Property="ToolTip">
<Setter.Value>
<MultiBinding x:Uid="MultiBinding_1" Converter="{StaticResource ModifiedTypeMessage}">
<Binding x:Uid="Binding_1" Path="Class"></Binding>
<Binding x:Uid="Binding_2" Path="OriginalClassName" ></Binding>
</MultiBinding>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</control:DataGrid.RowStyle>
and IMultiValueConverter looks like:
public class EnTypeModifiedMessageConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 2)
{
string current = values[0].ToString();
string original = values[1].ToString();
if (current != original)
{
return string.Format(LanguageHelper.LocalizedString("RawTypeModified"), original.ToUpper());
}
}
return string.Empty;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
}
when the data changes I get the control hitting this converter. but the UI still not updated. ie opacity and tooltip remains same.

How to trim text displayed in a ComboBox?

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"/>

WPF TemplateBinding Concatenate String for Value

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>

MVVM ListBox - changing the background of an item based on its index

I have a ListBox that represents the lines of text in a scripting language. I need to "single-step" the script, highlighting the current script line (say with a green background).
The modelview has a "CurrentLine" property that is the index of the current line so I figured I would write a trigger on the ItemContainerStyle and use a converter to figure out if the index of the list box item is the same as the CurrentLine. But I'm struggling to pass in the current line value to the converter as it comes from the VM, and I can't pass non-constant values into the converter as parameters. Neither can I do so with the "Value" of the trigger. How can I pass these values in?
CURRENTLINE EXPOSED AS REFERENCE TYPE
To modify the ViewModel to save the current line as an actual LineViewModel (or whatever, depending on how your VM exposes the Lines), rather than the selected index is much cleaner solution in my opinion.
Then you can define the trigger using a multibinding with a converter that checks for equality:
<ListBox ItemsSource="{Binding Lines}" x:Name="List">
<ListBox.Resources>
<sample:EqualityConverter x:Key="Converter" />
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="Control">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource Converter}">
<Binding Path="DataContext.CurrentLine" ElementName="List" />
<Binding />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Blue"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
And the converter:
public class EqualityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0] == values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
That's it.
CURRENTLINE EXPOSED AS STRING (VALUE TYPE)
If you still want to use an integer index, you can uitilize the AlternationIndex. You set the AlternationCount of the list to the number of its items, which ensures that every item has its unique index and then bind to the attached property ItemsSource.AlternationIndex of the ListBoxItem. This way, you won't mix up lines with the same content.
<ListBox ItemsSource="{Binding Resources}" x:Name="List" AlternationCount="{Binding Resources.Count}">
<ListBox.Resources>
<sample:EqualityConverter x:Key="Converter" />
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource Converter}">
<Binding Path="DataContext.CurrentItem" ElementName="List"/>
<Binding RelativeSource="{RelativeSource Self}" Path="(ItemsControl.AlternationIndex)"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Blue"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
You need to modify the converter slightly:
public class EqualityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return Equals(values[1], values[0] );
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Categories

Resources