CSLA.NET 6.0.0
CSLA .NET is a software development framework that helps you build a reusable, maintainable object-oriented business layer for your app.
Csla.Xaml.Shared/PropertyInfo.cs
Go to the documentation of this file.
1//-----------------------------------------------------------------------
2// <copyright file="PropertyInfo.cs" company="Marimer LLC">
3// Copyright (c) Marimer LLC. All rights reserved.
4// Website: https://cslanet.com
5// </copyright>
6// <summary>Expose metastate information about a property.</summary>
7//-----------------------------------------------------------------------
8using System;
9using System.Linq;
10using System.ComponentModel;
11using Csla.Reflection;
12using Csla.Core;
13using Csla.Rules;
14using System.Reflection;
15using System.Collections.ObjectModel;
16#if NETFX_CORE
17using Windows.UI.Xaml;
18using Windows.UI.Xaml.Data;
19#elif XAMARIN
20using Xamarin.Forms;
21using System.Collections.Generic;
22#else
23using System.Windows.Controls;
24using System.Windows;
25using System.Windows.Data;
26#endif
27
28namespace Csla.Xaml
29{
33#if XAMARIN
34 public class PropertyInfo : View, INotifyPropertyChanged
35 {
36
37#else
38 public class PropertyInfo : FrameworkElement, INotifyPropertyChanged
39 {
40 private const string _dependencyPropertySuffix = "Property";
41#endif
42 private bool _loading = true;
43
47 public PropertyInfo()
48 {
49 BrokenRules = new ObservableCollection<BrokenRule>();
50#if XAMARIN
51 _loading = false;
52 BindingContextChanged += (o, e) => SetSource();
54#else
55 Visibility = Visibility.Collapsed;
56 Height = 20;
57 Width = 20;
58 Loaded += (o, e) =>
59 {
60 _loading = false;
62 };
63#endif
64 }
65
70 public PropertyInfo(bool testing)
71 : this()
72 {
73 _loading = false;
75 }
76
77 #region BrokenRules property
78
79#if XAMARIN
84 public static readonly BindableProperty BrokenRulesProperty =
85 BindableProperty.Create("BrokenRules", typeof(ObservableCollection<BrokenRule>), typeof(PropertyInfo), new ObservableCollection<BrokenRule>());
90 public ObservableCollection<BrokenRule> BrokenRules
91 {
92 get { return (ObservableCollection<BrokenRule>)this.GetValue(BrokenRulesProperty); }
93 set { SetValue(BrokenRulesProperty, value); }
94 }
95#else
100 public static readonly DependencyProperty BrokenRulesProperty = DependencyProperty.Register(
101 "BrokenRules",
102 typeof(ObservableCollection<BrokenRule>),
103 typeof(PropertyInfo),
104 null);
105
110 [Category("Property Status")]
111 public ObservableCollection<BrokenRule> BrokenRules
112 {
113 get { return (ObservableCollection<BrokenRule>)GetValue(BrokenRulesProperty); }
114 private set { SetValue(BrokenRulesProperty, value); }
115 }
116#endif
117
118 #endregion
119
120 #region MyDataContext Property
121
122#if XAMARIN
123#else
127 public static readonly DependencyProperty MyDataContextProperty =
128 DependencyProperty.Register("MyDataContext",
129 typeof(Object),
130 typeof(PropertyInfo),
131#if NETFX_CORE
132 new PropertyMetadata(null, MyDataContextPropertyChanged));
133#else
134 new PropertyMetadata(MyDataContextPropertyChanged));
135#endif
136
137 private static void MyDataContextPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
138 {
139 ((PropertyInfo)sender).SetSource(true);
140 }
141#endif
142
143 #endregion
144
145 #region RelativeBinding Property
146
147#if XAMARIN
148#else
149
153 public static readonly DependencyProperty RelativeBindingProperty =
154 DependencyProperty.Register("RelativeBinding",
155 typeof(Object),
156 typeof(PropertyInfo),
157#if NETFX_CORE
158 new PropertyMetadata(null, RelativeBindingPropertyChanged));
159#else
160 new PropertyMetadata(RelativeBindingPropertyChanged));
161#endif
162
163 private static void RelativeBindingPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
164 {
165 ((PropertyInfo)sender).SetSource(true);
166 }
167#endif
168
169 #endregion
170
171 #region Source property
172
177 protected object Source { get; set; }
178
183 protected string BindingPath { get; set; }
184
185#if XAMARIN
186 private string _bindingPath;
192 public string Path
193 {
194 get { return _bindingPath; }
195 set
196 {
197 if (_bindingPath != value)
198 {
199 _bindingPath = value;
200 OnPropertyChanged("Path");
201 SetSource();
202 }
203 }
204 }
205
206 private class SourceReference
207 {
208 public SourceReference(PropertyInfo parent, object source, string propertyName)
209 {
210 Parent = parent;
211 Source = source;
212 PropertyName = propertyName;
213 var p = Source as INotifyPropertyChanged;
214 if (p != null)
215 p.PropertyChanged += P_PropertyChanged;
216 }
217
218 private void P_PropertyChanged(object sender, PropertyChangedEventArgs e)
219 {
220 if (e.PropertyName == PropertyName)
221 Parent.SetSource();
222 }
223
224 public void DetachHandlers()
225 {
226 var p = Source as INotifyPropertyChanged;
227 if (p != null)
228 p.PropertyChanged -= P_PropertyChanged;
229 }
230
231 public PropertyInfo Parent { get; private set; }
232 public object Source { get; private set; }
233 public string PropertyName { get; private set; }
234 }
235
236 private List<SourceReference> _sources = new List<SourceReference>();
237
238 private void SetSource()
239 {
240 foreach (var item in _sources)
241 item.DetachHandlers();
242 _sources.Clear();
243 var oldSource = Source;
244 Source = BindingContext;
245 BindingPath = Path;
246 if (Source != null && !string.IsNullOrWhiteSpace(BindingPath))
247 {
248 try
249 {
250 while (BindingPath.Contains(".") && Source != null)
251 {
252 var refName = BindingPath.Substring(0, BindingPath.IndexOf("."));
253 _sources.Add(new SourceReference(this, Source, refName));
254 BindingPath = BindingPath.Substring(BindingPath.IndexOf(".") + 1);
255 Source = MethodCaller.CallPropertyGetter(Source, refName);
256 }
257 }
258 catch (Exception ex)
259 {
260 throw new InvalidOperationException(
261 string.Format("SetSource: BindingContext:{0}, Path={1}", BindingPath.GetType().Name, Path), ex);
262 }
263 }
264 HandleSourceEvents(oldSource, Source);
265 UpdateState();
266 OnPropertyChanged("Value");
267 }
268#else
273 public static readonly DependencyProperty PropertyProperty = DependencyProperty.Register(
274 "Property",
275 typeof(object),
276 typeof(PropertyInfo),
277 new PropertyMetadata(new object(), (o, e) =>
278 {
279 bool changed = true;
280 if (e.NewValue == null)
281 {
282 if (e.OldValue == null)
283 changed = false;
284 }
285 else if (e.NewValue.Equals(e.OldValue))
286 {
287 changed = false;
288 }
289 ((PropertyInfo)o).SetSource(changed);
290 }));
291
296 [Category("Common")]
297 public object Property
298 {
299 get { return GetValue(PropertyProperty); }
300 set
301 {
302 SetValue(PropertyProperty, value);
303 SetSource(false);
304 }
305 }
306
307 //private object _oldDataContext;
308 //private System.Windows.Data.BindingExpression _oldBinding;
309
317 protected virtual BindingExpression ParseRelativeBinding(BindingExpression sourceBinding)
318 {
319 if (sourceBinding != null
320 && sourceBinding.ParentBinding.RelativeSource != null
321 && sourceBinding.ParentBinding.RelativeSource.Mode == RelativeSourceMode.TemplatedParent
322 && sourceBinding.DataItem is FrameworkElement)
323 {
324 var control = (FrameworkElement)sourceBinding.DataItem;
325 var path = sourceBinding.ParentBinding.Path.Path;
326
327 var type = control.GetType();
328 FieldInfo fi = null;
329 while (type != null)
330 {
331#if NETFX_CORE
332 fi = type.GetField(string.Format("{0}{1}", path, _dependencyPropertySuffix), BindingFlags.Instance | BindingFlags.Public);
333#else
334 fi = type.GetField(string.Format("{0}{1}", path, _dependencyPropertySuffix));
335#endif
336
337 if (fi != null)
338 {
339 DependencyProperty mappedDP = (DependencyProperty)fi.GetValue(control.GetType());
340 return control.GetBindingExpression(mappedDP);
341 }
342 else
343 {
344#if NETFX_CORE
345 type = type.GetTypeInfo().BaseType;
346#else
347 type = type.BaseType;
348#endif
349 }
350 }
351
352 return null;
353 }
354
355 return sourceBinding;
356 }
357
361 protected virtual void SetSource(bool propertyValueChanged)
362 {
363 var binding = GetBindingExpression(PropertyProperty);
364 if (binding != null)
365 {
366 SetSource(binding.DataItem);
367 }
368 }
369
373 protected virtual void SetSource(object dataItem)
374 {
375 bool isDataLoaded = true;
376
377 SetBindingValues(GetBindingExpression(PropertyProperty));
378 var newSource = GetRealSource(dataItem, BindingPath);
379
380 // Check to see if PropertyInfo is inside a control template
381 ClearValue(MyDataContextProperty);
382 if (newSource != null && newSource is FrameworkElement)
383 {
384 var data = ((FrameworkElement)newSource).DataContext;
385 SetBindingValues(ParseRelativeBinding(GetBindingExpression(PropertyProperty)));
386
387 if (data != null && GetBindingExpression(RelativeBindingProperty) == null)
388 {
389 var relativeBinding = ParseRelativeBinding(GetBindingExpression(PropertyProperty));
390 if (relativeBinding != null)
391 SetBinding(RelativeBindingProperty, relativeBinding.ParentBinding);
392 }
393
394 newSource = GetRealSource(data, BindingPath);
395
396 if (newSource != null)
397 {
398 Binding b = new Binding();
399 b.Source = newSource;
400 if (BindingPath.IndexOf('.') > 0)
401 {
402 var path = GetRelativePath(newSource, BindingPath);
403 if (path != null)
404 b.Path = path;
405 }
406 if (b.Path != null
407 && !string.IsNullOrEmpty(b.Path.Path)
408 && b.Path.Path != BindingPath.Substring(BindingPath.LastIndexOf('.') + 1))
409 {
410 SetBinding(MyDataContextProperty, b);
411 isDataLoaded = false;
412 }
413 }
414 }
415
416 if (BindingPath.IndexOf('.') > 0)
417 BindingPath = BindingPath.Substring(BindingPath.LastIndexOf('.') + 1);
418
419 if (isDataLoaded)
420 {
421 if (!ReferenceEquals(Source, newSource))
422 {
423 var old = Source;
424 Source = newSource;
425
426 HandleSourceEvents(old, Source);
427 }
428
429 UpdateState();
430 }
431 }
432
436 private void SetBindingValues(BindingExpression binding)
437 {
438 var bindingPath = string.Empty;
439
440 if (binding != null)
441 {
442 if (binding.ParentBinding != null && binding.ParentBinding.Path != null)
443 bindingPath = binding.ParentBinding.Path.Path;
444 else
445 bindingPath = string.Empty;
446 }
447
448 BindingPath = bindingPath;
449 }
450
457 protected object GetRealSource(object source, string bindingPath)
458 {
459 var icv = source as ICollectionView;
460 if (icv != null)
461 source = icv.CurrentItem;
462 if (source != null && bindingPath.IndexOf('.') > 0)
463 {
464 var firstProperty = bindingPath.Substring(0, bindingPath.IndexOf('.'));
465 var p = MethodCaller.GetProperty(source.GetType(), firstProperty);
466 if (p != null)
467 {
468 source = GetRealSource(
469 MethodCaller.GetPropertyValue(source, p),
470 bindingPath.Substring(bindingPath.IndexOf('.') + 1));
471 }
472 }
473
474 return source;
475 }
476
483 protected PropertyPath GetRelativePath(object source, string bindingPath)
484 {
485 if (source != null)
486 {
487 if (bindingPath.IndexOf('.') > 0)
488 {
489 var firstProperty = bindingPath.Substring(0, bindingPath.IndexOf('.'));
490 var p = MethodCaller.GetProperty(source.GetType(), firstProperty);
491
492 if (p != null)
493 return new PropertyPath(firstProperty);
494 else
495 return GetRelativePath(source, bindingPath.Substring(bindingPath.IndexOf('.') + 1));
496 }
497 else
498 return new PropertyPath(bindingPath);
499 }
500
501 return null;
502 }
503#endif
504
505 private void HandleSourceEvents(object old, object source)
506 {
507 if (!ReferenceEquals(old, source))
508 {
509 DetachSource(old);
510 AttachSource(source);
512 if (bb != null && !string.IsNullOrWhiteSpace(BindingPath))
513 {
515 }
516 }
517 }
518
519 private void DetachSource(object source)
520 {
521 var p = source as INotifyPropertyChanged;
522 if (p != null)
523 p.PropertyChanged -= source_PropertyChanged;
524 INotifyBusy busy = source as INotifyBusy;
525 if (busy != null)
526 busy.BusyChanged -= source_BusyChanged;
527 }
528
529 private void AttachSource(object source)
530 {
531 var p = source as INotifyPropertyChanged;
532 if (p != null)
533 p.PropertyChanged += source_PropertyChanged;
534 INotifyBusy busy = source as INotifyBusy;
535 if (busy != null)
536 busy.BusyChanged += source_BusyChanged;
537 }
538
539 void source_PropertyChanged(object sender, PropertyChangedEventArgs e)
540 {
541 if (e.PropertyName == BindingPath || string.IsNullOrEmpty(e.PropertyName))
542 {
543 OnPropertyChanged("Value");
544 UpdateState();
545 }
546 }
547
548 void source_BusyChanged(object sender, BusyChangedEventArgs e)
549 {
550 if (e.PropertyName == BindingPath || string.IsNullOrEmpty(e.PropertyName))
551 {
552 bool busy = e.Busy;
554 if (bb != null)
555 busy = bb.IsPropertyBusy(BindingPath);
556
557 if (busy != IsBusy)
558 {
559 IsBusy = busy;
560 UpdateState();
561 }
562 }
563 }
564
565 #endregion
566
567 #region Error/Warn/Info Text
568
574 public string ErrorText
575 {
576 get
577 {
578 var result = string.Empty;
579 if (Source is Core.BusinessBase obj && !string.IsNullOrEmpty(BindingPath))
580 result = obj.BrokenRulesCollection.ToString(",", RuleSeverity.Error, BindingPath);
581 return result;
582 }
583 }
584
590 public string WarningText
591 {
592 get
593 {
594 var result = string.Empty;
595 if (Source is Core.BusinessBase obj && !string.IsNullOrEmpty(BindingPath))
596 result = obj.BrokenRulesCollection.ToString(",", RuleSeverity.Warning, BindingPath);
597 return result;
598 }
599 }
600
606 public string InformationText
607 {
608 get
609 {
610 var result = string.Empty;
611 if (Source is Core.BusinessBase obj && !string.IsNullOrEmpty(BindingPath))
612 result = obj.BrokenRulesCollection.ToString(",", RuleSeverity.Information, BindingPath);
613 return result;
614 }
615 }
616
617 #endregion
618
619 #region State properties
620
625 [Category("Property Status")]
626 public object Value
627 {
628 get
629 {
630 object result = null;
631 if (Source != null && !string.IsNullOrWhiteSpace(BindingPath))
632 result = MethodCaller.CallPropertyGetter(Source, BindingPath);
633 return result;
634 }
635 set
636 {
637 if (Source != null && !string.IsNullOrWhiteSpace(BindingPath))
638 MethodCaller.CallPropertySetter(Source, BindingPath, value);
639 }
640 }
641
642
643 private bool _canRead = true;
648 [Category("Property Status")]
649 public bool CanRead
650 {
651 get { return _canRead; }
652 protected set
653 {
654 if (value != _canRead)
655 {
656 _canRead = value;
657 OnPropertyChanged("CanRead");
658 }
659 }
660 }
661
662 private bool _canWrite = true;
667 [Category("Property Status")]
668 public bool CanWrite
669 {
670 get { return _canWrite; }
671 protected set
672 {
673 if (value != _canWrite)
674 {
675 _canWrite = value;
676 OnPropertyChanged("CanWrite");
677 }
678 }
679 }
680
681 private bool _isBusy = false;
686 [Category("Property Status")]
687 public bool IsBusy
688 {
689 get { return _isBusy; }
690 private set
691 {
692 if (value != _isBusy)
693 {
694 _isBusy = value;
695 OnPropertyChanged("IsBusy");
696 }
697 }
698 }
699
700 private bool _isValid = true;
705 [Category("Property Status")]
706 public bool IsValid
707 {
708 get { return _isValid; }
709 private set
710 {
711 if (value != _isValid)
712 {
713 _isValid = value;
714 OnPropertyChanged("IsValid");
715 }
716 }
717 }
718
719 private RuleSeverity _worst;
726 [Category("Property Status")]
728 {
729 get { return _worst; }
730 private set
731 {
732 if (value != _worst)
733 {
734 _worst = value;
735 OnPropertyChanged("RuleSeverity");
736 }
737 }
738 }
739
740 private string _ruleDescription = string.Empty;
745 [Category("Property Status")]
746 public string RuleDescription
747 {
748 get { return _ruleDescription; }
749 private set
750 {
751 if (value != _ruleDescription)
752 {
753 _ruleDescription = value;
754 OnPropertyChanged("RuleDescription");
755 }
756 }
757 }
758
759 private object _customTag;
764 [Category("Property Status")]
765 public object CustomTag
766 {
767 get
768 {
769 return _customTag;
770 }
771 set
772 {
773 if (!ReferenceEquals(_customTag, value))
774 {
775 _customTag = value;
776 OnPropertyChanged("CustomTag");
777 }
778 }
779 }
780
781 #endregion
782
783 #region State management
784
790 public virtual void UpdateState()
791 {
792 if (_loading) return;
793
794 if (Source == null || string.IsNullOrEmpty(BindingPath))
795 {
796 CanWrite = false;
797 CanRead = false;
798 IsValid = false;
799 IsBusy = false;
800 BrokenRules.Clear();
801 RuleDescription = string.Empty;
802 }
803 else
804 {
806 {
807 CanWrite = iarw.CanWriteProperty(BindingPath);
808 CanRead = iarw.CanReadProperty(BindingPath);
809 }
810
811 if (Source is BusinessBase businessObject)
812 {
813 var allRules = (from r in businessObject.BrokenRulesCollection
814 where r.Property == BindingPath
815 select r).ToArray();
816
817 var removeRules = (from r in BrokenRules
818 where !allRules.Contains(r)
819 select r).ToArray();
820
821 var addRules = (from r in allRules
822 where !BrokenRules.Contains(r)
823 select r).ToArray();
824
825 foreach (var rule in removeRules)
826 BrokenRules.Remove(rule);
827 foreach (var rule in addRules)
828 BrokenRules.Add(rule);
829
830 IsValid = BrokenRules.Count == 0;
831
832 if (!IsValid)
833 {
834 BrokenRule worst = (from r in BrokenRules
835 orderby r.Severity
836 select r).FirstOrDefault();
837
838 if (worst != null)
839 {
840 RuleSeverity = worst.Severity;
842 }
843 else
844 {
845 RuleDescription = string.Empty;
846 }
847 }
848 else
849 {
850 RuleDescription = string.Empty;
851 }
852 }
853 else
854 {
855 BrokenRules.Clear();
856 RuleDescription = string.Empty;
857 }
858 }
862 }
863
864 #endregion
865
866 #region INotifyPropertyChanged Members
867
868#if !XAMARIN
872 public event PropertyChangedEventHandler PropertyChanged;
873
878 protected virtual void OnPropertyChanged(string propertyName)
879 {
880 if (PropertyChanged != null)
881 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
882 }
883#endif
884
885 #endregion
886 }
887}
This is the base class from which most business objects will be derived.
Definition: BusinessBase.cs:38
This is the non-generic base class from which most business objects will be derived.
virtual bool IsPropertyBusy(Csla.Core.IPropertyInfo property)
Gets a value indicating whether a specific property is busy (has a currently executing async rule).
Event arguments for the BusyChanged event.
string PropertyName
Property for which the Busy value has changed.
Stores details about a specific broken business rule.
Definition: BrokenRule.cs:19
string Description
Provides access to the description of the broken rule.
Definition: BrokenRule.cs:55
RuleSeverity Severity
Gets the severity of the broken rule.
Definition: BrokenRule.cs:77
Expose metastate information about a property.
object Value
Gets and sets the value of the property on the business object.
string WarningText
Gets the validation warning messages for a property on the Model
string ErrorText
Gets the validation error messages for a property on the Model
bool CanRead
Gets a value indicating whether the user is authorized to read the property.
string InformationText
Gets the validation information messages for a property on the Model
string RuleDescription
Gets the description of the most severe broken rule for this property.
virtual void UpdateState()
Updates the metastate values on control based on the current state of the business object and propert...
string BindingPath
Gets or sets the binding path.
PropertyInfo(bool testing)
Creates an instance of the object for testing.
object Source
Gets or sets the Source.
bool IsBusy
Gets a value indicating whether the property is busy with an asynchronous operation.
bool CanWrite
Gets a value indicating whether the user is authorized to write the property.
virtual void SetSource(bool propertyValueChanged)
Sets the source binding and updates status.
static readonly DependencyProperty RelativeBindingProperty
Used to monitor for changes in the binding path.
PropertyInfo()
Creates an instance of the object.
static readonly DependencyProperty MyDataContextProperty
Used to monitor for changes in the binding path.
PropertyChangedEventHandler PropertyChanged
Event raised when a property has changed.
virtual void OnPropertyChanged(string propertyName)
Raises the PropertyChanged event.
static readonly DependencyProperty PropertyProperty
Gets or sets the source business property to which this control is bound.
static readonly DependencyProperty BrokenRulesProperty
Gets the broken rules collection from the business object.
bool IsValid
Gets a value indicating whether the property is valid.
virtual BindingExpression ParseRelativeBinding(BindingExpression sourceBinding)
Checks a binding expression to see if it is a relative source binding used in a control template.
ObservableCollection< BrokenRule > BrokenRules
Gets the broken rules collection from the business object.
object CustomTag
Gets or sets an arbitrary value associated with this PropertyInfo instance.
object Property
Gets or sets the source business property to which this control is bound.
virtual void SetSource(object dataItem)
Sets the source binding and updates status.
PropertyPath GetRelativePath(object source, string bindingPath)
Gets the part of the binding path relevant to the given source.
object GetRealSource(object source, string bindingPath)
Gets the real source helper method.
Interface defining an object that notifies when it is busy executing an asynchronous operation.
Definition: INotifyBusy.cs:17
BusyChangedEventHandler BusyChanged
Event raised when the object's busy status changes.
Definition: INotifyBusy.cs:22
Defines the authorization interface through which an object can indicate which properties the current...
RuleSeverity
Values for validation rule severities.
Definition: RuleSeverity.cs:16