123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- #if XFORMS
- namespace Caliburn.Micro.Core.Xamarin.Forms
- #else
- namespace Caliburn.Micro
- #endif
- {
- using System;
- using System.Collections.Generic;
- using System.Linq;
- #if WinRT81
- using System.Reflection;
- using Windows.UI.Xaml;
- using Microsoft.Xaml.Interactivity;
- using TriggerBase = Microsoft.Xaml.Interactivity.IBehavior;
- using EventTrigger = Microsoft.Xaml.Interactions.Core.EventTriggerBehavior;
- using TriggerAction = Microsoft.Xaml.Interactivity.IAction;
- using System.Text;
- using System.Text.RegularExpressions;
- using Windows.UI.Xaml.Data;
- #elif XFORMS
- using System.Reflection;
- using System.Text.RegularExpressions;
- using global::Xamarin.Forms;
- using DependencyObject = global::Xamarin.Forms.BindableObject;
- using FrameworkElement = global::Xamarin.Forms.VisualElement;
- #else
- using System.Reflection;
- using System.Text.RegularExpressions;
- using System.Windows;
- using System.Windows.Data;
- using EventTrigger = System.Windows.Interactivity.EventTrigger;
- using TriggerBase = System.Windows.Interactivity.TriggerBase;
- using TriggerAction = System.Windows.Interactivity.TriggerAction;
- using System.Text;
- using Caliburn.Micro.Core;
- #endif
- /// <summary>
- /// Parses text into a fully functional set of <see cref="TriggerBase"/> instances with <see cref="ActionMessage"/>.
- /// </summary>
- public static class Parser
- {
- static readonly Regex LongFormatRegularExpression = new Regex(@"^[\s]*\[[^\]]*\][\s]*=[\s]*\[[^\]]*\][\s]*$");
- static readonly ILog Log = LogManager.GetLog(typeof(Parser));
- /// <summary>
- /// Parses the specified message text.
- /// </summary>
- /// <param name="target">The target.</param>
- /// <param name="text">The message text.</param>
- /// <returns>The triggers parsed from the text.</returns>
- public static IEnumerable<TriggerBase> Parse(DependencyObject target, string text)
- {
- if (string.IsNullOrEmpty(text))
- {
- return new TriggerBase[0];
- }
- var triggers = new List<TriggerBase>();
- var messageTexts = StringSplitter.Split(text, ';');
- foreach (var messageText in messageTexts)
- {
- var triggerPlusMessage = LongFormatRegularExpression.IsMatch(messageText)
- ? StringSplitter.Split(messageText, '=')
- : new[] { null, messageText };
- var messageDetail = triggerPlusMessage.Last()
- .Replace("[", string.Empty)
- .Replace("]", string.Empty)
- .Trim();
- var trigger = CreateTrigger(target, triggerPlusMessage.Length == 1 ? null : triggerPlusMessage[0]);
- var message = CreateMessage(target, messageDetail);
- #if WinRT81 || XFORMS
- AddActionToTrigger(target, message, trigger);
- #else
- trigger.Actions.Add(message);
- #endif
- triggers.Add(trigger);
- }
- return triggers;
- }
- #if XFORMS
- private static void AddActionToTrigger(DependencyObject target, TriggerAction message, TriggerBase trigger) {
- if (trigger is EventTrigger) {
- var eventTrigger = (EventTrigger) trigger;
- eventTrigger.Actions.Add(message);
- }
- trigger.EnterActions.Add(message);
- // TriggerAction doesn't have an associated object property so we have
- // to create it ourselves, could be potential issues here with leaking the associated
- // object and not correctly detaching, this may depend if the trigger implements it's
- // AssociatedObject as a DependencyProperty.
- var actionMessage = message as ActionMessage;
- var targetElement = target as FrameworkElement;
- if (actionMessage != null && targetElement != null)
- {
- actionMessage.AssociatedObject = targetElement;
- }
- }
- #endif
- #if WinRT81
- private static void AddActionToTrigger(DependencyObject target, TriggerAction message, TriggerBase trigger)
- {
- // This is stupid, but there a number of limitiations in the 8.1 Behaviours SDK
- // The first is that there is no base class for a Trigger, just IBehaviour. Which
- // means there's no strongly typed way to add an action to a trigger. Every trigger
- // in the SDK implements the same pattern but no interface, we're going to have to
- // use reflection to set it.
- // More stupidity, ActionCollection doesn't care about IAction, but DependencyObject
- // and there's no actual implementation of
- var messageDependencyObject = message as DependencyObject;
- if (messageDependencyObject == null)
- {
- Log.Warn("{0} doesn't inherit DependencyObject and can't be added to ActionCollection", trigger.GetType().FullName);
- return;
- }
- // 95% of the time the trigger will be an EventTrigger, let's optimise for that case
- if (trigger is EventTrigger)
- {
- var eventTrigger = (EventTrigger) trigger;
- eventTrigger.Actions.Add(messageDependencyObject);
- }
- else
- {
- var actionsProperty = trigger.GetType().GetRuntimeProperty("Actions");
- if (actionsProperty == null)
- {
- Log.Warn("Could not find Actions collection on trigger {0}.", trigger.GetType().FullName);
- return;
- }
- var actionCollection = actionsProperty.GetValue(trigger) as ActionCollection;
- if (actionCollection == null)
- {
- Log.Warn("{0}.Actions is either not an ActionCollection or is null.", trigger.GetType().FullName);
- return;
- }
- actionCollection.Add(messageDependencyObject);
- }
- // The second is the IAction doesn't have an associated object property so we have
- // to create it ourselves, could be potential issues here with leaking the associated
- // object and not correctly detaching, this may depend if the trigger implements it's
- // AssociatedObject as a DependencyProperty.
- // Turns out trying to a binding won't work because the trigger doesn't notify the
- // binding of changes, so we just need to set it, yay.
- var actionMessage = message as ActionMessage;
- var targetElement = target as FrameworkElement;
- if (actionMessage != null && targetElement != null)
- {
- //var binding = new Binding { Source = trigger, Path = new PropertyPath("AssociatedObject") };
- //BindingOperations.SetBinding(actionMessage, ActionMessage.AssociatedObjectProperty, binding);
- actionMessage.AssociatedObject = targetElement;
- }
-
- }
- #endif
- /// <summary>
- /// The function used to generate a trigger.
- /// </summary>
- /// <remarks>The parameters passed to the method are the the target of the trigger and string representing the trigger.</remarks>
- public static Func<DependencyObject, string, TriggerBase> CreateTrigger = (target, triggerText) =>
- {
- if (triggerText == null)
- {
- var defaults = ConventionManager.GetElementConvention(target.GetType());
- return defaults.CreateTrigger();
- }
- var triggerDetail = triggerText
- .Replace("[", string.Empty)
- .Replace("]", string.Empty)
- .Replace("Event", string.Empty)
- .Trim();
- #if XFORMS
- return new EventTrigger { Event = triggerDetail };
- #else
- return new EventTrigger { EventName = triggerDetail };
- #endif
- };
- /// <summary>
- /// Creates an instance of <see cref="ActionMessage"/> by parsing out the textual dsl.
- /// </summary>
- /// <param name="target">The target of the message.</param>
- /// <param name="messageText">The textual message dsl.</param>
- /// <returns>The created message.</returns>
- public static TriggerAction CreateMessage(DependencyObject target, string messageText)
- {
- var openingParenthesisIndex = messageText.IndexOf('(');
- if (openingParenthesisIndex < 0)
- {
- openingParenthesisIndex = messageText.Length;
- }
- var closingParenthesisIndex = messageText.LastIndexOf(')');
- if (closingParenthesisIndex < 0)
- {
- closingParenthesisIndex = messageText.Length;
- }
- var core = messageText.Substring(0, openingParenthesisIndex).Trim();
- var message = InterpretMessageText(target, core);
- var withParameters = message as IHaveParameters;
- if (withParameters != null)
- {
- if (closingParenthesisIndex - openingParenthesisIndex > 1)
- {
- var paramString = messageText.Substring(openingParenthesisIndex + 1, closingParenthesisIndex - openingParenthesisIndex - 1);
- var parameters = StringSplitter.SplitParameters(paramString);
- foreach (var parameter in parameters)
- withParameters.Parameters.Add(CreateParameter(target, parameter.Trim()));
- }
- }
- return message;
- }
- /// <summary>
- /// Function used to parse a string identified as a message.
- /// </summary>
- public static Func<DependencyObject, string, TriggerAction> InterpretMessageText = (target, text) =>
- {
- return new ActionMessage { MethodName = Regex.Replace(text, "^Action", string.Empty).Trim() };
- };
- /// <summary>
- /// Function used to parse a string identified as a message parameter.
- /// </summary>
- public static Func<DependencyObject, string, Parameter> CreateParameter = (target, parameterText) =>
- {
- var actualParameter = new Parameter();
- if (parameterText.StartsWith("'") && parameterText.EndsWith("'"))
- {
- actualParameter.Value = parameterText.Substring(1, parameterText.Length - 2);
- }
- else if (MessageBinder.SpecialValues.ContainsKey(parameterText.ToLower()) || char.IsNumber(parameterText[0]))
- {
- actualParameter.Value = parameterText;
- }
- else if (target is FrameworkElement)
- {
- var fe = (FrameworkElement)target;
- var nameAndBindingMode = parameterText.Split(':').Select(x => x.Trim()).ToArray();
- var index = nameAndBindingMode[0].IndexOf('.');
- View.ExecuteOnLoad(fe, delegate
- {
- BindParameter(
- fe,
- actualParameter,
- index == -1 ? nameAndBindingMode[0] : nameAndBindingMode[0].Substring(0, index),
- index == -1 ? null : nameAndBindingMode[0].Substring(index + 1),
- nameAndBindingMode.Length == 2 ? (BindingMode)Enum.Parse(typeof(BindingMode), nameAndBindingMode[1], true) : BindingMode.OneWay
- );
- });
- }
- return actualParameter;
- };
- /// <summary>
- /// Creates a binding on a <see cref="Parameter"/>.
- /// </summary>
- /// <param name="target">The target to which the message is applied.</param>
- /// <param name="parameter">The parameter object.</param>
- /// <param name="elementName">The name of the element to bind to.</param>
- /// <param name="path">The path of the element to bind to.</param>
- /// <param name="bindingMode">The binding mode to use.</param>
- public static void BindParameter(FrameworkElement target, Parameter parameter, string elementName, string path, BindingMode bindingMode)
- {
- #if XFORMS
- var element = elementName == "$this" ? target : null;
- if (element == null)
- {
- return;
- }
- if (string.IsNullOrEmpty(path))
- {
- path = ConventionManager.GetElementConvention(element.GetType()).ParameterProperty;
- }
- var binding = new Binding(path) {
- Source = element,
- Mode = bindingMode
- };
- parameter.SetBinding(Parameter.ValueProperty, binding);
- #else
- var element = elementName == "$this"
- ? target
- : BindingScope.GetNamedElements(target).FindName(elementName);
- if (element == null)
- {
- return;
- }
- if (string.IsNullOrEmpty(path))
- {
- path = ConventionManager.GetElementConvention(element.GetType()).ParameterProperty;
- }
- #if WinRT
- var binding = new Binding
- {
- Path = new PropertyPath(path),
- Source = element,
- Mode = bindingMode
- };
- #else
- var binding = new Binding(path) {
- Source = element,
- Mode = bindingMode
- };
- #endif
- #if (SILVERLIGHT && !SL5)
- var expression = (BindingExpression)BindingOperations.SetBinding(parameter, Parameter.ValueProperty, binding);
- var field = element.GetType().GetField(path + "Property", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
- if (field == null) {
- return;
- }
- ConventionManager.ApplySilverlightTriggers(element, (DependencyProperty)field.GetValue(null), x => expression, null, null);
- #else
- #if !WinRT
- binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
- #endif
- BindingOperations.SetBinding(parameter, Parameter.ValueProperty, binding);
- #endif
- #endif
- }
- }
- }
|