$eventArgs.PropertyName ?

Jan 20, 2011 at 2:26 AM

Hello,

I have the following Message.Attach on a DataForm:

Micro:Message.Attach="[Event EditEnded] = [Action Submit( $eventArgs.EditAction )]"

The Submit method gets called, however, the parameter is not evaluated properly.  I did some digging and it looks like MessageBinder does a hard-check on $eventArgs but doesn't evaluate any property expressions.  Is this supported in Caliburn.Micro in some way that I'm missing?

Thank you!

Michael

Coordinator
Jan 20, 2011 at 2:32 AM

It's not supported in Micro.

Jan 20, 2011 at 2:33 AM

Boo. :)

Coordinator
Jan 20, 2011 at 2:41 AM

Well, I have to find some way to make it "Micro" ;) It's probably not to hard to add that in yourself if you really need it for your own scenarios.

Jan 20, 2011 at 8:07 AM

Rob, have you checked out the Microsoft DynamicQuery sample (you can download it, together with some other samples, at this link)? It provides a single class (Dynamic.cs) that can be used to generate an Expression from a string.

I integrated this sample in my project to create a multi-purpose converter (to avoid converters proliferation), and so far I had no issues.

I read somewhere that you are not interested in creating a full-featured parser/language, but in this case you would just allow for normal C# code to be put in the action body.

If you are interested, I could provide an integration sample (I would just change a bit the MessageBinder.DetermineParameters).

Coordinator
Jan 20, 2011 at 2:17 PM

It's a bit to large an d complex for me to add natively to Caliburn.Micro and would open up some possibilities that I explicitly don't want to support. But, I think that people could definitely benefit from seeing how to do this. I would love to put an article up on the site in the recipes section where you explain how to integrate it.

Jan 20, 2011 at 2:28 PM

Nice, I'll try to find a bit of time this weekend to write up a post here... I should even provide a ViewCache sample, by the way... --'

Jan 23, 2011 at 6:42 PM
Edited Jan 23, 2011 at 6:55 PM

Somehow I get the feeling we need a Caliburn.JustRight.

;)

 

Jan 24, 2011 at 2:14 AM
Edited Jan 24, 2011 at 1:43 PM

Well, I just finished experimenting with this stuff, and I got some nice results, in my opinion... following a brief overview on what I wanted to achieve, and what I achieved.

Let's consider this scenario

<Rectangle Fill="Red" cm:Message.Attach="[Event MouseLeftButtonDown] = [Action ShowMessage($eventArgs.ButtonState)]"/>

where I want to retrieve the value of a property exposed by the event arguments provided by the event. The current CM implementation does not allow for this kind of definition.

 I really wanted to provide a solution just using existing extension points, but it proved to be impossible, due to the fact that... well, there are none. This means that the provided solution requires a modification to the Caliburn.Micro base code, unless proper extension points are implemented.

After a bit of studying, I identified the critical places where modifications were required:

  • Parser.CreateParameter: this function parses the Message body returning a set of Parameter used to retrieve the values to pass to the target Action. Unfortunally, such method tries to resolve the above code using a binding (to be exact, searches for a named element $eventArgs and forces a binding over its ButtonState property).
  • MessageBinder.DetermineParamters: this function tries to resolve the values of the parameters, substituting special values (i.e. $datacontext, $eventArgs and $source) with actual reference and enforcing type consistency.

To allow property resolution, I decided to use the Dynamic Query source code provided by Microsoft (and cited before), that provides a parser able to create an Expression from a string. Such Expression can be then compiled in a Delegate and executed.

Using this approach gives more than simple property resolution, since it allows to define simple code in the Action body, that gets generated and executed at runtime. In other words, with the proposed apprach, it is possible to do something like this:

<Rectangle Fill="Red" cm:Message.Attach="[Event MouseLeftButtonDown] = [Action ShowMessage($eventArgs.ClickCount * 2)]"/>

or even something like

<Rectangle Fill="Red" cm:Message.Attach="[Event MouseLeftButtonDown] = [Action ShowMessage($datacontext.SomeMethod($eventArgs.ClickCount), $datacontext.Property1.Property))]"/>

Note that I do not encourage to inject code in xaml files, yet there could be cases where such feature comes in handy... just do not abuse it... you know... 'with great power comes great responsiblities'.

A final note before presenting the source code: the current Parser.CreateParameter method will try to create a Binding unless the parameter is exactly a special value (i.e. $datacontext, $eventArgs or $source), a string or a (supposed) numeric value. Moreover, it uses the special value $this to create bindings associated to the message target. This approach is not compatible with property name resolution (it is not possible to distinguish between a binding or a normal parameter, unless the context is known) and is not so flexible. That's why I decided to provide another approach, that is not compatible with the previous one. I decided to allow the developer to define bindings using the proper markup extension. In other words, I just integrated the Xaml parser within the action parser, to allow (limited) support for markup extensions over action parameters. A sample should be clearer:

<Rectangle x:Name="rectangle" Fill="Red" cm:Message.Attach="[Event MouseLeftButtonDown] = [Action ShowMessage($eventArgs.ClickCount * 2, {Binding Path=Foreground, ElementName=rectangle})]"/>

This appraoch does not require a special keyword for the current target (you got the full power of a Binding) and allows for whatever markup extension to be used to retrieve a parameter value, given that the proper namespace is known to the Parser. To support markup extensions properly, I had to modify the parsing process to identify proper separators for multiple messages, triggers/actions and parameters. Such changes involve Parser.Parse and Parser.CreateMessage.

Finally, here are the changes I made to support all this stuff.

