[issue #149] Using $this without a path behaves as an alias of DataContext

Topics: Actions & Coroutines, Bugs, Conventions
Aug 19, 2011 at 10:36 AM

I'm opening this thread to discuss about the issue reported here: http://caliburnmicro.codeplex.com/workitem/149

First of all, I want to thank Federico for taking time to investigate the problem.
I'll add here the comment I already did on Twitter.

As it turned out, using
<Button cal:Message.Attach="Click = MyAction($this)" />
causes CM to pass the DataContext to MyAction, while
<Button cal:Message.Attach="Click = MyAction($this.SomeButtonProperty)" />  
picks a property of the button.

I agree that this could be misleading if the rationale behind this is not properly stressed. I had to check myself the code multiple times and still missed the root problem of the issue.
Yet, after understanding it, I think the behavior is correct.

What actually happens is that when you don't specify a property, CM uses a default one, which is specified on the particular control convention.
For button, that property happens to be "DataContext", while a TextBox defaults to Text, a Selector to SelectedItem, etc.
The same happens when using a reference to another named control in the View instead of $this. The following:
<Button cal:Message.Attach="Click = MyAction(someTextBox)" />  
causes CM to pass the Text contained in the TextBox named "someTextBox" to MyAction.

The reason why the actual control is *never* passed to the action is that VMs should never directly deal with UI elements, so the convention discourages it.
Note, however, that the control itself could easily be accessed anyway using the extended syntax (based on System.Windows.Interactivity) to populate the parameters, or customizing the Parser.

Thoughts?

Aug 19, 2011 at 10:56 AM

I think that the current implementation is consistent, even if misleading unless clearly stated in the docs.

Passing an UI control to a view-model function tends to 'dirty' the logical separation beween view and view-model; in my opinion, in case multiple information from a control are required to evaluate the method, it would be better to clearly state it expanding the method's parameters, instead of passing the instance.

Coordinator
Aug 19, 2011 at 1:04 PM

Perhaps this is just an instance where I need to provide more information in the documentation?

Aug 19, 2011 at 2:14 PM

I tend to think so. I suppose that it would be better to modify the All About Actions chapter, and in particulare the following paragraph:

Docs wrote:
In addition to literal values and Binding Expressions, there are a number of helpful “special” values that you can use with parameters. These allow you a convenient way to access common contextual information:
  • $eventArgs – Passes the Trigger’s EventArgs or input parameter to your Action. Note: This will be null for guard methods since the trigger hasn’t actually occurred.
  • $dataContext – Passes the DataContext of the element that the ActionMessage is attached to. This is very useful in Master/Detail scenarios where the ActionMessage may bubble to a parent VM but needs to carry with it the child instance to be acted upon.
  • $source – The actual FrameworkElement that triggered the ActionMessage to be sent.
  • $view - The view (usually a UserControl or Window) that is bound to the ViewModel.
  • $executionContext - The actions's execution context, which contains all the above information and more. This is useful in advanced scenarios.
  • $this - The actual ui element to which the action is attached.
You must start the variable with a “$” but the name is treated in a case-insensitive way by CM. These can be extended through adding values to MessageBinder.SpecialValues.

Just adding Marco's details would be enough to clear every doubt:

marcoamendola wrote:
[Using special values like $this or a named element as action paramenters,] when you don't specify a property, CM uses a default one, which is specified on the particular control convention.
For button, that property happens to be "DataContext", while a TextBox defaults to Text, a Selector to SelectedItem, etc. The same happens when using a reference to another named control in the View instead of $this. The following:
<Button cal:Message.Attach="Click = MyAction(someTextBox)" />
causes CM to pass the Text contained in the TextBox named "someTextBox" to MyAction.
The reason why the actual control is *never* passed to the action is that VMs should never directly deal with UI elements, so the convention discourages it.Note, however, that the control itself could easily be accessed anyway using the extended syntax (based on System.Windows.Interactivity) to populate the parameters, or customizing the Parser.
Aug 19, 2011 at 2:49 PM

Hi guys,

First I understand that changing the behavior of $this is actually a breaking change (dont know if that would be good at this point), however, from the new guy using the framework (aka me in this case) I can explain how I ended up using $this instead of $datacontext that would have been the right choice (with some limits).

I have used the very clear $this.Text syntax, my though was niiice I can do lotsa stuff with this.

Somehow working on other things then I needed something else. I mistakenly went along and pass $this (because the language bias always creep in)... For the purposes at the time I checked that what I got was the proper value and the method just worked. After a couple of minutes (15 or so) I realized that actually what I would need was only an inner property (that was a INotifyPropertyChanged enabled class itself). So the extension was obvious if $this returns the datacontext then $this.Owner would return the Owner property. The worst part is that somehow in my mind the actual use of $this.Text didnt matter anymore... it was only 2 lines on top of this one.

What I am trying to show is basically  that the same uncontextualized name actually resolves to a different instance depending on the actual use case that it is used with. if $this resolves to the DataContext then $this.Owner should resolve on the same instance, however it resolves on the UIElement; and as a user I wasnt pleasently surprised (surprises are good when they outcode is good :D). 

In the discussion Rob said, $this is not supposed to be used without a property and I also incline to believe that it should be unsupported instead of reverting to an implicit convention.

As a side note, and while we add at it, why the rules for $datacontext do not allow to use $datacontext.Owner?

Regards,
Federico

Aug 19, 2011 at 3:05 PM

Hi Federico,

I can understand your concern, still it is just a documentation issue to me. If you were informed properly about the $this behaviour, you would have used the proper approach from the beginning. :)

