Files
TheXamlGuy/UI/WPF/VisualStateExtension.cs
2022-11-01 15:26:08 +00:00

214 lines
5.8 KiB
C#

using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media.Animation;
namespace TheXamlGuy.UI.WPF;
public class VisualStateExtension : VisualState, ISupportInitialize
{
public static readonly DependencyProperty IsStateTriggersAttachedProperty =
DependencyProperty.RegisterAttached("IsStateTriggersAttached",
typeof(bool), typeof(VisualStateExtension),
new PropertyMetadata(false, EnableStateTriggersChanged));
private readonly ObservableCollection<StateTriggerBase> stateTriggers = new();
private Action? afterInit;
private FrameworkElement? element;
private bool isInitializing;
private bool lastState;
private bool storyboardStateInvalidated;
public VisualStateExtension()
{
Name = Guid.NewGuid().ToString();
Setters = new SetterBaseCollection();
Setters.CollectionChanged += OnSettersCollectionChanged;
stateTriggers.CollectionChanged += OnTriggersCollectionChanged;
}
public SetterBaseCollection Setters { get; }
public IList StateTriggers => stateTriggers;
internal FrameworkElement? Element
{
get => element;
set
{
element = value;
if (element is not null)
{
if (element.IsLoaded)
{
UpdateActiveState();
}
else
{
element.Loaded += OnElementLoaded;
}
}
}
}
public static bool GetIsStateTriggersAttached(FrameworkElement element)
{
return (bool)element.GetValue(IsStateTriggersAttachedProperty);
}
public static void SetIsStateTriggersAttached(FrameworkElement element, bool value)
{
element.SetValue(IsStateTriggersAttachedProperty, value);
}
void ISupportInitialize.BeginInit()
{
isInitializing = true;
}
void ISupportInitialize.EndInit()
{
isInitializing = false;
afterInit?.Invoke();
}
internal void SetActive(bool active)
{
if (Element is null)
{
return;
}
if (isInitializing)
{
afterInit = () => SetActive(active);
return;
}
if (Storyboard is not null && lastState != active)
{
if (active)
{
ClockState state = storyboardStateInvalidated ? Storyboard.GetCurrentState(Element) : ClockState.Stopped;
if (state == ClockState.Stopped)
{
Storyboard.Begin(Element, true);
}
}
else
{
if (storyboardStateInvalidated)
{
Storyboard.Stop(Element);
}
}
}
if (active)
{
foreach (Setter setter in Setters.OfType<Setter>())
{
DependencyProperty property = setter.Property;
object value = setter.Value;
string targetName = setter.TargetName;
DependencyObject? target = Element.FindName(targetName) as DependencyObject;
if (setter.Value is BindingBase binding)
{
BindingOperations.SetBinding(target, property, value as BindingBase);
}
else
{
target?.SetValue(property, value);
}
}
}
lastState = active;
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs args)
{
base.OnPropertyChanged(args);
if (args.Property.Name == nameof(Storyboard))
{
if (args.OldValue is Storyboard oldStoryBoard)
{
oldStoryBoard.CurrentStateInvalidated -= OnStoryboardCurrentStateInvalidated;
}
if (args.NewValue is Storyboard newStoryBoard)
{
newStoryBoard.CurrentStateInvalidated += OnStoryboardCurrentStateInvalidated;
}
storyboardStateInvalidated = false;
}
}
private static void EnableStateTriggersChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
if (dependencyObject is FrameworkElement element)
{
if ((bool)args.NewValue)
{
VisualStateManagerContext.Set(element);
}
else
{
VisualStateManagerContext.UnSet(element);
}
}
}
private void OnElementLoaded(object sender, RoutedEventArgs args)
{
((FrameworkElement)sender).Loaded -= OnElementLoaded;
UpdateActiveState();
}
private void OnSettersCollectionChanged(object? sender, EventArgs args)
{
UpdateActiveState();
}
private void OnStoryboardCurrentStateInvalidated(object? sender, EventArgs args)
{
storyboardStateInvalidated = true;
}
private void OnTriggersCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args)
{
if (args.NewItems is not null)
{
foreach (StateTriggerBase item in args.NewItems.OfType<StateTriggerBase>())
{
item.Owner = this;
}
}
if (args.OldItems is not null)
{
foreach (StateTriggerBase? item in args.OldItems.OfType<StateTriggerBase>().Where(item => item.Owner == this))
{
item.Owner = null;
}
}
UpdateActiveState();
}
private void UpdateActiveState()
{
SetActive(stateTriggers.Any(t => t.IsTriggerActive));
}
}