313 lines
12 KiB
C#
313 lines
12 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |