Conversion error in a property in PropertyGrid - winforms

I programmed a C# Winforms application. One of the forms allow to configure something, by mean of a PropertyGrid control. I have programmed TypeConverters for a property that is an object that works perfectly, but a little detail.
The property shows a stardard value collection, whose elements are of type Simbologia. The target property is of type CodigoBarra.
The process is as follows: when the first element in the values collection is selected, the property assigns null and no object properties are displayed. When a different value is selected in the collection, the CodigoBarra properties expand, allowing to change the object properties individually.
As I told, all that process work, however, the problem occurs when I set the collection as exclusive. When I press a key, the system is not able to convert from Simbologia to CodigoBarra.
I have tried with ConvertFrom methods, and even, I added an implicit cast to Simbologia class, but it did not help.
The object I am configuring with the Propertygrid is called Equipo, and this is the definition:
public class Equipo : IConfiguracion
{
[Category("Impresora"),
DefaultValue(null),
PropertyOrder(16),
TypeConverter(typeof(PropertyGrid.BarCodeConverter)),
DisplayName("Código de Barras"),
Description("Definición del código de barra cuando se imprime el ticket. El código aparecerá a continuación del texto.")]
public CodigoBarra CodigoBarra { get; set; }
public Equipo()
{
this.NombreBiometrico = this.NombreImpresora = String.Empty;
this.PuertoBiometrico = 4370;
this.IpImpresora = "0.0.0.0";
this.PuertoImpresora = 0;
this.AutoCorte = true;
this.ImprimeTicket = true;
}
public Equipo(string ipBiometrico)
: this()
{
this.ID = 0;
this.IpBiometrico = ipBiometrico;
this.Nuevo = true;
}
public Equipo(string nombreBiometrico, string ipBiometrico)
: this()
{
this.ID = 0;
this.NombreBiometrico = nombreBiometrico;
this.IpBiometrico = ipBiometrico;
this.Nuevo = true;
}
public Equipo(int id, string ipBiometrico)
: this()
{
this.ID = id;
this.IpBiometrico = ipBiometrico;
this.Nuevo = id == 0;
}
public Equipo(int id, string nombreBiometrico, string ipBiometrico)
: this()
{
this.ID = id;
this.NombreBiometrico = nombreBiometrico;
this.IpBiometrico = ipBiometrico;
this.Nuevo = id == 0;
}
}
I have left only CodigoBarra property.
BarCodeConverter is as follows:
public class BarCodeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || sourceType == typeof(MonitorView.Configuracion.Simbologia) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string stringValue = value as string;
object result = null;
if (!string.IsNullOrEmpty(stringValue))
{
var valor = new Configuracion.Simbologia(stringValue);
if (valor.Codigo != InDriver.BarCodeInfo.SymbologyDef.None)
result = new MonitorView.Configuracion.CodigoBarra(valor);
}
return result;
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
MonitorView.Configuracion.CodigoBarra codigoBarra = value as MonitorView.Configuracion.CodigoBarra;
object result = null;
if (codigoBarra == null)
{
if (value != null)
{
Configuracion.Simbologia simbologia = value as Configuracion.Simbologia;
if (destinationType == typeof(string) && !Configuracion.Simbologia.IsNone(simbologia))
result = simbologia.ToString();
}
}
else
{
if (destinationType == typeof(string) && !Configuracion.Simbologia.IsNone(codigoBarra.Symbology))
result = codigoBarra.ToString();
}
if (String.IsNullOrEmpty((string)result))
result = "[ ninguno ]";
return result ?? base.ConvertTo(context, culture, value, destinationType);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(Configuracion.Simbologia.GetValues());
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
return TypeDescriptor.GetProperties(value, attributes);
}
}
As you see in GetStandardValues method, the collection is built with Configuracion.Simbologia.GetValues() call, which returns an array of Simbologia objects.
It is obvious that the mismatch will be produced but, why the error is produced only when I press a key? When I select the Simbologia object using the dropdownlist, it works.
Finally, this is the implicit cast I implemented in Simbologia class:
public static implicit operator CodigoBarra(Simbologia simbologia)
{
return new CodigoBarra(simbologia);
}
How can I solve it?
Any help will be greatly appreciated.
Thanks
Jaime

Finally, I have solved it using this type converter:
public class BarCodeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || sourceType == typeof(MonitorView.Configuracion.Simbologia) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string stringValue = value as string;
object result = null;
if (!string.IsNullOrEmpty(stringValue))
{
var valor = new Configuracion.Simbologia(stringValue);
if (valor.Codigo != InDriver.BarCodeInfo.SymbologyDef.None)
result = new MonitorView.Configuracion.CodigoBarra(valor);
}
return result;
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
MonitorView.Configuracion.CodigoBarra codigoBarra = value as MonitorView.Configuracion.CodigoBarra;
object result = null;
if (codigoBarra == null)
{
if (value != null)
{
Configuracion.Simbologia simbologia = value as Configuracion.Simbologia;
if (destinationType == typeof(string) && !Configuracion.Simbologia.IsNone(simbologia))
result = simbologia.ToString();
}
}
else
{
if (destinationType == typeof(string) && !Configuracion.Simbologia.IsNone(codigoBarra.Symbology))
result = codigoBarra.ToString();
}
if (String.IsNullOrEmpty((string)result))
result = "[ ninguno ]";
return result ?? base.ConvertTo(context, culture, value, destinationType);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(Configuracion.Simbologia.GetValues());
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
return TypeDescriptor.GetProperties(value, attributes);
}
}
This is Symbologia class:
public class Simbologia
{
public InDriver.BarCodeInfo.SymbologyDef Codigo { get; set; }
public Simbologia(InDriver.BarCodeInfo.SymbologyDef codigo)
{
this.Codigo = codigo;
}
public Simbologia(string nombreCodigo)
{
this.Codigo = Enum.GetValues(typeof(InDriver.BarCodeInfo.SymbologyDef)).Cast<InDriver.BarCodeInfo.SymbologyDef>().Where(s => s.ToString() == nombreCodigo).FirstOrDefault();
}
public static implicit operator CodigoBarra(Simbologia simbologia)
{
return new CodigoBarra(simbologia);
}
public override string ToString()
{
return this.Codigo.ToString();
}
public static Array GetValues(params object[] excludes)
{
var valores = Enum.GetValues(typeof(InDriver.BarCodeInfo.SymbologyDef)).Cast<InDriver.BarCodeInfo.SymbologyDef>();
List<Simbologia> simbologias = new List<Simbologia>(valores.Count());
foreach (var valor in valores)
if (excludes.Count() == 0 || excludes.FirstOrDefault(e => e.ToString() == valor.ToString()) == null)
simbologias.Add(new Simbologia(valor));
return simbologias.ToArray();
}
public static bool IsNone(Simbologia simbologia)
{
return simbologia == null || simbologia.Codigo == InDriver.BarCodeInfo.SymbologyDef.None;
}
And finally, this is CodigoBarra class:
[TypeConverter(typeof(PropertySorter))]
public class CodigoBarra
{
[DefaultValue(70),
PropertyOrder(1),
TypeConverter(typeof(StringConverter)),
DisplayName("Alto"),
Description("Alto del código de barras, en puntos (depende de la impresora).")]
public byte Height { get; set; }
[DefaultValue(2),
PropertyOrder(2),
TypeConverter(typeof(StringConverter)),
DisplayName("Ancho"),
Description("Ancho del código de barras, en puntos (depende de la impresora).")]
public byte Width { get; set; }
[DefaultValue(0),
PropertyOrder(3),
TypeConverter(typeof(StringConverter)),
DisplayName("Alineamiento"),
Description("Distancia desde el borde izquierdo del código de barras, en puntos (depende de la impresora).")]
public byte Alignment { get; set; }
[DefaultValue(InDriver.BarCodeInfo.TextPositionDef.None),
PropertyOrder(4),
DisplayName("Posición del Texto"),
Description("Posición del texto con respecto al código de barras.")]
public InDriver.BarCodeInfo.TextPositionDef TextPosition { get; set; }
[DefaultValue(null),
PropertyOrder(5),
DisplayName("Simbología"),
TypeConverter(typeof(PropertyGrid.SymbologyConverter)),
Description("Tipo de simbología con la cual se codifica el código de barra. No todas las impresoras soportan las mismas simbologías.")]
public Simbologia Symbology { get; set; }
public CodigoBarra()
{
this.Symbology = null;
this.Alignment = 0;
this.Height = 70;
this.Width = 2;
this.TextPosition = InDriver.BarCodeInfo.TextPositionDef.None;
}
public CodigoBarra(Simbologia symbology, byte alignment, byte height, byte width, InDriver.BarCodeInfo.TextPositionDef textPosition)
{
this.Symbology = symbology;
this.Alignment = alignment;
this.Height = height;
this.Width = width;
this.TextPosition = textPosition;
}
public CodigoBarra(Simbologia symbology)
: this()
{
this.Symbology = symbology;
}
public CodigoBarra(InDriver.BarCodeInfo info)
: this()
{
SetBarCodeInfo(info);
}
public CodigoBarra(string info)
: this()
{
SetBarCodeInfo(InDriver.BarCodeInfo.GetInformation(info));
}
public InDriver.BarCodeInfo GetBarCodeInfo()
{
InDriver.BarCodeInfo info = new InDriver.BarCodeInfo();
info.Symbology = this.Symbology == null ? InDriver.BarCodeInfo.SymbologyDef.None : this.Symbology.Codigo;
info.Height = this.Height;
info.Width = this.Width;
info.Alignment = this.Alignment;
info.TextPosition = this.TextPosition;
return info;
}
public void SetBarCodeInfo(InDriver.BarCodeInfo info)
{
if (info != null)
{
this.Symbology = new Simbologia(info.Symbology);
this.Height = info.Height;
this.Width = info.Width;
this.Alignment = info.Alignment;
this.TextPosition = info.TextPosition;
}
}
public override string ToString()
{
return this.Symbology.ToString();
}
}
Regards,
Jaime

Related

Unsubscribe from SceneView draw calls when Edited Unity PropertyDrawer Array element got deleted

I'm making an editor in PropertyDrawer using SceneView.duringSceneGui. So it involves subscribing to SceneView.duringSceneGui when a property needs to draw stuff in SceneView and unsubscribing when it's gone. However I have no idea how to know if edited array element was removed from an array. It still exists in the memory and SceneView.duringSceneGui subscribed method is still there. I need to know when to stop editing and unsubscribe from it.
I guess I need to implement some context object, to store property value, edited object, PropertyDrawer and that subscription method should be there, to be able to unsubscribe exactly that editor... Although there may be only one editor running at once.
Does anybody found that out? Couldn't find anything with PropertyDrawers and array elements being deleted or removed.
TL.DR. Does Unity has an event to tell that PropertyDrawer's array element was removed or is there a simple or neat way to figure this out?
So, while making an example, I've solved my problem by getting property value every SceneView draw call and on catching an exception or if that value isn't edited stopped editor. I've added [NonSerialized] to _IsInEditMode to fix a new issue that I caught at the last moment, so that one is crucial.
Not sure if it's the best way to do that. If anybody will ever need to make a SceneView editor for some class, here's the example that works on arrays and lists also. Just separate it into 3 files and put them in respective folders, like Editor/ for MyClassDrawer.
using InspectorSerializedUtility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MyScript : MonoBehaviour
{
public MyClass field;
public List<MyClass> list = new List<MyClass>();
}
[Serializable]
public class MyClass
{
#if UNITY_EDITOR
[NonSerialized]
public bool _IsInEditMode;
#endif
public Vector3 position;
public void Reset()
{
position = Vector3.zero;
#if UNITY_EDITOR
_IsInEditMode = false;
#endif
}
}
[CustomPropertyDrawer(typeof(MyClass))]
public class MyClassDrawer : PropertyDrawer
{
public MyClass value;
public Transform targetTransform;
private Tool internalTool;
bool editorStarted {
get => value?._IsInEditMode ?? false;
set {
if (this.value != null)
this.value._IsInEditMode = value;
}
}
private SerializedProperty currentProperty;
private SerializedProperty drawerProperty;
private static MyClassDrawer currentlyEditedDrawer;
string editorButtonText(bool isInEditMode) => isInEditMode ? "Stop Editing" : "Start Editing";
Color editorButtonColor(bool isInEditMode) => isInEditMode ? Color.red + Color.white / 2f : Color.white;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
//Debug.Log("OnGUI");
drawerProperty = property;
targetTransform = ((Component)property.serializedObject.targetObject).transform;
var val = property.GetValue<MyClass>();
GUI.color = editorButtonColor(val._IsInEditMode);
var toggle = GUI.Toggle(position, val._IsInEditMode, editorButtonText(val._IsInEditMode), "Button");
if (toggle != val._IsInEditMode)
{
if (toggle && currentlyEditedDrawer != null && currentlyEditedDrawer.editorStarted)
currentlyEditedDrawer.StopEditor();
value = val;
currentlyEditedDrawer = this;
if (toggle)
StartEditor();
else
StopEditor();
}
GUI.color = Color.white;
}
public void OnDrawScene(SceneView sv) => OnDrawScene();
public void OnDrawScene()
{
//Debug.Log("OnDrawScene");
MyClass value = null;
try
{
value = currentProperty.GetValue<MyClass>();
if (!value._IsInEditMode)
{
StopEditor();
return;
}
} catch
{
StopEditor();
return;
}
var m = Handles.matrix;
Handles.matrix = targetTransform.localToWorldMatrix;
if (Tools.current == Tool.Move)
{
internalTool = Tool.Move;
Tools.current = Tool.None;
}
if (internalTool == Tool.Move)
{
var pos = Handles.PositionHandle(value.position, Quaternion.identity);
if (value.position != pos)
{
Undo.RecordObject(targetTransform, "position changed");
value.position = pos;
}
}
Handles.matrix = m;
}
public void StartEditor()
{
currentProperty = drawerProperty;
editorStarted = true;
Debug.Log("StartEditor");
Subscribe();
CallAllSceneViewRepaint();
}
private void Subscribe()
{
Unsubscribe();
SceneView.duringSceneGui += OnDrawScene;
Selection.selectionChanged += StopEditor;
EditorSceneManager.sceneClosed += StopEditor;
AssemblyReloadEvents.beforeAssemblyReload += StopEditor;
}
public void StopEditor(Scene s) => StopEditor();
public void StopEditor()
{
Tools.current = internalTool;
editorStarted = false;
Unsubscribe();
currentProperty = null;
CallAllSceneViewRepaint();
}
private void Unsubscribe()
{
SceneView.duringSceneGui -= OnDrawScene;
Selection.selectionChanged -= StopEditor;
EditorSceneManager.sceneClosed -= StopEditor;
AssemblyReloadEvents.beforeAssemblyReload -= StopEditor;
}
private void CallAllSceneViewRepaint()
{
foreach (SceneView sv in SceneView.sceneViews)
sv.Repaint();
}
}
namespace InspectorSerializedUtility
{
/// <summary>
/// https://gist.github.com/douduck08/6d3e323b538a741466de00c30aa4b61f
/// </summary>
public static class InspectorSeriallizedUtils
{
public static T GetValue<T>(this SerializedProperty property) where T : class
{
try
{
if (property.serializedObject.targetObject == null) return null;
}
catch
{
return null;
}
object obj = property.serializedObject.targetObject;
string path = property.propertyPath.Replace(".Array.data", "");
string[] fieldStructure = path.Split('.');
Regex rgx = new Regex(#"\[\d+\]");
for (int i = 0; i < fieldStructure.Length; i++)
{
if (fieldStructure[i].Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldStructure[i].Where(c => char.IsDigit(c)).ToArray()));
obj = GetFieldValueWithIndex(rgx.Replace(fieldStructure[i], ""), obj, index);
}
else
{
obj = GetFieldValue(fieldStructure[i], obj);
}
}
return (T)obj;
}
public static bool SetValue<T>(this SerializedProperty property, T value) where T : class
{
object obj = property.serializedObject.targetObject;
string path = property.propertyPath.Replace(".Array.data", "");
string[] fieldStructure = path.Split('.');
Regex rgx = new Regex(#"\[\d+\]");
for (int i = 0; i < fieldStructure.Length - 1; i++)
{
if (fieldStructure[i].Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldStructure[i].Where(c => char.IsDigit(c)).ToArray()));
obj = GetFieldValueWithIndex(rgx.Replace(fieldStructure[i], ""), obj, index);
}
else
{
obj = GetFieldValue(fieldStructure[i], obj);
}
}
string fieldName = fieldStructure.Last();
if (fieldName.Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldName.Where(c => char.IsDigit(c)).ToArray()));
return SetFieldValueWithIndex(rgx.Replace(fieldName, ""), obj, index, value);
}
else
{
Debug.Log(value);
return SetFieldValue(fieldName, obj, value);
}
}
private static object GetFieldValue(string fieldName, object obj, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
return field.GetValue(obj);
}
return default(object);
}
private static object GetFieldValueWithIndex(string fieldName, object obj, int index, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
object list = field.GetValue(obj);
if (list.GetType().IsArray)
{
return ((object[])list)[index];
}
else if (list is IEnumerable)
{
return ((IList)list)[index];
}
}
return default(object);
}
public static bool SetFieldValue(string fieldName, object obj, object value, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
field.SetValue(obj, value);
return true;
}
return false;
}
public static bool SetFieldValueWithIndex(string fieldName, object obj, int index, object value, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
object list = field.GetValue(obj);
if (list.GetType().IsArray)
{
((object[])list)[index] = value;
return true;
}
else if (value is IEnumerable)
{
((IList)list)[index] = value;
return true;
}
}
return false;
}
}
}

