DataContextChangeSynchronizer.cs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. namespace Perspex.Markup.Xaml.DataBinding
  2. {
  3. using System;
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using System.Reflection;
  7. using ChangeTracking;
  8. using Glass;
  9. using OmniXaml.TypeConversion;
  10. public class DataContextChangeSynchronizer
  11. {
  12. private readonly ITypeConverter targetPropertyTypeConverter;
  13. private readonly TargetBindingEndpoint bindingEndpoint;
  14. private readonly ObservablePropertyBranch sourceEndpoint;
  15. public DataContextChangeSynchronizer(PerspexObject target, PerspexProperty targetProperty,
  16. PropertyPath sourcePropertyPath, object source, ITypeConverterProvider typeConverterProvider)
  17. {
  18. Guard.ThrowIfNull(target, nameof(target));
  19. Guard.ThrowIfNull(targetProperty, nameof(targetProperty));
  20. Guard.ThrowIfNull(sourcePropertyPath, nameof(sourcePropertyPath));
  21. Guard.ThrowIfNull(source, nameof(source));
  22. Guard.ThrowIfNull(typeConverterProvider, nameof(typeConverterProvider));
  23. bindingEndpoint = new TargetBindingEndpoint(target, targetProperty);
  24. sourceEndpoint = new ObservablePropertyBranch(source, sourcePropertyPath);
  25. targetPropertyTypeConverter = typeConverterProvider.GetTypeConverter(targetProperty.PropertyType);
  26. }
  27. private bool CanAssignWithoutConversion
  28. {
  29. get
  30. {
  31. var sourceTypeInfo = sourceEndpoint.Type.GetTypeInfo();
  32. var targetTypeInfo = bindingEndpoint.Property.PropertyType.GetTypeInfo();
  33. var compatible = targetTypeInfo.IsAssignableFrom(sourceTypeInfo);
  34. return compatible;
  35. }
  36. }
  37. public void SubscribeModelToUI()
  38. {
  39. bindingEndpoint.Object.GetObservable(bindingEndpoint.Property).Subscribe(UpdateModelFromUI);
  40. }
  41. public void SubscribeUIToModel()
  42. {
  43. sourceEndpoint.Changed.Subscribe(_ => UpdateUIFromModel());
  44. UpdateUIFromModel();
  45. }
  46. private void UpdateUIFromModel()
  47. {
  48. object contextGetter = sourceEndpoint.Value;
  49. SetCompatibleValue(contextGetter, bindingEndpoint.Property.PropertyType, o => bindingEndpoint.Object.SetValue(bindingEndpoint.Property, o));
  50. }
  51. private void SetCompatibleValue(object originalValue, Type targetType, Action<object> setValueFunc)
  52. {
  53. if (originalValue == null)
  54. {
  55. setValueFunc(null);
  56. }
  57. else
  58. {
  59. if (CanAssignWithoutConversion)
  60. {
  61. setValueFunc(originalValue);
  62. }
  63. else
  64. {
  65. var synchronizationOk = false;
  66. if (targetPropertyTypeConverter != null)
  67. {
  68. if (targetPropertyTypeConverter.CanConvertTo(null, targetType))
  69. {
  70. object convertedValue = targetPropertyTypeConverter.ConvertTo(null, CultureInfo.InvariantCulture, originalValue,
  71. targetType);
  72. if (convertedValue != null)
  73. {
  74. setValueFunc(convertedValue);
  75. synchronizationOk = true;
  76. }
  77. }
  78. }
  79. if (!synchronizationOk)
  80. {
  81. LogCannotConvertError(originalValue);
  82. }
  83. }
  84. }
  85. }
  86. private void UpdateModelFromUI(object valueFromUI)
  87. {
  88. SetCompatibleValue(valueFromUI, sourceEndpoint.Type, o => sourceEndpoint.Value = o);
  89. }
  90. private void LogCannotConvertError(object value)
  91. {
  92. Contract.Requires<ArgumentException>(value != null);
  93. var loggableValue = value.ToString();
  94. var valueToWrite = string.IsNullOrWhiteSpace(loggableValue) ? "'(empty/whitespace string)'" : loggableValue;
  95. Debug.WriteLine("Cannot convert value {0} ({1}) to {2}", valueToWrite, value.GetType(), bindingEndpoint.Property.PropertyType);
  96. }
  97. }
  98. }