AvaloniaObject.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. // Copyright (c) The Avalonia 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 Avalonia.Data;
  10. using Avalonia.Diagnostics;
  11. using Avalonia.Logging;
  12. using Avalonia.Threading;
  13. using Avalonia.Utilities;
  14. using System.Reactive.Concurrency;
  15. namespace Avalonia
  16. {
  17. /// <summary>
  18. /// An object with <see cref="AvaloniaProperty"/> support.
  19. /// </summary>
  20. /// <remarks>
  21. /// This class is analogous to DependencyObject in WPF.
  22. /// </remarks>
  23. public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IPriorityValueOwner
  24. {
  25. /// <summary>
  26. /// The parent object that inherited values are inherited from.
  27. /// </summary>
  28. private IAvaloniaObject _inheritanceParent;
  29. /// <summary>
  30. /// The set values/bindings on this object.
  31. /// </summary>
  32. private readonly Dictionary<AvaloniaProperty, PriorityValue> _values =
  33. new Dictionary<AvaloniaProperty, PriorityValue>();
  34. /// <summary>
  35. /// Maintains a list of direct property binding subscriptions so that the binding source
  36. /// doesn't get collected.
  37. /// </summary>
  38. private List<IDisposable> _directBindings;
  39. /// <summary>
  40. /// Event handler for <see cref="INotifyPropertyChanged"/> implementation.
  41. /// </summary>
  42. private PropertyChangedEventHandler _inpcChanged;
  43. /// <summary>
  44. /// Event handler for <see cref="PropertyChanged"/> implementation.
  45. /// </summary>
  46. private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
  47. /// <summary>
  48. /// Initializes a new instance of the <see cref="AvaloniaObject"/> class.
  49. /// </summary>
  50. public AvaloniaObject()
  51. {
  52. foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this))
  53. {
  54. object value = property.IsDirect ?
  55. ((IDirectPropertyAccessor)property).GetValue(this) :
  56. ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
  57. var e = new AvaloniaPropertyChangedEventArgs(
  58. this,
  59. property,
  60. AvaloniaProperty.UnsetValue,
  61. value,
  62. BindingPriority.Unset);
  63. property.NotifyInitialized(e);
  64. }
  65. }
  66. /// <summary>
  67. /// Raised when a <see cref="AvaloniaProperty"/> value changes on this object.
  68. /// </summary>
  69. public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged
  70. {
  71. add { _propertyChanged += value; }
  72. remove { _propertyChanged -= value; }
  73. }
  74. /// <summary>
  75. /// Raised when a <see cref="AvaloniaProperty"/> value changes on this object.
  76. /// </summary>
  77. event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
  78. {
  79. add { _inpcChanged += value; }
  80. remove { _inpcChanged -= value; }
  81. }
  82. /// <summary>
  83. /// Gets or sets the parent object that inherited <see cref="AvaloniaProperty"/> values
  84. /// are inherited from.
  85. /// </summary>
  86. /// <value>
  87. /// The inheritance parent.
  88. /// </value>
  89. protected IAvaloniaObject InheritanceParent
  90. {
  91. get
  92. {
  93. return _inheritanceParent;
  94. }
  95. set
  96. {
  97. if (_inheritanceParent != value)
  98. {
  99. if (_inheritanceParent != null)
  100. {
  101. _inheritanceParent.PropertyChanged -= ParentPropertyChanged;
  102. }
  103. var inherited = (from property in AvaloniaPropertyRegistry.Instance.GetRegistered(this)
  104. where property.Inherits
  105. select new
  106. {
  107. Property = property,
  108. Value = GetValue(property),
  109. }).ToList();
  110. _inheritanceParent = value;
  111. foreach (var i in inherited)
  112. {
  113. object newValue = GetValue(i.Property);
  114. if (!Equals(i.Value, newValue))
  115. {
  116. RaisePropertyChanged(i.Property, i.Value, newValue, BindingPriority.LocalValue);
  117. }
  118. }
  119. if (_inheritanceParent != null)
  120. {
  121. _inheritanceParent.PropertyChanged += ParentPropertyChanged;
  122. }
  123. }
  124. }
  125. }
  126. /// <summary>
  127. /// Gets or sets the value of a <see cref="AvaloniaProperty"/>.
  128. /// </summary>
  129. /// <param name="property">The property.</param>
  130. public object this[AvaloniaProperty property]
  131. {
  132. get { return GetValue(property); }
  133. set { SetValue(property, value); }
  134. }
  135. /// <summary>
  136. /// Gets or sets a binding for a <see cref="AvaloniaProperty"/>.
  137. /// </summary>
  138. /// <param name="binding">The binding information.</param>
  139. public IBinding this[IndexerDescriptor binding]
  140. {
  141. get
  142. {
  143. return new IndexerBinding(this, binding.Property, binding.Mode);
  144. }
  145. set
  146. {
  147. var sourceBinding = value as IBinding;
  148. this.Bind(binding.Property, sourceBinding);
  149. }
  150. }
  151. public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
  152. public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
  153. /// <summary>
  154. /// Clears a <see cref="AvaloniaProperty"/>'s local value.
  155. /// </summary>
  156. /// <param name="property">The property.</param>
  157. public void ClearValue(AvaloniaProperty property)
  158. {
  159. Contract.Requires<ArgumentNullException>(property != null);
  160. VerifyAccess();
  161. SetValue(property, AvaloniaProperty.UnsetValue);
  162. }
  163. /// <summary>
  164. /// Gets a <see cref="AvaloniaProperty"/> value.
  165. /// </summary>
  166. /// <param name="property">The property.</param>
  167. /// <returns>The value.</returns>
  168. public object GetValue(AvaloniaProperty property)
  169. {
  170. Contract.Requires<ArgumentNullException>(property != null);
  171. VerifyAccess();
  172. if (property.IsDirect)
  173. {
  174. return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this);
  175. }
  176. else
  177. {
  178. if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
  179. {
  180. ThrowNotRegistered(property);
  181. }
  182. return GetValueInternal(property);
  183. }
  184. }
  185. /// <summary>
  186. /// Gets a <see cref="AvaloniaProperty"/> value.
  187. /// </summary>
  188. /// <typeparam name="T">The type of the property.</typeparam>
  189. /// <param name="property">The property.</param>
  190. /// <returns>The value.</returns>
  191. public T GetValue<T>(AvaloniaProperty<T> property)
  192. {
  193. Contract.Requires<ArgumentNullException>(property != null);
  194. return (T)GetValue((AvaloniaProperty)property);
  195. }
  196. /// <summary>
  197. /// Checks whether a <see cref="AvaloniaProperty"/> is set on this object.
  198. /// </summary>
  199. /// <param name="property">The property.</param>
  200. /// <returns>True if the property is set, otherwise false.</returns>
  201. /// <remarks>
  202. /// Checks whether a value is assigned to the property, or that there is a binding to the
  203. /// property that is producing a value other than <see cref="AvaloniaProperty.UnsetValue"/>.
  204. /// </remarks>
  205. public bool IsSet(AvaloniaProperty property)
  206. {
  207. Contract.Requires<ArgumentNullException>(property != null);
  208. VerifyAccess();
  209. PriorityValue value;
  210. if (_values.TryGetValue(property, out value))
  211. {
  212. return value.Value != AvaloniaProperty.UnsetValue;
  213. }
  214. return false;
  215. }
  216. /// <summary>
  217. /// Sets a <see cref="AvaloniaProperty"/> value.
  218. /// </summary>
  219. /// <param name="property">The property.</param>
  220. /// <param name="value">The value.</param>
  221. /// <param name="priority">The priority of the value.</param>
  222. public void SetValue(
  223. AvaloniaProperty property,
  224. object value,
  225. BindingPriority priority = BindingPriority.LocalValue)
  226. {
  227. Contract.Requires<ArgumentNullException>(property != null);
  228. VerifyAccess();
  229. if (property.IsDirect)
  230. {
  231. SetDirectValue(property, value);
  232. }
  233. else
  234. {
  235. SetStyledValue(property, value, priority);
  236. }
  237. }
  238. /// <summary>
  239. /// Sets a <see cref="AvaloniaProperty"/> value.
  240. /// </summary>
  241. /// <typeparam name="T">The type of the property.</typeparam>
  242. /// <param name="property">The property.</param>
  243. /// <param name="value">The value.</param>
  244. /// <param name="priority">The priority of the value.</param>
  245. public void SetValue<T>(
  246. AvaloniaProperty<T> property,
  247. T value,
  248. BindingPriority priority = BindingPriority.LocalValue)
  249. {
  250. Contract.Requires<ArgumentNullException>(property != null);
  251. SetValue((AvaloniaProperty)property, value, priority);
  252. }
  253. /// <summary>
  254. /// Binds a <see cref="AvaloniaProperty"/> to an observable.
  255. /// </summary>
  256. /// <param name="property">The property.</param>
  257. /// <param name="source">The observable.</param>
  258. /// <param name="priority">The priority of the binding.</param>
  259. /// <returns>
  260. /// A disposable which can be used to terminate the binding.
  261. /// </returns>
  262. public IDisposable Bind(
  263. AvaloniaProperty property,
  264. IObservable<object> source,
  265. BindingPriority priority = BindingPriority.LocalValue)
  266. {
  267. Contract.Requires<ArgumentNullException>(property != null);
  268. Contract.Requires<ArgumentNullException>(source != null);
  269. VerifyAccess();
  270. var description = GetDescription(source);
  271. var scheduler = AvaloniaLocator.Current.GetService<IScheduler>() ?? ImmediateScheduler.Instance;
  272. source = source.ObserveOn(scheduler);
  273. if (property.IsDirect)
  274. {
  275. if (property.IsReadOnly)
  276. {
  277. throw new ArgumentException($"The property {property.Name} is readonly.");
  278. }
  279. Logger.Verbose(
  280. LogArea.Property,
  281. this,
  282. "Bound {Property} to {Binding} with priority LocalValue",
  283. property,
  284. description);
  285. IDisposable subscription = null;
  286. if (_directBindings == null)
  287. {
  288. _directBindings = new List<IDisposable>();
  289. }
  290. subscription = source
  291. .Select(x => CastOrDefault(x, property.PropertyType))
  292. .Do(_ => { }, () => _directBindings.Remove(subscription))
  293. .Subscribe(x => SetDirectValue(property, x));
  294. _directBindings.Add(subscription);
  295. return Disposable.Create(() =>
  296. {
  297. subscription.Dispose();
  298. _directBindings.Remove(subscription);
  299. });
  300. }
  301. else
  302. {
  303. PriorityValue v;
  304. if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
  305. {
  306. ThrowNotRegistered(property);
  307. }
  308. if (!_values.TryGetValue(property, out v))
  309. {
  310. v = CreatePriorityValue(property);
  311. _values.Add(property, v);
  312. }
  313. Logger.Verbose(
  314. LogArea.Property,
  315. this,
  316. "Bound {Property} to {Binding} with priority {Priority}",
  317. property,
  318. description,
  319. priority);
  320. return v.Add(source, (int)priority);
  321. }
  322. }
  323. /// <summary>
  324. /// Binds a <see cref="AvaloniaProperty"/> to an observable.
  325. /// </summary>
  326. /// <typeparam name="T">The type of the property.</typeparam>
  327. /// <param name="property">The property.</param>
  328. /// <param name="source">The observable.</param>
  329. /// <param name="priority">The priority of the binding.</param>
  330. /// <returns>
  331. /// A disposable which can be used to terminate the binding.
  332. /// </returns>
  333. public IDisposable Bind<T>(
  334. AvaloniaProperty<T> property,
  335. IObservable<T> source,
  336. BindingPriority priority = BindingPriority.LocalValue)
  337. {
  338. Contract.Requires<ArgumentNullException>(property != null);
  339. return Bind(property, source.Select(x => (object)x), priority);
  340. }
  341. /// <summary>
  342. /// Forces the specified property to be revalidated.
  343. /// </summary>
  344. /// <param name="property">The property.</param>
  345. public void Revalidate(AvaloniaProperty property)
  346. {
  347. VerifyAccess();
  348. PriorityValue value;
  349. if (_values.TryGetValue(property, out value))
  350. {
  351. value.Revalidate();
  352. }
  353. }
  354. /// <inheritdoc/>
  355. void IPriorityValueOwner.Changed(PriorityValue sender, object oldValue, object newValue)
  356. {
  357. var property = sender.Property;
  358. var priority = (BindingPriority)sender.ValuePriority;
  359. oldValue = (oldValue == AvaloniaProperty.UnsetValue) ?
  360. GetDefaultValue(property) :
  361. oldValue;
  362. newValue = (newValue == AvaloniaProperty.UnsetValue) ?
  363. GetDefaultValue(property) :
  364. newValue;
  365. if (!Equals(oldValue, newValue))
  366. {
  367. RaisePropertyChanged(property, oldValue, newValue, priority);
  368. Logger.Verbose(
  369. LogArea.Property,
  370. this,
  371. "{Property} changed from {$Old} to {$Value} with priority {Priority}",
  372. property,
  373. oldValue,
  374. newValue,
  375. priority);
  376. }
  377. }
  378. /// <inheritdoc/>
  379. void IPriorityValueOwner.BindingNotificationReceived(PriorityValue sender, BindingNotification notification)
  380. {
  381. UpdateDataValidation(sender.Property, notification);
  382. }
  383. /// <inheritdoc/>
  384. Delegate[] IAvaloniaObjectDebug.GetPropertyChangedSubscribers()
  385. {
  386. return _propertyChanged?.GetInvocationList();
  387. }
  388. /// <summary>
  389. /// Gets all priority values set on the object.
  390. /// </summary>
  391. /// <returns>A collection of property/value tuples.</returns>
  392. internal IDictionary<AvaloniaProperty, PriorityValue> GetSetValues()
  393. {
  394. return _values;
  395. }
  396. /// <summary>
  397. /// Forces revalidation of properties when a property value changes.
  398. /// </summary>
  399. /// <param name="property">The property to that affects validation.</param>
  400. /// <param name="affected">The affected properties.</param>
  401. protected static void AffectsValidation(AvaloniaProperty property, params AvaloniaProperty[] affected)
  402. {
  403. property.Changed.Subscribe(e =>
  404. {
  405. foreach (var p in affected)
  406. {
  407. e.Sender.Revalidate(p);
  408. }
  409. });
  410. }
  411. /// <summary>
  412. /// Called to update the validation state for properties for which data validation is
  413. /// enabled.
  414. /// </summary>
  415. /// <param name="property">The property.</param>
  416. /// <param name="status">The new validation status.</param>
  417. protected virtual void UpdateDataValidation(
  418. AvaloniaProperty property,
  419. BindingNotification status)
  420. {
  421. }
  422. /// <summary>
  423. /// Called when a avalonia property changes on the object.
  424. /// </summary>
  425. /// <param name="e">The event arguments.</param>
  426. protected virtual void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
  427. {
  428. }
  429. /// <summary>
  430. /// Raises the <see cref="PropertyChanged"/> event.
  431. /// </summary>
  432. /// <param name="property">The property that has changed.</param>
  433. /// <param name="oldValue">The old property value.</param>
  434. /// <param name="newValue">The new property value.</param>
  435. /// <param name="priority">The priority of the binding that produced the value.</param>
  436. protected void RaisePropertyChanged(
  437. AvaloniaProperty property,
  438. object oldValue,
  439. object newValue,
  440. BindingPriority priority = BindingPriority.LocalValue)
  441. {
  442. Contract.Requires<ArgumentNullException>(property != null);
  443. VerifyAccess();
  444. AvaloniaPropertyChangedEventArgs e = new AvaloniaPropertyChangedEventArgs(
  445. this,
  446. property,
  447. oldValue,
  448. newValue,
  449. priority);
  450. property.Notifying?.Invoke(this, true);
  451. try
  452. {
  453. OnPropertyChanged(e);
  454. property.NotifyChanged(e);
  455. _propertyChanged?.Invoke(this, e);
  456. if (_inpcChanged != null)
  457. {
  458. PropertyChangedEventArgs e2 = new PropertyChangedEventArgs(property.Name);
  459. _inpcChanged(this, e2);
  460. }
  461. }
  462. finally
  463. {
  464. property.Notifying?.Invoke(this, false);
  465. }
  466. }
  467. /// <summary>
  468. /// Sets the backing field for a direct avalonia property, raising the
  469. /// <see cref="PropertyChanged"/> event if the value has changed.
  470. /// </summary>
  471. /// <typeparam name="T">The type of the property.</typeparam>
  472. /// <param name="property">The property.</param>
  473. /// <param name="field">The backing field.</param>
  474. /// <param name="value">The value.</param>
  475. /// <returns>
  476. /// True if the value changed, otherwise false.
  477. /// </returns>
  478. protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value)
  479. {
  480. VerifyAccess();
  481. if (!object.Equals(field, value))
  482. {
  483. var old = field;
  484. field = value;
  485. RaisePropertyChanged(property, old, value, BindingPriority.LocalValue);
  486. return true;
  487. }
  488. else
  489. {
  490. return false;
  491. }
  492. }
  493. /// <summary>
  494. /// Tries to cast a value to a type, taking into account that the value may be a
  495. /// <see cref="BindingNotification"/>.
  496. /// </summary>
  497. /// <param name="value">The value.</param>
  498. /// <param name="type">The type.</param>
  499. /// <returns>The cast value, or a <see cref="BindingNotification"/>.</returns>
  500. private static object CastOrDefault(object value, Type type)
  501. {
  502. var notification = value as BindingNotification;
  503. if (notification == null)
  504. {
  505. return TypeUtilities.ConvertImplicitOrDefault(value, type);
  506. }
  507. else
  508. {
  509. if (notification.HasValue)
  510. {
  511. notification.SetValue(TypeUtilities.ConvertImplicitOrDefault(notification.Value, type));
  512. }
  513. return notification;
  514. }
  515. }
  516. /// <summary>
  517. /// Creates a <see cref="PriorityValue"/> for a <see cref="AvaloniaProperty"/>.
  518. /// </summary>
  519. /// <param name="property">The property.</param>
  520. /// <returns>The <see cref="PriorityValue"/>.</returns>
  521. private PriorityValue CreatePriorityValue(AvaloniaProperty property)
  522. {
  523. var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(GetType());
  524. Func<object, object> validate2 = null;
  525. if (validate != null)
  526. {
  527. validate2 = v => validate(this, v);
  528. }
  529. PriorityValue result = new PriorityValue(
  530. this,
  531. property,
  532. property.PropertyType,
  533. validate2);
  534. return result;
  535. }
  536. /// <summary>
  537. /// Gets the default value for a property.
  538. /// </summary>
  539. /// <param name="property">The property.</param>
  540. /// <returns>The default value.</returns>
  541. private object GetDefaultValue(AvaloniaProperty property)
  542. {
  543. if (property.Inherits && _inheritanceParent is AvaloniaObject aobj)
  544. return aobj.GetValueInternal(property);
  545. return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
  546. }
  547. /// <summary>
  548. /// Gets a <see cref="AvaloniaProperty"/> value
  549. /// without check for registered as this can slow getting the value
  550. /// this method is intended for internal usage in AvaloniaObject only
  551. /// it's called only after check the property is registered
  552. /// </summary>
  553. /// <param name="property">The property.</param>
  554. /// <returns>The value.</returns>
  555. private object GetValueInternal(AvaloniaProperty property)
  556. {
  557. object result = AvaloniaProperty.UnsetValue;
  558. PriorityValue value;
  559. if (_values.TryGetValue(property, out value))
  560. {
  561. result = value.Value;
  562. }
  563. if (result == AvaloniaProperty.UnsetValue)
  564. {
  565. result = GetDefaultValue(property);
  566. }
  567. return result;
  568. }
  569. /// <summary>
  570. /// Sets the value of a direct property.
  571. /// </summary>
  572. /// <param name="property">The property.</param>
  573. /// <param name="value">The value.</param>
  574. private void SetDirectValue(AvaloniaProperty property, object value)
  575. {
  576. var notification = value as BindingNotification;
  577. if (notification != null)
  578. {
  579. notification.LogIfError(this, property);
  580. value = notification.Value;
  581. }
  582. if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue)
  583. {
  584. var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType());
  585. var accessor = (IDirectPropertyAccessor)GetRegistered(property);
  586. var finalValue = value == AvaloniaProperty.UnsetValue ?
  587. metadata.UnsetValue : value;
  588. LogPropertySet(property, value, BindingPriority.LocalValue);
  589. accessor.SetValue(this, finalValue);
  590. }
  591. if (notification != null)
  592. {
  593. UpdateDataValidation(property, notification);
  594. }
  595. }
  596. /// <summary>
  597. /// Sets the value of a styled property.
  598. /// </summary>
  599. /// <param name="property">The property.</param>
  600. /// <param name="value">The value.</param>
  601. /// <param name="priority">The priority of the value.</param>
  602. private void SetStyledValue(AvaloniaProperty property, object value, BindingPriority priority)
  603. {
  604. var notification = value as BindingNotification;
  605. // We currently accept BindingNotifications for non-direct properties but we just
  606. // strip them to their underlying value.
  607. if (notification != null)
  608. {
  609. if (!notification.HasValue)
  610. {
  611. return;
  612. }
  613. else
  614. {
  615. value = notification.Value;
  616. }
  617. }
  618. var originalValue = value;
  619. if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
  620. {
  621. ThrowNotRegistered(property);
  622. }
  623. if (!TypeUtilities.TryConvertImplicit(property.PropertyType, value, out value))
  624. {
  625. throw new ArgumentException(string.Format(
  626. "Invalid value for Property '{0}': '{1}' ({2})",
  627. property.Name,
  628. originalValue,
  629. originalValue?.GetType().FullName ?? "(null)"));
  630. }
  631. PriorityValue v;
  632. if (!_values.TryGetValue(property, out v))
  633. {
  634. if (value == AvaloniaProperty.UnsetValue)
  635. {
  636. return;
  637. }
  638. v = CreatePriorityValue(property);
  639. _values.Add(property, v);
  640. }
  641. LogPropertySet(property, value, priority);
  642. v.SetValue(value, (int)priority);
  643. }
  644. /// <summary>
  645. /// Given a <see cref="AvaloniaProperty"/> returns a registered avalonia property that is
  646. /// equal or throws if not found.
  647. /// </summary>
  648. /// <param name="property">The property.</param>
  649. /// <returns>The registered property.</returns>
  650. public AvaloniaProperty GetRegistered(AvaloniaProperty property)
  651. {
  652. var result = AvaloniaPropertyRegistry.Instance.FindRegistered(this, property);
  653. if (result == null)
  654. {
  655. ThrowNotRegistered(property);
  656. }
  657. return result;
  658. }
  659. /// <summary>
  660. /// Called when a property is changed on the current <see cref="InheritanceParent"/>.
  661. /// </summary>
  662. /// <param name="sender">The event sender.</param>
  663. /// <param name="e">The event args.</param>
  664. /// <remarks>
  665. /// Checks for changes in an inherited property value.
  666. /// </remarks>
  667. private void ParentPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
  668. {
  669. Contract.Requires<ArgumentNullException>(e != null);
  670. if (e.Property.Inherits && !IsSet(e.Property))
  671. {
  672. RaisePropertyChanged(e.Property, e.OldValue, e.NewValue, BindingPriority.LocalValue);
  673. }
  674. }
  675. /// <summary>
  676. /// Gets a description of an observable that van be used in logs.
  677. /// </summary>
  678. /// <param name="o">The observable.</param>
  679. /// <returns>The description.</returns>
  680. private string GetDescription(IObservable<object> o)
  681. {
  682. var description = o as IDescription;
  683. return description?.Description ?? o.ToString();
  684. }
  685. /// <summary>
  686. /// Logs a property set message.
  687. /// </summary>
  688. /// <param name="property">The property.</param>
  689. /// <param name="value">The new value.</param>
  690. /// <param name="priority">The priority.</param>
  691. private void LogPropertySet(AvaloniaProperty property, object value, BindingPriority priority)
  692. {
  693. Logger.Verbose(
  694. LogArea.Property,
  695. this,
  696. "Set {Property} to {$Value} with priority {Priority}",
  697. property,
  698. value,
  699. priority);
  700. }
  701. /// <summary>
  702. /// Throws an exception indicating that the specified property is not registered on this
  703. /// object.
  704. /// </summary>
  705. /// <param name="p">The property</param>
  706. private void ThrowNotRegistered(AvaloniaProperty p)
  707. {
  708. throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}");
  709. }
  710. }
  711. }