WPF Validation with Data Annotations

Hi am struggling with validation with data annotations in WPF
I have a class in a separate project because we are reusing the class across multiple projects.Several of the fields have data annotations for validation.
the validation class combines the errors returned from data annotations with some custom business rules which are different per client
internal class Validation<T> : ValidationRule where T : class, ICore
{
IUnitofWork unitofWork;
List<Func<T, bool>> CompiledRules = new List<Func<T, bool>>();
List<Rule<T>> Rules = new List<Rule<T>>();
Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
List<Func<Object, bool>> FieldRules = new List<Func<object, bool>>();
public Validation()
{
unitofWork = new UnitOfWork();
CompileRules();
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
return IsValid((T)value);
}
public ValidationResult Validate(object value,string PropertyName ,CultureInfo cultureInfo)
{
return IsValid((T)value,PropertyName);
}
void CompileRules()
{
Type t = typeof(T);
List<BusinessRule> rules = unitofWork.Repository<BusinessRule>().GetAll(b => b.Name == t.Name && b.Enabled == true);
foreach (BusinessRule b in rules)
{
Func<T, bool> CompiledRule = CompileRule(b);
CompiledRules.Add(CompiledRule);
Rule<T> rule = new Rule<T>();
rule.CompiledRule = CompiledRule;
rule.ErrorMessage = b.MessageTemplate;
rule.FieldName = b.Field;
Rules.Add(rule);
}
}
ValidationResult IsValid(T Value)
{
bool valid = true;
_errors.Clear();
if (CompiledRules.Count > 0 || Value.Errors.Count > 0)
{
//valid = CompiledRules.All(rule => rule(Value));
foreach (Rule<T> r in Rules)
{
bool isValid = r.CompiledRule(Value);
r.PassedRule = isValid;
string field = r.FieldName;
if (!isValid )
{
valid = false;
string ErrorMessage = string.Format("{0} {1}", r.FieldName, r.ErrorMessage);
if (_errors.ContainsKey(field))
_errors[field].Add(ErrorMessage);
else
{
List<string> el = new List<string>();
el.Add(ErrorMessage);
_errors.Add(field, el);
}
}
}
}
ValidateAnnotations(Value,ref valid);
return new ValidationResult(valid, _errors);
}
void ValidateAnnotations(object Value,ref bool Valid)
{
DataAnnotationValidator annotationValidator = new DataAnnotationValidator();
List< System.ComponentModel.DataAnnotations.ValidationResult> results = annotationValidator.ValidateObject(Value);
if (results.Count > 0)
{
Valid = false;
foreach (System.ComponentModel.DataAnnotations.ValidationResult r in results)
{
if (_errors.ContainsKey(r.MemberNames.First()))
{
_errors[r.MemberNames.First()].Add(r.ErrorMessage);
}
else
{
List<string> propErrors = new List<string>();
propErrors.Add(r.ErrorMessage);
_errors.Add(r.MemberNames.First(), propErrors);
}
}
}
}
void ValidateAnnotations(object Value,string PropertyName, ref bool Valid)
{
DataAnnotationValidator annotationValidator = new DataAnnotationValidator();
List<System.ComponentModel.DataAnnotations.ValidationResult> results = annotationValidator.ValidateObject(Value, PropertyName);
if (results.Count > 0)
{
Valid = false;
foreach (System.ComponentModel.DataAnnotations.ValidationResult r in results)
{
if (_errors.ContainsKey(r.MemberNames.First()))
{
_errors[r.MemberNames.First()].Add(r.ErrorMessage);
}
else
{
List<string> propErrors = new List<string>();
propErrors.Add(r.ErrorMessage);
_errors.Add(r.MemberNames.First(), propErrors);
}
}
}
}
ValidationResult IsValid(T Value, string PropertyName)
{
_errors.Remove(PropertyName);
bool valid = true;
if (CompiledRules.Count > 0)
{
//valid = CompiledRules.All(rule => rule(Value));
foreach (Rule<T> r in Rules.Where(b=>b.FieldName == PropertyName))
{
bool isValid = r.CompiledRule(Value);
r.PassedRule = isValid;
string field = r.FieldName;
// string field = "SelectedRow." + r.FieldName;
if (!isValid)
{
valid = false;
string ErrorMessage = string.Format("{0} {1}", r.FieldName, r.ErrorMessage);
if (_errors.ContainsKey(field))
_errors[field].Add(ErrorMessage);
else
{
List<string> el = new List<string>();
el.Add(ErrorMessage);
_errors.Add(field, el);
}
}
}
}
ValidateAnnotations(Value,PropertyName, ref valid);
return new ValidationResult(valid, _errors);
}
public Func<T, bool> CompileRule(BusinessRule r)
{
var paramT = Expression.Parameter(typeof(T));
Expression expression = BuildExpr(r, paramT);
return Expression.Lambda<Func<T, bool>>(expression, paramT).Compile();
}
static Expression BuildExpr(BusinessRule r, ParameterExpression param)
{
var left = MemberExpression.Property(param, r.Field);
var tProp = typeof(T).GetProperty(r.Field).PropertyType;
ExpressionType tBinary;
// is the operator a known .NET operator?
if (ExpressionType.TryParse(r.Operator, out tBinary))
{
var right = Expression.Constant(Convert.ChangeType(r.CompareValue, tProp));
// use a binary operation, e.g. 'Equal' -> 'u.Age == 15'
return Expression.MakeBinary(tBinary, left, right);
}
else
{
var method = tProp.GetMethod(r.Operator);
var tParam = method.GetParameters()[0].ParameterType;
var right = Expression.Constant(Convert.ChangeType(r.CompareValue, tParam));
// use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)'
return Expression.Call(left, method, right);
}
}
}
internal class Rule<T> where T : class, ICore
{
public string FieldName
{ get;set; }
public Func<T, bool> CompiledRule
{ get; set; }
public Func<object,bool> RevisedRule
{ get; set; }
public string ErrorMessage
{ get; set; }
public bool PassedRule
{ get; set; }
}
internal class Error
{
public string ErrorMessage { get; set; }
public string FieldName { get; set; }
}
The is working as the _errors list has the field names and any errors associated with them.
once we have these we loop through and raise the onPropertyErrorsChangedEvent
internal void SetErrorDetails(ValidationResult Result)
{
propErrors = (Dictionary<string, List<string>>)Result.ErrorContent;
foreach (string key in propErrors.Keys)
{
OnPropertyErrorsChanged(key);
}
}
on my view the 2 fields are
<TextBox Canvas.Left="138"
Canvas.Top="75"
FontFamily="Verdana"
HorizontalAlignment="Left"
Height="20"
Text="{Binding OrganizationName,ValidatesOnDataErrors=True,NotifyOnValidationError=True,ValidatesOnNotifyDataErrors=True,ValidatesOnExceptions=True}" VerticalAlignment="Top" Width="137"
Validation.ErrorTemplate="{StaticResource ValidationTemplate }"
Style="{StaticResource TextErrorStyle}"/>
<TextBox Canvas.Left="138"
Canvas.Top="225"
FontFamily="Verdana"
HorizontalAlignment="Left"
Height="20"
Text="{Binding SelectedRow.Postcode,ValidatesOnDataErrors=True,ValidatesOnNotifyDataErrors=True,ValidatesOnExceptions=True}"
VerticalAlignment="Top"
Width="137"
Validation.ErrorTemplate="{StaticResource ValidationTemplate }"
Style="{StaticResource TextErrorStyle}" />
I am encountering 2 problems i am encountering
When bound directly to the selectedrow (Postcode) my data annotations appear when the form is loaded but when bound via a field on my view model (Organisation Name) they do not . We need to bind these to fields on the view model so that the business rules get run as part of validation.
Second problem if i save the form with an invalid entry for the organisation the save stops because it is invalid however i don't get an error notification even though there is an error for the property in the _errors.
I am not sure what i am doing wrong could someone point me in the right direction please?
[Edit]
We use a third party document service to create and show the view
void CreateDocument(object Arg)
{
string title = string.Empty;
if (Arg.ToString().ToLower() == "edit" && SelectedRow !=null)
{
if (SelectedRow.OrganizationName != null)
title = SelectedRow.OrganizationName;
}
else
{
SelectedRow = new Address();
title = "New Address";
}
AddressDetailVM detail = new AddressDetailVM(SelectedRow,this);
Document = iInternal.CreateDocument("AddressDetails",
detail,
title
);
detail.Document = Document;
// Document = iInternal.CreateDocument("AddressDetails", null, this, title);
Document.Show();
}

