diff options
author | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 13:02:25 -0700 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 16:13:41 -0700 |
commit | 17fdde66d94155fc62a034fa6658995bef6fd6e5 (patch) | |
tree | b5e5073a2a7b15cdbe826faa5c763e270a505729 /Xamarin.Forms.Core/Interactivity | |
download | xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.gz xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.bz2 xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.zip |
Initial import
Diffstat (limited to 'Xamarin.Forms.Core/Interactivity')
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/AttachedCollection.cs | 127 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/Behavior.cs | 65 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/BindingCondition.cs | 100 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/Condition.cs | 51 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/DataTrigger.cs | 57 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/EventTrigger.cs | 91 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/IAttachedObject.cs | 8 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/MultiCondition.cs | 66 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/MultiTrigger.cs | 23 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/PropertyCondition.cs | 100 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/Trigger.cs | 60 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/TriggerAction.cs | 37 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Interactivity/TriggerBase.cs | 212 |
13 files changed, 997 insertions, 0 deletions
diff --git a/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs b/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs new file mode 100644 index 00000000..6aff5147 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Xamarin.Forms +{ + internal class AttachedCollection<T> : ObservableCollection<T>, ICollection<T>, IAttachedObject where T : BindableObject, IAttachedObject + { + readonly List<WeakReference> _associatedObjects = new List<WeakReference>(); + + public AttachedCollection() + { + } + + public AttachedCollection(IEnumerable<T> collection) : base(collection) + { + } + + public AttachedCollection(IList<T> list) : base(list) + { + } + + public void AttachTo(BindableObject bindable) + { + if (bindable == null) + throw new ArgumentNullException("bindable"); + OnAttachedTo(bindable); + } + + public void DetachFrom(BindableObject bindable) + { + OnDetachingFrom(bindable); + } + + protected override void ClearItems() + { + foreach (WeakReference weakbindable in _associatedObjects) + { + foreach (T item in this) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item.DetachFrom(bindable); + } + } + base.ClearItems(); + } + + protected override void InsertItem(int index, T item) + { + base.InsertItem(index, item); + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item.AttachTo(bindable); + } + } + + protected virtual void OnAttachedTo(BindableObject bindable) + { + lock(_associatedObjects) + { + _associatedObjects.Add(new WeakReference(bindable)); + } + foreach (T item in this) + item.AttachTo(bindable); + } + + protected virtual void OnDetachingFrom(BindableObject bindable) + { + foreach (T item in this) + item.DetachFrom(bindable); + lock(_associatedObjects) + { + for (var i = 0; i < _associatedObjects.Count; i++) + { + object target = _associatedObjects[i].Target; + + if (target == null || target == bindable) + { + _associatedObjects.RemoveAt(i); + i--; + } + } + } + } + + protected override void RemoveItem(int index) + { + T item = this[index]; + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item.DetachFrom(bindable); + } + + base.RemoveItem(index); + } + + protected override void SetItem(int index, T item) + { + T old = this[index]; + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + old.DetachFrom(bindable); + } + + base.SetItem(index, item); + + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item.AttachTo(bindable); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/Behavior.cs b/Xamarin.Forms.Core/Interactivity/Behavior.cs new file mode 100644 index 00000000..f8689041 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/Behavior.cs @@ -0,0 +1,65 @@ +using System; + +namespace Xamarin.Forms +{ + public abstract class Behavior : BindableObject, IAttachedObject + { + internal Behavior(Type associatedType) + { + if (associatedType == null) + throw new ArgumentNullException("associatedType"); + AssociatedType = associatedType; + } + + protected Type AssociatedType { get; } + + void IAttachedObject.AttachTo(BindableObject bindable) + { + if (bindable == null) + throw new ArgumentNullException("bindable"); + if (!AssociatedType.IsInstanceOfType(bindable)) + throw new InvalidOperationException("bindable not an instance of AssociatedType"); + OnAttachedTo(bindable); + } + + void IAttachedObject.DetachFrom(BindableObject bindable) + { + OnDetachingFrom(bindable); + } + + protected virtual void OnAttachedTo(BindableObject bindable) + { + } + + protected virtual void OnDetachingFrom(BindableObject bindable) + { + } + } + + public abstract class Behavior<T> : Behavior where T : BindableObject + { + protected Behavior() : base(typeof(T)) + { + } + + protected override void OnAttachedTo(BindableObject bindable) + { + base.OnAttachedTo(bindable); + OnAttachedTo((T)bindable); + } + + protected virtual void OnAttachedTo(T bindable) + { + } + + protected override void OnDetachingFrom(BindableObject bindable) + { + OnDetachingFrom((T)bindable); + base.OnDetachingFrom(bindable); + } + + protected virtual void OnDetachingFrom(T bindable) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/BindingCondition.cs b/Xamarin.Forms.Core/Interactivity/BindingCondition.cs new file mode 100644 index 00000000..88b7cf36 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/BindingCondition.cs @@ -0,0 +1,100 @@ +using System; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + public sealed class BindingCondition : Condition, IValueProvider + { + readonly BindableProperty _boundProperty; + + BindingBase _binding; + object _triggerValue; + + public BindingCondition() + { + _boundProperty = BindableProperty.CreateAttached("Bound", typeof(object), typeof(DataTrigger), null, propertyChanged: OnBoundPropertyChanged); + } + + public BindingBase Binding + { + get { return _binding; } + set + { + if (_binding == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Binding once the Trigger has been applied."); + _binding = value; + } + } + + public object Value + { + get { return _triggerValue; } + set + { + if (_triggerValue == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Value once the Trigger has been applied."); + _triggerValue = value; + } + } + + internal IServiceProvider ServiceProvider { get; set; } + + internal IValueConverterProvider ValueConverter { get; set; } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + ValueConverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider; + ServiceProvider = serviceProvider; + + return this; + } + + internal override bool GetState(BindableObject bindable) + { + object newValue = bindable.GetValue(_boundProperty); + return EqualsToValue(newValue); + } + + internal override void SetUp(BindableObject bindable) + { + if (Binding != null) + bindable.SetBinding(_boundProperty, Binding.Clone()); + } + + internal override void TearDown(BindableObject bindable) + { + bindable.RemoveBinding(_boundProperty); + bindable.ClearValue(_boundProperty); + } + + bool EqualsToValue(object other) + { + if ((other == Value) || (other != null && other.Equals(Value))) + return true; + + object converted = null; + if (ValueConverter != null) + converted = ValueConverter.Convert(Value, other != null ? other.GetType() : typeof(object), null, ServiceProvider); + else + return false; + + return (other == converted) || (other != null && other.Equals(converted)); + } + + void OnBoundPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + bool oldState = EqualsToValue(oldValue); + bool newState = EqualsToValue(newValue); + + if (newState == oldState) + return; + + if (ConditionChanged != null) + ConditionChanged(bindable, oldState, newState); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/Condition.cs b/Xamarin.Forms.Core/Interactivity/Condition.cs new file mode 100644 index 00000000..aad921cf --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/Condition.cs @@ -0,0 +1,51 @@ +using System; + +namespace Xamarin.Forms +{ + public abstract class Condition + { + Action<BindableObject, bool, bool> _conditionChanged; + + bool _isSealed; + + internal Condition() + { + } + + internal Action<BindableObject, bool, bool> ConditionChanged + { + get { return _conditionChanged; } + set + { + if (_conditionChanged == value) + return; + if (_conditionChanged != null) + throw new InvalidOperationException("The same condition instance can not be reused"); + _conditionChanged = value; + } + } + + internal bool IsSealed + { + get { return _isSealed; } + set + { + if (_isSealed == value) + return; + if (!value) + throw new InvalidOperationException("What is sealed can not be unsealed."); + _isSealed = value; + OnSealed(); + } + } + + internal abstract bool GetState(BindableObject bindable); + + internal virtual void OnSealed() + { + } + + internal abstract void SetUp(BindableObject bindable); + internal abstract void TearDown(BindableObject bindable); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/DataTrigger.cs b/Xamarin.Forms.Core/Interactivity/DataTrigger.cs new file mode 100644 index 00000000..e27ec134 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/DataTrigger.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + [ContentProperty("Setters")] + public sealed class DataTrigger : TriggerBase, IValueProvider + { + public DataTrigger([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType) : base(new BindingCondition(), targetType) + { + } + + public BindingBase Binding + { + get { return ((BindingCondition)Condition).Binding; } + set + { + if (((BindingCondition)Condition).Binding == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Binding once the Trigger has been applied."); + OnPropertyChanging(); + ((BindingCondition)Condition).Binding = value; + OnPropertyChanged(); + } + } + + public new IList<Setter> Setters + { + get { return base.Setters; } + } + + public object Value + { + get { return ((BindingCondition)Condition).Value; } + set + { + if (((BindingCondition)Condition).Value == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Value once the Trigger has been applied."); + OnPropertyChanging(); + ((BindingCondition)Condition).Value = value; + OnPropertyChanged(); + } + } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider; + (Condition as BindingCondition).ValueConverter = valueconverter; + + return this; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/EventTrigger.cs b/Xamarin.Forms.Core/Interactivity/EventTrigger.cs new file mode 100644 index 00000000..52e221a0 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/EventTrigger.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Xamarin.Forms +{ + [ContentProperty("Actions")] + public sealed class EventTrigger : TriggerBase + { + static readonly MethodInfo s_handlerinfo = typeof(EventTrigger).GetRuntimeMethods().Single(mi => mi.Name == "OnEventTriggered" && mi.IsPublic == false); + readonly List<BindableObject> _associatedObjects = new List<BindableObject>(); + + EventInfo _eventinfo; + + string _eventname; + Delegate _handlerdelegate; + + public EventTrigger() : base(typeof(BindableObject)) + { + Actions = new SealedList<TriggerAction>(); + } + + public IList<TriggerAction> Actions { get; } + + public string Event + { + get { return _eventname; } + set + { + if (_eventname == value) + return; + if (IsSealed) + throw new InvalidOperationException("Event cannot be changed once the Trigger has been applied"); + OnPropertyChanging(); + _eventname = value; + OnPropertyChanged(); + } + } + + internal override void OnAttachedTo(BindableObject bindable) + { + base.OnAttachedTo(bindable); + if (!string.IsNullOrEmpty(Event)) + AttachHandlerTo(bindable); + _associatedObjects.Add(bindable); + } + + internal override void OnDetachingFrom(BindableObject bindable) + { + _associatedObjects.Remove(bindable); + DetachHandlerFrom(bindable); + base.OnDetachingFrom(bindable); + } + + internal override void OnSeal() + { + base.OnSeal(); + ((SealedList<TriggerAction>)Actions).IsReadOnly = true; + } + + void AttachHandlerTo(BindableObject bindable) + { + try + { + _eventinfo = bindable.GetType().GetRuntimeEvent(Event); + _handlerdelegate = s_handlerinfo.CreateDelegate(_eventinfo.EventHandlerType, this); + } + catch (Exception) + { + Log.Warning("EventTrigger", "Can not attach EventTrigger to {0}.{1}. Check if the handler exists and if the signature is right.", bindable.GetType(), Event); + } + if (_eventinfo != null && _handlerdelegate != null) + _eventinfo.AddEventHandler(bindable, _handlerdelegate); + } + + void DetachHandlerFrom(BindableObject bindable) + { + if (_eventinfo != null && _handlerdelegate != null) + _eventinfo.RemoveEventHandler(bindable, _handlerdelegate); + } + + [Preserve] + void OnEventTriggered(object sender, EventArgs e) + { + var bindable = (BindableObject)sender; + foreach (TriggerAction action in Actions) + action.DoInvoke(bindable); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/IAttachedObject.cs b/Xamarin.Forms.Core/Interactivity/IAttachedObject.cs new file mode 100644 index 00000000..09748873 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/IAttachedObject.cs @@ -0,0 +1,8 @@ +namespace Xamarin.Forms +{ + internal interface IAttachedObject + { + void AttachTo(BindableObject bindable); + void DetachFrom(BindableObject bindable); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/MultiCondition.cs b/Xamarin.Forms.Core/Interactivity/MultiCondition.cs new file mode 100644 index 00000000..23ca41c5 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/MultiCondition.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + internal sealed class MultiCondition : Condition + { + readonly BindableProperty _aggregatedStateProperty; + + public MultiCondition() + { + _aggregatedStateProperty = BindableProperty.CreateAttached("AggregatedState", typeof(bool), typeof(DataTrigger), false, propertyChanged: OnAggregatedStatePropertyChanged); + Conditions = new TriggerBase.SealedList<Condition>(); + } + + public IList<Condition> Conditions { get; } + + internal override bool GetState(BindableObject bindable) + { + return (bool)bindable.GetValue(_aggregatedStateProperty); + } + + internal override void OnSealed() + { + ((TriggerBase.SealedList<Condition>)Conditions).IsReadOnly = true; + foreach (Condition condition in Conditions) + condition.ConditionChanged = OnConditionChanged; + } + + internal override void SetUp(BindableObject bindable) + { + foreach (Condition condition in Conditions) + condition.SetUp(bindable); + } + + internal override void TearDown(BindableObject bindable) + { + foreach (Condition condition in Conditions) + condition.TearDown(bindable); + } + + void OnAggregatedStatePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if ((bool)oldValue == (bool)newValue) + return; + + if (ConditionChanged != null) + ConditionChanged(bindable, (bool)oldValue, (bool)newValue); + } + + void OnConditionChanged(BindableObject bindable, bool oldValue, bool newValue) + { + var oldState = (bool)bindable.GetValue(_aggregatedStateProperty); + var newState = true; + foreach (Condition condition in Conditions) + { + if (!condition.GetState(bindable)) + { + newState = false; + break; + } + } + if (newState != oldState) + bindable.SetValue(_aggregatedStateProperty, newState); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/MultiTrigger.cs b/Xamarin.Forms.Core/Interactivity/MultiTrigger.cs new file mode 100644 index 00000000..3c85467c --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/MultiTrigger.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + [ContentProperty("Setters")] + public sealed class MultiTrigger : TriggerBase + { + public MultiTrigger([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType) : base(new MultiCondition(), targetType) + { + } + + public IList<Condition> Conditions + { + get { return ((MultiCondition)Condition).Conditions; } + } + + public new IList<Setter> Setters + { + get { return base.Setters; } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/PropertyCondition.cs b/Xamarin.Forms.Core/Interactivity/PropertyCondition.cs new file mode 100644 index 00000000..be37d48f --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/PropertyCondition.cs @@ -0,0 +1,100 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + public sealed class PropertyCondition : Condition, IValueProvider + { + readonly BindableProperty _stateProperty; + + BindableProperty _property; + object _triggerValue; + + public PropertyCondition() + { + _stateProperty = BindableProperty.CreateAttached("State", typeof(bool), typeof(DataTrigger), false, propertyChanged: OnStatePropertyChanged); + } + + public BindableProperty Property + { + get { return _property; } + set + { + if (_property == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Property once the Trigger has been applied."); + _property = value; + } + } + + public object Value + { + get { return _triggerValue; } + set + { + if (_triggerValue == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Value once the Trigger has been applied."); + _triggerValue = value; + } + } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider; + Func<MemberInfo> minforetriever = () => Property.DeclaringType.GetRuntimeProperty(Property.PropertyName); + + object value = valueconverter.Convert(Value, Property.ReturnType, minforetriever, serviceProvider); + Value = value; + return this; + } + + internal override bool GetState(BindableObject bindable) + { + return (bool)bindable.GetValue(_stateProperty); + } + + internal override void SetUp(BindableObject bindable) + { + object newvalue = bindable.GetValue(Property); + + bool newState = (newvalue == Value) || (newvalue != null && newvalue.Equals(Value)); + bindable.SetValue(_stateProperty, newState); + bindable.PropertyChanged += OnAttachedObjectPropertyChanged; + } + + internal override void TearDown(BindableObject bindable) + { + bindable.ClearValue(_stateProperty); + bindable.PropertyChanged -= OnAttachedObjectPropertyChanged; + } + + void OnAttachedObjectPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var bindable = (BindableObject)sender; + var oldState = (bool)bindable.GetValue(_stateProperty); + + if (Property == null) + return; + if (e.PropertyName != Property.PropertyName) + return; + object newvalue = bindable.GetValue(Property); + bool newstate = (newvalue == Value) || (newvalue != null && newvalue.Equals(Value)); + if (oldState != newstate) + bindable.SetValue(_stateProperty, newstate); + } + + void OnStatePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if ((bool)oldValue == (bool)newValue) + return; + + if (ConditionChanged != null) + ConditionChanged(bindable, (bool)oldValue, (bool)newValue); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/Trigger.cs b/Xamarin.Forms.Core/Interactivity/Trigger.cs new file mode 100644 index 00000000..ea3dc5ae --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/Trigger.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + [ContentProperty("Setters")] + public sealed class Trigger : TriggerBase, IValueProvider + { + public Trigger([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType) : base(new PropertyCondition(), targetType) + { + } + + public BindableProperty Property + { + get { return ((PropertyCondition)Condition).Property; } + set + { + if (((PropertyCondition)Condition).Property == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Property once the Trigger has been applied."); + OnPropertyChanging(); + ((PropertyCondition)Condition).Property = value; + OnPropertyChanged(); + } + } + + public new IList<Setter> Setters + { + get { return base.Setters; } + } + + public object Value + { + get { return ((PropertyCondition)Condition).Value; } + set + { + if (((PropertyCondition)Condition).Value == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Value once the Trigger has been applied."); + OnPropertyChanging(); + ((PropertyCondition)Condition).Value = value; + OnPropertyChanged(); + } + } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider; + Func<MemberInfo> minforetriever = () => Property.DeclaringType.GetRuntimeProperty(Property.PropertyName); + + object value = valueconverter.Convert(Value, Property.ReturnType, minforetriever, serviceProvider); + Value = value; + return this; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/TriggerAction.cs b/Xamarin.Forms.Core/Interactivity/TriggerAction.cs new file mode 100644 index 00000000..bb9dc08f --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/TriggerAction.cs @@ -0,0 +1,37 @@ +using System; + +namespace Xamarin.Forms +{ + public abstract class TriggerAction + { + internal TriggerAction(Type associatedType) + { + if (associatedType == null) + throw new ArgumentNullException("associatedType"); + AssociatedType = associatedType; + } + + protected Type AssociatedType { get; private set; } + + protected abstract void Invoke(object sender); + + internal virtual void DoInvoke(object sender) + { + Invoke(sender); + } + } + + public abstract class TriggerAction<T> : TriggerAction where T : BindableObject + { + protected TriggerAction() : base(typeof(T)) + { + } + + protected override void Invoke(object sender) + { + Invoke((T)sender); + } + + protected abstract void Invoke(T sender); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/TriggerBase.cs b/Xamarin.Forms.Core/Interactivity/TriggerBase.cs new file mode 100644 index 00000000..9418c7ae --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/TriggerBase.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + public abstract class TriggerBase : BindableObject, IAttachedObject + { + bool _isSealed; + + internal TriggerBase(Type targetType) + { + if (targetType == null) + throw new ArgumentNullException("targetType"); + TargetType = targetType; + + EnterActions = new SealedList<TriggerAction>(); + ExitActions = new SealedList<TriggerAction>(); + } + + internal TriggerBase(Condition condition, Type targetType) : this(targetType) + { + Setters = new SealedList<Setter>(); + Condition = condition; + Condition.ConditionChanged = OnConditionChanged; + } + + public IList<TriggerAction> EnterActions { get; } + + public IList<TriggerAction> ExitActions { get; } + + public bool IsSealed + { + get { return _isSealed; } + private set + { + if (_isSealed == value) + return; + if (!value) + throw new InvalidOperationException("What is sealed can not be unsealed."); + _isSealed = value; + OnSeal(); + } + } + + public Type TargetType { get; } + + internal Condition Condition { get; } + + //Setters and Condition are used by Trigger, DataTrigger and MultiTrigger + internal IList<Setter> Setters { get; } + + void IAttachedObject.AttachTo(BindableObject bindable) + { + IsSealed = true; + + if (bindable == null) + throw new ArgumentNullException("bindable"); + if (!TargetType.IsInstanceOfType(bindable)) + throw new InvalidOperationException("bindable not an instance of AssociatedType"); + OnAttachedTo(bindable); + } + + void IAttachedObject.DetachFrom(BindableObject bindable) + { + if (bindable == null) + throw new ArgumentNullException("bindable"); + OnDetachingFrom(bindable); + } + + internal virtual void OnAttachedTo(BindableObject bindable) + { + if (Condition != null) + Condition.SetUp(bindable); + } + + internal virtual void OnDetachingFrom(BindableObject bindable) + { + if (Condition != null) + Condition.TearDown(bindable); + } + + internal virtual void OnSeal() + { + ((SealedList<TriggerAction>)EnterActions).IsReadOnly = true; + ((SealedList<TriggerAction>)ExitActions).IsReadOnly = true; + if (Setters != null) + ((SealedList<Setter>)Setters).IsReadOnly = true; + if (Condition != null) + Condition.IsSealed = true; + } + + void OnConditionChanged(BindableObject bindable, bool oldValue, bool newValue) + { + if (newValue) + { + foreach (TriggerAction action in EnterActions) + action.DoInvoke(bindable); + foreach (Setter setter in Setters) + setter.Apply(bindable); + } + else + { + foreach (Setter setter in Setters) + setter.UnApply(bindable); + foreach (TriggerAction action in ExitActions) + action.DoInvoke(bindable); + } + } + + internal class SealedList<T> : IList<T> + { + readonly IList<T> _actual; + + bool _isReadOnly; + + public SealedList() + { + _actual = new List<T>(); + } + + public void Add(T item) + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + _actual.Add(item); + } + + public void Clear() + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + _actual.Clear(); + } + + public bool Contains(T item) + { + return _actual.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _actual.CopyTo(array, arrayIndex); + } + + public int Count + { + get { return _actual.Count; } + } + + public bool IsReadOnly + { + get { return _isReadOnly; } + set + { + if (_isReadOnly == value) + return; + if (!value) + throw new InvalidOperationException("Can't change this back to non readonly"); + _isReadOnly = value; + } + } + + public bool Remove(T item) + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + return _actual.Remove(item); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_actual).GetEnumerator(); + } + + public IEnumerator<T> GetEnumerator() + { + return _actual.GetEnumerator(); + } + + public int IndexOf(T item) + { + return _actual.IndexOf(item); + } + + public void Insert(int index, T item) + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + _actual.Insert(index, item); + } + + public T this[int index] + { + get { return _actual[index]; } + set + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + _actual[index] = value; + } + } + + public void RemoveAt(int index) + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + _actual.RemoveAt(index); + } + } + } +}
\ No newline at end of file |