When presenting the user with a ComboBox with ObservableCollection<Type> as its ItemsSource, how can I instantiate a class in the property that SelectedItem is bound to?
The elements in the ElementList list in parentItem is either of a generic class type Element, or is of a type inheriting from Element (e.g. DigitalOutputButton or TrendGraph).
XAML:
<StackPanel Orientation="Horizontal">
<TextBlock Width="100" Text="Element Type:" />
<ComboBox Width="300" ItemsSource="{Binding Path=Element.ElementTypeList}"
SelectedItem="{Binding Path=Element.SelectedElementType}" />
</StackPanel>
C# code:
private static ObservableCollection<Type> _elementTypeList
= new ObservableCollection<Type> { typeof(Element), typeof(DigitalOutputButton), typeof(TrendGraph) };
public static ObservableCollection<Type> ElementTypeList { get { return _elementTypeList; } }
public Type SelectedElementType {
get { return GetType(); }
set {
if (value != GetType()) {
var parentItem = Controller.ConfigurationHandler.FindParentItem(this);
var currentItemIndex = parentItem.ElementList.IndexOf(this);
parentItem.ElementList[currentItemIndex] = new typeof(value)();
}
}
}
The above set code will not build. But is it possible to achieve this behavior in another way?
EDIT: Ok, this way works:
public Type SelectedElementType {
get { return GetType(); }
set {
if (value != GetType()) {
var parentItem = Controller.ConfigurationHandler.FindParentItem(this);
var currentItemIndex = parentItem.ElementList.IndexOf(this);
if (value == typeof(Element)) {
parentItem.ElementList[currentItemIndex] = new Element();
}
else if (value == typeof(DigitalOutputButton)) {
parentItem.ElementList[currentItemIndex] = new DigitalOutputButton();
}
else if (value == typeof(TrendGraph)) {
parentItem.ElementList[currentItemIndex] = new TrendGraph();
}
}
}
}
But it would be great it there was a way to do this that were a little more "maintenance free" (no need to edit when adding a new element type).
var instance = Activator.CreateInstance(value, Controller, ParentElementGroup, ItemLabel);
parentItem.ElementList[currentItemIndex] = (TYPE)instance;
The only missing link is the type of your collection so it can be cast. But that should be compile time knowledge.
Related
I have a ComboBox listing an Enum.
enum StatusEnum {
Open = 1, Closed = 2, InProgress = 3
}
<ComboBox ItemsSource="{Binding StatusList}"
SelectedItem="{Binding SelectedStatus}" />
I want to display localized names for the enum values in English
Open
Closed
In Progress
but also in German (and other languages in the future)
Offen
Geschlossen
In Arbeit
In my ViewModel using
public IEnumerable<StatusEnum> StatusList
{
get
{
return Enum.GetValues(typeof(StatusEnum)).Cast<StatusEnum>();
}
}
only gets me the names of the enum in the code and not the translated ones.
I have general localization in place and can access them using i.e.
Resources.Strings.InProgress
which gets me the translation for the current language.
How can I bind the localization automatically?
It's an example of the simple Enum to translated string converter.
public sealed class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{ return null; }
return Resources.ResourceManager.GetString(value.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string str = (string)value;
foreach (object enumValue in Enum.GetValues(targetType))
{
if (str == Resources.ResourceManager.GetString(enumValue.ToString()))
{ return enumValue; }
}
throw new ArgumentException(null, "value");
}
}
Also you need a MarkupExtension which will provide values:
public sealed class EnumerateExtension : MarkupExtension
{
public Type Type { get; set; }
public EnumerateExtension(Type type)
{
this.Type = type;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
string[] names = Enum.GetNames(Type);
string[] values = new string[names.Length];
for (int i = 0; i < names.Length; i++)
{ values[i] = Resources.ResourceManager.GetString(names[i]); }
return values;
}
}
Usage:
<ComboBox ItemsSource="{local:Enumerate {x:Type local:StatusEnum}}"
SelectedItem="{Binding SelectedStatus, Converter={StaticResource EnumToStringConverter}}" />
EDIT: You can make a more complex value converter and markup extension. The EnumToStringConverter can use DescriptionAttribute's to get the translated strings. And the EnumerateExtension can use TypeConverter.GetStandardValues() and a converter. This allows to get standard values of the specified type (not only Enums) and convert them to strings or something another depending on the converter.
Example:
<ComboBox ItemsSource="{local:Enumerate {x:Type sg:CultureInfo}, Converter={StaticResource CultureToNameConverter}}"
SelectedItem="{Binding SelectedCulture, Converter={StaticResource CultureToNameConverter}}" />
EDIT: The more complex solution described above is published on GitHub now.
You can do using a Attribute for the enum and writing an extension method for the enum. Refer the below code.
<ComboBox Width="200" Height="25" ItemsSource="{Binding ComboSource}"
DisplayMemberPath="Value"
SelectedValuePath="Key"/>
public class MainViewModel
{
public List<KeyValuePair<Status, string>> ComboSource { get; set; }
public MainViewModel()
{
ComboSource = new List<KeyValuePair<Status, string>>();
Status st=Status.Open;
ComboSource = re.GetValuesForComboBox<Status>();
}
}
public enum Status
{
[Description("Open")]
Open,
[Description("Closed")]
Closed,
[Description("InProgress")]
InProgress
}
public static class ExtensionMethods
{
public static List<KeyValuePair<T, string>> GetValuesForComboBox<T>(this Enum theEnum)
{
List<KeyValuePair<T, string>> _comboBoxItemSource = null;
if (_comboBoxItemSource == null)
{
_comboBoxItemSource = new List<KeyValuePair<T, string>>();
foreach (T level in Enum.GetValues(typeof(T)))
{
string Description = string.Empty;
FieldInfo fieldInfo = level.GetType().GetField(level.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
{
Description = GetDataFromResourceFile(attributes.FirstOrDefault().Description);
}
KeyValuePair<T, string> TypeKeyValue = new KeyValuePair<T, string>(level, Description);
_comboBoxItemSource.Add(TypeKeyValue);
}
}
return _comboBoxItemSource;
}
public static string GetDataFromResourceFile(string key)
{
//Do you logic to get from resource file based on key for a language.
}
}
I have already posted a similar thing in SO Is it possible to databind to a Enum, and show user-friendly values?
You can't, out of the box.
But you can create an ObservableList<KeyValuePair<StatusEnum, string>> property and fill it with your enum/localized text and then bind it to your ComboBox.
As for the string itself:
var localizedText = (string)Application.Current.FindResource("YourEnumStringName");
Getting the Enum string representation with Enum.GetName/Enum.GetNames methods.
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 was looking for a way to add a custom property to a xaml control. I found this solution: Adding custom attributes to an element in XAML?
Class1.cs:
public static Class1
{
public static readonly DependencyProperty IsTestProperty =
DependencyProperty.RegisterAttached("IsTest",
typeof(bool),
typeof(Class1),
new FrameworkPropertyMetadata(false));
public static bool GetIsTestProperty(UIElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(IsTestProperty);
}
public static void SetIsTestProperty(UIElement element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(IsTestProperty, value);
}
}
UserControl.xaml
<StackPanel x:Name="Container">
<ComboBox x:Name="cfg_Test" local:Class1.IsTest="True" />
<ComboBox x:Name="cfg_Test" local:Class1.IsTest="False" />
...
...
Now is my question, how can i get the value of the property?
Now I want to read the value of all elements, in the StackPanel.
// get all elementes in the stackpanel
foreach (FrameworkElement child in
Helpers.FindVisualChildren<FrameworkElement>(control, true))
{
if(child.GetValue(Class1.IsTest))
{
//
}
}
but child.GetValue(Class1.IsTest) is always false... what's wrong?
First of all, it seems, that your code is full of errors, so iam not sure, if you did not copy it properly, or whats the reason for that.
So whats wrong in your example?
The getter and setter of your DependencyProperty are wrongly created. (There should be no "Property" attached to the names.) It chould be:
public static bool GetIsTest(UIElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(IsTestProperty);
}
public static void SetIsTest(UIElement element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(IsTestProperty, value);
}
Secondly, both of your child controls of the StackPanel share the same name, thats not possible too.
And third, you getting the property in your foreach-statement wrongly. This should be:
if ((bool)child.GetValue(Class1.IsTestProperty))
{
// ...
}
Be sure, that your Helpers.FindVisualChildren is working correctly. You could use the following instead:
foreach (FrameworkElement child in Container.Children)
{
// ...
}
Hope this helps.
I have flag enum say this -
[Flags]
public enum Department
{
None = 0,
A = 1,
B = 2,
C = 4,
D = 8
}
I want to show values of this enum on view. I thought of creating a listbox and binding its source to the collection of this enum List<Department> Departments.
All works so good until i thought of having a checkbox which binds to a property on my Viewmodel -
public Department SelectedDepartments { get; set; }
The solution here http://compilewith.net/2008/12/wpf-flagsenumvalueconverter.html provides elegant solution for binding enum values to checkboxes but its have one limitation of creating checkboxes equal to the number of enum values in list.
But, in my case i can't afford of having so many checkboxes lying on my UI since my Enum contains 20 values (so that means having 20 checkboxes on UI).
I tried using MultiBindingConverter but that fails in ConvertBack Method.
I want to bind the state of checkboxes with property SelectedDepartments. Say if property value is "A | B" then A and B checkbox should be checked whereas C and D should remain unchecked.
I don't think there's a way of doing this without using some code-behind.
I took the sample solution you linked to above, removed all of the CheckBoxes from MainWindow.xaml, added the following method to MainWindow.xaml.cs and called it from the MainWindow constructor:
private void AddCheckBoxes()
{
var converter = new FlagsEnumValueConverter();
foreach (Department dept in Enum.GetValues(typeof(Department)))
{
if (dept != Department.None)
{
var binding = new Binding()
{
Path = new PropertyPath("Department"),
Converter = converter,
ConverterParameter = dept
};
var checkBox = new CheckBox() { Content = dept.ToString() };
checkBox.SetBinding(CheckBox.IsCheckedProperty, binding);
DepartmentsPanel.Children.Add(checkBox);
}
}
}
This method does the work of creating all of the checkboxes, one for each named enum constant apart from None. I could then add further departments to the Department enum, rerun the solution and see additional checkboxes for the newly-added departments.
There were a few further minor changes that I had to make to this solution to get it working completely. You may or may not need to make these changes to your code. Firstly, I made the DataObject class implement INotifyPropertyChanged. Secondly, I rewrote the XAML in MainWindow.xaml as follows:
<StackPanel>
<StackPanel x:Name="DepartmentsPanel" />
<TextBlock Margin="5,20,0,0">
<TextBlock Text="Raw Value:" FontWeight="Bold" />
<TextBlock Text="{Binding Department}" />
</TextBlock>
</StackPanel>
(Basically, I wrapped the existing DepartmentsPanel in another StackPanel and moved the 'Raw Value' display into this outer StackPanel.) Finally, I set the DataContext of the whole MainWindow, rather than the DataContext of the DepartmentsPanel, to the DataObject created. This step was necessary to make the 'Raw Value' display work.
I have created an IValueConverter that supports binding to the enum directly without codebehind or helper classes. It has only two restrictions:
One converter instance has to be used for each source property. If the model contains more properties of the same enum type, each of them needs to use a separate converter instance. This can be done by instantiating the converters in the XAML.
When the enum type is a flags enum it has to be an Integer.
The solution is based on the empirical fact that there is always a Convert first before a ConvertBack. And there is always a Convert if a ConvertBack has changed the value. This one only works when the INotifyPropertyChanged is properly implemented on the model. So between the two calls the last known value can be stored in the converter and used in the ConvertBack method.
The converter instance should get the Type of the enum and whether it is a Flags enum or not.
<EnumToCheckedConverter x:Key="InstanceName" Type="{x:Type MyEnum}" Flags="True" />
Then the checkboxes can be bound using this converter.
<CheckBox Content="ValueText" IsChecked="{Binding Source, Converter={StaticResource InstanceName}, ConverterParameter=Value}"/>
Radio buttons can be bound by the same mechanism using an instance with Flags="False"
The source code of the converter
public class EnumToCheckedConverter : IValueConverter
{
public Type Type { get; set; }
public int? LastValue { get; private set; }
public bool Flags { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.GetType() == Type)
{
try
{
var parameterValue = Enum.Parse(Type, parameter as string);
if (Flags == true)
{
var intParameter = (int)parameterValue;
var intValue = (int)value;
LastValue = intValue;
return (intValue & intParameter) == intParameter;
}
else
{
return Equals(parameterValue, value);
}
}
catch (ArgumentNullException)
{
return false;
}
catch (ArgumentException)
{
throw new NotSupportedException();
}
}
else if (value == null)
{
return false;
}
throw new NotSupportedException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value is bool check)
{
if (check)
{
try
{
if (Flags == true && LastValue.HasValue)
{
var parameterValue = Enum.Parse(Type, parameter as string);
var intParameter = (int)parameterValue;
return Enum.ToObject(Type, LastValue | intParameter);
}
else
{
return Enum.Parse(Type, parameter as string);
}
}
catch (ArgumentNullException)
{
return Binding.DoNothing;
}
catch (ArgumentException)
{
return Binding.DoNothing;
}
}
else
{
try
{
if (Flags == true && LastValue.HasValue)
{
var parameterValue = Enum.Parse(Type, parameter as string);
var intParameter = (int)parameterValue;
return Enum.ToObject(Type, LastValue ^ intParameter);
}
else
{
return Binding.DoNothing;
}
}
catch (ArgumentNullException)
{
return Binding.DoNothing;
}
catch (ArgumentException)
{
return Binding.DoNothing;
}
}
}
throw new NotSupportedException();
}
}
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}"/>