First of all, I changed Parser.Parse to properly split the string:

        public static IEnumerable<TriggerBase> Parse(DependencyObject target, string text)
        {
            var triggers = new List<TriggerBase>();
            var messageTexts = Split(text, ';', true);   //CHANGE: avoid to split inside action body

            foreach(var messageText in messageTexts)
            {
                var triggerPlusMessage = Split(messageText, '=', true); //CHANGE: avoid to split inside action body
                string messageDetail = triggerPlusMessage.Last()
                    .Replace("[", string.Empty)
                    .Replace("]", string.Empty)
                    .Trim();

                var trigger = CreateTrigger(target.GetType(), triggerPlusMessage);
                var message = CreateMessage(target, messageDetail);

                trigger.Actions.Add(message);
                triggers.Add(trigger);
            }

            return triggers;
        }

        static string[] Split(string message, char separator, bool removeEmptyLines)
        {
            //Splits a string using the specified separator, if it is found outside of relevant places
            //delimited by [ and ]
            string str;
            var list = new List<string>();
            var builder = new StringBuilder();

            int squareBrackets = 0;
            foreach (var current in message)
            {
                //Square brackets are used as delimiters, so only separators outside them count...
                if (current == '[')
                    squareBrackets++;
                else if (current == ']')
                    squareBrackets--;
                else if (current == separator)
                {
                    if (squareBrackets == 0)
                    {
                        str = builder.ToString();
                        if (!removeEmptyLines || !string.IsNullOrEmpty(str))
                            list.Add(builder.ToString());
#if WP7
                        builder = new StringBuilder();
#else
                        builder.Clear();
#endif
                        continue;
                    }
                }

                builder.Append(current);
            }

            str = builder.ToString();
            if (!removeEmptyLines || !string.IsNullOrEmpty(str)) 
                list.Add(builder.ToString());

            return list.ToArray();
        }

Note that the string splitting takes into account the message definition syntax, avoid splitting inside square brackets.

Next I changed the Parser.CreateMessage, avoid splitting inside markup extensions: 

        public static ActionMessage CreateMessage(DependencyObject target, string messageText)
        {
            var message = new ActionMessage();
            messageText = Regex.Replace(messageText, "^Action", string.Empty);

            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();

            message.MethodName = core;

            if (closingParenthesisIndex - openingParenthesisIndex > 1)
            {
                var paramString = messageText.Substring(openingParenthesisIndex + 1,
                    closingParenthesisIndex - openingParenthesisIndex - 1);

                var parameters = SplitMethodParameters(paramString); //Regex.Split(paramString);

                foreach (var parameter in parameters)
                {
                    message.Parameters.Add(CreateParameter(target, parameter.Trim()));
                }
            }

            return message;
        }

        private static string[] SplitMethodParameters(string parameters)
        {
            //Splits parameter string taking into account brackets...
            List<string> list = new List<string>();
            var builder = new StringBuilder();

            bool isInString = false;

            int curlyBrackets = 0;
            int squareBrackets = 0;
            int roundBrackets = 0;
            for (int i = 0; i < parameters.Length; i++ )
            {
                var current = parameters[i];
                
                if (current == '"')
                {
                    if (i == 0 || parameters[i - 1] != '\\')
                        isInString = !isInString;
                }

                if(!isInString)
                {
                    switch (current)
                    {
                        case '{':
                            curlyBrackets++;
                            break;
                        case '}':
                            curlyBrackets--;
                            break;
                        case '[':
                            squareBrackets++;
                            break;
                        case ']':
                            squareBrackets--;
                            break;
                        case '(':
                            roundBrackets++;
                            break;
                        case ')':
                            roundBrackets--;
                            break;
                        default:
                            if (current == ',' && roundBrackets == 0 && squareBrackets == 0 && curlyBrackets == 0)
                            {
                                //The only commas to be considered as parameter separators are outside:
                                //- Strings
                                //- Square brackets (to ignore indexers)
                                //- Parantheses (to ignore method invocations)
                                //- Curly brackets (to ignore initializers and Bindings)

                                list.Add(builder.ToString());
#if WP7
                                builder = new StringBuilder();
#else
                                builder.Clear();
#endif
                                continue;
                            }
                            break;
                    }
                }
                
                builder.Append(current);
            }

            list.Add(builder.ToString());

            return list.ToArray();
        }

 Finally I changed the Parser.CreateParameter function, to support markup extesion and allow property resultion:

        public static string XamlNamespaces = "xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:cm=\"clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro\"";

        /// <summary>
        /// The fragment used to generate a parameter.
        /// </summary>
        private static readonly string parameterXamlFragment = "<cm:Parameter {0} Value=\"{1}\"/>";

        static Parameter CreateParameter(DependencyObject target, string parameter)
        {
            var actualParameter = new Parameter();

            if (parameter.StartsWith("{") && parameter.EndsWith("}"))
            {
                try
                {
                    parameter = string.Format(parameterXamlFragment, XamlNamespaces, parameter);
                    var parsed = LoadXaml(parameter);

                    return (Parameter)parsed;
                }
                catch (Exception exc)
                {
                    LogManager.GetLog(typeof (Parser)).Error(exc);
                }
            }

            actualParameter.Value = parameter;  //Let the string representation be the actual value...
            return actualParameter;
        }

        static object LoadXaml(string parameter)
        {
#if SILVERLIGHT
            return System.Windows.Markup.XamlReader.Load(parameter);
#else
            using (var input = new System.IO.StringReader(parameter))
            {
                using (var reader = System.Xml.XmlReader.Create(input))
                    return System.Windows.Markup.XamlReader.Load(reader);
            }
#endif
        }

As you can see, every parameter is passed as a string, unless it is recognized as a markup extension. In such a case, the markup extension is parsed using a XAML fragment and associated directly to the Parameter.Value property.

The last step involves MessageBinder.DetermineParameters. I decided to avoid to merge the System.Linq.Dynamic sourcecode into CM, so I first replaced the method with a delegate