No Overload for method "GetValue" takes 1 arguments

I am trying to run a Devexpress Dxgrid sample program.
Which is here
The Vertical grid code is :
using DevExpress.Data;
using DevExpress.Xpf.Editors.Settings;
using DevExpress.Xpf.Grid;
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace dxExample.VGrid
{
public partial class VerticalGridControl : GridControl
{
INotifyCollectionChanged backItemsSourceEvents;
object InternalItemsSource
{
get { return base.ItemsSource; }
set { base.ItemsSource = value; }
}
GridColumnCollection InternalColumns
{
get { return base.Columns; }
}
public VerticalRowCollection Rows { get; set; }
public bool AutoPopulateRows
{
get { return (bool)GetValue(AutoPopulateRowsProperty); }
set { SetValue(AutoPopulateRowsProperty, value); }
}
public new object ItemsSource
{
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public VerticalGridControl()
{
InitializeComponent();
InitializeRowsCollection();
SubscribeItemsSourcePropertyChanged();
}
void InitializeRowsCollection()
{
Rows = new VerticalRowCollection();
Rows.CollectionChanged += OnRowsCollectionChanged;
}
void UpdateRowsCollection()
{
if (AutoPopulateRows)
{
PopulateRows();
}
}
void SubscribeItemsSourcePropertyChanged()
{
DependencyPropertyDescriptor itemsSourceDropertyDescriptor = DependencyPropertyDescriptor.FromProperty(VerticalGridControl.ItemsSourceProperty, typeof(VerticalGridControl));
itemsSourceDropertyDescriptor.AddValueChanged(this, new EventHandler(OnItemsSourcePropertyChanged));
}
void UpdateInternalColumns()
{
ICollection itemsSource = (ItemsSource as ICollection);
if (itemsSource == null)
{
Columns.Clear();
return;
}
Columns.BeginUpdate();
int columnsCount = itemsSource.Count;
if (InternalColumns.Count == columnsCount) return;
int delta = columnsCount - InternalColumns.Count;
if (columnsCount > InternalColumns.Count)
{
for (int i = InternalColumns.Count; i < columnsCount; i++)
{
InternalColumns.Add(new GridColumn() { FieldName = i.ToString(), UnboundType = UnboundColumnType.Object });
}
}
else
{
for (int i = InternalColumns.Count - 1; i >= columnsCount; i--)
{
InternalColumns.RemoveAt(i);
}
}
Columns.EndUpdate();
}
void UpdateItemsSourceEventsSubscription()
{
if (backItemsSourceEvents != null)
{
backItemsSourceEvents.CollectionChanged -= OnItemsSourceCollectionChanged;
}
if (!(ItemsSource is INotifyCollectionChanged)) return;
INotifyCollectionChanged itemsSourceEvents = (ItemsSource as INotifyCollectionChanged);
itemsSourceEvents.CollectionChanged += OnItemsSourceCollectionChanged;
backItemsSourceEvents = itemsSourceEvents;
}
void PopulateRows()
{
IEnumerable itemsSource = (ItemsSource as IEnumerable);
if (itemsSource == null) return;
IEnumerator itemsSourceEnumerator = itemsSource.GetEnumerator();
itemsSourceEnumerator.MoveNext();
object item = itemsSourceEnumerator.Current;
if (item == null) return;
PropertyInfo[] itemProps = item.GetType().GetProperties();
for (int i = 0; i < itemProps.Length; i++)
{
Rows.Add(VerticalRowData.FromPropertyInfo(itemProps[i]));
}
}
void OnRowsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
InternalItemsSource = Rows;
}
void OnItemsSourcePropertyChanged(object sender, EventArgs e)
{
UpdateInternalColumns();
UpdateRowsCollection();
UpdateItemsSourceEventsSubscription();
}
void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Remove)
{
UpdateInternalColumns();
}
}
void OnProcessUnboundColumnData(object sender, GridColumnDataEventArgs e)
{
IList itemsSource = (ItemsSource as IList);
if (itemsSource == null) return;
VerticalRowData row = Rows[e.ListSourceRowIndex];
object item = itemsSource[Convert.ToInt32(e.Column.FieldName)];
PropertyInfo itemProperty = item.GetType().GetProperty(row.RowName);
if (itemProperty == null) return;
if (e.IsGetData)
{
e.Value = itemProperty.GetValue(item);
}
if (e.IsSetData)
{
itemProperty.SetValue(item, e.Value);
}
}
public static readonly DependencyProperty AutoPopulateRowsProperty = DependencyProperty.Register("AutoPopulateRows", typeof(bool), typeof(VerticalGridControl), new PropertyMetadata(false));
public static new readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(VerticalGridControl), new PropertyMetadata(null));
}
public class BottomIndicatorRowVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values.Count() < 2)
return Visibility.Collapsed;
if (!((values[0] is int) && (values[1] is int)))
return Visibility.Collapsed;
return ((int)values[0]) > ((int)values[1]) ? Visibility.Visible : Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class DefaultCellTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
VerticalRowData row = ((item as EditGridCellData).RowData.Row as VerticalRowData);
if (row.CellTemplate == null) return base.SelectTemplate(item, container);
return row.CellTemplate;
}
}
public class VerticalRowData : DependencyObject
{
public string RowName { get; set; }
public DataTemplate CellTemplate
{
get { return (DataTemplate)GetValue(CellTemplateProperty); }
set { SetValue(CellTemplateProperty, value); }
}
public static readonly DependencyProperty CellTemplateProperty = DependencyProperty.Register("CellTemplate", typeof(DataTemplate), typeof(VerticalRowData), new PropertyMetadata(null));
public static VerticalRowData FromPropertyInfo(PropertyInfo info)
{
return new VerticalRowData() { RowName = info.Name };
}
}
public class VerticalRowCollection : ObservableCollection<VerticalRowData>
{
protected override void InsertItem(int index, VerticalRowData item)
{
int existsIndex = IndexOf(item.RowName);
if (existsIndex > -1)
{
if (Items[existsIndex].CellTemplate != null) return;
Items[existsIndex].CellTemplate = item.CellTemplate;
return;
}
base.InsertItem(index, item);
}
int IndexOf(string rowName)
{
for (int i = 0; i < Items.Count; i++)
{
if (Items[i].RowName == rowName) return i;
}
return -1;
}
}
}
I am using Visual Studio 2010 and .NET 4.0,
And the only error i am getting in "GetValue" and "SetValue" method in OnProcessunboundData function,
telling no overload operator takes "1" and "2" operators respectively .
Is it cause of Platform mismatching.
Please try downloading the sample code mentioned above and tell me the answer.
Thanks,
Vivek
I was looking through a lot of forums
And then i tried
e.Value = itemProperty.GetValue(item,null);
and for SetValue :-
itemProperty.SetValue(item, e.Value,null);
And it worked :)
Thanks Anyways... :) :D

