Monday, May 11, 2009

Writing custom TriggerAction using Interaction to enhance MVVM

The Blend team has introduced a new concept called Interactions for both WPF and Silverlight.According to my understanding it is a method to interact with the xaml elements without writing code behind.Expression Blend is developed using the new architectural concept MVVM and I think they mainly introduced it to enhance MVVM.

There are 2 methods to implement this interaction.One is Behaviors and other is Triggers.Behaviors will be covered in a separate post.This post is related with Triggers, Actions and creation of  a custom Action called FollowMouse.

I have already wrote a post about Microsoft.Expression.Interactivity.dll which tells about creating a custom trigger named RoutedEventTrigger which fires on the associated RoutedEvent.In that post, the intention was to invoke a command upon attached events like Mouse.MouseDown,Mouse.MouseMove,Contacts.ContactDown etc…That is too, very much useful to achieve a perfect MVVM architecture.

In the perfect MVVM world there should be no code behind for xaml files.But when we implement in the real world ,sometimes we will be forced to write code behind to implement some special cases such as MessageBox.Show.Using the Microsoft.Expression.Interactivity.dll we can write actions which display the messagebox.

More links about the behaviors and interactions are below

http://electricbeach.org/?p=147
http://blog.kirupa.com/?p=341

What are these new Interactions

As I told earlier this is a method to interact without writing code behind.The code will be encapsulated in the action classes.See the example below which shows  a MessageBox using only xaml upon a MouseDown.

<Grid Background="Red">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<myactions:ShowMessageBoxAction MessageBoxText="Mouse down" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>


Hope the above code is clear.It tells to show a MessageBox upon MouseDown in the Grid.We know that it is not possible or don’t have default support in xaml to show the message itself.Then where is the C# code to show the message.The answer is simple.It is in the ShowMessageBoxAction class.That class has a MessageBoxText property which is displayed on invoking that action.


To implement interaction it is using attached properties.Interaction.Triggers is an attached property of type TriggerCollection.We can add as many triggers we want.The condition is that all those triggers should be derived from TriggerBase.Here I have used built in EventTrigger and a custom action ShowMessageBoxAction.


Developing our own custom Trigger Action : FollowMouseAction


Writing a custom trigger action is very simple.There should be a reference to the Microsoft.Expression.Interactivity.dll in our project.That dll is from Expression Blend 3 so it should be blend installed in your system to refer.Path is <Install drive>:\Program Files\Microsoft Expression\Blend 3 Preview\Libraries\WPF.Or you can download from any blogs.


The action class ,we need to derive from the base class named Microsoft.Expression.Interactivity.TargetedTriggerAction<T> this has a generic parameter which is the type of the target.


Here we are going to implement a FollowMouseAction.The purpose of this is to create an action which follows mouse and updates it’s position according to that.For that we have to associate trigger to a Canvas.Canvas is the simple panel in which children can be easily placed by specifying Left and Top.So at the time of inheriting class we have to specify the Canvas as it’s action type.


One more thing we need is which child of canvas is following the mouse.So we have to add one more property called Element which is of type FrameworkElement.On action invocation we will be setting the Canvas.Left and Canvas.Top properties of Element according to the mouse position.




public class FollowMouseAction : Microsoft.Expression.Interactivity.TargetedTriggerAction<Canvas>
{
public FrameworkElement Element
{
get { return (FrameworkElement)GetValue(ElementProperty); }
set { SetValue(ElementProperty, value); }
}

// Using a DependencyProperty as the backing store for element. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ElementProperty =
DependencyProperty.Register("Element", typeof(FrameworkElement), typeof(FollowMouseAction), new UIPropertyMetadata(null));

protected override void Invoke(object parameter)
{
Point pt = Mouse.GetPosition(this.Target);
if (Element != null)
{
Element.SetValue(Canvas.LeftProperty, pt.X);
Element.SetValue(Canvas.TopProperty, pt.Y);
}
}
}

Using the FollowMouseAction in xaml.



<Grid>
<Canvas Width="300" Height="300" Background="Red">
<Ellipse Width="50"
Height="50"
Fill="Green"
x:Name="ele">
</Ellipse>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<myactions:FollowMouseAction Element="{Binding ElementName=ele}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
</Grid>


The above code updates mouse position when we click on the mouse.How can we make it movable accoring mouse ?Easy just change the event name to “MouseMove”.See below if you are still not clear.



<Grid>
<Canvas Width="300" Height="300" Background="Red">
<Ellipse Width="50"
Height="50"
Fill="Green"
x:Name="ele">
</Ellipse>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseMove">
<myactions:FollowMouseAction Element="{Binding ElementName=ele}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
</Grid>



NB: Please don’t confuse these Triggers and Actions with the standard triggers and actions available in WPF.They are entirely different where the Triggers mentioned here comes from the namespace Microsoft.Expression.Interactivity and the standard WPF Triggers comes from System.Windows



The above is applicable to Silverlight too.The only change you have to to do is change the reference to the interactivity dll located at <install drive>:\Program Files\Microsoft Expression\Blend 3 Preview\Libraries\Silverlight.If you need more and more details please see this great article from my colleague Laurent Bugnion 


Sample can be downloaded from here.

2 comments:

  1. Hello,
    Nice article. I think there is one thing not working with your binding in the Action in Silverlight 3 beta 1.

    Element="{Binding ElementName=ele}"

    This works in WPF but not in SL. Have you found a workaround for this in Silverlight? I'm trying to find the best way of solving this but I'm still looking for a solution.

    /anders

    ReplyDelete
  2. Ya ElementName binding is not working in SL 3 beta 2.I suggest adding an int ChildIndex property instead of Element and you can get the object by Children[ChildIndex] and set the Canvas.Left and Top.
    Hope this is clear...

    ReplyDelete