public static Func<ActionExecutionContext, ParameterInfo[], object[]> DetermineParameters =
            (context, requiredParameters) =>
                { //Default implementation here... };

And then defined the new method to determine parameters values:

MessageBinder.DetermineParameters = (context, requiredParameters) =>
            {
                var providedValues = context.Message.Parameters.Select(x => x.Value).ToArray();
                var values = new object[requiredParameters.Length];

                for (int i = 0; i < requiredParameters.Length; i++)
                {
                    var value = providedValues[i];
                    var stringValue = value as string;
                    object potentialValue;

                    if (stringValue != null)
                        potentialValue = EvaluateParameter(stringValue, context.Source.DataContext, context.EventArgs,
                                                           context.Source, requiredParameters[i].ParameterType);
                    else potentialValue = value;

                    values[i] = MessageBinder.CoerceValue(requiredParameters[i].ParameterType, potentialValue);
                }

                return values;
            };

Such method calls the EvaluateParameter function, used to retrieve the final parameter value:

        private object EvaluateParameter(string expression, object dataContext, object eventArgs, object source, Type resultType)
        {
            try
            {

                expression = expression.Replace("$dataContext", "@0").Replace("$eventArgs", "@1").Replace("$source",
                                                                                                          "@2");
                var exp =
                    DynamicExpression.ParseLambda(
                        new[]
                            {
                                Expression.Parameter(GetParameterType(dataContext), "@0"),
                                Expression.Parameter(GetParameterType(eventArgs), "@1"),
                                Expression.Parameter(GetParameterType(source), "@2")
                            }, resultType, expression);
                return exp.Compile().DynamicInvoke(dataContext, eventArgs, source);
            }
            catch (Exception exc)
            {
                LogManager.GetLog(typeof(MessageBinder)).Error(exc);
                return null;
            }
        }

        private static Type GetParameterType(object value)
        {
            return value != null ? value.GetType() : typeof(object);
        }

 

I created a sample to test this stuff, both in WPF and Silverlight, and it seems to work fine.

You can download the sample project here.

 

P.S.

I made some changes to the Dynamic source code to overcome some problems and limitations. You can check them comparing the provided code with the original source code, whose link has been posted in my previous post.

 

Jan 24, 2011 at 1:35 PM

FANTASTIC work, Blade!  I would love to see this in a Recipe library.  Maybe create a new fork with this?

Mr. Eisenberg: Is there any way we can get the core library updated with Parser.CreateParameter + MessageBinder.DetermineParameters as Func's? :) :) :)

Coordinator
Jan 24, 2011 at 2:09 PM

@BladeWise

Can you confirm that the only two points you need made extensible are Parser.CreateParameter + MessageBinder.DetermineParameters? If so I can probably make those into funcs so that this could be plugged in without altering code.

Jan 24, 2011 at 2:24 PM
Edited Jan 24, 2011 at 2:48 PM

Yes, the only required extension points are

  • Parser.CreateParameter
  • MessageBinder.DetermineParameters

but a minor change to the Parser.Parse is required to allow the above stuff.

The current Parser.Parse does not honor the action message syntax, blindly splitting first by ';' and then by '=', thus incorrectly splitting the message.

I'm referring to these lines:

Line 29: var items = text.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
Line 33: var triggerPlusMessage = messageText.Split('=');

If you could change such calls to

Line 29: var messageTexts = Split(text, ';', true);
Line 33:  var triggerPlusMessage = Split(messageText, '=', true);

where the Split function is implemented as

        static string[] Split(string message, char separator, bool removeEmptyLines)
        {
            //Splits a string using the specified separator, if it is found outside of relevant places
            //delimited by [ and ]
            string str;
            var list = new List<string>();
            var builder = new StringBuilder();

            int squareBrackets = 0;
            foreach (var current in message)
            {
                //Square brackets are used as delimiters, so only separators outside them count...
                if (current == '[')
                    squareBrackets++;
                else if (current == ']')
                    squareBrackets--;
                else if (current == separator)
                {
                    if (squareBrackets == 0)
                    {
                        str = builder.ToString();
                        if (!removeEmptyLines || !string.IsNullOrEmpty(str))
                            list.Add(builder.ToString());
#if WP7
                        builder = new StringBuilder();
#else
                        builder.Clear();
#endif
                        continue;
                    }
                }

                builder.Append(current);
            }

            str = builder.ToString();
            if (!removeEmptyLines || !string.IsNullOrEmpty(str)) 
                list.Add(builder.ToString());

            return list.ToArray();
        }

 

everything would be feasible without changing CM base code.

Note that the above function does not break the current parsing, since it simply avoids to perform splitting inside square brackets.

Coordinator
Jan 24, 2011 at 2:28 PM

Just to clarify one final time before I make the changes. I need to do the following:

1. Fix a potential parse bug (definitely in your case, but possible in others) by adding Split and using that inside Parse .

2. Change Parser.CreateParameter and MessageBinder.DetermineParameters into funcs.

That should do it?

Coordinator
Jan 24, 2011 at 2:30 PM

Do we need the "removeEmptyLines" flag?

Jan 24, 2011 at 2:34 PM

Exactly, those are the only required changes to implement the above stuff without changing the base code. I double-checked to be sure of it.

Jan 24, 2011 at 2:37 PM

I suppose we don't. I just put it there since the current implementation acts differently when splitting by ';' or by '='. The second invocation should be called with 'false' to honor current implementation, even if it doesn't make sense to me.

Jan 24, 2011 at 7:20 PM
Edited Jan 24, 2011 at 7:29 PM

Great stuff here! I think you fullfilled my hidden desire of a way to access EventArgs from the VM through a wrapper class!
Nice - yet *very* dangerous! :-)

Jan 24, 2011 at 8:21 PM

Hehe, glad to have fullified such a 'perverted' desire! I hope people will handle this stuff with care...!

Coordinator
Jan 25, 2011 at 2:41 PM

Ok. Everything should be good to go as of revision 6b10c6173f02 

I made the parser fix, removing the extra parameter from the split method. In the ParameterBinder, I added an EvaluateParamter func that you can override to place your custom logic. In the process I also realized I could make the special values collection extensible, so I went ahead and did that, adding two new values along the way: $executionContext and $view.

Jan 25, 2011 at 4:22 PM

I think that there is still a missing extension point, since Parser.CreateParameter cannot be customized. Am I wrong?

Unless that part can be customized, a Binding would eat-up every not-recognized parameter specification.

Moreover, I just figured out that the code to split parameters should probably be changed with this (the one used above):

        private static string[] SplitMethodParameters(string parameters)
        {
            //Splits parameter string taking into account brackets...
            List<string> list = new List<string>();
            var builder = new StringBuilder();

            bool isInString = false;

            int curlyBrackets = 0;
            int squareBrackets = 0;
            int roundBrackets = 0;
            for (int i = 0; i < parameters.Length; i++ )
            {
                var current = parameters[i];
                
                if (current == '"')
                {
                    if (i == 0 || parameters[i - 1] != '\\')
                        isInString = !isInString;
                }

                if(!isInString)
                {
                    switch (current)
                    {
                        case '{':
                            curlyBrackets++;
                            break;
                        case '}':
                            curlyBrackets--;
                            break;
                        case '[':
                            squareBrackets++;
                            break;
                        case ']':
                            squareBrackets--;
                            break;
                        case '(':
                            roundBrackets++;
                            break;
                        case ')':
                            roundBrackets--;
                            break;
                        default:
                            if (current == ',' && roundBrackets == 0 && squareBrackets == 0 && curlyBrackets == 0)
                            {
                                //The only commas to be considered as parameter separators are outside:
                                //- Strings
                                //- Square brackets (to ignore indexers)
                                //- Parantheses (to ignore method invocations)
                                //- Curly brackets (to ignore initializers and Bindings)

                                list.Add(builder.ToString());
#if WP7
                                builder = new StringBuilder();
#else
                                builder.Clear();
#endif
                                continue;
                            }
                            break;
                    }
                }
                
                builder.Append(current);
            }

            list.Add(builder.ToString());

            return list.ToArray();
        }