how to set display name on enum fields by BLToolkit?

i have a enum like that :
public enum Mode
{
[*"I need a display name attribute to show in combobox"*]
Active,
[*"I need a display name attribute to show in combobox"*]
DeActive
}
I want to use it as a dataSource in a comboBox and i need to set display name.
Anyone can help me?
it is not the BLT deal...
my solution was:
Test.cs
using System;
using DK.Common.Caption;
using NUnit.Framework;
namespace DK.Common.Tests
{
[TestFixture]
public class LocalizedEnumConverterTests
{
const string FirstName = "Первый";
const string FirstResName = "TestEnumFirst";
const string SecondName = "Второй";
const string SecondResName = "SecondResName";
const string ThirdName = "Третий";
const string FourthName = "Fourth";
const string EmptyName = "This is empty!";
[Flags]
enum TestEnum
{
[Caption(FirstName)]
First = 1,
[Caption(SecondName, SecondResName)]
Second = 2,
[Caption(ThirdName)]
Third = 4,
Fourth = 8,
[Caption]
Empty = 16,
}
readonly LocalizedEnumConverter _converter = new LocalizedEnumConverter(typeof (TestEnum));
[Test]
public void ToStringTest()
{
Func<TestEnum, string> c = (e) => _converter.ConvertToString(null, e);
Assert.AreEqual(FirstResName, c(TestEnum.First));
Assert.AreEqual(SecondResName, c(TestEnum.Second));
Assert.AreEqual(ThirdName, c(TestEnum.Third));
Assert.AreEqual(EmptyName, c(TestEnum.Empty));
Assert.AreEqual(SecondResName + ", " + FourthName, c(TestEnum.Second | TestEnum.Fourth));
}
[Test]
public void FromStringTest()
{
Func<string, TestEnum> c = (s) => (TestEnum) _converter.ConvertFromString(null, s);
Assert.AreEqual(TestEnum.First, c(FirstResName));
Assert.AreEqual(TestEnum.Second, c(SecondResName));
Assert.AreEqual(TestEnum.Third, c(ThirdName));
Assert.AreEqual(TestEnum.Empty, c(EmptyName));
Assert.AreEqual(TestEnum.Second | TestEnum.Fourth, c(SecondResName + ", " + FourthName));
}
[Test]
public void FromToTest()
{
const TestEnum #enum = TestEnum.First | TestEnum.Second | TestEnum.Third | TestEnum.Fourth | TestEnum.Empty;
var str = LocalizedEnumConverter.EnumToString(#enum);
var en = LocalizedEnumConverter.StringToEnum<TestEnum>(str);
Assert.AreEqual(#enum, en);
}
}
}
Caption.cs
using System;
namespace DK.Common.Caption
{
public class CaptionAttribute : Attribute
{
readonly string _caption;
public CaptionAttribute()
:this(string.Empty)
{
}
public CaptionAttribute(string caption)
:this(caption, string.Empty)
{
}
public CaptionAttribute(string caption, string resourceName)
{
_caption = caption;
ResourceName = resourceName;
}
public string Caption { get { return _caption; } }
public string ResourceName { get; private set; }
}
}
LocalizedEnumConverter.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Resources;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Threading;
namespace DK.Common.Caption
{
using Names = Dictionary<object, string>;
using Cultures = Dictionary<CultureInfo, Dictionary<object, string>>;
public class LocalizedEnumConverter : EnumConverter
{
private int Dummy(int myI)
{
int i = 10;
int j = 5;
return myI+i*j;
} public LocalizedEnumConverter(Type type) : base(type)
{
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
Type type = this.EnumType;
bool isFlags = type.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0;
if (destinationType == typeof(string))
{
string s = "";
foreach (object val in Enum.GetValues(type))
{
if ((((int)value & (int)val) != 0 && isFlags)
|| ((int)value == 0 && (int)val == 0)
|| Enum.Equals(value, val))
{
CaptionAttribute[] atr = (CaptionAttribute[])(type.GetField(Enum.GetName(type, val))).GetCustomAttributes(typeof(CaptionAttribute), true);
if (atr.Length > 0)
s += ", " + GetNames(EnumType, culture)[val];
else
s += ", " + base.ConvertTo(context, culture, val, destinationType);
}
}
if (s != "") return s.Substring(2);
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value.GetType() == typeof(string))
{
string caption = (string)value;
List<string> splitted = new List<string>(caption.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries));
//FieldInfo[] fields = EnumType.GetFields();
string mapped = "";
//foreach (FieldInfo fi in fields)
//{
// CaptionAttribute[] atr = (CaptionAttribute[])fi.GetCustomAttributes(typeof(CaptionAttribute), true);
// if (atr.Length > 0 && splitted.IndexOf(GetLocalizedCaption(atr[0], value, culture)) >= 0)
// mapped += "," + fi.Name;
//}
var names = GetNames(EnumType, culture);
foreach (var val in splitted)
{
foreach (var name in names)
{
if(name.Value == val)
{
mapped += "," + name.Key.ToString();
break;
}
}
}
if (mapped.Length > 0)
{
object o = base.ConvertFrom(context, culture, mapped.Substring(1)/*fi.Name*/);
return o;
}
}
return base.ConvertFrom(context, culture, value);
}
private static string GetLocalizedCaption(Type type, CaptionAttribute attr, object val, System.Globalization.CultureInfo culture)
{
var resName = attr.ResourceName;
if (string.IsNullOrEmpty(resName))
{
resName = type.Name + val.ToString();
}
var m = GetResourceManager(type);
try
{
var caption = m != null ? m.GetString(resName, culture) : string.Empty;
return string.IsNullOrEmpty(caption) ? attr.Caption : caption;
}
catch
{
return attr.Caption;
}
}
private static readonly Dictionary<Assembly, ResourceManager> _managers = new Dictionary<Assembly, ResourceManager>();
private static readonly Dictionary<Type, Cultures> _names = new Dictionary<Type, Cultures>();
private static Names GetNames(Type type, CultureInfo cultureInfo)
{
lock (_names)
{
Cultures cult;
if(!_names.TryGetValue(type, out cult))
{
_names[type] = new Cultures();
cult = _names[type];
}
Names n;
if (cultureInfo == null)
cultureInfo = Thread.CurrentThread.CurrentCulture;
if(!cult.TryGetValue(cultureInfo, out n))
{
n = new Names();
foreach (object val in Enum.GetValues(type))
{
CaptionAttribute[] atr = (CaptionAttribute[])(type.GetField(Enum.GetName(type, val))).GetCustomAttributes(typeof(CaptionAttribute), true);
string cap;
if (atr.Length > 0)
cap = GetLocalizedCaption(type, atr[0], val, cultureInfo);
else
cap = val.ToString();
n[val] = cap;
}
cult[cultureInfo] = n;
}
return n;
}
}
private static ResourceManager GetResourceManager(Type type)
{
ResourceManager res;
if (_managers.TryGetValue(type.Assembly, out res))
return res;
lock (_managers)
{
if (_managers.TryGetValue(type.Assembly, out res))
return res;
var names = type.Assembly.GetManifestResourceNames();
// вот только имя сборки и ее ресурсов это вопрос левый... решает не имя сборки, а неймспейс
//var assemblyName = type.Assembly.GetName().Name;
//var resName = assemblyName + "Properties.Resources.resources";
foreach (var name in names)
{
if (/*name.StartsWith(assemblyName, StringComparison.InvariantCultureIgnoreCase)
||*/ name.EndsWith("Properties.Resources.resources", StringComparison.InvariantCultureIgnoreCase))
{
var n = name.Replace(".resources", "");
res = new ResourceManager(n, type.Assembly);
break;
}
}
_managers[type.Assembly] = res;
}
return res;
}
public static string EnumToString(object from)
{
LocalizedEnumConverter conv = new LocalizedEnumConverter(from.GetType());
return (string)conv.ConvertTo(from, typeof(string));
}
public static T StringToEnum<T>(string value)
{
var conv = new LocalizedEnumConverter(typeof (T));
return (T) conv.ConvertFromString(null, value);
}
}
}

Databind a read only dependency property to ViewModel in Xaml

I'm trying to databind a Button's IsMouseOver read-only dependency property to a boolean read/write property in my view model.
Basically I need the Button's IsMouseOver property value to be read to a view model's property.
<Button IsMouseOver="{Binding Path=IsMouseOverProperty, Mode=OneWayToSource}" />
I'm getting a compile error: 'IsMouseOver' property is read-only and cannot be set from markup. What am I doing wrong?
No mistake. This is a limitation of WPF - a read-only property cannot be bound OneWayToSource unless the source is also a DependencyProperty.
An alternative is an attached behavior.
As many people have mentioned, this is a bug in WPF and the best way is to do it is attached property like Tim/Kent suggested. Here is the attached property I use in my project. I intentionally do it this way for readability, unit testability, and sticking to MVVM without codebehind on the view to handle the events manually everywhere.
public interface IMouseOverListener
{
void SetIsMouseOver(bool value);
}
public static class ControlExtensions
{
public static readonly DependencyProperty MouseOverListenerProperty =
DependencyProperty.RegisterAttached("MouseOverListener", typeof (IMouseOverListener), typeof (ControlExtensions), new PropertyMetadata(OnMouseOverListenerChanged));
private static void OnMouseOverListenerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = ((UIElement)d);
if(e.OldValue != null)
{
element.MouseEnter -= ElementOnMouseEnter;
element.MouseLeave -= ElementOnMouseLeave;
}
if(e.NewValue != null)
{
element.MouseEnter += ElementOnMouseEnter;
element.MouseLeave += ElementOnMouseLeave;
}
}
public static void SetMouseOverListener(UIElement element, IMouseOverListener value)
{
element.SetValue(MouseOverListenerProperty, value);
}
public static IMouseOverListener GetMouseOverListener(UIElement element)
{
return (IMouseOverListener) element.GetValue(MouseOverListenerProperty);
}
private static void ElementOnMouseLeave(object sender, MouseEventArgs mouseEventArgs)
{
var element = ((UIElement)sender);
var listener = GetMouseOverListener(element);
if(listener != null)
listener.SetIsMouseOver(false);
}
private static void ElementOnMouseEnter(object sender, MouseEventArgs mouseEventArgs)
{
var element = ((UIElement)sender);
var listener = GetMouseOverListener(element);
if (listener != null)
listener.SetIsMouseOver(true);
}
}
Here's a rough draft of what i resorted to while seeking a general solution to this problem. It employs a css-style formatting to specify Dependency-Properties to be bound to model properties (models gotten from the DataContext); this also means it will work only on FrameworkElements.
I haven't thoroughly tested it, but the happy path works just fine for the few test cases i ran.
public class BindingInfo
{
internal string sourceString = null;
public DependencyProperty source { get; internal set; }
public string targetProperty { get; private set; }
public bool isResolved => source != null;
public BindingInfo(string source, string target)
{
this.sourceString = source;
this.targetProperty = target;
validate();
}
private void validate()
{
//verify that targetProperty is a valid c# property access path
if (!targetProperty.Split('.')
.All(p => Identifier.IsMatch(p)))
throw new Exception("Invalid target property - " + targetProperty);
//verify that sourceString is a [Class].[DependencyProperty] formatted string.
if (!sourceString.Split('.')
.All(p => Identifier.IsMatch(p)))
throw new Exception("Invalid source property - " + sourceString);
}
private static readonly Regex Identifier = new Regex(#"[_a-z][_\w]*$", RegexOptions.IgnoreCase);
}
[TypeConverter(typeof(BindingInfoConverter))]
public class BindingInfoGroup
{
private List<BindingInfo> _infoList = new List<BindingInfo>();
public IEnumerable<BindingInfo> InfoList
{
get { return _infoList.ToArray(); }
set
{
_infoList.Clear();
if (value != null) _infoList.AddRange(value);
}
}
}
public class BindingInfoConverter: TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string)) return true;
return base.CanConvertFrom(context, sourceType);
}
// Override CanConvertTo to return true for Complex-to-String conversions.
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string)) return true;
return base.CanConvertTo(context, destinationType);
}
// Override ConvertFrom to convert from a string to an instance of Complex.
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string text = value as string;
return new BindingInfoGroup
{
InfoList = text.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(binfo =>
{
var parts = binfo.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2) throw new Exception("invalid binding info - " + binfo);
return new BindingInfo(parts[0].Trim(), parts[1].Trim());
})
};
}
// Override ConvertTo to convert from an instance of Complex to string.
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture,
object value, Type destinationType)
{
var bgroup = value as BindingInfoGroup;
return bgroup.InfoList
.Select(bi => $"{bi.sourceString}:{bi.targetProperty};")
.Aggregate((n, p) => n += $"{p} ")
.Trim();
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => false;
}
public class Bindings
{
#region Fields
private static ConcurrentDictionary<DependencyProperty, PropertyChangeHandler> _Properties =
new ConcurrentDictionary<DependencyProperty, PropertyChangeHandler>();
#endregion
#region OnewayBindings
public static readonly DependencyProperty OnewayBindingsProperty =
DependencyProperty.RegisterAttached("OnewayBindings", typeof(BindingInfoGroup), typeof(Bindings), new FrameworkPropertyMetadata
{
DefaultValue = null,
PropertyChangedCallback = (x, y) =>
{
var fwe = x as FrameworkElement;
if (fwe == null) return;
//resolve the bindings
resolve(fwe);
//add change delegates
(GetOnewayBindings(fwe)?.InfoList ?? new BindingInfo[0])
.Where(bi => bi.isResolved)
.ToList()
.ForEach(bi =>
{
var descriptor = DependencyPropertyDescriptor.FromProperty(bi.source, fwe.GetType());
PropertyChangeHandler listener = null;
if (_Properties.TryGetValue(bi.source, out listener))
{
descriptor.RemoveValueChanged(fwe, listener.callback); //cus there's no way to check if it had one before...
descriptor.AddValueChanged(fwe, listener.callback);
}
});
}
});
private static void resolve(FrameworkElement element)
{
var bgroup = GetOnewayBindings(element);
bgroup.InfoList
.ToList()
.ForEach(bg =>
{
//source
var sourceParts = bg.sourceString.Split('.');
if (sourceParts.Length == 1)
{
bg.source = element.GetType()
.baseTypes() //<- flattens base types, including current type
.SelectMany(t => t.GetRuntimeFields()
.Where(p => p.IsStatic)
.Where(p => p.FieldType == typeof(DependencyProperty)))
.Select(fi => fi.GetValue(null) as DependencyProperty)
.FirstOrDefault(dp => dp.Name == sourceParts[0])
.ThrowIfNull($"Dependency Property '{sourceParts[0]}' was not found");
}
else
{
//resolve the dependency property [ClassName].[PropertyName]Property - e.g FrameworkElement.DataContextProperty
bg.source = Type.GetType(sourceParts[0])
.GetField(sourceParts[1])
.GetValue(null)
.ThrowIfNull($"Dependency Property '{bg.sourceString}' was not found") as DependencyProperty;
}
_Properties.GetOrAdd(bg.source, ddp => new PropertyChangeHandler { property = ddp }); //incase it wasnt added before.
});
}
public static BindingInfoGroup GetOnewayBindings(FrameworkElement source)
=> source.GetValue(OnewayBindingsProperty) as BindingInfoGroup;
public static void SetOnewayBindings(FrameworkElement source, string value)
=> source.SetValue(OnewayBindingsProperty, value);
#endregion
}
public class PropertyChangeHandler
{
internal DependencyProperty property { get; set; }
public void callback(object obj, EventArgs args)
{
var fwe = obj as FrameworkElement;
var target = fwe.DataContext;
if (fwe == null) return;
if (target == null) return;
var bg = Bindings.GetOnewayBindings(fwe);
if (bg == null) return;
else bg.InfoList
.Where(bi => bi.isResolved)
.Where(bi => bi.source == property)
.ToList()
.ForEach(bi =>
{
//transfer data to the object
var data = fwe.GetValue(property);
KeyValuePair<object, PropertyInfo>? pinfo = resolveProperty(target, bi.targetProperty);
if (pinfo == null) return;
else pinfo.Value.Value.SetValue(pinfo.Value.Key, data);
});
}
private KeyValuePair<object, PropertyInfo>? resolveProperty(object target, string path)
{
try
{
var parts = path.Split('.');
if (parts.Length == 1) return new KeyValuePair<object, PropertyInfo>(target, target.GetType().GetProperty(parts[0]));
else //(parts.Length>1)
return resolveProperty(target.GetType().GetProperty(parts[0]).GetValue(target),
string.Join(".", parts.Skip(1)));
}
catch (Exception e) //too lazy to care :D
{
return null;
}
}
}
And to use the XAML...
<Grid ab:Bindings.OnewayBindings="IsMouseOver:mouseOver;">...</Grid>

Resources