373 lines
14 KiB
C#
373 lines
14 KiB
C#
#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
|
|
}
|
|
}
|
|
} |