otherwise parameters are not split properly (the regex check does not take into account commas inside parentheses and strings).

All in all, for the sake of extensibility, I would use the SplitParameters method and mark as extension points the functions used to:

  • Create the Trigger
  • Create the message MethodName
  • Create the Parameter

I provided a modified Parser.cs file at this address, it should be consistent with current behaviour while providing required extension points for parsing.

 

Coordinator
Jan 25, 2011 at 4:43 PM

Thanks, I'll have a look at the modified parser shortly.

Coordinator
Jan 25, 2011 at 5:31 PM

Ok, I took your code and did some refactoring because I figured out some ways to add even more extensibility. Have a look and let me know if that will work for you.

Jan 25, 2011 at 6:02 PM
Edited Jan 26, 2011 at 10:50 AM

I checked the code and it is perfect. This way it is possible to fully customize every aspect of the message parsing, while retaining the standard syntax

[TRIGGER_TYPE TRIGGER_NAME] = [ACTION_TYPE ACTION_NAME(PARAM0, PARAM1, ...)]

I expecially liked the Parser.InterpretMessageText, since it gives the ability to customize the message type if needed.

I'll update the sample I created to the current CM basecode as soon as possible.

Thanks a lot, Rob! :)

 

P.S.

I have a doubt about

 

///	<summary>
///	Function used to parse a string identified as a message.
///	</summary>
public static Func<DependencyObject, string, System.Windows.Interactivity.TriggerAction> InterpretMessageText = (target, text) => {
    return new ActionMessage { MethodName = Regex.Replace(text, "^Action", string.Empty) };
};

I fear that a Trim() is needed to get rid of the space before the method name (I couldn't verify it, tho):

///	<summary>
///	Function used to parse a string identified as a message.
///	</summary>
public static Func<DependencyObject, string, System.Windows.Interactivity.TriggerAction> InterpretMessageText = (target, text) => {
    return new ActionMessage { MethodName = Regex.Replace(text, "^Action", string.Empty).Trim() };
};

 

 

Edit:

I can confirm that the missing Trim() is indeed a bug and causes an exception due to the fact that the method cannot be found. I reported this.

 

Coordinator
Jan 27, 2011 at 7:45 PM

@BladeWise

Can you have a look at this forum post http://caliburnmicro.codeplex.com/discussions

Have we introduced a bug in the parsing?

Jan 27, 2011 at 8:44 PM
Edited May 8, 2011 at 12:19 AM

Rob, I checked it out and I dubt it is a parsing issue. I created a repro project using the posted code and this SearchTextBox implementation. Everything works fine.

I am going to answer to the other thread to try to pin point the issue.

Coordinator
Jan 27, 2011 at 9:05 PM

Thanks for spear-heading this issue! It's a big help :)

Jan 27, 2011 at 9:06 PM
Edited May 8, 2011 at 12:19 AM

No problem, I like this framework a lot and I'm quite grateful to you for your efforts... it's the least I can do! :)

Feb 14, 2011 at 1:39 PM

I resume this post to publish an update sample of the dynamic expression approach, using the latest CM implementation. Note that no changes to the existing code-base is required (thanks to Rob).

A small re-cap: the following code is used to provide more flexibility to the syntax used with Message.Attach. It even allows the shortened XAML syntax to be used to define parameters (this means that a Binding can be defined as parameter directly).

Only two changes are required to use this approach:

  • Parser.CreateParameter: the default implementation has to be changed to process inline XAML and avoid to resolve parameter values before they are actually needed.

/// <summary>
///   The additional namespaces to be used when trying to parse an action parameter defined into a markup extension.
/// </summary>
public static string XamlNamespaces = "xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:cm=\"clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro\"";

Parser.CreateParameter = (target, parameterText) =>
                            {
                                var parameter = new Parameter();

                                //Check if the parameter is defined as a markup...
                                if (parameterText.StartsWith("{") && parameterText.EndsWith("}"))
                                {
                                    try
                                    {
                                        parameterText = string.Format(ParameterXamlFragment, XamlNamespaces, parameterText);
                                        var parsed = LoadXaml(parameterText);

                                        return (Parameter)parsed;
                                    }
                                    catch (Exception exc)
                                    {
                                        LogManager.GetLog(typeof(Parser)).Error(exc);
                                    }
                                }

                                //Pass the string representation, it will be evaluated afterwards...
                                parameter.Value = parameterText;
                                return parameter;
                            };



private static object LoadXaml(string parameter)
{
#if SILVERLIGHT
    return XamlReader.Load(parameter);
#else
    using (var input = new System.IO.StringReader(parameter))
    {
        using (var reader = System.Xml.XmlReader.Create(input))
            return System.Windows.Markup.XamlReader.Load(reader);
    }
#endif
}

  • MessageBinder.EvaluateParameter: even in this case the default implementation has to be changed, to allow parameters evaluation every time an Action is executed

MessageBinder.EvaluateParameter = (text, parameterType, context) =>
                                    {
                                        var lookup = text.ToLower(CultureInfo.InvariantCulture);
                                        Func<ActionExecutionContext, object> resolver;

                                        return MessageBinder.SpecialValues.TryGetValue(lookup, out resolver) ?
                                               resolver(context) :
                                               EvaluateParameter(text, context.Source.DataContext, context.EventArgs, context.Source, parameterType);
                                    };

private static object EvaluateParameter(string expression, object dataContext, object eventArgs, object source, Type resultType)
{
    try
    {
        expression = expression.Replace("$dataContext", "@0").Replace("$eventArgs", "@1").Replace("$source", "@2");
        var exp = DynamicExpression.ParseLambda(new[] { Expression.Parameter(GetParameterType(dataContext), "@0"),
                                                        Expression.Parameter(GetParameterType(eventArgs), "@1"),
                                                        Expression.Parameter(GetParameterType(source), "@2") }, resultType, expression);
        return exp.Compile().DynamicInvoke(dataContext, eventArgs, source);
    }
    catch (Exception exc)
    {
        LogManager.GetLog(typeof(MessageBinder)).Error(exc);
        return null;
    }
}

