chore: separated System.Windows.Interactivity code
This commit is contained in:
1 parent
0da2875641
commit
ef4226ddf9
26 files changed
+54
-20
No files matched your search
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public abstract class AttachableCollection<T> : FreezableCollection<T>, IAttachedObject
|
||||
where T : DependencyObject, IAttachedObject
|
||||
{
|
||||
private DependencyObject _associatedObject;
|
||||
private Collection<T> _snapshot;
|
||||
|
||||
internal AttachableCollection()
|
||||
{
|
||||
INotifyCollectionChanged changed = this;
|
||||
changed.CollectionChanged += OnCollectionChanged;
|
||||
_snapshot = new Collection<T>();
|
||||
}
|
||||
|
||||
public DependencyObject AssociatedObject
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObject;
|
||||
}
|
||||
}
|
||||
|
||||
DependencyObject IAttachedObject.AssociatedObject => AssociatedObject;
|
||||
|
||||
public void Attach(DependencyObject dependencyObject)
|
||||
{
|
||||
if (Equals(dependencyObject, AssociatedObject))
|
||||
return;
|
||||
if (AssociatedObject != null)
|
||||
throw new InvalidOperationException();
|
||||
if (Interaction.ShouldRunInDesignMode || !(bool) GetValue(DesignerProperties.IsInDesignModeProperty))
|
||||
{
|
||||
WritePreamble();
|
||||
_associatedObject = dependencyObject;
|
||||
WritePostscript();
|
||||
}
|
||||
OnAttached();
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
OnDetaching();
|
||||
WritePreamble();
|
||||
_associatedObject = null;
|
||||
WritePostscript();
|
||||
}
|
||||
|
||||
protected abstract void OnAttached();
|
||||
|
||||
protected abstract void OnDetaching();
|
||||
|
||||
internal abstract void ItemAdded(T item);
|
||||
|
||||
internal abstract void ItemRemoved(T item);
|
||||
|
||||
private void VerifyAdd(T item)
|
||||
{
|
||||
if (item == null) throw new ArgumentNullException(nameof(item));
|
||||
if (_snapshot.Contains(item))
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.DuplicateItemInCollectionExceptionMessage, new object[]
|
||||
{
|
||||
typeof(T).Name,
|
||||
GetType().Name
|
||||
}));
|
||||
}
|
||||
|
||||
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
var enumerator1 = e.NewItems.GetEnumerator();
|
||||
try
|
||||
{
|
||||
while (enumerator1.MoveNext())
|
||||
{
|
||||
var current = (T) enumerator1.Current;
|
||||
try
|
||||
{
|
||||
VerifyAdd(current);
|
||||
ItemAdded(current);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_snapshot.Insert(IndexOf(current), current);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (enumerator1 is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
var enumerator2 = e.OldItems.GetEnumerator();
|
||||
try
|
||||
{
|
||||
while (enumerator2.MoveNext())
|
||||
{
|
||||
var current = (T) enumerator2.Current;
|
||||
ItemRemoved(current);
|
||||
_snapshot.Remove(current);
|
||||
}
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (enumerator2 is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (T oldItem in e.OldItems)
|
||||
{
|
||||
ItemRemoved(oldItem);
|
||||
_snapshot.Remove(oldItem);
|
||||
}
|
||||
var enumerator3 = e.NewItems.GetEnumerator();
|
||||
try
|
||||
{
|
||||
while (enumerator3.MoveNext())
|
||||
{
|
||||
var current = (T) enumerator3.Current;
|
||||
try
|
||||
{
|
||||
VerifyAdd(current);
|
||||
ItemAdded(current);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_snapshot.Insert(IndexOf(current), current);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (enumerator3 is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
foreach (var obj in _snapshot)
|
||||
ItemRemoved(obj);
|
||||
_snapshot = new Collection<T>();
|
||||
using (var enumerator4 = GetEnumerator())
|
||||
{
|
||||
while (enumerator4.MoveNext())
|
||||
{
|
||||
var current = enumerator4.Current;
|
||||
VerifyAdd(current);
|
||||
ItemAdded(current);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public abstract class Behavior : Animatable, IAttachedObject
|
||||
{
|
||||
private DependencyObject _associatedObject;
|
||||
private readonly Type _associatedType;
|
||||
|
||||
internal Behavior(Type associatedType)
|
||||
{
|
||||
_associatedType = associatedType;
|
||||
}
|
||||
|
||||
protected DependencyObject AssociatedObject
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObject;
|
||||
}
|
||||
}
|
||||
|
||||
protected Type AssociatedType
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedType;
|
||||
}
|
||||
}
|
||||
|
||||
public void Attach(DependencyObject dependencyObject)
|
||||
{
|
||||
if (!Equals(dependencyObject, AssociatedObject))
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostBehaviorMultipleTimesExceptionMessage);
|
||||
if (dependencyObject != null && !AssociatedType.IsInstanceOfType(dependencyObject))
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.TypeConstraintViolatedExceptionMessage,
|
||||
new object[] { GetType().Name, dependencyObject.GetType().Name, AssociatedType.Name }));
|
||||
WritePreamble();
|
||||
_associatedObject = dependencyObject;
|
||||
WritePostscript();
|
||||
OnAssociatedObjectChanged();
|
||||
OnAttached();
|
||||
}
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
OnDetaching();
|
||||
WritePreamble();
|
||||
_associatedObject = null;
|
||||
WritePostscript();
|
||||
OnAssociatedObjectChanged();
|
||||
}
|
||||
|
||||
DependencyObject IAttachedObject.AssociatedObject =>
|
||||
AssociatedObject;
|
||||
|
||||
internal event EventHandler AssociatedObjectChanged;
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return (Freezable) Activator.CreateInstance(GetType());
|
||||
}
|
||||
|
||||
private void OnAssociatedObjectChanged()
|
||||
{
|
||||
AssociatedObjectChanged?.Invoke(this, new EventArgs());
|
||||
}
|
||||
|
||||
protected virtual void OnAttached()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnDetaching()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public sealed class BehaviorCollection : AttachableCollection<Behavior>
|
||||
{
|
||||
internal BehaviorCollection()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
foreach (var behavior in this)
|
||||
behavior.Attach(AssociatedObject);
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
foreach (var behavior in this)
|
||||
behavior.Detach();
|
||||
}
|
||||
|
||||
internal override void ItemAdded(Behavior item)
|
||||
{
|
||||
if (item == null || AssociatedObject == null)
|
||||
return;
|
||||
item.Attach(AssociatedObject);
|
||||
}
|
||||
|
||||
internal override void ItemRemoved(Behavior item)
|
||||
{
|
||||
if (((IAttachedObject) item)?.AssociatedObject == null)
|
||||
return;
|
||||
item.Detach();
|
||||
}
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return new BehaviorCollection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public abstract class Behavior<T> : Behavior where T : DependencyObject
|
||||
{
|
||||
protected Behavior() : base(typeof(T))
|
||||
{
|
||||
}
|
||||
|
||||
protected new T AssociatedObject => (T) base.AssociatedObject;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = true)]
|
||||
public sealed class DefaultTriggerAttribute : Attribute
|
||||
{
|
||||
private readonly object[] _parameters;
|
||||
|
||||
public DefaultTriggerAttribute(Type targetType, Type triggerType, object parameter) : this(targetType,
|
||||
triggerType, new[] { parameter })
|
||||
{
|
||||
}
|
||||
|
||||
public DefaultTriggerAttribute(Type targetType, Type triggerType, params object[] parameters)
|
||||
{
|
||||
if (!typeof(TriggerBase).IsAssignableFrom(triggerType))
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.DefaultTriggerAttributeInvalidTriggerTypeSpecifiedExceptionMessage,
|
||||
new object[] { triggerType.Name }));
|
||||
TargetType = targetType;
|
||||
TriggerType = triggerType;
|
||||
_parameters = parameters;
|
||||
}
|
||||
|
||||
public IEnumerable Parameters =>
|
||||
_parameters;
|
||||
|
||||
public Type TargetType { get; }
|
||||
|
||||
public Type TriggerType { get; }
|
||||
|
||||
public TriggerBase Instantiate()
|
||||
{
|
||||
object obj2 = null;
|
||||
try
|
||||
{
|
||||
obj2 = Activator.CreateInstance(TriggerType, _parameters);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
return (TriggerBase) obj2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public class EventTrigger : EventTriggerBase<object>
|
||||
{
|
||||
public static readonly DependencyProperty EventNameProperty = DependencyProperty.Register("EventName", typeof(string), typeof(EventTrigger), new FrameworkPropertyMetadata("Loaded", OnEventNameChanged));
|
||||
|
||||
public EventTrigger()
|
||||
{
|
||||
}
|
||||
|
||||
public EventTrigger(string eventName) => EventName = eventName;
|
||||
|
||||
protected override string GetEventName() => EventName;
|
||||
|
||||
private static void OnEventNameChanged(object sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
((EventTrigger) sender).OnEventNameChanged((string) args.OldValue, (string) args.NewValue);
|
||||
}
|
||||
|
||||
public string EventName
|
||||
{
|
||||
get => (string) GetValue(EventNameProperty);
|
||||
|
||||
set => SetValue(EventNameProperty, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public abstract class EventTriggerBase : TriggerBase
|
||||
{
|
||||
public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register("SourceName",
|
||||
typeof(string), typeof(EventTriggerBase), new PropertyMetadata(OnSourceNameChanged));
|
||||
|
||||
public static readonly DependencyProperty SourceObjectProperty = DependencyProperty.Register("SourceObject",
|
||||
typeof(object), typeof(EventTriggerBase), new PropertyMetadata(OnSourceObjectChanged));
|
||||
|
||||
private MethodInfo _eventHandlerMethodInfo;
|
||||
|
||||
internal EventTriggerBase(Type sourceTypeConstraint) : base(typeof(DependencyObject))
|
||||
{
|
||||
SourceTypeConstraint = sourceTypeConstraint;
|
||||
SourceNameResolver = new NameResolver();
|
||||
RegisterSourceChanged();
|
||||
}
|
||||
|
||||
protected sealed override Type AssociatedObjectTypeConstraint
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TypeDescriptor.GetAttributes(GetType())[typeof(TypeConstraintAttribute)] is TypeConstraintAttribute attribute)
|
||||
return attribute.Constraint;
|
||||
return typeof(DependencyObject);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsLoadedRegistered { get; set; }
|
||||
|
||||
private bool IsSourceChangedRegistered { get; set; }
|
||||
|
||||
private bool IsSourceNameSet
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(SourceName))
|
||||
return ReadLocalValue(SourceNameProperty) != DependencyProperty.UnsetValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public object Source
|
||||
{
|
||||
get
|
||||
{
|
||||
object associatedObject = AssociatedObject;
|
||||
if (SourceObject != null)
|
||||
return SourceObject;
|
||||
if (IsSourceNameSet)
|
||||
{
|
||||
associatedObject = SourceNameResolver.Object;
|
||||
if (associatedObject != null && !SourceTypeConstraint.IsInstanceOfType(associatedObject))
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.RetargetedTypeConstraintViolatedExceptionMessage, GetType().Name,
|
||||
associatedObject.GetType(), SourceTypeConstraint, "Source"));
|
||||
}
|
||||
return associatedObject;
|
||||
}
|
||||
}
|
||||
|
||||
public string SourceName
|
||||
{
|
||||
get =>
|
||||
(string) GetValue(SourceNameProperty);
|
||||
set => SetValue(SourceNameProperty, value);
|
||||
}
|
||||
|
||||
private NameResolver SourceNameResolver { get; }
|
||||
|
||||
public object SourceObject
|
||||
{
|
||||
get =>
|
||||
GetValue(SourceObjectProperty);
|
||||
set => SetValue(SourceObjectProperty, value);
|
||||
}
|
||||
|
||||
protected Type SourceTypeConstraint { get; }
|
||||
|
||||
protected abstract string GetEventName();
|
||||
|
||||
private static bool IsValidEvent(EventInfo eventInfo)
|
||||
{
|
||||
var eventHandlerType = eventInfo.EventHandlerType;
|
||||
if (!typeof(Delegate).IsAssignableFrom(eventInfo.EventHandlerType))
|
||||
return false;
|
||||
var parameters = eventHandlerType.GetMethod("Invoke")?.GetParameters();
|
||||
return parameters != null && parameters.Length == 2 && typeof(object).IsAssignableFrom(parameters[0].ParameterType) && typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType);
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
var associatedObject = AssociatedObject;
|
||||
var behavior = associatedObject as Behavior;
|
||||
var element = associatedObject as FrameworkElement;
|
||||
RegisterSourceChanged();
|
||||
if (behavior != null)
|
||||
{
|
||||
behavior.AssociatedObjectChanged += OnBehaviorHostChanged;
|
||||
}
|
||||
else if (SourceObject != null || element == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnSourceChanged(null, Source);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SourceNameResolver.NameScopeReferenceElement = element;
|
||||
}
|
||||
if (string.Compare(GetEventName(), "Loaded", StringComparison.Ordinal) == 0 && element != null &&
|
||||
!Interaction.IsElementLoaded(element))
|
||||
RegisterLoaded(element);
|
||||
}
|
||||
|
||||
private void OnBehaviorHostChanged(object sender, EventArgs e)
|
||||
{
|
||||
SourceNameResolver.NameScopeReferenceElement =
|
||||
((IAttachedObject) sender).AssociatedObject as FrameworkElement;
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
base.OnDetaching();
|
||||
var associatedObject = AssociatedObject as Behavior;
|
||||
var associatedElement = AssociatedObject as FrameworkElement;
|
||||
try
|
||||
{
|
||||
OnSourceChanged(Source, null);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
UnregisterSourceChanged();
|
||||
if (associatedObject != null)
|
||||
associatedObject.AssociatedObjectChanged -= OnBehaviorHostChanged;
|
||||
SourceNameResolver.NameScopeReferenceElement = null;
|
||||
if (string.Compare(GetEventName(), "Loaded", StringComparison.Ordinal) == 0 && associatedElement != null)
|
||||
UnregisterLoaded(associatedElement);
|
||||
}
|
||||
|
||||
protected virtual void OnEvent(EventArgs eventArgs)
|
||||
{
|
||||
InvokeActions(eventArgs);
|
||||
}
|
||||
|
||||
private void OnEventImpl(object sender, EventArgs eventArgs)
|
||||
{
|
||||
OnEvent(eventArgs);
|
||||
}
|
||||
|
||||
internal void OnEventNameChanged(string oldEventName, string newEventName)
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
{
|
||||
var source = Source as FrameworkElement;
|
||||
if (source != null && string.Compare(oldEventName, "Loaded", StringComparison.Ordinal) == 0)
|
||||
UnregisterLoaded(source);
|
||||
else if (!string.IsNullOrEmpty(oldEventName))
|
||||
UnregisterEvent(Source, oldEventName);
|
||||
if (source != null && string.Compare(newEventName, "Loaded", StringComparison.Ordinal) == 0)
|
||||
RegisterLoaded(source);
|
||||
else if (!string.IsNullOrEmpty(newEventName))
|
||||
RegisterEvent(Source, newEventName);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSourceChanged(object oldSource, object newSource)
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
OnSourceChangedImpl(oldSource, newSource);
|
||||
}
|
||||
|
||||
internal virtual void OnSourceChangedImpl(object oldSource, object newSource)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(GetEventName()) &&
|
||||
string.Compare(GetEventName(), "Loaded", StringComparison.Ordinal) != 0)
|
||||
{
|
||||
if (oldSource != null && SourceTypeConstraint.IsInstanceOfType(oldSource))
|
||||
UnregisterEvent(oldSource, GetEventName());
|
||||
if (newSource != null && SourceTypeConstraint.IsInstanceOfType(newSource))
|
||||
RegisterEvent(newSource, GetEventName());
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnSourceNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var base2 = (EventTriggerBase) obj;
|
||||
base2.SourceNameResolver.Name = (string) args.NewValue;
|
||||
}
|
||||
|
||||
private void OnSourceNameResolverElementChanged(object sender, NameResolvedEventArgs e)
|
||||
{
|
||||
if (SourceObject == null)
|
||||
OnSourceChanged(e.OldObject, e.NewObject);
|
||||
}
|
||||
|
||||
private static void OnSourceObjectChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var base2 = (EventTriggerBase) obj;
|
||||
object newSource = base2.SourceNameResolver.Object;
|
||||
if (args.NewValue == null)
|
||||
{
|
||||
base2.OnSourceChanged(args.OldValue, newSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (args.OldValue == null && newSource != null)
|
||||
base2.UnregisterEvent(newSource, base2.GetEventName());
|
||||
base2.OnSourceChanged(args.OldValue, args.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterEvent(object obj, string eventName)
|
||||
{
|
||||
var eventInfo = obj.GetType().GetEvent(eventName);
|
||||
if (eventInfo == null)
|
||||
{
|
||||
if (SourceObject != null)
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.EventTriggerCannotFindEventNameExceptionMessage,
|
||||
new object[] { eventName, obj.GetType().Name }));
|
||||
}
|
||||
else if (!IsValidEvent(eventInfo))
|
||||
{
|
||||
if (SourceObject != null)
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.EventTriggerBaseInvalidEventExceptionMessage,
|
||||
new object[] { eventName, obj.GetType().Name }));
|
||||
}
|
||||
else
|
||||
{
|
||||
_eventHandlerMethodInfo =
|
||||
typeof(EventTriggerBase).GetMethod("OnEventImpl", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
eventInfo.AddEventHandler(obj,
|
||||
Delegate.CreateDelegate(eventInfo.EventHandlerType, this, _eventHandlerMethodInfo ?? throw new InvalidOperationException()));
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterLoaded(FrameworkElement associatedElement)
|
||||
{
|
||||
if (!IsLoadedRegistered && associatedElement != null)
|
||||
{
|
||||
associatedElement.Loaded += OnEventImpl;
|
||||
IsLoadedRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterSourceChanged()
|
||||
{
|
||||
if (!IsSourceChangedRegistered)
|
||||
{
|
||||
SourceNameResolver.ResolvedElementChanged += OnSourceNameResolverElementChanged;
|
||||
IsSourceChangedRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterEvent(object obj, string eventName)
|
||||
{
|
||||
if (string.Compare(eventName, "Loaded", StringComparison.Ordinal) == 0)
|
||||
{
|
||||
if (obj is FrameworkElement associatedElement)
|
||||
UnregisterLoaded(associatedElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnregisterEventImpl(obj, eventName);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterEventImpl(object obj, string eventName)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
if (_eventHandlerMethodInfo != null)
|
||||
{
|
||||
var info = type.GetEvent(eventName);
|
||||
info.RemoveEventHandler(obj,
|
||||
Delegate.CreateDelegate(info.EventHandlerType, this, _eventHandlerMethodInfo));
|
||||
_eventHandlerMethodInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterLoaded(FrameworkElement associatedElement)
|
||||
{
|
||||
if (IsLoadedRegistered && associatedElement != null)
|
||||
{
|
||||
associatedElement.Loaded -= OnEventImpl;
|
||||
IsLoadedRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterSourceChanged()
|
||||
{
|
||||
if (IsSourceChangedRegistered)
|
||||
{
|
||||
SourceNameResolver.ResolvedElementChanged -= OnSourceNameResolverElementChanged;
|
||||
IsSourceChangedRegistered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public abstract class EventTriggerBase<T> : EventTriggerBase where T : class
|
||||
{
|
||||
protected EventTriggerBase() : base(typeof(T))
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnSourceChanged(T oldSource, T newSource)
|
||||
{
|
||||
}
|
||||
|
||||
internal sealed override void OnSourceChangedImpl(object oldSource, object newSource)
|
||||
{
|
||||
base.OnSourceChangedImpl(oldSource, newSource);
|
||||
OnSourceChanged(oldSource as T, newSource as T);
|
||||
}
|
||||
|
||||
public new T Source => (T) base.Source;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public interface IAttachedObject
|
||||
{
|
||||
void Attach(DependencyObject dependencyObject);
|
||||
void Detach();
|
||||
|
||||
DependencyObject AssociatedObject { get; }
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public static class Interaction
|
||||
{
|
||||
private static readonly DependencyProperty TriggersProperty =
|
||||
DependencyProperty.RegisterAttached("ShadowTriggers", typeof(TriggerCollection), typeof(Interaction),
|
||||
new FrameworkPropertyMetadata(OnTriggersChanged));
|
||||
|
||||
private static readonly DependencyProperty BehaviorsProperty =
|
||||
DependencyProperty.RegisterAttached("ShadowBehaviors", typeof(BehaviorCollection), typeof(Interaction),
|
||||
new FrameworkPropertyMetadata(OnBehaviorsChanged));
|
||||
|
||||
internal static bool ShouldRunInDesignMode { get; set; }
|
||||
|
||||
public static TriggerCollection GetTriggers(DependencyObject obj)
|
||||
{
|
||||
var triggerCollection = (TriggerCollection) obj.GetValue(TriggersProperty);
|
||||
if (triggerCollection == null)
|
||||
{
|
||||
triggerCollection = new TriggerCollection();
|
||||
obj.SetValue(TriggersProperty, triggerCollection);
|
||||
}
|
||||
return triggerCollection;
|
||||
}
|
||||
|
||||
public static BehaviorCollection GetBehaviors(DependencyObject obj)
|
||||
{
|
||||
var behaviorCollection = (BehaviorCollection) obj.GetValue(BehaviorsProperty);
|
||||
if (behaviorCollection == null)
|
||||
{
|
||||
behaviorCollection = new BehaviorCollection();
|
||||
obj.SetValue(BehaviorsProperty, behaviorCollection);
|
||||
}
|
||||
return behaviorCollection;
|
||||
}
|
||||
|
||||
private static void OnBehaviorsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var oldValue = (BehaviorCollection) args.OldValue;
|
||||
var newValue = (BehaviorCollection) args.NewValue;
|
||||
if (Equals(oldValue, newValue))
|
||||
return;
|
||||
if (oldValue?.AssociatedObject != null)
|
||||
oldValue.Detach();
|
||||
if (newValue == null || obj == null)
|
||||
return;
|
||||
if (newValue.AssociatedObject != null)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostBehaviorCollectionMultipleTimesExceptionMessage);
|
||||
newValue.Attach(obj);
|
||||
}
|
||||
|
||||
private static void OnTriggersChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var oldValue = args.OldValue as TriggerCollection;
|
||||
var newValue = args.NewValue as TriggerCollection;
|
||||
if (Equals(oldValue, newValue))
|
||||
return;
|
||||
if (oldValue?.AssociatedObject != null)
|
||||
oldValue.Detach();
|
||||
if (newValue == null || obj == null)
|
||||
return;
|
||||
if (newValue.AssociatedObject != null)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostTriggerCollectionMultipleTimesExceptionMessage);
|
||||
newValue.Attach(obj);
|
||||
}
|
||||
|
||||
internal static bool IsElementLoaded(FrameworkElement element)
|
||||
{
|
||||
return element.IsLoaded;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public sealed class InvokeCommandAction : TriggerAction<DependencyObject>
|
||||
{
|
||||
public static readonly DependencyProperty CommandProperty =
|
||||
DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(InvokeCommandAction), null);
|
||||
|
||||
public static readonly DependencyProperty CommandParameterProperty =
|
||||
DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(InvokeCommandAction),
|
||||
null);
|
||||
|
||||
private string _commandName;
|
||||
|
||||
public string CommandName
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _commandName;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (CommandName == value)
|
||||
return;
|
||||
WritePreamble();
|
||||
_commandName = value;
|
||||
WritePostscript();
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand Command
|
||||
{
|
||||
get => (ICommand) GetValue(CommandProperty);
|
||||
set => SetValue(CommandProperty, value);
|
||||
}
|
||||
|
||||
public object CommandParameter
|
||||
{
|
||||
get => GetValue(CommandParameterProperty);
|
||||
set => SetValue(CommandParameterProperty, value);
|
||||
}
|
||||
|
||||
protected override void Invoke(object parameter)
|
||||
{
|
||||
if (AssociatedObject == null)
|
||||
return;
|
||||
var command = ResolveCommand();
|
||||
if (command == null || !command.CanExecute(CommandParameter))
|
||||
return;
|
||||
command.Execute(CommandParameter);
|
||||
}
|
||||
|
||||
private ICommand ResolveCommand()
|
||||
{
|
||||
var command = (ICommand) null;
|
||||
if (Command != null)
|
||||
command = Command;
|
||||
else if (AssociatedObject != null)
|
||||
foreach (var property in AssociatedObject.GetType()
|
||||
.GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
||||
if (typeof(ICommand).IsAssignableFrom(property.PropertyType) &&
|
||||
string.Equals(property.Name, CommandName, StringComparison.Ordinal))
|
||||
command = (ICommand) property.GetValue(AssociatedObject, null);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
internal sealed class NameResolvedEventArgs : EventArgs
|
||||
{
|
||||
public NameResolvedEventArgs(object oldObject, object newObject)
|
||||
{
|
||||
OldObject = oldObject;
|
||||
NewObject = newObject;
|
||||
}
|
||||
|
||||
public object NewObject { get; }
|
||||
|
||||
public object OldObject { get; }
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
internal sealed class NameResolver
|
||||
{
|
||||
private string _name;
|
||||
private FrameworkElement _nameScopeReferenceElement;
|
||||
|
||||
private FrameworkElement ActualNameScopeReferenceElement
|
||||
{
|
||||
get
|
||||
{
|
||||
if (NameScopeReferenceElement != null && Interaction.IsElementLoaded(NameScopeReferenceElement))
|
||||
return GetActualNameScopeReference(NameScopeReferenceElement);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasAttempedResolve { get; set; }
|
||||
|
||||
public string Name
|
||||
{
|
||||
get =>
|
||||
_name;
|
||||
set
|
||||
{
|
||||
var oldObject = Object;
|
||||
_name = value;
|
||||
UpdateObjectFromName(oldObject);
|
||||
}
|
||||
}
|
||||
|
||||
public FrameworkElement NameScopeReferenceElement
|
||||
{
|
||||
get =>
|
||||
_nameScopeReferenceElement;
|
||||
set
|
||||
{
|
||||
var nameScopeReferenceElement = NameScopeReferenceElement;
|
||||
_nameScopeReferenceElement = value;
|
||||
OnNameScopeReferenceElementChanged(nameScopeReferenceElement);
|
||||
}
|
||||
}
|
||||
|
||||
public DependencyObject Object
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name) && HasAttempedResolve)
|
||||
return NameScopeReferenceElement;
|
||||
return ResolvedObject;
|
||||
}
|
||||
}
|
||||
|
||||
private bool PendingReferenceElementLoad { get; set; }
|
||||
|
||||
private DependencyObject ResolvedObject { get; set; }
|
||||
|
||||
public event EventHandler<NameResolvedEventArgs> ResolvedElementChanged;
|
||||
|
||||
private FrameworkElement GetActualNameScopeReference(FrameworkElement initialReferenceElement)
|
||||
{
|
||||
var element = initialReferenceElement;
|
||||
if (!IsNameScope(initialReferenceElement))
|
||||
return element;
|
||||
return initialReferenceElement.Parent as FrameworkElement ?? element;
|
||||
}
|
||||
|
||||
private bool IsNameScope(FrameworkElement frameworkElement)
|
||||
{
|
||||
return frameworkElement.Parent is FrameworkElement parent && parent.FindName(Name) != null;
|
||||
}
|
||||
|
||||
private void OnNameScopeReferenceElementChanged(FrameworkElement oldNameScopeReference)
|
||||
{
|
||||
if (PendingReferenceElementLoad)
|
||||
{
|
||||
oldNameScopeReference.Loaded -= OnNameScopeReferenceLoaded;
|
||||
PendingReferenceElementLoad = false;
|
||||
}
|
||||
HasAttempedResolve = false;
|
||||
UpdateObjectFromName(Object);
|
||||
}
|
||||
|
||||
private void OnNameScopeReferenceLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PendingReferenceElementLoad = false;
|
||||
NameScopeReferenceElement.Loaded -= OnNameScopeReferenceLoaded;
|
||||
UpdateObjectFromName(Object);
|
||||
}
|
||||
|
||||
private void OnObjectChanged(DependencyObject oldTarget, DependencyObject newTarget)
|
||||
{
|
||||
ResolvedElementChanged?.Invoke(this, new NameResolvedEventArgs(oldTarget, newTarget));
|
||||
}
|
||||
|
||||
private void UpdateObjectFromName(DependencyObject oldObject)
|
||||
{
|
||||
DependencyObject obj2 = null;
|
||||
ResolvedObject = null;
|
||||
if (NameScopeReferenceElement != null)
|
||||
{
|
||||
if (!Interaction.IsElementLoaded(NameScopeReferenceElement))
|
||||
{
|
||||
NameScopeReferenceElement.Loaded += OnNameScopeReferenceLoaded;
|
||||
PendingReferenceElementLoad = true;
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
var actualNameScopeReferenceElement = ActualNameScopeReferenceElement;
|
||||
if (actualNameScopeReferenceElement != null)
|
||||
obj2 = actualNameScopeReferenceElement.FindName(Name) as DependencyObject;
|
||||
}
|
||||
}
|
||||
HasAttempedResolve = true;
|
||||
ResolvedObject = obj2;
|
||||
if (!Equals(oldObject, Object))
|
||||
OnObjectChanged(oldObject, Object);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public class PreviewInvokeEventArgs : EventArgs
|
||||
{
|
||||
public bool Cancelling { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' < '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<HasSharedItems>true</HasSharedItems>
|
||||
<SharedGUID>6f0c9cff-2269-46a7-9664-478354c582a4</SharedGUID>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Import_RootNamespace>System.Windows.Interactivity</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)AttachableCollection`1.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Behavior.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)BehaviorCollection.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Behavior`1.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DefaultTriggerAttribute.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)EventTrigger.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)EventTriggerBase.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)EventTriggerBase`1.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)IAttachedObject.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Interaction.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)InvokeCommandAction.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)NameResolvedEventArgs.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)NameResolver.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)PreviewInvokeEventArgs.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)TriggerAction.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)TriggerActionCollection.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)TriggerAction`1.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)TriggerBase.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)TriggerCollection.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)TypeConstraintAttribute.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>6f0c9cff-2269-46a7-9664-478354c582a4</ProjectGuid>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||
<PropertyGroup />
|
||||
<Import Project="System.Windows.Interactivity.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media.Animation;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
[DefaultTrigger(typeof(UIElement), typeof(EventTrigger), "MouseLeftButtonDown")]
|
||||
[DefaultTrigger(typeof(ButtonBase), typeof(EventTrigger), "Click")]
|
||||
public abstract class TriggerAction : Animatable, IAttachedObject
|
||||
{
|
||||
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.Register("IsEnabled",
|
||||
typeof(bool), typeof(TriggerAction), new FrameworkPropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
private readonly Type _associatedObjectTypeConstraint;
|
||||
|
||||
private DependencyObject _associatedObject;
|
||||
private bool _isHosted;
|
||||
|
||||
internal TriggerAction(Type associatedObjectTypeConstraint)
|
||||
{
|
||||
_associatedObjectTypeConstraint = associatedObjectTypeConstraint;
|
||||
}
|
||||
|
||||
protected DependencyObject AssociatedObject
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObject;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Type AssociatedObjectTypeConstraint
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObjectTypeConstraint;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => (bool) GetValue(IsEnabledProperty);
|
||||
set => SetValue(IsEnabledProperty, ValueBoxes.BooleanBox(value));
|
||||
}
|
||||
|
||||
internal bool IsHosted
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _isHosted;
|
||||
}
|
||||
set
|
||||
{
|
||||
WritePreamble();
|
||||
_isHosted = value;
|
||||
WritePostscript();
|
||||
}
|
||||
}
|
||||
|
||||
public void Attach(DependencyObject dependencyObject)
|
||||
{
|
||||
if (!Equals(dependencyObject, AssociatedObject))
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostTriggerActionMultipleTimesExceptionMessage);
|
||||
if (dependencyObject != null &&
|
||||
!AssociatedObjectTypeConstraint.IsInstanceOfType(dependencyObject))
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.TypeConstraintViolatedExceptionMessage,
|
||||
new object[]
|
||||
{GetType().Name, dependencyObject.GetType().Name, AssociatedObjectTypeConstraint.Name}));
|
||||
WritePreamble();
|
||||
_associatedObject = dependencyObject;
|
||||
WritePostscript();
|
||||
OnAttached();
|
||||
}
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
OnDetaching();
|
||||
WritePreamble();
|
||||
_associatedObject = null;
|
||||
WritePostscript();
|
||||
}
|
||||
|
||||
DependencyObject IAttachedObject.AssociatedObject =>
|
||||
AssociatedObject;
|
||||
|
||||
internal void CallInvoke(object parameter)
|
||||
{
|
||||
if (IsEnabled)
|
||||
Invoke(parameter);
|
||||
}
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return (Freezable) Activator.CreateInstance(GetType());
|
||||
}
|
||||
|
||||
protected abstract void Invoke(object parameter);
|
||||
|
||||
protected virtual void OnAttached()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnDetaching()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public class TriggerActionCollection : AttachableCollection<TriggerAction>
|
||||
{
|
||||
internal TriggerActionCollection()
|
||||
{
|
||||
}
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return new TriggerActionCollection();
|
||||
}
|
||||
|
||||
internal override void ItemAdded(TriggerAction item)
|
||||
{
|
||||
if (item.IsHosted)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostTriggerActionMultipleTimesExceptionMessage);
|
||||
if (AssociatedObject != null)
|
||||
item.Attach(AssociatedObject);
|
||||
item.IsHosted = true;
|
||||
}
|
||||
|
||||
internal override void ItemRemoved(TriggerAction item)
|
||||
{
|
||||
if (((IAttachedObject) item).AssociatedObject != null)
|
||||
item.Detach();
|
||||
item.IsHosted = false;
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
foreach (var action in this)
|
||||
action.Attach(AssociatedObject);
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
foreach (var action in this)
|
||||
action.Detach();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public abstract class TriggerAction<T> : TriggerAction where T : DependencyObject
|
||||
{
|
||||
protected TriggerAction() : base(typeof(T))
|
||||
{
|
||||
}
|
||||
|
||||
#pragma warning disable 108,114
|
||||
protected T AssociatedObject => (T) base.AssociatedObject;
|
||||
#pragma warning restore 108,114
|
||||
|
||||
protected sealed override Type AssociatedObjectTypeConstraint =>
|
||||
base.AssociatedObjectTypeConstraint;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
[ContentProperty("Actions")]
|
||||
public abstract class TriggerBase : Animatable, IAttachedObject
|
||||
{
|
||||
private static readonly DependencyPropertyKey ActionsPropertyKey =
|
||||
DependencyProperty.RegisterReadOnly("Actions", typeof(TriggerActionCollection), typeof(TriggerBase),
|
||||
new FrameworkPropertyMetadata());
|
||||
|
||||
public static readonly DependencyProperty ActionsProperty = ActionsPropertyKey.DependencyProperty;
|
||||
|
||||
private DependencyObject _associatedObject;
|
||||
private readonly Type _associatedObjectTypeConstraint;
|
||||
|
||||
internal TriggerBase(Type associatedObjectTypeConstraint)
|
||||
{
|
||||
_associatedObjectTypeConstraint = associatedObjectTypeConstraint;
|
||||
var actions = new TriggerActionCollection();
|
||||
SetValue(ActionsPropertyKey, actions);
|
||||
}
|
||||
|
||||
public TriggerActionCollection Actions =>
|
||||
(TriggerActionCollection) GetValue(ActionsProperty);
|
||||
|
||||
protected DependencyObject AssociatedObject
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObject;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Type AssociatedObjectTypeConstraint
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObjectTypeConstraint;
|
||||
}
|
||||
}
|
||||
|
||||
public void Attach(DependencyObject dependencyObject)
|
||||
{
|
||||
if (!Equals(dependencyObject, AssociatedObject))
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostTriggerMultipleTimesExceptionMessage);
|
||||
if (dependencyObject != null &&
|
||||
!AssociatedObjectTypeConstraint.IsInstanceOfType(dependencyObject))
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.TypeConstraintViolatedExceptionMessage,
|
||||
new object[]
|
||||
{GetType().Name, dependencyObject.GetType().Name, AssociatedObjectTypeConstraint.Name}));
|
||||
WritePreamble();
|
||||
_associatedObject = dependencyObject;
|
||||
WritePostscript();
|
||||
Actions.Attach(dependencyObject);
|
||||
OnAttached();
|
||||
}
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
OnDetaching();
|
||||
WritePreamble();
|
||||
_associatedObject = null;
|
||||
WritePostscript();
|
||||
Actions.Detach();
|
||||
}
|
||||
|
||||
DependencyObject IAttachedObject.AssociatedObject =>
|
||||
AssociatedObject;
|
||||
|
||||
public event EventHandler<PreviewInvokeEventArgs> PreviewInvoke;
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return (Freezable) Activator.CreateInstance(GetType());
|
||||
}
|
||||
|
||||
protected void InvokeActions(object parameter)
|
||||
{
|
||||
if (PreviewInvoke != null)
|
||||
{
|
||||
var e = new PreviewInvokeEventArgs();
|
||||
PreviewInvoke(this, e);
|
||||
if (e.Cancelling)
|
||||
return;
|
||||
}
|
||||
foreach (var action in Actions)
|
||||
action.CallInvoke(parameter);
|
||||
}
|
||||
|
||||
protected virtual void OnAttached()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnDetaching()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
public sealed class TriggerCollection : AttachableCollection<TriggerBase>
|
||||
{
|
||||
internal TriggerCollection()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
foreach (var triggerBase in this)
|
||||
triggerBase.Attach(AssociatedObject);
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
foreach (var triggerBase in this)
|
||||
triggerBase.Detach();
|
||||
}
|
||||
|
||||
internal override void ItemAdded(TriggerBase item)
|
||||
{
|
||||
if (AssociatedObject == null)
|
||||
return;
|
||||
item.Attach(AssociatedObject);
|
||||
}
|
||||
|
||||
internal override void ItemRemoved(TriggerBase item)
|
||||
{
|
||||
if (((IAttachedObject) item).AssociatedObject == null)
|
||||
return;
|
||||
item.Detach();
|
||||
}
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return new TriggerCollection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace HandyControl.Interactivity;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public sealed class TypeConstraintAttribute : Attribute
|
||||
{
|
||||
public TypeConstraintAttribute(Type constraint)
|
||||
{
|
||||
Constraint = constraint;
|
||||
}
|
||||
|
||||
public Type Constraint { get; }
|
||||
}
|
||||
Reference in new issue
Block a user