PerspexObject.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908
  1. // Copyright (c) The Perspex Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.ComponentModel;
  6. using System.Linq;
  7. using System.Reactive.Disposables;
  8. using System.Reactive.Linq;
  9. using System.Reactive.Subjects;
  10. using System.Reflection;
  11. using Perspex.Reactive;
  12. using Perspex.Threading;
  13. using Perspex.Utilities;
  14. using Serilog;
  15. using Serilog.Core.Enrichers;
  16. namespace Perspex
  17. {
  18. /// <summary>
  19. /// An object with <see cref="PerspexProperty"/> support.
  20. /// </summary>
  21. /// <remarks>
  22. /// This class is analogous to DependencyObject in WPF.
  23. /// </remarks>
  24. public class PerspexObject : IObservablePropertyBag, INotifyPropertyChanged
  25. {
  26. /// <summary>
  27. /// The parent object that inherited values are inherited from.
  28. /// </summary>
  29. private PerspexObject _inheritanceParent;
  30. /// <summary>
  31. /// The set values/bindings on this object.
  32. /// </summary>
  33. private readonly Dictionary<PerspexProperty, PriorityValue> _values =
  34. new Dictionary<PerspexProperty, PriorityValue>();
  35. /// <summary>
  36. /// Event handler for <see cref="INotifyPropertyChanged"/> implementation.
  37. /// </summary>
  38. private PropertyChangedEventHandler _inpcChanged;
  39. /// <summary>
  40. /// A serilog logger for logging property events.
  41. /// </summary>
  42. private readonly ILogger _propertyLog;
  43. /// <summary>
  44. /// Initializes a new instance of the <see cref="PerspexObject"/> class.
  45. /// </summary>
  46. public PerspexObject()
  47. {
  48. _propertyLog = Log.ForContext(new[]
  49. {
  50. new PropertyEnricher("Area", "Property"),
  51. new PropertyEnricher("SourceContext", GetType()),
  52. new PropertyEnricher("Id", GetHashCode()),
  53. });
  54. foreach (var property in PerspexPropertyRegistry.Instance.GetRegistered(this))
  55. {
  56. object value = property.IsDirect ?
  57. property.Getter(this) :
  58. property.GetDefaultValue(GetType());
  59. var e = new PerspexPropertyChangedEventArgs(
  60. this,
  61. property,
  62. PerspexProperty.UnsetValue,
  63. value,
  64. BindingPriority.Unset);
  65. property.NotifyInitialized(e);
  66. }
  67. }
  68. /// <summary>
  69. /// Raised when a <see cref="PerspexProperty"/> value changes on this object.
  70. /// </summary>
  71. public event EventHandler<PerspexPropertyChangedEventArgs> PropertyChanged;
  72. /// <summary>
  73. /// Raised when a <see cref="PerspexProperty"/> value changes on this object.
  74. /// </summary>
  75. event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
  76. {
  77. add { _inpcChanged += value; }
  78. remove { _inpcChanged -= value; }
  79. }
  80. /// <summary>
  81. /// Gets the object that inherited <see cref="PerspexProperty"/> values are inherited from.
  82. /// </summary>
  83. IPropertyBag IPropertyBag.InheritanceParent => InheritanceParent;
  84. /// <summary>
  85. /// Gets or sets the parent object that inherited <see cref="PerspexProperty"/> values
  86. /// are inherited from.
  87. /// </summary>
  88. /// <value>
  89. /// The inheritance parent.
  90. /// </value>
  91. protected PerspexObject InheritanceParent
  92. {
  93. get
  94. {
  95. return _inheritanceParent;
  96. }
  97. set
  98. {
  99. if (_inheritanceParent != value)
  100. {
  101. if (_inheritanceParent != null)
  102. {
  103. _inheritanceParent.PropertyChanged -= ParentPropertyChanged;
  104. }
  105. var inherited = (from property in PerspexPropertyRegistry.Instance.GetRegistered(this)
  106. where property.Inherits
  107. select new
  108. {
  109. Property = property,
  110. Value = GetValue(property),
  111. }).ToList();
  112. _inheritanceParent = value;
  113. foreach (var i in inherited)
  114. {
  115. object newValue = GetValue(i.Property);
  116. if (!Equals(i.Value, newValue))
  117. {
  118. RaisePropertyChanged(i.Property, i.Value, newValue, BindingPriority.LocalValue);
  119. }
  120. }
  121. if (_inheritanceParent != null)
  122. {
  123. _inheritanceParent.PropertyChanged += ParentPropertyChanged;
  124. }
  125. }
  126. }
  127. }
  128. /// <summary>
  129. /// Gets or sets the value of a <see cref="PerspexProperty"/>.
  130. /// </summary>
  131. /// <param name="property">The property.</param>
  132. public object this[PerspexProperty property]
  133. {
  134. get { return GetValue(property); }
  135. set { SetValue(property, value); }
  136. }
  137. /// <summary>
  138. /// Gets or sets a binding for a <see cref="PerspexProperty"/>.
  139. /// </summary>
  140. /// <param name="binding">The binding information.</param>
  141. public IObservable<object> this[BindingDescriptor binding]
  142. {
  143. get
  144. {
  145. return new BindingDescriptor
  146. {
  147. Mode = binding.Mode,
  148. Priority = binding.Priority,
  149. Property = binding.Property,
  150. Source = this,
  151. };
  152. }
  153. set
  154. {
  155. var mode = (binding.Mode == BindingMode.Default) ?
  156. binding.Property.DefaultBindingMode :
  157. binding.Mode;
  158. var sourceBinding = value as BindingDescriptor;
  159. if (sourceBinding == null && mode > BindingMode.OneWay)
  160. {
  161. mode = BindingMode.OneWay;
  162. }
  163. switch (mode)
  164. {
  165. case BindingMode.Default:
  166. case BindingMode.OneWay:
  167. Bind(binding.Property, value, binding.Priority);
  168. break;
  169. case BindingMode.OneTime:
  170. SetValue(binding.Property, sourceBinding.Source.GetValue(sourceBinding.Property), binding.Priority);
  171. break;
  172. case BindingMode.OneWayToSource:
  173. sourceBinding.Source.Bind(sourceBinding.Property, GetObservable(binding.Property), binding.Priority);
  174. break;
  175. case BindingMode.TwoWay:
  176. BindTwoWay(binding.Property, sourceBinding.Source, sourceBinding.Property);
  177. break;
  178. }
  179. }
  180. }
  181. public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
  182. public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
  183. /// <summary>
  184. /// Clears a <see cref="PerspexProperty"/>'s local value.
  185. /// </summary>
  186. /// <param name="property">The property.</param>
  187. public void ClearValue(PerspexProperty property)
  188. {
  189. Contract.Requires<ArgumentNullException>(property != null);
  190. SetValue(property, PerspexProperty.UnsetValue);
  191. }
  192. /// <summary>
  193. /// Gets an observable for a <see cref="PerspexProperty"/>.
  194. /// </summary>
  195. /// <param name="property">The property.</param>
  196. /// <returns>An observable.</returns>
  197. public IObservable<object> GetObservable(PerspexProperty property)
  198. {
  199. Contract.Requires<ArgumentNullException>(property != null);
  200. return new PerspexObservable<object>(
  201. observer =>
  202. {
  203. EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
  204. {
  205. if (e.Property == property)
  206. {
  207. observer.OnNext(e.NewValue);
  208. }
  209. };
  210. observer.OnNext(GetValue(property));
  211. PropertyChanged += handler;
  212. return Disposable.Create(() =>
  213. {
  214. PropertyChanged -= handler;
  215. });
  216. },
  217. GetDescription(property));
  218. }
  219. /// <summary>
  220. /// Gets an observable for a <see cref="PerspexProperty"/>.
  221. /// </summary>
  222. /// <typeparam name="T">The property type.</typeparam>
  223. /// <param name="property">The property.</param>
  224. /// <returns>An observable.</returns>
  225. public IObservable<T> GetObservable<T>(PerspexProperty<T> property)
  226. {
  227. Contract.Requires<ArgumentNullException>(property != null);
  228. return GetObservable((PerspexProperty)property).Cast<T>();
  229. }
  230. /// <summary>
  231. /// Gets an observable for a <see cref="PerspexProperty"/>.
  232. /// </summary>
  233. /// <typeparam name="T">The type of the property.</typeparam>
  234. /// <param name="property">The property.</param>
  235. /// <returns>An observable which when subscribed pushes the old and new values of the
  236. /// property each time it is changed.</returns>
  237. public IObservable<Tuple<T, T>> GetObservableWithHistory<T>(PerspexProperty<T> property)
  238. {
  239. return new PerspexObservable<Tuple<T, T>>(
  240. observer =>
  241. {
  242. EventHandler<PerspexPropertyChangedEventArgs> handler = (s, e) =>
  243. {
  244. if (e.Property == property)
  245. {
  246. observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue));
  247. }
  248. };
  249. PropertyChanged += handler;
  250. return Disposable.Create(() =>
  251. {
  252. PropertyChanged -= handler;
  253. });
  254. },
  255. GetDescription(property));
  256. }
  257. /// <summary>
  258. /// Gets a <see cref="PerspexProperty"/> value.
  259. /// </summary>
  260. /// <param name="property">The property.</param>
  261. /// <returns>The value.</returns>
  262. public object GetValue(PerspexProperty property)
  263. {
  264. Contract.Requires<ArgumentNullException>(property != null);
  265. if (property.IsDirect)
  266. {
  267. return GetRegistered(property).Getter(this);
  268. }
  269. else
  270. {
  271. object result = PerspexProperty.UnsetValue;
  272. PriorityValue value;
  273. if (!PerspexPropertyRegistry.Instance.IsRegistered(this, property))
  274. {
  275. ThrowNotRegistered(property);
  276. }
  277. if (_values.TryGetValue(property, out value))
  278. {
  279. result = value.Value;
  280. }
  281. if (result == PerspexProperty.UnsetValue)
  282. {
  283. result = GetDefaultValue(property);
  284. }
  285. return result;
  286. }
  287. }
  288. /// <summary>
  289. /// Gets a <see cref="PerspexProperty"/> value.
  290. /// </summary>
  291. /// <typeparam name="T">The type of the property.</typeparam>
  292. /// <param name="property">The property.</param>
  293. /// <returns>The value.</returns>
  294. public T GetValue<T>(PerspexProperty<T> property)
  295. {
  296. Contract.Requires<ArgumentNullException>(property != null);
  297. if (property.IsDirect)
  298. {
  299. return ((PerspexProperty<T>)GetRegistered(property)).Getter(this);
  300. }
  301. else
  302. {
  303. return (T)GetValue((PerspexProperty)property);
  304. }
  305. }
  306. /// <summary>
  307. /// Checks whether a <see cref="PerspexProperty"/> is set on this object.
  308. /// </summary>
  309. /// <param name="property">The property.</param>
  310. /// <returns>True if the property is set, otherwise false.</returns>
  311. public bool IsSet(PerspexProperty property)
  312. {
  313. Contract.Requires<ArgumentNullException>(property != null);
  314. PriorityValue value;
  315. if (_values.TryGetValue(property, out value))
  316. {
  317. return value.Value != PerspexProperty.UnsetValue;
  318. }
  319. return false;
  320. }
  321. /// <summary>
  322. /// Sets a <see cref="PerspexProperty"/> value.
  323. /// </summary>
  324. /// <param name="property">The property.</param>
  325. /// <param name="value">The value.</param>
  326. /// <param name="priority">The priority of the value.</param>
  327. public void SetValue(
  328. PerspexProperty property,
  329. object value,
  330. BindingPriority priority = BindingPriority.LocalValue)
  331. {
  332. Contract.Requires<ArgumentNullException>(property != null);
  333. VerifyAccess();
  334. if (property.IsDirect)
  335. {
  336. property = GetRegistered(property);
  337. if (property.Setter == null)
  338. {
  339. throw new ArgumentException($"The property {property.Name} is readonly.");
  340. }
  341. LogPropertySet(property, value, priority);
  342. property.Setter(this, UnsetToDefault(value, property));
  343. }
  344. else
  345. {
  346. PriorityValue v;
  347. var originalValue = value;
  348. if (!PerspexPropertyRegistry.Instance.IsRegistered(this, property))
  349. {
  350. ThrowNotRegistered(property);
  351. }
  352. if (!TypeUtilities.TryCast(property.PropertyType, value, out value))
  353. {
  354. throw new ArgumentException(string.Format(
  355. "Invalid value for Property '{0}': '{1}' ({2})",
  356. property.Name,
  357. originalValue,
  358. originalValue?.GetType().FullName ?? "(null)"));
  359. }
  360. if (!_values.TryGetValue(property, out v))
  361. {
  362. if (value == PerspexProperty.UnsetValue)
  363. {
  364. return;
  365. }
  366. v = CreatePriorityValue(property);
  367. _values.Add(property, v);
  368. }
  369. LogPropertySet(property, value, priority);
  370. v.SetValue(value, (int)priority);
  371. }
  372. }
  373. /// <summary>
  374. /// Sets a <see cref="PerspexProperty"/> value.
  375. /// </summary>
  376. /// <typeparam name="T">The type of the property.</typeparam>
  377. /// <param name="property">The property.</param>
  378. /// <param name="value">The value.</param>
  379. /// <param name="priority">The priority of the value.</param>
  380. public void SetValue<T>(
  381. PerspexProperty<T> property,
  382. T value,
  383. BindingPriority priority = BindingPriority.LocalValue)
  384. {
  385. Contract.Requires<ArgumentNullException>(property != null);
  386. VerifyAccess();
  387. if (property.IsDirect)
  388. {
  389. property = (PerspexProperty<T>)GetRegistered(property);
  390. if (property.Setter == null)
  391. {
  392. throw new ArgumentException($"The property {property.Name} is readonly.");
  393. }
  394. LogPropertySet(property, value, priority);
  395. property.Setter(this, value);
  396. }
  397. else
  398. {
  399. SetValue((PerspexProperty)property, value, priority);
  400. }
  401. }
  402. /// <summary>
  403. /// Binds a <see cref="PerspexProperty"/> to an observable.
  404. /// </summary>
  405. /// <param name="property">The property.</param>
  406. /// <param name="source">The observable.</param>
  407. /// <param name="priority">The priority of the binding.</param>
  408. /// <returns>
  409. /// A disposable which can be used to terminate the binding.
  410. /// </returns>
  411. public IDisposable Bind(
  412. PerspexProperty property,
  413. IObservable<object> source,
  414. BindingPriority priority = BindingPriority.LocalValue)
  415. {
  416. Contract.Requires<ArgumentNullException>(property != null);
  417. VerifyAccess();
  418. if (property.IsDirect)
  419. {
  420. property = GetRegistered(property);
  421. if (property.Setter == null)
  422. {
  423. throw new ArgumentException($"The property {property.Name} is readonly.");
  424. }
  425. _propertyLog.Verbose(
  426. "Bound {Property} to {Binding} with priority LocalValue",
  427. property,
  428. GetDescription(source));
  429. return source
  430. .Select(x => TypeUtilities.CastOrDefault(x, property.PropertyType))
  431. .Subscribe(x => SetValue(property, x));
  432. }
  433. else
  434. {
  435. PriorityValue v;
  436. if (!PerspexPropertyRegistry.Instance.IsRegistered(this, property))
  437. {
  438. ThrowNotRegistered(property);
  439. }
  440. if (!_values.TryGetValue(property, out v))
  441. {
  442. v = CreatePriorityValue(property);
  443. _values.Add(property, v);
  444. }
  445. _propertyLog.Verbose(
  446. "Bound {Property} to {Binding} with priority {Priority}",
  447. property,
  448. GetDescription(source),
  449. priority);
  450. return v.Add(source, (int)priority);
  451. }
  452. }
  453. /// <summary>
  454. /// Binds a <see cref="PerspexProperty"/> to an observable.
  455. /// </summary>
  456. /// <typeparam name="T">The type of the property.</typeparam>
  457. /// <param name="property">The property.</param>
  458. /// <param name="source">The observable.</param>
  459. /// <param name="priority">The priority of the binding.</param>
  460. /// <returns>
  461. /// A disposable which can be used to terminate the binding.
  462. /// </returns>
  463. public IDisposable Bind<T>(
  464. PerspexProperty<T> property,
  465. IObservable<T> source,
  466. BindingPriority priority = BindingPriority.LocalValue)
  467. {
  468. Contract.Requires<ArgumentNullException>(property != null);
  469. VerifyAccess();
  470. if (property.IsDirect)
  471. {
  472. property = (PerspexProperty<T>)GetRegistered(property);
  473. if (property.Setter == null)
  474. {
  475. throw new ArgumentException($"The property {property.Name} is readonly.");
  476. }
  477. return source.Subscribe(x => SetValue(property, x));
  478. }
  479. else
  480. {
  481. return Bind((PerspexProperty)property, source.Select(x => (object)x), priority);
  482. }
  483. }
  484. /// <summary>
  485. /// Initiates a two-way binding between <see cref="PerspexProperty"/>s.
  486. /// </summary>
  487. /// <param name="property">The property on this object.</param>
  488. /// <param name="source">The source object.</param>
  489. /// <param name="sourceProperty">The property on the source object.</param>
  490. /// <param name="priority">The priority of the binding.</param>
  491. /// <returns>
  492. /// A disposable which can be used to terminate the binding.
  493. /// </returns>
  494. /// <remarks>
  495. /// The binding is first carried out from <paramref name="source"/> to this.
  496. /// </remarks>
  497. public IDisposable BindTwoWay(
  498. PerspexProperty property,
  499. PerspexObject source,
  500. PerspexProperty sourceProperty,
  501. BindingPriority priority = BindingPriority.LocalValue)
  502. {
  503. VerifyAccess();
  504. _propertyLog.Verbose(
  505. "Bound two way {Property} to {Binding} with priority {Priority}",
  506. property,
  507. source,
  508. priority);
  509. return new CompositeDisposable(
  510. Bind(property, source.GetObservable(sourceProperty)),
  511. source.Bind(sourceProperty, GetObservable(property)));
  512. }
  513. /// <summary>
  514. /// Initiates a two-way binding between a <see cref="PerspexProperty"/> and an
  515. /// <see cref="ISubject{Object}"/>.
  516. /// </summary>
  517. /// <param name="property">The property on this object.</param>
  518. /// <param name="source">The subject to bind to.</param>
  519. /// <param name="priority">The priority of the binding.</param>
  520. /// <returns>
  521. /// A disposable which can be used to terminate the binding.
  522. /// </returns>
  523. /// <remarks>
  524. /// The binding is first carried out from <paramref name="source"/> to this.
  525. /// </remarks>
  526. public IDisposable BindTwoWay(
  527. PerspexProperty property,
  528. ISubject<object> source,
  529. BindingPriority priority = BindingPriority.LocalValue)
  530. {
  531. VerifyAccess();
  532. _propertyLog.Verbose(
  533. "Bound two way {Property} to {Binding} with priority {Priority}",
  534. property,
  535. GetDescription(source),
  536. priority);
  537. return new CompositeDisposable(
  538. Bind(property, source),
  539. GetObservable(property).Subscribe(source));
  540. }
  541. /// <summary>
  542. /// Forces the specified property to be revalidated.
  543. /// </summary>
  544. /// <param name="property">The property.</param>
  545. public void Revalidate(PerspexProperty property)
  546. {
  547. VerifyAccess();
  548. PriorityValue value;
  549. if (_values.TryGetValue(property, out value))
  550. {
  551. value.Revalidate();
  552. }
  553. }
  554. /// <inheritdoc/>
  555. bool IPropertyBag.IsRegistered(PerspexProperty property)
  556. {
  557. return PerspexPropertyRegistry.Instance.IsRegistered(this, property);
  558. }
  559. /// <summary>
  560. /// Gets all priority values set on the object.
  561. /// </summary>
  562. /// <returns>A collection of property/value tuples.</returns>
  563. internal IDictionary<PerspexProperty, PriorityValue> GetSetValues()
  564. {
  565. return _values;
  566. }
  567. /// <summary>
  568. /// Forces revalidation of properties when a property value changes.
  569. /// </summary>
  570. /// <param name="property">The property to that affects validation.</param>
  571. /// <param name="affected">The affected properties.</param>
  572. protected static void AffectsValidation(PerspexProperty property, params PerspexProperty[] affected)
  573. {
  574. property.Changed.Subscribe(e =>
  575. {
  576. foreach (var p in affected)
  577. {
  578. e.Sender.Revalidate(p);
  579. }
  580. });
  581. }
  582. /// <summary>
  583. /// Called when a perspex property changes on the object.
  584. /// </summary>
  585. /// <param name="e">The event arguments.</param>
  586. protected virtual void OnPropertyChanged(PerspexPropertyChangedEventArgs e)
  587. {
  588. }
  589. /// <summary>
  590. /// Raises the <see cref="PropertyChanged"/> event.
  591. /// </summary>
  592. /// <param name="property">The property that has changed.</param>
  593. /// <param name="oldValue">The old property value.</param>
  594. /// <param name="newValue">The new property value.</param>
  595. /// <param name="priority">The priority of the binding that produced the value.</param>
  596. protected void RaisePropertyChanged(
  597. PerspexProperty property,
  598. object oldValue,
  599. object newValue,
  600. BindingPriority priority)
  601. {
  602. Contract.Requires<ArgumentNullException>(property != null);
  603. VerifyAccess();
  604. PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs(
  605. this,
  606. property,
  607. oldValue,
  608. newValue,
  609. priority);
  610. if (property.Notifying != null)
  611. {
  612. property.Notifying(this, true);
  613. }
  614. try
  615. {
  616. OnPropertyChanged(e);
  617. property.NotifyChanged(e);
  618. if (PropertyChanged != null)
  619. {
  620. PropertyChanged(this, e);
  621. }
  622. if (_inpcChanged != null)
  623. {
  624. PropertyChangedEventArgs e2 = new PropertyChangedEventArgs(property.Name);
  625. _inpcChanged(this, e2);
  626. }
  627. }
  628. finally
  629. {
  630. if (property.Notifying != null)
  631. {
  632. property.Notifying(this, false);
  633. }
  634. }
  635. }
  636. /// <summary>
  637. /// Sets the backing field for a direct perspex property, raising the
  638. /// <see cref="PropertyChanged"/> event if the value has changed.
  639. /// </summary>
  640. /// <typeparam name="T">The type of the property.</typeparam>
  641. /// <param name="property">The property.</param>
  642. /// <param name="field">The backing field.</param>
  643. /// <param name="value">The value.</param>
  644. /// <returns>
  645. /// True if the value changed, otherwise false.
  646. /// </returns>
  647. protected bool SetAndRaise<T>(PerspexProperty<T> property, ref T field, T value)
  648. {
  649. VerifyAccess();
  650. if (!object.Equals(field, value))
  651. {
  652. var old = field;
  653. field = value;
  654. RaisePropertyChanged(property, old, value, BindingPriority.LocalValue);
  655. return true;
  656. }
  657. else
  658. {
  659. return false;
  660. }
  661. }
  662. /// <summary>
  663. /// Converts an unset value to the default value for a property type.
  664. /// </summary>
  665. /// <param name="value">The value.</param>
  666. /// <param name="property">The property.</param>
  667. /// <returns>The value.</returns>
  668. private static object UnsetToDefault(object value, PerspexProperty property)
  669. {
  670. return value == PerspexProperty.UnsetValue ?
  671. TypeUtilities.Default(property.PropertyType) :
  672. value;
  673. }
  674. /// <summary>
  675. /// Creates a <see cref="PriorityValue"/> for a <see cref="PerspexProperty"/>.
  676. /// </summary>
  677. /// <param name="property">The property.</param>
  678. /// <returns>The <see cref="PriorityValue"/>.</returns>
  679. private PriorityValue CreatePriorityValue(PerspexProperty property)
  680. {
  681. Func<PerspexObject, object, object> validate = property.GetValidationFunc(GetType());
  682. Func<object, object> validate2 = null;
  683. if (validate != null)
  684. {
  685. validate2 = v => validate(this, v);
  686. }
  687. PriorityValue result = new PriorityValue(property.Name, property.PropertyType, validate2);
  688. result.Changed.Subscribe(x =>
  689. {
  690. object oldValue = (x.Item1 == PerspexProperty.UnsetValue) ?
  691. GetDefaultValue(property) :
  692. x.Item1;
  693. object newValue = (x.Item2 == PerspexProperty.UnsetValue) ?
  694. GetDefaultValue(property) :
  695. x.Item2;
  696. if (!Equals(oldValue, newValue))
  697. {
  698. RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)result.ValuePriority);
  699. _propertyLog.Verbose(
  700. "{Property} changed from {$Old} to {$Value} with priority {Priority}",
  701. property,
  702. oldValue,
  703. newValue,
  704. (BindingPriority)result.ValuePriority);
  705. }
  706. });
  707. return result;
  708. }
  709. /// <summary>
  710. /// Gets the default value for a property.
  711. /// </summary>
  712. /// <param name="property">The property.</param>
  713. /// <returns>The default value.</returns>
  714. private object GetDefaultValue(PerspexProperty property)
  715. {
  716. if (property.Inherits && _inheritanceParent != null)
  717. {
  718. return _inheritanceParent.GetValue(property);
  719. }
  720. else
  721. {
  722. return property.GetDefaultValue(GetType());
  723. }
  724. }
  725. /// <summary>
  726. /// Given a <see cref="PerspexProperty"/> returns a registered perspex property that is
  727. /// equal or throws if not found.
  728. /// </summary>
  729. /// <param name="property">The property.</param>
  730. /// <returns>The registered property.</returns>
  731. public PerspexProperty GetRegistered(PerspexProperty property)
  732. {
  733. var result = PerspexPropertyRegistry.Instance.FindRegistered(this, property);
  734. if (result == null)
  735. {
  736. ThrowNotRegistered(property);
  737. }
  738. return result;
  739. }
  740. /// <summary>
  741. /// Called when a property is changed on the current <see cref="InheritanceParent"/>.
  742. /// </summary>
  743. /// <param name="sender">The event sender.</param>
  744. /// <param name="e">The event args.</param>
  745. /// <remarks>
  746. /// Checks for changes in an inherited property value.
  747. /// </remarks>
  748. private void ParentPropertyChanged(object sender, PerspexPropertyChangedEventArgs e)
  749. {
  750. Contract.Requires<ArgumentNullException>(e != null);
  751. if (e.Property.Inherits && !IsSet(e.Property))
  752. {
  753. RaisePropertyChanged(e.Property, e.OldValue, e.NewValue, BindingPriority.LocalValue);
  754. }
  755. }
  756. /// <summary>
  757. /// Gets a description of a property that van be used in observables.
  758. /// </summary>
  759. /// <param name="property">The property</param>
  760. /// <returns>The description.</returns>
  761. private string GetDescription(PerspexProperty property)
  762. {
  763. return string.Format("{0}.{1}", GetType().Name, property.Name);
  764. }
  765. /// <summary>
  766. /// Gets a description of an observable that van be used in logs.
  767. /// </summary>
  768. /// <param name="o">The observable.</param>
  769. /// <returns>The description.</returns>
  770. private string GetDescription(IObservable<object> o)
  771. {
  772. var description = o as IDescription;
  773. return description?.Description ?? o.ToString();
  774. }
  775. /// <summary>
  776. /// Logs a property set message.
  777. /// </summary>
  778. /// <param name="property">The property.</param>
  779. /// <param name="value">The new value.</param>
  780. /// <param name="priority">The priority.</param>
  781. private void LogPropertySet(PerspexProperty property, object value, BindingPriority priority)
  782. {
  783. _propertyLog.Verbose(
  784. "Set {Property} to {$Value} with priority {Priority}",
  785. property,
  786. value,
  787. priority);
  788. }
  789. /// <summary>
  790. /// Throws an exception indicating that the specified property is not registered on this
  791. /// object.
  792. /// </summary>
  793. /// <param name="p">The property</param>
  794. private void ThrowNotRegistered(PerspexProperty p)
  795. {
  796. throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}");
  797. }
  798. }
  799. }