private static Type GetParameterType(object value)
{
    return value != null ? value.GetType() : typeof(object);
}

The above function uses a class provided by Microsoft in this sample, but every parser using System.Linq.Expressions would do the trick (honestly, you could even compile code on the fly using the CS compiler).

The solution attached to this post uses a modified version of such code, to overcome some parser limitations. You can just compare the original version with the shipped one.

On a final note, consider that the syntax used by this implementation is slightly different from CM default one. In other words, a string like 'MyControl.Name' is converted to a Binding in the original CM implementation, while it is not in the proposed one. To achieve the same behaviour, the previous string should substituted with '{Binding Path=Name, ElementName=MyControl}'.

 

You can download the sample solution (containing both WPF and Silverlight samples) here.

Feb 19, 2011 at 8:22 PM

Wow... that Dynamic.cs weighs in @ 84k.  That's a pretty hefty price, but it's all very much worth it now that you can have full expression support.  I can't believe bindings even work!!! AMAZING!  I just wired it up and got your latest solution working just fine in my application.  One suggestion tho:

You should modify MessageBinder.EvaluateParameter to the following:

MessageBinder.EvaluateParameter = ( text, parameterType, context ) =>
{
var lookup = text.ToLowerCultureInfo.InvariantCulture );
Func<ActionExecutionContextobject> resolver;

return MessageBinder.SpecialValues.TryGetValue( lookup, out resolver ) ? resolver( context ) : EvaluateParameter( text, context.Source.DataContext, context.EventArgs, context.Source, parameterType ) ?? text;
};

It's exactly the same with the "?? text" appended to it.  This ensures that the text parameter gets passed along in case the expression does not get evaluated properly (whereas originally it was just passing along null).

Really tasty stuff here.  I'm grateful you put the work into this!!!

Feb 19, 2011 at 11:46 PM

Nice addition, thanks for looking into this! :)

 

About the Dynamic.cs, I suppose it would be possible to avoid the class completely and use the compiler service (given that the methods are encapsulated into a class)... well, you should determine usings etc., but I suppose it could work, and you would have support for enums etc! :)

Feb 19, 2011 at 11:53 PM

Is the compiler service available in Silverlight as well?  My framework is already a little beefy, with the Xap weighing in at around 384k before the Dynamic.cs... I cringe to think where it is now.  I'm not too worried about it until a client starts to complain about download size (you know it's just a matter of time before THAT happens)...  Anyways, if it is possible in Silverlight, at some point (read: when the complaint arrises) it might be beneficial to figure out a solution without it... See... the reward for hard work is more hard work. :)

Feb 19, 2011 at 11:55 PM

I should check it... since I mostly work with WPF I am not really into the SL stuff... :) Another option is to write down your own parser... you know, it's not that hard thanks to SystemLinq.Expressions... even if it's a pain!

Mar 23, 2011 at 8:54 AM

Regarding Issue #95:

If anyone is interested in adding only XAML Binding support without dynamic expressions, I changed the sample code from BladeWise a bit:

Remove:

MessageBinder.EvaluateParameter = ...

(And "Dynamic.cs" etc.)

Change:

 

var createParameterDefault = Parser.CreateParameter;
Parser.CreateParameter = (target, parameterText) =>
                            {
                                var parameter = new Parameter();

                                Check if the parameter is defined as a markup...
                                if (parameterText.StartsWith("{") && parameterText.EndsWith("}"))
                                {
                                    try
                                    {
                                        parameterText = string.Format(ParameterXamlFragment, XamlNamespaces, parameterText);
                                        var parsed = LoadXaml(parameterText);

                                        return (Parameter)parsed;
                                    }
                                    catch (Exception exc)
                                    {
                                        LogManager.GetLog(typeof(Parser)).Error(exc);
                                    }
                                }

                                return createParameterDefault(target, parameterText);
                            };


I only added a call to the default Parser.CreateParameter() if the parameter is not a XAML binding, so that standard Caliburn binding applies.

 

 

Mar 23, 2011 at 8:56 AM

Thanks a lot for your contribution! :)

Mar 23, 2011 at 9:06 AM

Oh, regarding the issue of string values being incorrectly evaluated when not needed (see issue #95), the following change is required:

MessageBinder.EvaluateParameter = (text, parameterType, context) =>
                                    {
                                        var lookup = text.ToLower(CultureInfo.InvariantCulture);
                                        Func<ActionExecutionContext, object> resolver;

                                        return MessageBinder.SpecialValues.TryGetValue(lookup, out resolver) ?
                                               resolver(context) :
                                               (typeof(string) == parameterType ? text : EvaluateParameter(text, context.Source.DataContext, context.EventArgs, context.Source, parameterType));
                                    };
My bad for not taking into account this scenario! ><'

Mar 23, 2011 at 10:10 AM

Sorry to keep posting, but I found the reason the short syntax is not working properly.

The fact is that Parser.Split(string message, char separator) is fooled by bindings and incorrectly divides the string.

Consider this sample (taken from a slight modification of the sample project attached previouly):

 

cm:Message.Attach="ShowMessage($dataContext, $eventArgs, $source, $dataContext.DisplayName, $source.ToString(), "This is a " + "composed string", 5 + 3 * 4, string.Format("{0} | {1} | {2}", $dataContext.DisplayName, $eventArgs.OriginalSource, $source.GetType()), {Binding Path=Foreground, ElementName=textBlockSample})"

The Parser.Parse will try to identify the Trigger and the Action searching for an '='. Unfortunally, the Binding syntax can contain such symbol and the above string is split into

  • ShowMessage($dataContext, $eventArgs, $source, $dataContext.DisplayName, $source.ToString(), "This is a " + "composed string", 5 + 3 * 4, string.Format("{0} | {1} | {2}", $dataContext.DisplayName, $eventArgs.OriginalSource, $source.GetType()), {Binding Path
  • Foreground, ElementName
  • textBlockSample})

 

Where the first part is interpreted as a Trigger, the second is ignored and the last is considered the Action. A plain mess.

 

To avoid this issue the Parser.Parse should be more robust... maybe a plain regular expression match should be performed before parsing, just to identify the short or long format... something like:

 

        /// <summary>
        /// The regular expression used to identify a long syntax format.
        /// </summary>
        private static readonly Regex LongFormatRegularExpression = new Regex(@"^[\s]*\[[^\]]*\][\s]*=[\s]*\[[^\]]*\][\s]*$");

        /// <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 = Split(text, ';');

            foreach (var messageText in messageTexts) {
                var triggerPlusMessage = LongFormatRegularExpression.IsMatch(messageText) ? 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);

                trigger.Actions.Add(message);
                triggers.Add(trigger);
            }

            return triggers;
        }

