summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Core/Interactivity
diff options
context:
space:
mode:
authorJason Smith <jason.smith@xamarin.com>2016-03-22 13:02:25 -0700
committerJason Smith <jason.smith@xamarin.com>2016-03-22 16:13:41 -0700
commit17fdde66d94155fc62a034fa6658995bef6fd6e5 (patch)
treeb5e5073a2a7b15cdbe826faa5c763e270a505729 /Xamarin.Forms.Core/Interactivity
downloadxamarin-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.cs127
-rw-r--r--Xamarin.Forms.Core/Interactivity/Behavior.cs65
-rw-r--r--Xamarin.Forms.Core/Interactivity/BindingCondition.cs100
-rw-r--r--Xamarin.Forms.Core/Interactivity/Condition.cs51
-rw-r--r--Xamarin.Forms.Core/Interactivity/DataTrigger.cs57
-rw-r--r--Xamarin.Forms.Core/Interactivity/EventTrigger.cs91
-rw-r--r--Xamarin.Forms.Core/Interactivity/IAttachedObject.cs8
-rw-r--r--Xamarin.Forms.Core/Interactivity/MultiCondition.cs66
-rw-r--r--Xamarin.Forms.Core/Interactivity/MultiTrigger.cs23
-rw-r--r--Xamarin.Forms.Core/Interactivity/PropertyCondition.cs100
-rw-r--r--Xamarin.Forms.Core/Interactivity/Trigger.cs60
-rw-r--r--Xamarin.Forms.Core/Interactivity/TriggerAction.cs37
-rw-r--r--Xamarin.Forms.Core/Interactivity/TriggerBase.cs212
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