I have 2 PasswordBoxes. I need to check are passwords equal. I don't want to write this condition into [].xaml.cs code, but i want to mark PasswordBox in red when passwords aren't equal.
Should i write special ValidationRule, some code in ViewModel or something else? Can anyone help me? Now the validation is written in the [].xaml.cs but i want to avoid it.
Using:
<PasswordBox Name="tbPassword" />
<PasswordBox Name="tbPasswordConf" />
<PasswordValidator
Box1="{Binding ElementName=tbPassword}"
Box2="{Binding ElementName=tbPasswordConf}" />
Code (this code is not cover all cases):
public class PasswordValidator : FrameworkElement
{
static IDictionary<PasswordBox, Brush> _passwordBoxes = new Dictionary<PasswordBox, Brush>();
public static readonly DependencyProperty Box1Property = DependencyProperty.Register("Box1", typeof(PasswordBox), typeof(PasswordValidator), new PropertyMetadata(Box1Changed));
public static readonly DependencyProperty Box2Property = DependencyProperty.Register("Box2", typeof(PasswordBox), typeof(PasswordValidator), new PropertyMetadata(Box2Changed));
public PasswordBox Box1
{
get { return (PasswordBox)GetValue(Box1Property); }
set { SetValue(Box1Property, value); }
}
public PasswordBox Box2
{
get { return (PasswordBox)GetValue(Box2Property); }
set { SetValue(Box2Property, value); }
}
private static void Box1Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
private static void Box2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var pv = (PasswordValidator)d;
_passwordBoxes[pv.Box2] = pv.Box2.BorderBrush;
pv.Box2.LostFocus += (obj, evt) =>
{
if (pv.Box1.Password != pv.Box2.Password)
{
pv.Box2.BorderBrush = new SolidColorBrush(Colors.Red);
}
else
{
pv.Box2.BorderBrush = _passwordBoxes[pv.Box2];
}
};
}
}
Also, it's possible to define dependency property with style of error and setting it instead of BorderBrush. But i don't know how to use in this case the standard ErrorTemplate.
Create a readonly property in your viewModel and return a brush for the border of the PasswordConfirmBox and then bind the textbox to the property
EDIT after comments received I've not tested this and there might be minor errors but this way you can return a different type depending on the converterparameter
public bool PasswordValidation
{
get
{
if (textBox1.Text == textBox2.Text)
return true;
else
return
false;
}
}
Value Converters:
public class ValConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter == "")
{
if ((value as bool?) ?? false)
return Visibility.Hidden;
else
return Visibility.Visible;
}
else if (parameter == "")
{
if ((value as bool?) ?? false)
return new SolidColorBrush(Colors.Black);
else
return new SolidColorBrush(Colors.Red);
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter == "Visibility")
{
if ((System.Windows.Visibility)value == Visibility.Visible)
return false;
else
return true;
}
else if (parameter == "Brush")
{
if (((SolidColorBrush)value).Color == Colors.Black)
return true;
else
return false;
}
}
Xaml:
<Window.Resources>
<vis:ValConverter x:Key="valConverter"/>
</Window.Resources>
<TextBox Name="textBox1"/>
<TextBox Name="textBox2" BorderBrush="{Binding ConfirmBorder,Converter={StaticResource valConverter},ConverterParameter=Brush}" />
<TextBlock Text="Passwords Does not Match!" BorderBrush="{Binding ConfirmBorder,Converter={StaticResource valConverter},ConverterParameter=Visibility}"/>
Related
I write code for multilanguage using IValueConverter.
Text will be change dynamically and so many texts has. So I cannot be make using Resource file.
I decided to make using IValueConverter. And First time it displayed well.
But change language runtime, it will not work I expected.
IValueConverter will return a value by current language type.
And language type is change in runtime by user.
This is my simplified code:
internal class MessageConverter : IValueConverter, INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string? valueOrigin = parameter == null ? ":" : parameter.ToString();
string[] values = valueOrigin == null ? new string[2] : valueOrigin.Split(':');
if (values.Length == 0)
{
values = new string[] { string.Empty, string.Empty };
}
else if (values.Length == 1 && false == values[0].StartsWith("#"))
{
values = new string[] { string.Empty, values[0] };
}
if ((bool)(DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue))
{
return values[1];
}
MessageHelper.GetInstance().LanguageChanged += MessageConverter_langchanged;
return MessageHelper.GetMessage(values[0]);
}
private void MessageConverter_langchanged(object? sender, EventArgs e)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("")); // ???
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class MessageHelper
{
private static object lockObject = new object();
private static MessageHelper? instance = null;
private Dictionary<string, string> ko = new Dictionary<string, string>();
private Dictionary<string, string> en = new Dictionary<string, string>();
private Dictionary<string, string> jp = new Dictionary<string, string>();
private MessageHelper()
{
// smaple
ko.Add("#MSG_0001", "시작");
en.Add("#MSG_0001", "Start");
}
public static MessageHelper GetInstance()
{
lock (lockObject)
{
if (instance == null)
instance = new MessageHelper();
return instance;
}
}
private string language = "KO";
public static string GetLanguage(string language)
{
return language;
}
public static void SetLanguage(string language)
{
MessageHelper.GetInstance().language = language;
GetInstance().LanguageChanged?.Invoke(GetInstance(), EventArgs.Empty);
}
public event EventHandler? LanguageChanged;
public static string GetMessage(string messageCode)
{
var dic = MessageHelper.GetInstance().language == "en" ? MessageHelper.GetInstance().en : MessageHelper.GetInstance().ko;
if (dic.ContainsKey(messageCode))
return dic[messageCode];
return messageCode;
}
}
<Button Style="{StaticResource BoldButtonFontStyle}"
Content="{Binding Converter={StaticResource MessageConverter}, ConverterParameter=#MSG_0001:시작하기, UpdateSourceTrigger=PropertyChanged}"
Command="{Binding CommonCommand}"
CommandParameter="SaleStart" />
<Button Style="{StaticResource BoldButtonFontStyle}"
Content="한국어"
Command="{Binding CommonCommand}"
CommandParameter="Language_Korean" />
<Button Style="{StaticResource BoldButtonFontStyle}"
Content="English"
Command="{Binding CommonCommand}"
CommandParameter="Language_English" />
private void CommonCommandProc(string param)
{
if (param == "Language_Korean")
{
MessageHelper.SetLanguage("ko");
}
else if (param == "Language_English")
{
MessageHelper.SetLanguage("en");
}
}
I code using inherit 'MarkupExtension'.
It works fine.
Thank you every one.
I've searched all over google and am still trying to come up with the correct answer. The problem is as follows when I clear this text field, the bound value does not get triggered.
So, the problem is that the bounded value of this text field is not getting changed if it is made empty, but my validation rule does detect it and sends out the warning.
Underneath, you can find my XAML snippet, the belonging validation rule, and the mentioned property.
The XAML-Snippet
<TextBox Style="{StaticResource TextBoxErrorStyle}"
Margin="8 0 0 0"
Visibility="{Binding AdditionalSurfaceTreatmentInfoVisibility,
Converter={StaticResource BoolToVisConverter}}"
FontSize="12" MaxLength="4" Width="40"
Height="25">
<TextBox.Text>
<Binding Path="AdditionalSurfaceTreatmentInfo"
UpdateSourceTrigger="PropertyChanged"
NotifyOnSourceUpdated="True" Mode="TwoWay"
NotifyOnValidationError="True">
<Binding.ValidationRules>
<classes:StandardValidationRule ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
The StandardValidationRule:
public class StandardValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var valueToValidate = value as string;
if (string.IsNullOrEmpty(valueToValidate))
{
return new ValidationResult(false, "Field is mandatory.");
}
return new ValidationResult(true, null);
}
}
The property:
private string _additionalSurfaceTreatmentInfo;
public string AdditionalSurfaceTreatmentInfo
{
get => _additionalSurfaceTreatmentInfo;
set
{
_additionalSurfaceTreatmentInfo = value;
OnPropertyChanged();
SetOrModifySurfaceTreatment();
Console.WriteLine(string.IsNullOrEmpty(_additionalSurfaceTreatmentInfo).ToString());
}
}
Thank you in advance for your efforts. Any help is much appreciated!
The code above does work as I prefer. I already tried everything regarding the different properties I can fill in within the ValidationRule. The only thing that needs to be changed is that when the textbox is empty, it must trigger the OnPropertyChanged() method. This way, I can later validate the property when I, for example, submit a save command.
Use the ValidationStep property.
Example:
<local:StandardValidationRule ValidationStep="UpdatedValue"
ValidatesOnTargetUpdated="True"/>
But to validate the source property, more complex logic is needed, since the validator does not receive the value of the property, but the binding expression from the target property.
public class StandardValidationRule : ValidationRule
{
private bool gettingValue = false;
private bool isValueReceived = false;
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (gettingValue)
{
isValueReceived = true;
return ValidationResult.ValidResult;
}
string? valueToValidate = value as string;
ValidationResult? result = null;
if (valueToValidate is null && value is not null)
{
if (value is BindingExpressionBase bindingExpression)
{
gettingValue = true;
isValueReceived = false;
DependencyObject target = bindingExpression.Target;
var gettingValueExpression = BindingOperations.SetBinding(target, SourceValueProperty, bindingExpression.ParentBindingBase);
if (!isValueReceived)
{
gettingValueExpression.UpdateTarget();
}
valueToValidate = target.GetValue(SourceValueProperty)?.ToString();
target.ClearValue(SourceValueProperty);
gettingValue = false;
}
else
{
result = unvalid;
}
}
if (result is null)
{
result = string.IsNullOrEmpty(valueToValidate)
? unvalid
: ValidationResult.ValidResult;
}
return result;
}
private static readonly ValidationResult unvalid = new ValidationResult(false, "Field is mandatory.");
// Using a DependencyProperty as the backing store for SourceValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceValueProperty =
DependencyProperty.RegisterAttached("SourceValue", typeof(object), typeof(StandardValidationRule), new PropertyMetadata(null));
}
There is another method for getting the source value.
It is based on using the internal method through reflection.
Which is considered by many to be a «bad» way.
But it works much more efficiently.
And I think it is unlikely that someone will make changes to the internal method, which is already used in many places.
public static class BindingExpressionHelper
{
private static readonly Func<BindingExpressionBase, DependencyObject, DependencyProperty, object> GetValueOfBindingExpression;
static BindingExpressionHelper()
{
Type beType = typeof(BindingExpressionBase);
var beMethod = beType
.GetMethod("GetValue", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, new Type[] { typeof(DependencyObject), typeof(DependencyProperty) })
?? throw new Exception("GetValue method not found.");
var beFunc = (Func<BindingExpressionBase, DependencyObject, DependencyProperty, object>)
beMethod.CreateDelegate(typeof(Func<BindingExpressionBase, DependencyObject, DependencyProperty, object>));
GetValueOfBindingExpression = beFunc;
}
/// <summary>Returns the source value of this binding expression.</summary>
/// <param name="bindingExpression">The binding expression whose value to get.</param>
/// <returns>The value of the binding expression received.</returns>
public static object? GetSourceValue(this BindingExpressionBase bindingExpression)
{
DependencyObject target = bindingExpression.Target;
DependencyProperty targetProperty = bindingExpression.TargetProperty;
var value = GetValueOfBindingExpression(bindingExpression, target, targetProperty);
return value;
}
}
public class StandardValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string? valueToValidate = value as string;
ValidationResult? result = null;
if (valueToValidate is null && value is not null)
{
if (value is BindingExpressionBase bindingExpression)
{
valueToValidate = bindingExpression.GetSourceValue()?.ToString();
}
else
{
result = unvalid;
}
}
if (result is null)
{
result = string.IsNullOrEmpty(valueToValidate)
? unvalid
: ValidationResult.ValidResult;
}
return result;
}
private static readonly ValidationResult unvalid = new ValidationResult(false, "Field is mandatory.");
}
I made a text box to input and show color Hex value.The binding is twoway to a color property of a parent.
Everything is working but, I need to make sure, in case I enter manually a Hex in the text box, and if this a not correct string, then use and display the current Hex value of the color, rather than trying to change it.
Here is what I tried but obviously it's not working, I'm a beginner and I have only a little experience with converter and WPF. If I write anything but not a valid Hex string, at the moment the textbox gets a red outline, but I wish that in this case, the Hex previous string reappears.
[ValueConversion(typeof(Color), typeof(String))]
public class ColorToStringConverter : IValueConverter
{
public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
{
Color colorValue = (Color)value;
return ColorNames.GetColorName(colorValue);
}
public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ColorHexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var hexCode = System.Convert.ToString(value);
//if (string.IsNullOrEmpty(hexCode))
// return null;
try
{
var color = (Color)ColorConverter.ConvertFromString(hexCode);
return color;
}
catch
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var hexCode = System.Convert.ToString(value);
Regex myRegex = new Regex("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
bool isValid = false;
if (string.IsNullOrEmpty(hexCode))
{
isValid = false;
}
else
{
isValid = myRegex.IsMatch(hexCode);
}
try
{
return hexCode;
}
catch
{
return null;
}
}
}
And the C# class for the TextBox
public class ColorHex : TextBox
{
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter)
{
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(this, TextProperty);
if (bindingExpression != null)
bindingExpression.UpdateSource();
}
}
}
And its xaml in Generic.xaml
<local:ColorHex x:Name="PART_ColorHex" Style="{StaticResource ColorPickerTextBox}" Text="{Binding SelectedColor, Converter={StaticResource ColorToHexConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ColorPicker}}}" />
Any idea ?
thank you
A valid hex color has the form '#nnn' or '#nnnnnn'.
So, the regex for this case would be: (^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)
NOw, you could add these lines of code:
var regex = #"(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)"
var match = Regex.Match(inputYouWantToCheck, regex,RegexOptions.IgnoreCase);
if (!match.Success)
{
// Color is not valid
}
Hope this helps.
What if you did something where you add a label next to the textbox to show an example of what color has been entered. You would just need to change the label color change each time.
I'd do it differently, using property to validate the color:
public Color Color { get; set; } = Colors.Red;
public string ColorText
{
get { return (new ColorConverter()).ConvertToString(Color); }
set
{
Color = (Color)ColorConverter.ConvertFromString(value);
OnPropertyChanged();
}
}
Bind to ColorText, it will throw in case it's wrong hex, and you can use ExceptionValidationRule to display it (as a red border):
<TextBox Text="{local:ExceptionBinding Path=ColorText}" />
where
public class ExceptionBinding : Binding
{
public ExceptionBinding() : base()
{
ValidationRules.Add(new ExceptionValidationRule());
}
}
As a bonus you can enter known colors as text, e.g. "Red":
I'm using columns autogeneration feature of WPF DataGrid control. One of it's columns is a select column - based on some enum.
The enum looks like this:
public enum MyTypes {
Integer = 1,
Float = 2
IntegerArray = 3,
FloatArray = 4
}
I'd like to show the array types not as IntegerArray, FloatArray, but as Integer[],
Float[] in the autogenerated dropdown list.
In other words - cell will contain a dropdown list with values Integer, Float, IntegerArray, FloatArray, and I want them to be Integer, Float, Integer[], Float[]. Obviously I can't change IntegerArray to Integer[] inside my MyTypes declaration.
How do I do that?
EDIT:
Pushpray's answer below works only partially - I get enum fields description (so instead of having FloatArr in the ComboBox I'm getting Float[], but when the column holding theComboBox looses focus, then I get NullReferenceException.
here is how I offer to solve your issue
result
we will start by defining the desired values as Description attribute to the enum values
public enum MyTypes
{
Integer = 1,
Float = 2,
[Description("Integer[]")]
IntegerArray = 3,
[Description("Float[]")]
FloatArray = 4
}
then create a class with a method to enumerate the list from the enum type that will take Description attribute into account if applied
namespace CSharpWPF
{
public class EnumHelper
{
public static IEnumerable<string> GetEnumDescriptions(Type enumType)
{
foreach (var item in Enum.GetNames(enumType))
{
FieldInfo fi = enumType.GetField(item);
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
yield return attributes[0].Description;
else
yield return item;
}
}
}
}
finally use ObjectDataProvider to use the enumerator method GetEnumDescriptions in the class EnumHelper, and use the same as the source for the DataGridComboBoxColumn's ItemsSource
sample xaml
<DataGrid xmlns:l="clr-namespace:CSharpWPF">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="EnumValues" >
<DataGridComboBoxColumn.ItemsSource>
<Binding>
<Binding.Source>
<ObjectDataProvider MethodName="GetEnumDescriptions"
ObjectType="{x:Type l:EnumHelper}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="l:MyTypes" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Binding.Source>
</Binding>
</DataGridComboBoxColumn.ItemsSource>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
Using with Auto generating columns
<DataGrid x:Name="dGrid"
AutoGenerateColumns="True"
AutoGeneratingColumn="DataGrid_OnAutoGeneratingColumn"
xmlns:l="clr-namespace:CSharpWPF">
<DataGrid.Resources>
<l:EnumHelper x:Key="EnumHelper" />
<ObjectDataProvider x:Key="EnumValues"
MethodName="GetEnumDescriptions"
ObjectType="{x:Type l:EnumHelper}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="l:MyTypes" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<DataTemplate x:Key="MyTypesCellTemplate">
<TextBlock Text="{Binding EnumValue, Converter={StaticResource EnumHelper}}"/>
</DataTemplate>
<DataTemplate x:Key="MyTypesCellEditingTemplate">
<ComboBox SelectedItem="{Binding EnumValue, Converter={StaticResource EnumHelper}}"
ItemsSource="{Binding Source={StaticResource EnumValues}}" />
</DataTemplate>
</DataGrid.Resources>
</DataGrid>
event handler
private void DataGrid_OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyType == typeof(MyTypes))
{
DataGridTemplateColumn templateColumn = new DataGridTemplateColumn();
templateColumn.Header = e.Column.Header;
templateColumn.CellTemplate = (DataTemplate)dGrid.Resources["MyTypesCellTemplate"];
templateColumn.CellEditingTemplate = (DataTemplate)dGrid.Resources["MyTypesCellEditingTemplate"];
e.Column = templateColumn;
}
}
EnumHelper class
public class EnumHelper : IValueConverter
{
public static IEnumerable<string> GetEnumDescriptions(Type enumType)
{
foreach (var item in Enum.GetNames(enumType))
{
FieldInfo fi = enumType.GetField(item);
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
yield return attributes[0].Description;
else
yield return item;
}
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return string.Empty;
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return MyTypes.Float;
Type enumType = typeof(MyTypes);
foreach (var item in Enum.GetNames(enumType))
{
FieldInfo fi = enumType.GetField(item);
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0 && string.Equals(attributes[0].Description, value))
return Enum.Parse(enumType, item);
}
return Enum.Parse(enumType, value.ToString());
}
}
Demo
Here is a working sample code based on the answer above
DataGridEnumsSample.zip (VS 2013)
MD5 Checksum: 9C34BB81857C78375582FAC0E1C8A95D
I have another solution that will allow you to display enums in a DataGrid using auto-generated columns. This works for enum types that are created using reflection.
First, create an EnumTemplateColumn that inherits from DataGridBoundColumn:
public class EnumTemplateColumn : DataGridBoundColumn
{
private readonly Type enumType;
public EnumTemplateColumn(Type enumType)
{
this.enumType = enumType;
}
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
string columnHeader = cell.Column.Header.ToString();
TextBlock textBlock = new TextBlock();
var dataRowView = (DataRowView) dataItem;
var enumValue = dataRowView[columnHeader];
textBlock.Text = Enum.GetName(this.enumType, enumValue);
return textBlock;
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
throw new NotImplementedException();
}
}
Next, use the OnAutoGeneratingColumn event of the DataGrid to use the EnumTemplateColumn:
private void DataGrid_OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyType.IsEnum)
{
e.Column = new EnumTemplateColumn(e.PropertyType)
{
Header = e.Column.Header,
};
}
}
And the WPF component:
<DataGrid x:Name="dataGrid"
Grid.Row="5"
ItemsSource="{Binding Path=DataTable}" IsReadOnly="True"
AutoGeneratingColumn="DataGrid_OnAutoGeneratingColumn"/>
This is how I did it: in the AutoGeneratingColumnevent of the DataGrid, replace the default DataGridComboBoxColumn by DataGridTextColumn, and add the binding and converter manually.
private void dataGrid1_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyType.IsEnum)
{
//(e.Column as DataGridComboBoxColumn)
var col = new DataGridTextColumn
{
Header = e.PropertyName
};
col.Binding = new Binding(e.PropertyName)
{
Converter = new WPFCommon.BindingConverters.EnumConverter()
};
// Replace the auto-generated column with the new one.
e.Column = col;
}
}
The converter class,
namespace WPFCommon.BindingConverters
{
public class EnumConverter:IValueConverter
{
//** this does not work for enum value in DataTable
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || string.IsNullOrEmpty(value.ToString()))
return DependencyProperty.UnsetValue;
return ((Enum)value).GetDescription();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
}
The class for enum extension,
public static class EnumExtensions
{
public static string GetDisplayName(this Enum enu)
{
DisplayAttribute attr = GetDisplayAttribute(enu);
return attr != null ? attr.Name : enu.ToString();
}
public static string GetDescription(this Enum enu)
{
DescriptionAttribute attr = GetDescriptionAttribute(enu);
return attr != null ? attr.Description : enu.ToString();
}
private static DescriptionAttribute GetDescriptionAttribute(object value)
{
Type type = value.GetType();
if (!type.IsEnum)
{
throw new ArgumentException(string.Format("Type {0} is not an enum", type));
}
// Get the enum field.
var field = type.GetField(value.ToString());
return field == null ? null : field.GetCustomAttribute<DescriptionAttribute>();
}
private static DisplayAttribute GetDisplayAttribute(object value)
{
Type type = value.GetType();
if (!type.IsEnum)
{
throw new ArgumentException(string.Format("Type {0} is not an enum", type));
}
// Get the enum field.
var field = type.GetField(value.ToString());
return field == null ? null : field.GetCustomAttribute<DisplayAttribute>();
}
}
I have a UserControl that uses a binding converter. I've made the converter an inner class of
public partial class MyPanel : UserControl
{
public class CornerRadiusConverter : IValueConverter
{
How do I reference the Converter class from the XAML? The following does not work:
<controls:MyPanel.CornerRadiusConverter x:Key="CornerRadiusConverter" />
It gives this error:
The tag
'LensPanel.CornerRadiusConverter' does
not exist in XML namespace
'clr-namespace:MyApp.Windows.Controls'
I was thinking about this problem again, and I came up with something similar to Dennis's solution : create a "proxy" converter class, with a Type property, which will create the instance of the actual converter and delegate the conversion to it.
public class Converter : IValueConverter
{
private Type _type = null;
public Type Type
{
get { return _type; }
set
{
if (value != _type)
{
if (value.GetInterface("IValueConverter") != null)
{
_type = value;
_converter = null;
}
else
{
throw new ArgumentException(
string.Format("Type {0} doesn't implement IValueConverter", value.FullName),
"value");
}
}
}
}
private IValueConverter _converter = null;
private void CreateConverter()
{
if (_converter == null)
{
if (_type != null)
{
_converter = Activator.CreateInstance(_type) as IValueConverter;
}
else
{
throw new InvalidOperationException("Converter type is not defined");
}
}
}
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
CreateConverter();
return _converter.Convert(value, targetType, parameter, culture);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
CreateConverter();
return _converter.ConvertBack(value, targetType, parameter, culture);
}
#endregion
}
You use it like that :
<Window.Resources>
<my:Converter x:Key="CornerRadiusConverter" Type="{x:Type controls:MyPanel+CornerRadiusConverter}"/>
</Window.Resources>
It could be possible. A few months ago I wrote a markup extension to create the converter for you inline. It keeps a dictionary of weak references so that you don't create multiple instances of the same converter. Handles creating converters with different arguments too.
In XAML:
<TextBox Text="{Binding Converter={NamespaceForMarkupExt:InlineConverter {x:Type NamespaceForConverter:ConverterType}}}"/>
C#:
[MarkupExtensionReturnType(typeof(IValueConverter))]
public class InlineConverterExtension : MarkupExtension
{
static Dictionary<string, WeakReference> s_WeakReferenceLookup;
Type m_ConverterType;
object[] m_Arguments;
static InlineConverterExtension()
{
s_WeakReferenceLookup = new Dictionary<string, WeakReference>();
}
public InlineConverterExtension()
{
}
public InlineConverterExtension(Type converterType)
{
m_ConverterType = converterType;
}
/// <summary>
/// The type of the converter to create
/// </summary>
/// <value>The type of the converter.</value>
public Type ConverterType
{
get { return m_ConverterType; }
set { m_ConverterType = value; }
}
/// <summary>
/// The optional arguments for the converter's constructor.
/// </summary>
/// <value>The argumments.</value>
public object[] Arguments
{
get { return m_Arguments; }
set { m_Arguments = value; }
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
PropertyInfo propertyInfo = target.TargetProperty as PropertyInfo;
if (!propertyInfo.PropertyType.IsAssignableFrom(typeof(IValueConverter)))
throw new NotSupportedException("Property '" + propertyInfo.Name + "' is not assignable from IValueConverter.");
System.Diagnostics.Debug.Assert(m_ConverterType != null, "ConverterType is has not been set, ConverterType{x:Type converterType}");
try
{
string key = m_ConverterType.ToString();
if (m_Arguments != null)
{
List<string> args = new List<string>();
foreach (object obj in m_Arguments)
args.Add(obj.ToString());
key = String.Concat(key, "_", String.Join("|", args.ToArray()));
}
WeakReference wr = null;
if (s_WeakReferenceLookup.TryGetValue(key, out wr))
{
if (wr.IsAlive)
return wr.Target;
else
s_WeakReferenceLookup.Remove(key);
}
object converter = (m_Arguments == null) ? Activator.CreateInstance(m_ConverterType) : Activator.CreateInstance(m_ConverterType, m_Arguments);
s_WeakReferenceLookup.Add(key, new WeakReference(converter));
return converter;
}
catch(MissingMethodException)
{
// constructor for the converter does not exist!
throw;
}
}
}
What I do is:
<Window.Resources>
<ResourceDictionary>
<Converters:BooleanNotConverter x:Key="BooleanNotConverter"/>
</ResourceDictionary>
</Window.Resources>
And then in the control
<CheckBox IsChecked="{Binding Path=BoolProperty, Converter={StaticResource BooleanNotConverter} />