Note the regular expression match when deciding how to split the text (it should identify the long syntax as a string composed by two squared-brackets-enclosed sections, joined by an equals).

I will propose this to the CM core, since there is no other way to get around this issue.

 

Mar 24, 2011 at 2:15 PM

Is this feature, $eventArgs.PropertyName available in the 1.0 RC version of CM?
Or is it a user-added enhancement?

thanks
John

Mar 24, 2011 at 2:20 PM

As stated above, it is a possible customization to the framework. It is not (and will never be, as far as I can tell) part of the official CM distribution.

The bit related to having Binding standard syntax as parameters in Message.Attach could be indeed added in future releases, but it is up to Rob to decide! ;)

Coordinator
Mar 24, 2011 at 2:27 PM

If you guys keep coming up with new features, I'll never get this thing released! :)  I've made the parser changes we discussed to allow for these things to be added easily by developers if desired, but I don't plan to add these things in this version. I'd like to consider some of this again for vNext though. So, keep the conversation going.

Mar 24, 2011 at 2:59 PM

Hi, thanks for the quick replies.
Unfortunately the MediaFire links to examples earlier in this thread have gone away (BladeWise, 14th Feb), and I'm having difficulty in piecing it all together. Any chance of a re-post?
Do I need the MS DynamicQuery stuff?

thanks
John

Mar 24, 2011 at 3:27 PM
Edited Mar 24, 2011 at 3:29 PM

I have just checked and I can download the sample from this link (the one from 14th February)... are you sure it is not an isue with your connection?

If you can download the sample, the dynamic stuff is included... in any case, yes, you need a parser to generate dynamic code.

Mar 24, 2011 at 3:53 PM

I tried from two different routes - perhaps my employer blocks that site. I will try from Home later tonight.

In looking at the CM source, I see on Line 117 of Parser.cs, this -
                var nameAndBindingMode = parameterText.Split(':').Select(x => x.Trim()).ToArray();

I can't think of what the : (colon) is needed for. Can you post a short example of what the colon is used for in the Message.Attach syntax

thanks
John

Mar 24, 2011 at 4:22 PM
Edited Mar 24, 2011 at 4:24 PM

The default CM implementation allows for bindings to be added as parameters in Message.Attach, using a non-standard syntax:

 

cm:Message.Attach="[Event Click] = [Action DoSomething(elementName.PropertyName:TwoWay)]"


You can specify an ElementName, followed by a dot and the Property to bind to. Finally, after a colon, you can specifiy the BindingMode.

Edit: for your issue with media fire, try this HTTP proxy... maybe it can help.

Mar 24, 2011 at 10:33 PM

Hi, The Proxy worked, but I still couldn't download the file at work - the browser is so locked down!! But I've got it now I'm home.
Thanks very much for such a comprehensive example.
Is the example completely up-to-date - or do I need to apply some of the more recent comments?; the revised Regex for example.

thanks
John

Mar 24, 2011 at 10:59 PM

Everything is up to date, The Regex check is now part of the CM codebase.

Apr 11, 2011 at 12:01 PM
Edited Apr 11, 2011 at 12:03 PM

I've used this enhancement to CM in my WPF VB.Net project and it works a treat.

However, am I right in thinking that the parameters specified to action methods are input only? For example, when handling key down presses such as the following...

cal:Message.Attach="[Event PreviewKeyDown]=[Action HandleKeyDown($eventArgs.Key, $eventArgs.Handled)]"

...and with an action method declared as follows...

Public Sub HandleKeyDown(ByVal key As System.Windows.Input.Key, ByRef handled As Boolean)

...then the handled property of the key event args won't be updated if the HandleKeyDown method sets it's handled parameter to true?

I also tried the following...

cal:Message.Attach="[Event PreviewKeyDown]=[Action $eventArgs.Handled=HandleKeyDown($eventArgs.Key)]"

...with an updated action method of...

Public Function HandleKeyDown(ByVal key as System.Windows.Input.Key) As Boolean

...never really expecting it to work, and, of course, it didn't!

So, is what I'm trying to do just not possible, or am I missing something?

Apr 11, 2011 at 1:05 PM

The parameter value reference cannot be altered, since it's reference is passed to the action method (or its value in case of a value type).

To handle your scenario, you need to pass the $eventArgs directly and set the Handled property to true. That would work, since you can set the flag through the proper reference.

Method parameters set as ref or out are not supported.

Apr 11, 2011 at 1:35 PM

Thanks, yes that's what I thought. Passing the entire event args through to the action method was how I had originally implemented it.

However, I was just trying to see if I could de-couple the view model from any presentation specific references, such as the KeyEventArgs that is raised with the key down and key up events.

For the record, I am currently exploring other possibilities as well, such as attached properties and handling the events in the code behind first!

Actually, I think the latter solution kind of makes sense, unless you're an MVVM purist, as event handling of this nature is kind of platform specific.

Apr 11, 2011 at 1:47 PM

It would definitely makes sense, since the handling logic is pretty much tied with the presentation layer.

Another option is to create a custom trigger used to handle the situation...

something like

cal:Message.Attach="[HandledEvent PreviewKeyDown]=[Action HandleKeyDown($eventArgs.Key)]"

where the action has to be an Action<bool> and the result is used to populate the Handled property (if any).

 

It is just a hunch, and I don't know if this is actually feasible or simple to achieve, but I suppose it is worth a try... :)

May 5, 2011 at 4:55 PM

I resume this post once again to provide a simple way to enable this extension

Allow Xaml syntax only:

namespace Caliburn.Micro
{
    #region Namespaces
    using System;
    using System.IO;
    using System.Windows;
    using System.Windows.Markup;
    using System.Xml;
    using Caliburn.Micro;