Regarding the reason why it is not possible to target a property of the $dataContext, the reason is that the short-syntax parser handles in different ways named elements and the $this special value, wrapping them inside a binding.

I posted some stuff on improving (abusing?) the Action short syntax parser, you can have a look at it if interested.

Aug 19, 2011 at 3:35 PM
Edited Aug 19, 2011 at 3:35 PM

I recognize some advantages in explicitly disallowing "$this", but I think that the misunderstanding was only caused by the ButtonBase default parameter property (which I suppose was set to "DataContext" for the lack of a more meaningful state, while other controls usually have a pretty obvious one).
Had you dealt with a TextBox or a ComboBox, you wouldn't had the erroneous impression of $this pointing to the data context.  

Plus, throwing on plain "$this" would require (out of uniformity) to also throw on this scenario:

<TextBox Name="Username" />
<TextBox Name="Password" />
<Button cal:Message.Attach="Click = DoLogin(Username, Password)" />  
<!-- equivalent to  Click = DoLogin(Username.Text, Password.Text) -->

which is quite commonly used.

How about just throwing, if ever, only on controls not holding a meaningful state?

On $datacontext limitation: while $this is resolved at Parse time and converted into a simple binding against Parameter element, all other special values are resolved at invocation time, because they have to be aware of execution context.
So $datacontext refers to the data context of the object triggering the action, which could be a child node of the one where the cal:Message.Attach is specified.
Supporting a full property path syntax without delegating to a binding would have required writing a full-blown expression parser (see Rob's threat, oops, warning :-D, here: http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Actions ).

Aug 19, 2011 at 7:33 PM
BladeWise wrote:

I can understand your concern, still it is just a documentation issue to me. If you were informed properly about the $this behaviour, you would have used the proper approach from the beginning. :)

I am not that sure on that one... $this is semantically quite clear, the semantic definition shouldnt change. I would agree that the convenience to write $this instead of $this.Text or in the case Marco shows may be interesting, but always consider that it is being done at the expense of a radical change in binding behavior.

Personally, I already have my mental rule to avoid using $this without a propertly anymore (as I have several others like to avoid null returns), because now I know of the underlying inconsistency in behavior (but that is after I was bitten and took the time to debug the framework). However, what I am wondering is: The clear intend of Caliburn seams to be (at least from a newbie perspective) to simplify and avoid promoting shooting yourself (XAML is deadly enough already), then why to promote a behavior that requires a deep knowledge on how things work in the inside.

Now dont get me wrong, this has zero impact for me, and I see far fetching breaking behaviors all over the place. But I am sure that a deep understanding of the roots of the issue may provide some interesting results.

Regards,
Federico

Aug 19, 2011 at 7:38 PM
marcoamendola wrote:

So $datacontext refers to the data context of the object triggering the action, which could be a child node of the one where the cal:Message.Attach is specified.
Supporting a full property path syntax without delegating to a binding would have required writing a full-blown expression parser (see Rob's threat, oops, warning :-D, here: http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Actions ).

Yes, I read that line and I wholeheartly agree with it. However, I can also see the benefits of consistency with being able to at least uniformly work as $this does. Haven't tried though if I can do $this.Property1.Property2 but being able to do $datacontext.Property seams to be like a "good" tradeoff.

Regards,
Federico