StyledElement.cs 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Collections.Specialized;
  5. using System.ComponentModel;
  6. using System.Diagnostics;
  7. using System.Linq;
  8. using Avalonia.Animation;
  9. using Avalonia.Collections;
  10. using Avalonia.Controls;
  11. using Avalonia.Data;
  12. using Avalonia.Diagnostics;
  13. using Avalonia.Logging;
  14. using Avalonia.LogicalTree;
  15. using Avalonia.PropertyStore;
  16. using Avalonia.Styling;
  17. namespace Avalonia
  18. {
  19. /// <summary>
  20. /// Extends an <see cref="Animatable"/> with the following features:
  21. ///
  22. /// - An inherited <see cref="DataContext"/>.
  23. /// - Implements <see cref="IStyleable"/> to allow styling to work on the styled element.
  24. /// - Implements <see cref="ILogical"/> to form part of a logical tree.
  25. /// - A collection of class strings for custom styling.
  26. /// </summary>
  27. public class StyledElement : Animatable,
  28. IDataContextProvider,
  29. ILogical,
  30. IThemeVariantHost,
  31. IStyleHost,
  32. IStyleable,
  33. ISetLogicalParent,
  34. ISetInheritanceParent,
  35. ISupportInitialize
  36. {
  37. /// <summary>
  38. /// Defines the <see cref="DataContext"/> property.
  39. /// </summary>
  40. public static readonly StyledProperty<object?> DataContextProperty =
  41. AvaloniaProperty.Register<StyledElement, object?>(
  42. nameof(DataContext),
  43. defaultValue: null,
  44. inherits: true,
  45. defaultBindingMode: BindingMode.OneWay,
  46. validate: null,
  47. coerce: null,
  48. enableDataValidation: false,
  49. notifying: DataContextNotifying);
  50. /// <summary>
  51. /// Defines the <see cref="Name"/> property.
  52. /// </summary>
  53. public static readonly DirectProperty<StyledElement, string?> NameProperty =
  54. AvaloniaProperty.RegisterDirect<StyledElement, string?>(nameof(Name), o => o.Name, (o, v) => o.Name = v);
  55. /// <summary>
  56. /// Defines the <see cref="Parent"/> property.
  57. /// </summary>
  58. public static readonly DirectProperty<StyledElement, StyledElement?> ParentProperty =
  59. AvaloniaProperty.RegisterDirect<StyledElement, StyledElement?>(nameof(Parent), o => o.Parent);
  60. /// <summary>
  61. /// Defines the <see cref="TemplatedParent"/> property.
  62. /// </summary>
  63. public static readonly DirectProperty<StyledElement, AvaloniaObject?> TemplatedParentProperty =
  64. AvaloniaProperty.RegisterDirect<StyledElement, AvaloniaObject?>(
  65. nameof(TemplatedParent),
  66. o => o.TemplatedParent);
  67. /// <summary>
  68. /// Defines the <see cref="Theme"/> property.
  69. /// </summary>
  70. public static readonly StyledProperty<ControlTheme?> ThemeProperty =
  71. AvaloniaProperty.Register<StyledElement, ControlTheme?>(nameof(Theme));
  72. private static readonly ControlTheme s_invalidTheme = new ControlTheme();
  73. private int _initCount;
  74. private string? _name;
  75. private readonly Classes _classes = new Classes();
  76. private ILogicalRoot? _logicalRoot;
  77. private IAvaloniaList<ILogical>? _logicalChildren;
  78. private IResourceDictionary? _resources;
  79. private Styles? _styles;
  80. private bool _stylesApplied;
  81. private bool _themeApplied;
  82. private bool _templatedParentThemeApplied;
  83. private AvaloniaObject? _templatedParent;
  84. private bool _dataContextUpdating;
  85. private ControlTheme? _implicitTheme;
  86. /// <summary>
  87. /// Initializes static members of the <see cref="StyledElement"/> class.
  88. /// </summary>
  89. static StyledElement()
  90. {
  91. DataContextProperty.Changed.AddClassHandler<StyledElement>((x,e) => x.OnDataContextChangedCore(e));
  92. }
  93. /// <summary>
  94. /// Initializes a new instance of the <see cref="StyledElement"/> class.
  95. /// </summary>
  96. public StyledElement()
  97. {
  98. _logicalRoot = this as ILogicalRoot;
  99. }
  100. /// <summary>
  101. /// Raised when the styled element is attached to a rooted logical tree.
  102. /// </summary>
  103. public event EventHandler<LogicalTreeAttachmentEventArgs>? AttachedToLogicalTree;
  104. /// <summary>
  105. /// Raised when the styled element is detached from a rooted logical tree.
  106. /// </summary>
  107. public event EventHandler<LogicalTreeAttachmentEventArgs>? DetachedFromLogicalTree;
  108. /// <summary>
  109. /// Occurs when the <see cref="DataContext"/> property changes.
  110. /// </summary>
  111. /// <remarks>
  112. /// This event will be raised when the <see cref="DataContext"/> property has changed and
  113. /// all subscribers to that change have been notified.
  114. /// </remarks>
  115. public event EventHandler? DataContextChanged;
  116. /// <summary>
  117. /// Occurs when the styled element has finished initialization.
  118. /// </summary>
  119. /// <remarks>
  120. /// The Initialized event indicates that all property values on the styled element have been set.
  121. /// When loading the styled element from markup, it occurs when
  122. /// <see cref="ISupportInitialize.EndInit"/> is called *and* the styled element
  123. /// is attached to a rooted logical tree. When the styled element is created by code and
  124. /// <see cref="ISupportInitialize"/> is not used, it is called when the styled element is attached
  125. /// to the visual tree.
  126. /// </remarks>
  127. public event EventHandler? Initialized;
  128. /// <summary>
  129. /// Occurs when a resource in this styled element or a parent styled element has changed.
  130. /// </summary>
  131. public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged;
  132. /// <inheritdoc />
  133. public event EventHandler? ActualThemeVariantChanged;
  134. /// <summary>
  135. /// Gets or sets the name of the styled element.
  136. /// </summary>
  137. /// <remarks>
  138. /// An element's name is used to uniquely identify an element within the element's name
  139. /// scope. Once the element is added to a logical tree, its name cannot be changed.
  140. /// </remarks>
  141. public string? Name
  142. {
  143. get => _name;
  144. set
  145. {
  146. if (_stylesApplied)
  147. {
  148. throw new InvalidOperationException("Cannot set Name : styled element already styled.");
  149. }
  150. _name = value;
  151. }
  152. }
  153. /// <summary>
  154. /// Gets or sets the styled element's classes.
  155. /// </summary>
  156. /// <remarks>
  157. /// <para>
  158. /// Classes can be used to apply user-defined styling to styled elements, or to allow styled elements
  159. /// that share a common purpose to be easily selected.
  160. /// </para>
  161. /// <para>
  162. /// Even though this property can be set, the setter is only intended for use in object
  163. /// initializers. Assigning to this property does not change the underlying collection,
  164. /// it simply clears the existing collection and adds the contents of the assigned
  165. /// collection.
  166. /// </para>
  167. /// </remarks>
  168. public Classes Classes
  169. {
  170. get
  171. {
  172. return _classes;
  173. }
  174. set
  175. {
  176. if (_classes != value)
  177. {
  178. _classes.Replace(value);
  179. }
  180. }
  181. }
  182. /// <summary>
  183. /// Gets or sets the control's data context.
  184. /// </summary>
  185. /// <remarks>
  186. /// The data context is an inherited property that specifies the default object that will
  187. /// be used for data binding.
  188. /// </remarks>
  189. public object? DataContext
  190. {
  191. get { return GetValue(DataContextProperty); }
  192. set { SetValue(DataContextProperty, value); }
  193. }
  194. /// <summary>
  195. /// Gets a value that indicates whether the element has finished initialization.
  196. /// </summary>
  197. /// <remarks>
  198. /// For more information about when IsInitialized is set, see the <see cref="Initialized"/>
  199. /// event.
  200. /// </remarks>
  201. public bool IsInitialized { get; private set; }
  202. /// <summary>
  203. /// Gets the styles for the styled element.
  204. /// </summary>
  205. /// <remarks>
  206. /// Styles for the entire application are added to the Application.Styles collection, but
  207. /// each styled element may in addition define its own styles which are applied to the styled element
  208. /// itself and its children.
  209. /// </remarks>
  210. public Styles Styles => _styles ??= new Styles(this);
  211. /// <summary>
  212. /// Gets or sets the styled element's resource dictionary.
  213. /// </summary>
  214. public IResourceDictionary Resources
  215. {
  216. get => _resources ??= new ResourceDictionary(this);
  217. set
  218. {
  219. value = value ?? throw new ArgumentNullException(nameof(value));
  220. _resources?.RemoveOwner(this);
  221. _resources = value;
  222. _resources.AddOwner(this);
  223. }
  224. }
  225. /// <summary>
  226. /// Gets the styled element whose lookless template this styled element is part of.
  227. /// </summary>
  228. public AvaloniaObject? TemplatedParent
  229. {
  230. get => _templatedParent;
  231. internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value);
  232. }
  233. /// <summary>
  234. /// Gets or sets the theme to be applied to the element.
  235. /// </summary>
  236. public ControlTheme? Theme
  237. {
  238. get => GetValue(ThemeProperty);
  239. set => SetValue(ThemeProperty, value);
  240. }
  241. /// <summary>
  242. /// Gets the styled element's logical children.
  243. /// </summary>
  244. protected internal IAvaloniaList<ILogical> LogicalChildren
  245. {
  246. get
  247. {
  248. if (_logicalChildren == null)
  249. {
  250. var list = new AvaloniaList<ILogical>
  251. {
  252. ResetBehavior = ResetBehavior.Remove,
  253. Validate = logical => ValidateLogicalChild(logical)
  254. };
  255. list.CollectionChanged += LogicalChildrenCollectionChanged;
  256. _logicalChildren = list;
  257. }
  258. return _logicalChildren;
  259. }
  260. }
  261. /// <summary>
  262. /// Gets the <see cref="Classes"/> collection in a form that allows adding and removing
  263. /// pseudoclasses.
  264. /// </summary>
  265. protected IPseudoClasses PseudoClasses => Classes;
  266. /// <summary>
  267. /// Gets a value indicating whether the element is attached to a rooted logical tree.
  268. /// </summary>
  269. bool ILogical.IsAttachedToLogicalTree => _logicalRoot != null;
  270. /// <summary>
  271. /// Gets the styled element's logical parent.
  272. /// </summary>
  273. public StyledElement? Parent { get; private set; }
  274. /// <inheritdoc />
  275. public ThemeVariant ActualThemeVariant => GetValue(ThemeVariant.ActualThemeVariantProperty);
  276. /// <summary>
  277. /// Gets the styled element's logical parent.
  278. /// </summary>
  279. ILogical? ILogical.LogicalParent => Parent;
  280. /// <summary>
  281. /// Gets the styled element's logical children.
  282. /// </summary>
  283. IAvaloniaReadOnlyList<ILogical> ILogical.LogicalChildren => LogicalChildren;
  284. /// <inheritdoc/>
  285. bool IResourceNode.HasResources => (_resources?.HasResources ?? false) ||
  286. (((IResourceNode?)_styles)?.HasResources ?? false);
  287. /// <inheritdoc/>
  288. IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
  289. /// <summary>
  290. /// Gets the type by which the styled element is styled.
  291. /// </summary>
  292. /// <remarks>
  293. /// Usually controls are styled by their own type, but there are instances where you want
  294. /// a styled element to be styled by its base type, e.g. creating SpecialButton that
  295. /// derives from Button and adds extra functionality but is still styled as a regular
  296. /// Button.
  297. /// </remarks>
  298. Type IStyleable.StyleKey => GetType();
  299. /// <inheritdoc/>
  300. bool IStyleHost.IsStylesInitialized => _styles != null;
  301. /// <inheritdoc/>
  302. IStyleHost? IStyleHost.StylingParent => (IStyleHost?)InheritanceParent;
  303. /// <inheritdoc/>
  304. public virtual void BeginInit()
  305. {
  306. ++_initCount;
  307. }
  308. /// <inheritdoc/>
  309. public virtual void EndInit()
  310. {
  311. if (_initCount == 0)
  312. {
  313. throw new InvalidOperationException("BeginInit was not called.");
  314. }
  315. if (--_initCount == 0 && _logicalRoot is not null)
  316. {
  317. ApplyStyling();
  318. InitializeIfNeeded();
  319. }
  320. }
  321. /// <summary>
  322. /// Applies styling to the control if the control is initialized and styling is not
  323. /// already applied.
  324. /// </summary>
  325. /// <remarks>
  326. /// The styling system will automatically apply styling when required, so it should not
  327. /// usually be necessary to call this method manually.
  328. /// </remarks>
  329. /// <returns>
  330. /// A value indicating whether styling is now applied to the control.
  331. /// </returns>
  332. public bool ApplyStyling()
  333. {
  334. if (_initCount == 0 && (!_stylesApplied || !_themeApplied || !_templatedParentThemeApplied))
  335. {
  336. GetValueStore().BeginStyling();
  337. try
  338. {
  339. if (!_themeApplied)
  340. {
  341. ApplyControlTheme();
  342. _themeApplied = true;
  343. }
  344. if (!_templatedParentThemeApplied)
  345. {
  346. ApplyTemplatedParentControlTheme();
  347. _templatedParentThemeApplied = true;
  348. }
  349. if (!_stylesApplied)
  350. {
  351. ApplyStyles(this);
  352. _stylesApplied = true;
  353. }
  354. }
  355. finally
  356. {
  357. GetValueStore().EndStyling();
  358. }
  359. }
  360. return _stylesApplied;
  361. }
  362. protected void InitializeIfNeeded()
  363. {
  364. if (_initCount == 0 && !IsInitialized)
  365. {
  366. IsInitialized = true;
  367. OnInitialized();
  368. Initialized?.Invoke(this, EventArgs.Empty);
  369. }
  370. }
  371. internal StyleDiagnostics GetStyleDiagnosticsInternal()
  372. {
  373. var styles = new List<IStyleInstance>();
  374. foreach (var frame in GetValueStore().Frames)
  375. {
  376. if (frame is IStyleInstance style)
  377. styles.Add(style);
  378. }
  379. return new StyleDiagnostics(styles);
  380. }
  381. /// <inheritdoc/>
  382. void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
  383. {
  384. OnAttachedToLogicalTreeCore(e);
  385. }
  386. /// <inheritdoc/>
  387. void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
  388. {
  389. OnDetachedFromLogicalTreeCore(e);
  390. }
  391. /// <inheritdoc/>
  392. void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e) => NotifyResourcesChanged(e);
  393. /// <inheritdoc/>
  394. void IResourceHost.NotifyHostedResourcesChanged(ResourcesChangedEventArgs e) => NotifyResourcesChanged(e);
  395. /// <inheritdoc/>
  396. public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
  397. {
  398. value = null;
  399. return (_resources?.TryGetResource(key, theme, out value) ?? false) ||
  400. (_styles?.TryGetResource(key, theme, out value) ?? false);
  401. }
  402. /// <summary>
  403. /// Sets the styled element's logical parent.
  404. /// </summary>
  405. /// <param name="parent">The parent.</param>
  406. void ISetLogicalParent.SetParent(ILogical? parent)
  407. {
  408. var old = Parent;
  409. if (parent != old)
  410. {
  411. if (old != null && parent != null)
  412. {
  413. throw new InvalidOperationException("The Control already has a parent.");
  414. }
  415. if (InheritanceParent == null || parent == null)
  416. {
  417. InheritanceParent = parent as AvaloniaObject;
  418. }
  419. Parent = (StyledElement?)parent;
  420. if (_logicalRoot != null)
  421. {
  422. var e = new LogicalTreeAttachmentEventArgs(_logicalRoot, this, old!);
  423. OnDetachedFromLogicalTreeCore(e);
  424. }
  425. var newRoot = FindLogicalRoot(this);
  426. if (newRoot is object)
  427. {
  428. var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent!);
  429. OnAttachedToLogicalTreeCore(e);
  430. }
  431. else if (parent is null)
  432. {
  433. // If we were attached to the logical tree, we piggyback on the tree traversal
  434. // there to raise resources changed notifications. If we're being removed from
  435. // the logical tree, then traverse the tree raising notifications now.
  436. //
  437. // We don't raise resources changed notifications if we're being attached to a
  438. // non-rooted control beacuse it's unlikely that dynamic resources need to be
  439. // correct until the control is added to the tree, and it causes a *lot* of
  440. // notifications.
  441. NotifyResourcesChanged();
  442. }
  443. RaisePropertyChanged(ParentProperty, old, Parent);
  444. }
  445. }
  446. /// <summary>
  447. /// Sets the styled element's inheritance parent.
  448. /// </summary>
  449. /// <param name="parent">The parent.</param>
  450. void ISetInheritanceParent.SetParent(AvaloniaObject? parent)
  451. {
  452. InheritanceParent = parent;
  453. }
  454. void IStyleHost.StylesAdded(IReadOnlyList<IStyle> styles)
  455. {
  456. if (HasSettersOrAnimations(styles))
  457. InvalidateStyles(recurse: true);
  458. }
  459. void IStyleHost.StylesRemoved(IReadOnlyList<IStyle> styles)
  460. {
  461. if (FlattenStyles(styles) is { } allStyles)
  462. DetachStyles(allStyles);
  463. }
  464. protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
  465. {
  466. switch (e.Action)
  467. {
  468. case NotifyCollectionChangedAction.Add:
  469. SetLogicalParent(e.NewItems!);
  470. break;
  471. case NotifyCollectionChangedAction.Remove:
  472. ClearLogicalParent(e.OldItems!);
  473. break;
  474. case NotifyCollectionChangedAction.Replace:
  475. ClearLogicalParent(e.OldItems!);
  476. SetLogicalParent(e.NewItems!);
  477. break;
  478. case NotifyCollectionChangedAction.Reset:
  479. throw new NotSupportedException("Reset should not be signaled on LogicalChildren collection");
  480. }
  481. }
  482. /// <summary>
  483. /// Notifies child controls that a change has been made to resources that apply to them.
  484. /// </summary>
  485. /// <param name="e">The event args.</param>
  486. protected virtual void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
  487. {
  488. if (_logicalChildren is object)
  489. {
  490. var count = _logicalChildren.Count;
  491. if (count > 0)
  492. {
  493. e ??= ResourcesChangedEventArgs.Empty;
  494. for (var i = 0; i < count; ++i)
  495. {
  496. _logicalChildren[i].NotifyResourcesChanged(e);
  497. }
  498. }
  499. }
  500. }
  501. /// <summary>
  502. /// Called when the styled element is added to a rooted logical tree.
  503. /// </summary>
  504. /// <param name="e">The event args.</param>
  505. protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
  506. {
  507. }
  508. /// <summary>
  509. /// Called when the styled element is removed from a rooted logical tree.
  510. /// </summary>
  511. /// <param name="e">The event args.</param>
  512. protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
  513. {
  514. }
  515. /// <summary>
  516. /// Called when the <see cref="DataContext"/> property changes.
  517. /// </summary>
  518. /// <param name="e">The event args.</param>
  519. protected virtual void OnDataContextChanged(EventArgs e)
  520. {
  521. DataContextChanged?.Invoke(this, EventArgs.Empty);
  522. }
  523. /// <summary>
  524. /// Called when the <see cref="DataContext"/> begins updating.
  525. /// </summary>
  526. protected virtual void OnDataContextBeginUpdate()
  527. {
  528. }
  529. /// <summary>
  530. /// Called when the <see cref="DataContext"/> finishes updating.
  531. /// </summary>
  532. protected virtual void OnDataContextEndUpdate()
  533. {
  534. }
  535. /// <summary>
  536. /// Called when the control finishes initialization.
  537. /// </summary>
  538. protected virtual void OnInitialized()
  539. {
  540. }
  541. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
  542. {
  543. base.OnPropertyChanged(change);
  544. if (change.Property == ThemeProperty)
  545. {
  546. OnControlThemeChanged();
  547. }
  548. else if (change.Property == ThemeVariant.RequestedThemeVariantProperty)
  549. {
  550. if (change.GetNewValue<ThemeVariant>() is {} themeVariant && themeVariant != ThemeVariant.Default)
  551. SetValue(ThemeVariant.ActualThemeVariantProperty, themeVariant);
  552. else
  553. ClearValue(ThemeVariant.ActualThemeVariantProperty);
  554. }
  555. else if (change.Property == ThemeVariant.ActualThemeVariantProperty)
  556. {
  557. ActualThemeVariantChanged?.Invoke(this, EventArgs.Empty);
  558. }
  559. }
  560. private protected virtual void OnControlThemeChanged()
  561. {
  562. var values = GetValueStore();
  563. values.BeginStyling();
  564. try
  565. {
  566. values.RemoveFrames(FrameType.Theme);
  567. }
  568. finally
  569. {
  570. values.EndStyling();
  571. _themeApplied = false;
  572. }
  573. }
  574. internal virtual void OnTemplatedParentControlThemeChanged()
  575. {
  576. var values = GetValueStore();
  577. values.BeginStyling();
  578. try
  579. {
  580. values.RemoveFrames(FrameType.TemplatedParentTheme);
  581. }
  582. finally
  583. {
  584. values.EndStyling();
  585. _templatedParentThemeApplied = false;
  586. }
  587. }
  588. internal ControlTheme? GetEffectiveTheme()
  589. {
  590. var theme = Theme;
  591. // Explicitly set Theme property takes precedence.
  592. if (theme is not null)
  593. return theme;
  594. // If the Theme property is not set, try to find a ControlTheme resource with our StyleKey.
  595. if (_implicitTheme is null)
  596. {
  597. var key = ((IStyleable)this).StyleKey;
  598. if (this.TryFindResource(key, out var value) && value is ControlTheme t)
  599. _implicitTheme = t;
  600. else
  601. _implicitTheme = s_invalidTheme;
  602. }
  603. if (_implicitTheme != s_invalidTheme)
  604. return _implicitTheme;
  605. return null;
  606. }
  607. internal virtual void InvalidateStyles(bool recurse)
  608. {
  609. var values = GetValueStore();
  610. values.BeginStyling();
  611. try { values.RemoveFrames(FrameType.Style); }
  612. finally { values.EndStyling(); }
  613. _stylesApplied = false;
  614. if (recurse && GetInheritanceChildren() is { } children)
  615. {
  616. var childCount = children.Count;
  617. for (var i = 0; i < childCount; ++i)
  618. (children[i] as StyledElement)?.InvalidateStyles(recurse);
  619. }
  620. }
  621. private static void DataContextNotifying(AvaloniaObject o, bool updateStarted)
  622. {
  623. if (o is StyledElement element)
  624. {
  625. DataContextNotifying(element, updateStarted);
  626. }
  627. }
  628. private static void DataContextNotifying(StyledElement element, bool updateStarted)
  629. {
  630. if (updateStarted)
  631. {
  632. if (!element._dataContextUpdating)
  633. {
  634. element._dataContextUpdating = true;
  635. element.OnDataContextBeginUpdate();
  636. var logicalChildren = element.LogicalChildren;
  637. var logicalChildrenCount = logicalChildren.Count;
  638. for (var i = 0; i < logicalChildrenCount; i++)
  639. {
  640. if (element.LogicalChildren[i] is StyledElement s &&
  641. s.InheritanceParent == element &&
  642. !s.IsSet(DataContextProperty))
  643. {
  644. DataContextNotifying(s, updateStarted);
  645. }
  646. }
  647. }
  648. }
  649. else
  650. {
  651. if (element._dataContextUpdating)
  652. {
  653. element.OnDataContextEndUpdate();
  654. element._dataContextUpdating = false;
  655. }
  656. }
  657. }
  658. private static ILogicalRoot? FindLogicalRoot(IStyleHost? e)
  659. {
  660. while (e != null)
  661. {
  662. if (e is ILogicalRoot root)
  663. {
  664. return root;
  665. }
  666. e = e.StylingParent;
  667. }
  668. return null;
  669. }
  670. private static void ValidateLogicalChild(ILogical c)
  671. {
  672. if (c == null)
  673. {
  674. throw new ArgumentException("Cannot add null to LogicalChildren.");
  675. }
  676. }
  677. private void ApplyControlTheme()
  678. {
  679. if (GetEffectiveTheme() is { } theme)
  680. ApplyControlTheme(theme, FrameType.Theme);
  681. }
  682. private void ApplyTemplatedParentControlTheme()
  683. {
  684. if ((TemplatedParent as StyledElement)?.GetEffectiveTheme() is { } parentTheme)
  685. {
  686. ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme);
  687. }
  688. }
  689. private void ApplyControlTheme(ControlTheme theme, FrameType type)
  690. {
  691. Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme);
  692. if (theme.BasedOn is ControlTheme basedOn)
  693. ApplyControlTheme(basedOn, type);
  694. theme.TryAttach(this, type);
  695. if (theme.HasChildren)
  696. {
  697. var children = theme.Children;
  698. for (var i = 0; i < children.Count; i++)
  699. {
  700. ApplyStyle(children[i], null, type);
  701. }
  702. }
  703. }
  704. private void ApplyStyles(IStyleHost host)
  705. {
  706. var parent = host.StylingParent;
  707. if (parent != null)
  708. ApplyStyles(parent);
  709. if (host.IsStylesInitialized)
  710. {
  711. var styles = host.Styles;
  712. for (var i = 0; i < styles.Count; ++i)
  713. {
  714. ApplyStyle(styles[i], host, FrameType.Style);
  715. }
  716. }
  717. }
  718. private void ApplyStyle(IStyle style, IStyleHost? host, FrameType type)
  719. {
  720. if (style is Style s)
  721. s.TryAttach(this, host, type);
  722. var children = style.Children;
  723. for (var i = 0; i < children.Count; i++)
  724. {
  725. ApplyStyle(children[i], host, type);
  726. }
  727. }
  728. private void ReevaluateImplicitTheme()
  729. {
  730. // We only need to check if the theme has changed when Theme isn't set (i.e. when we
  731. // have an implicit theme).
  732. if (Theme is not null)
  733. return;
  734. // Refetch the implicit theme.
  735. var oldImplicitTheme = _implicitTheme == s_invalidTheme ? null : _implicitTheme;
  736. _implicitTheme = null;
  737. GetEffectiveTheme();
  738. var newImplicitTheme = _implicitTheme == s_invalidTheme ? null : _implicitTheme;
  739. // If the implicit theme has changed, detach the existing theme.
  740. if (newImplicitTheme != oldImplicitTheme)
  741. {
  742. OnControlThemeChanged();
  743. _themeApplied = false;
  744. }
  745. }
  746. private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
  747. {
  748. if (this.GetLogicalParent() == null && !(this is ILogicalRoot))
  749. {
  750. throw new InvalidOperationException(
  751. $"AttachedToLogicalTreeCore called for '{GetType().Name}' but control has no logical parent.");
  752. }
  753. // This method can be called when a control is already attached to the logical tree
  754. // in the following scenario:
  755. // - ListBox gets assigned Items containing ListBoxItem
  756. // - ListBox makes ListBoxItem a logical child
  757. // - ListBox template gets applied; making its Panel get attached to logical tree
  758. // - That AttachedToLogicalTree signal travels down to the ListBoxItem
  759. if (_logicalRoot == null)
  760. {
  761. _logicalRoot = e.Root;
  762. ReevaluateImplicitTheme();
  763. ApplyStyling();
  764. NotifyResourcesChanged(propagate: false);
  765. OnAttachedToLogicalTree(e);
  766. AttachedToLogicalTree?.Invoke(this, e);
  767. }
  768. var logicalChildren = LogicalChildren;
  769. var logicalChildrenCount = logicalChildren.Count;
  770. for (var i = 0; i < logicalChildrenCount; i++)
  771. {
  772. if (logicalChildren[i] is StyledElement child)
  773. {
  774. child.OnAttachedToLogicalTreeCore(e);
  775. }
  776. }
  777. }
  778. private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
  779. {
  780. if (_logicalRoot != null)
  781. {
  782. _logicalRoot = null;
  783. InvalidateStyles(recurse: false);
  784. OnDetachedFromLogicalTree(e);
  785. DetachedFromLogicalTree?.Invoke(this, e);
  786. var logicalChildren = LogicalChildren;
  787. var logicalChildrenCount = logicalChildren.Count;
  788. for (var i = 0; i < logicalChildrenCount; i++)
  789. {
  790. if (logicalChildren[i] is StyledElement child)
  791. {
  792. child.OnDetachedFromLogicalTreeCore(e);
  793. }
  794. }
  795. #if DEBUG
  796. if (((INotifyCollectionChangedDebug)Classes).GetCollectionChangedSubscribers()?.Length > 0)
  797. {
  798. Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(
  799. this,
  800. "{Type} detached from logical tree but still has class listeners",
  801. GetType());
  802. }
  803. #endif
  804. }
  805. }
  806. private void OnDataContextChangedCore(AvaloniaPropertyChangedEventArgs e)
  807. {
  808. OnDataContextChanged(EventArgs.Empty);
  809. }
  810. private void SetLogicalParent(IList children)
  811. {
  812. var count = children.Count;
  813. for (var i = 0; i < count; i++)
  814. {
  815. var logical = (ILogical) children[i]!;
  816. if (logical.LogicalParent is null)
  817. {
  818. ((ISetLogicalParent)logical).SetParent(this);
  819. }
  820. }
  821. }
  822. private void ClearLogicalParent(IList children)
  823. {
  824. var count = children.Count;
  825. for (var i = 0; i < count; i++)
  826. {
  827. var logical = (ILogical) children[i]!;
  828. if (logical.LogicalParent == this)
  829. {
  830. ((ISetLogicalParent)logical).SetParent(null);
  831. }
  832. }
  833. }
  834. private void DetachStyles(IReadOnlyList<Style> styles)
  835. {
  836. var values = GetValueStore();
  837. values.BeginStyling();
  838. try { values.RemoveFrames(styles); }
  839. finally { values.EndStyling(); }
  840. if (_logicalChildren is not null)
  841. {
  842. var childCount = _logicalChildren.Count;
  843. for (var i = 0; i < childCount; ++i)
  844. {
  845. (_logicalChildren[i] as StyledElement)?.DetachStyles(styles);
  846. }
  847. }
  848. }
  849. private void NotifyResourcesChanged(
  850. ResourcesChangedEventArgs? e = null,
  851. bool propagate = true)
  852. {
  853. if (ResourcesChanged is object)
  854. {
  855. e ??= ResourcesChangedEventArgs.Empty;
  856. ResourcesChanged(this, e);
  857. }
  858. if (propagate)
  859. {
  860. e ??= ResourcesChangedEventArgs.Empty;
  861. NotifyChildResourcesChanged(e);
  862. }
  863. }
  864. private static IReadOnlyList<Style>? FlattenStyles(IReadOnlyList<IStyle> styles)
  865. {
  866. List<Style>? result = null;
  867. static void FlattenStyle(IStyle style, ref List<Style>? result)
  868. {
  869. if (style is Style s)
  870. (result ??= new()).Add(s);
  871. FlattenStyles(style.Children, ref result);
  872. }
  873. static void FlattenStyles(IReadOnlyList<IStyle> styles, ref List<Style>? result)
  874. {
  875. var count = styles.Count;
  876. for (var i = 0; i < count; ++i)
  877. FlattenStyle(styles[i], ref result);
  878. }
  879. FlattenStyles(styles, ref result);
  880. return result;
  881. }
  882. private static bool HasSettersOrAnimations(IReadOnlyList<IStyle> styles)
  883. {
  884. static bool StyleHasSettersOrAnimations(IStyle style)
  885. {
  886. if (style is StyleBase s && s.HasSettersOrAnimations)
  887. return true;
  888. return HasSettersOrAnimations(style.Children);
  889. }
  890. var count = styles.Count;
  891. for (var i = 0; i < count; ++i)
  892. {
  893. if (StyleHasSettersOrAnimations(styles[i]))
  894. return true;
  895. }
  896. return false;
  897. }
  898. private static IReadOnlyList<StyleBase> RecurseStyles(IReadOnlyList<IStyle> styles)
  899. {
  900. var result = new List<StyleBase>();
  901. RecurseStyles(styles, result);
  902. return result;
  903. }
  904. private static void RecurseStyles(IReadOnlyList<IStyle> styles, List<StyleBase> result)
  905. {
  906. var count = styles.Count;
  907. for (var i = 0; i < count; ++i)
  908. {
  909. var s = styles[i];
  910. if (s is StyleBase style)
  911. result.Add(style);
  912. else if (s is IReadOnlyList<IStyle> children)
  913. RecurseStyles(children, result);
  914. }
  915. }
  916. }
  917. }