    #endregion

    /// <summary>
    ///   Static class used to store Caliburn.Micro extensions.
    /// </summary>
    public static class FrameworkExtensions
    {
        #region Nested type: Message
        /// <summary>
        ///   Static class used to store extensions related to the <see cref = "Caliburn.Micro.Message" />.
        /// </summary>
        public static class Message
        {
            #region Nested type: Attach
            /// <summary>
            ///   Static class used to store extensions related to the <see cref = "Caliburn.Micro.Message.AttachProperty" />.
            /// </summary>
            public static class Attach
            {
                #region Static Fields
                /// <summary>
                ///   The additional namespaces to be used when trying to parse an action parameter defined into a markup extension.
                /// </summary>
                public static string XamlNamespaces = "xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:cm=\"clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro\"";

                /// <summary>
                ///   The default implementation of the <see cref = "Parser.CreateParameter" />.
                /// </summary>
                private static readonly Func<DependencyObject, string, Parameter> m_BaseCreateParameter = Parser.CreateParameter;
                #endregion

                #region Static Members
                /// <summary>
                ///   The fragment used to generate a parameter.
                /// </summary>
                private const string PARAMETER_XAML_FRAGMENT = "<cm:Parameter {0} Value=\"{1}\"/>";

                /// <summary>
                ///   Loads the specified parameter using a <see cref = "XamlReader" />.
                /// </summary>
                /// <param name = "parameter">The parameter.</param>
                /// <returns>The deserialized object.</returns>
                private static object LoadXaml(string parameter)
                {
#if SILVERLIGHT
                    return XamlReader.Load(parameter);
#else
                    using (var input = new StringReader(parameter))
                    {
                        using (var reader = XmlReader.Create(input))
                            return XamlReader.Load(reader);
                    }
#endif
                }

                /// <summary>
                ///   Allows action parameters to be specified using Xaml compact syntax.
                /// </summary>
                public static void AllowXamlSyntax()
                {
                    Parser.CreateParameter = (target, parameterText) =>
                                             {
                                                 //Check if the parameter is defined as a markup...
                                                 if (parameterText.StartsWith("{") && parameterText.EndsWith("}"))
                                                 {
                                                     try
                                                     {
                                                         parameterText = string.Format(PARAMETER_XAML_FRAGMENT, XamlNamespaces, parameterText);
                                                         var parsed = LoadXaml(parameterText);

                                                         return (Parameter)parsed;
                                                     }
                                                     catch (Exception exc)
                                                     {
                                                         LogManager.GetLog(typeof(Parser)).Error(exc);
                                                     }
                                                 }

                                                 //Use the default implementation if the parameter is not identified as a binding...
                                                 return m_BaseCreateParameter(target, parameterText);
                                             };
                }
                #endregion
            }
            #endregion
        }
        #endregion
    }
}

Allow both Xaml syntax and (optionally) parameters evaluation:

namespace Caliburn.Micro
{
    #region Namespaces
    using System;
    using System.Globalization;
    using System.IO;
    using System.Linq.Expressions;
    using System.Windows;
    using System.Windows.Markup;
    using System.Xml;
    using Caliburn.Micro;
    using DynamicExpression = Your.DynamicExpression;
    using Expression = System.Linq.Expressions.Expression;

    #endregion

    /// <summary>
    ///   Static class used to store Caliburn.Micro extensions.
    /// </summary>
    public static class FrameworkExtensions
    {
        #region Nested type: Message
        /// <summary>
        ///   Static class used to store extensions related to the <see cref = "Caliburn.Micro.Message" />.
        /// </summary>
        public static class Message
        {
            #region Nested type: Attach
            /// <summary>
            ///   Static class used to store extensions related to the <see cref = "Caliburn.Micro.Message.AttachProperty" />.
            /// </summary>
            public static class Attach
            {
                #region Static Fields
                /// <summary>
                ///   The additional namespaces to be used when trying to parse an action parameter defined into a markup extension.
                /// </summary>
                public static string XamlNamespaces = "xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:cm=\"clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro\"";

                /// <summary>
                ///   The default implementation of the <see cref = "Parser.CreateParameter" />.
                /// </summary>
                private static readonly Func<DependencyObject, string, Parameter> m_BaseCreateParameter = Parser.CreateParameter;

                /// <summary>
                ///   The default implementation of the <see cref = "MessageBinder.EvaluateParameter" />.
                /// </summary>
                private static readonly Func<string, Type, ActionExecutionContext, object> m_BaseEvaluateParameter = MessageBinder.EvaluateParameter;
                #endregion

                #region Static Members
                /// <summary>
                ///   The fragment used to generate a parameter.
                /// </summary>
                private const string PARAMETER_XAML_FRAGMENT = "<cm:Parameter {0} Value=\"{1}\"/>";

                /// <summary>
                ///   Loads the specified parameter using a <see cref = "XamlReader" />.
                /// </summary>
                /// <param name = "parameter">The parameter.</param>
                /// <returns>The deserialized object.</returns>
                private static object LoadXaml(string parameter)
                {
#if SILVERLIGHT
                    return XamlReader.Load(parameter);
#else
                    using (var input = new StringReader(parameter))
                    {
                        using (var reader = XmlReader.Create(input))
                            return XamlReader.Load(reader);
                    }
#endif
                }

                /// <summary>
                ///   Evaluates the parameter.
                /// </summary>
                /// <param name = "expression">The expression.</param>
                /// <param name = "context">The context.</param>
                /// <param name = "resultType">Type of the result.</param>
                /// <returns>The evaluated parameter.</returns>
                private static object EvaluateParameter(string expression, ActionExecutionContext context, Type resultType)
                {
                    try
                    {
                        var index = 0;
                        var parameters = new ParameterExpression[MessageBinder.SpecialValues.Count];
                        var values = new object[MessageBinder.SpecialValues.Count];
                        foreach (var pair in MessageBinder.SpecialValues)
                        {
                            var name = "@" + index;
                            expression = expression.Replace(pair.Key, name);
                            var value = pair.Value(context);
                            parameters[index] = Expression.Parameter(GetParameterType(value), name);
                            values[index] = value;
                            index++;
                        }

                        var exp = DynamicExpression.ParseLambda(parameters, resultType, expression);
                        return exp.Compile().DynamicInvoke(values);
                    }
                    catch (Exception exc)
                    {
                        LogManager.GetLog(typeof(MessageBinder)).Error(exc);
                        return null;
                    }
                }

                /// <summary>
                ///   Gets the parameter type.
                /// </summary>
                /// <param name = "value">The value.</param>
                /// <returns>The parameter type.</returns>
                private static Type GetParameterType(object value)
                {
                    return value != null ? value.GetType() : typeof(object);
                }

                /// <summary>
                ///   Allows action parameters to be specified using Xaml compact syntax and (optionally) parameters evaluation.
                /// </summary>
                /// <param name = "enableEvaluation">If set to <c>true</c> action parameters will be evaluated, if needed.</param>
                public static void AllowXamlSyntax(bool enableEvaluation = true)
                {
                    if (enableEvaluation)
                    {
                        Parser.CreateParameter = (target, parameterText) =>
                                                 {
                                                     //Check if the parameter is defined as a markup...
                                                     if (parameterText.StartsWith("{") && parameterText.EndsWith("}"))
                                                     {
                                                         try
                                                         {
                                                             parameterText = string.Format(PARAMETER_XAML_FRAGMENT, XamlNamespaces, parameterText);
                                                             var parsed = LoadXaml(parameterText);

                                                             return (Parameter)parsed;
                                                         }
                                                         catch (Exception exc)
                                                         {
                                                             LogManager.GetLog(typeof(Parser)).Error(exc);
                                                         }
                                                     }

                                                     //Pass the textual value and let it be evaluated afterwards...
                                                     return new Parameter {
                                                                                  Value = parameterText
                                                                          };
                                                 };

                        MessageBinder.EvaluateParameter = (text, parameterType, context) =>
                                                          {
                                                              var lookup = text.ToLower(CultureInfo.InvariantCulture);
                                                              Func<ActionExecutionContext, object> resolver;

                                                              return MessageBinder.SpecialValues.TryGetValue(lookup, out resolver) ? resolver(context) : (typeof(string) == parameterType ? text : EvaluateParameter(text, context, parameterType));
                                                          };
                    }
                    else
                    {
                        Parser.CreateParameter = (target, parameterText) =>
                                                 {
                                                     //Check if the parameter is defined as a markup...
                                                     if (parameterText.StartsWith("{") && parameterText.EndsWith("}"))
                                                     {
                                                         try
                                                         {
                                                             parameterText = string.Format(PARAMETER_XAML_FRAGMENT, XamlNamespaces, parameterText);
                                                             var parsed = LoadXaml(parameterText);

                                                             return (Parameter)parsed;
                                                         }
                                                         catch (Exception exc)
                                                         {
                                                             LogManager.GetLog(typeof(Parser)).Error(exc);
                                                         }
                                                     }

                                                     //Use the default implementation if the parameter is not identified as a binding...
                                                     return m_BaseCreateParameter(target, parameterText);
                                                 };

                        MessageBinder.EvaluateParameter = m_BaseEvaluateParameter;
                    }
                }
                #endregion
            }
            #endregion
        }
        #endregion
    }
}

Usage example:

public class MyBootstrapper : Bootstrapper
{
      protected override void Configure()
      {
              FrameworkExtensions.Message.Attach.AllowXamlSyntax();
      }
}

Note that the current implementation of the EvaluateParameter method takes into account every SpecialValue during evaluation, instead of just a limited set of them.

May 7, 2011 at 3:01 PM
Edited May 7, 2011 at 6:34 PM

hey bladewise,

there's an error in your code. In case somebody writes $eventArgs instead of $eventargs it doesn't work. You can add

 

expression = Regex.Replace(expression, @"\$[a-zA-Z]+", x => x.ToString().ToLower();

 just before you replace the special values to make it work again.

 

Another thing is, that for example the string concatenation doesn't work anymore, i.e. "a" + "b" as a parameter won't be replaced with "ab". Is this intentionally? This doesn't work because of

return MessageBinder.SpecialValues.TryGetValue(lookup, out resolver) ? resolver(context) : (typeof(string) == parameterType ? text : EvaluateParameter(text, context, parameterType));

When you replace this with

return MessageBinder.SpecialValues.TryGetValue(lookup, out resolver) ? resolver(context) : EvaluateParameter(text, context, parameterType));

it works again but this might introduce other problems?

 

EDIT:

well, changing that last result breaks alot (like direkt binding to an element property) :/

I also found out that with the extensions enabled, stuff like 'false' or MyTextBox.Text won't work anymore as parameter (only false - without the '' - does work though). But this can be easily fixes by changing the 

return new Parameter {Value = parameterText};

to

if (Regex.IsMatch(parameterText, @"'[a-zA-Z]+'")
                        || Regex.IsMatch(parameterText, @"[a-zA-Z]+\.[a-zA-Z]+(:?[a-zA-Z]+)"))
                    {
                        return m_BaseCreateParameter(target, parameterText);
                    }

                    return new Parameter {Value = parameterText};
Although thats quite hacky 

May 8, 2011 at 1:04 AM

I forgot to include the suggestion from MichaelDBang, regarding case-insensitive keys for special values... your proposed modification, kmees, is fine (note, by the way, that there is no restriction in CM for special values keys...).

Regarding the reason behind the fact that strings were ignored for evaluation, I remember that it was added since strings like "2 + 3" were evaluated to 5... I just checked if it is still true with the above implmentation, and it is not. So, it should be perfectly fine to remove the check.

Now, the fact that the default CM convention is not available when enabling parameter evaluation is intentional. Note the following quote from one of the above messages:

... the current Parser.CreateParameter method will try to create a Binding unless the parameter is exactly a special value (i.e. $datacontext, $eventArgs or $source), a string or a (supposed) numeric value. Moreover, it uses the special value $this to create bindings associated to the message target. This approach is not compatible with property name resolution (it is not possible to distinguish between a binding or a normal parameter, unless the context is known) and is not so flexible. That's why I decided to provide another approach, that is not compatible with the previous one.

To be clearer:

  • The CM-way to define bindings in Message.Attach is equivalent to property access (plus the optional BindingMode), so applying the code you provided, values like int.MaxValue would not be evaluated.
  • Single quotes are used to delimit a char, not a string. It is impossible to undestand if a value is defined as a char or as a string composed by a single character. 

Nevertheless, your proposed approach makes sense, if you are dealing with a scenario where the above conflicts are not exploited.