Home > English > Databinding and Triggers – Part III

Databinding and Triggers – Part III

This is our final post in a series to provide a simple yet effective user enhancement. Without happy customers, we would be out of business :). We left our las post looking at a set of Xaml:

<i:Interaction.Triggers>
    <int:KeyTrigger Key="F5">
        <int:ExecuteDataMethod Method="RunQuery" />
    </int:KeyTrigger>
    <int:KeyTrigger Key="F5"  Modifiers="Control">
        <int:ExecuteDataMethod Method="ParseQuery" />
    </int:KeyTrigger>
</i:Interaction.Triggers>

The culprit in question is the ExecuteDataMethod object. Let’s take a look and see how that is implemented:

/// <summary>
/// This trigger looks up the VisualTree from AssociatedObject and finds the
/// first UserControl and takes its Datacontext.  It then takes
/// the Method property and invokes it when ever the trigger is fired.
/// Also, you cannot overload methods or it will throw an execption.
/// </summary>
public class ExecuteDataMethod : TriggerAction<FrameworkElement>
{
    private FrameworkElement _element = null;

    #region Method
    /// <summary>
    /// This property the method name to invoke when the trigger is fired.
    /// </summary>
    public string Method
    {
        get { return (string)GetValue(MethodProperty); }
        set { SetValue(MethodProperty, value); }
    }
    public static readonly DependencyProperty MethodProperty =
        DependencyProperty.Register("Method",
        typeof(string), typeof(ExecuteDataMethod), null);

    #endregion

    #region UserState
    /// <summary>
    /// The UserState provides a means to supply state information.
    /// </summary>
    public string UserState
    {
        get { return (string)GetValue(UserStateProperty); }
        set { SetValue(UserStateProperty, value); }
    }
    public static readonly DependencyProperty UserStateProperty =
        DependencyProperty.Register("UserState", typeof(string),
        typeof(ExecuteDataMethod), null);

    #endregion

    #region Overrides
    protected override void Invoke(object parameter)
    {
        // Get the root of the UI.
        if (_element == null)
        {
            _element = Utilities.VisualTreeUtility
                .FindAncestor<UserControl>(this.AssociatedObject);
            if (_element == null && this.AssociatedObject.DataContext != null)
                _element = this.AssociatedObject;
        }

        if (_element != null && _element.DataContext != null)
        {
            object bindingTarget = _element.DataContext;
            MethodInfo method = null;
            try
            {
                method = bindingTarget.GetType().GetMethod(this.Method);
            }
            catch (AmbiguousMatchException ex)
            {
                throw new InvalidOperationException(
                  "Method name cannot be overloaded!", ex);
            }

            if (method != null)
            {
                ParameterInfo[] parameters = method.GetParameters();
                if (parameters.Length == 0)
                {
                    method.Invoke(bindingTarget, null);
                }
                else if (parameters.Length == 1)
                {
                    if (UserState != null &&
                        parameters[0].ParameterType.IsAssignableFrom(
                        this.UserState.GetType()))
                    {
                        method.Invoke(bindingTarget, new object[] { UserState });
                    }
                    else if (this.AssociatedObject != null &&
                             parameters[0].ParameterType.IsAssignableFrom(
                                 this.AssociatedObject.GetType()))
                    {
                        method.Invoke(bindingTarget, new object[] { AssociatedObject });
                    }
                }
                else if (parameters.Length == 2 && this.AssociatedObject != null && 
                    parameter != null)
                {
                    if (parameters[0].ParameterType.IsAssignableFrom(
                        this.AssociatedObject.GetType()) && 
                        parameters[1].ParameterType.IsAssignableFrom(
                        parameter.GetType()))
                    {
                        method.Invoke(bindingTarget, new object[] {
                            this.AssociatedObject, parameter 
                        });
                    }
                }
                else if (parameters.Length == 3 && 
                    this.AssociatedObject != null && parameter != null 
                    && this.UserState != null)
                {
                    if (parameters[0].ParameterType.IsAssignableFrom(
                        this.AssociatedObject.GetType()) &&
                        parameters[1].ParameterType.IsAssignableFrom(
                        parameter.GetType()) &&
                        parameters[2].ParameterType.IsAssignableFrom(
                        this.UserState.GetType()))
                    {
                        method.Invoke(bindingTarget, new object[] {
                            this.AssociatedObject, parameter, UserState 
                        });
                    }
                }
            }
        }
    }
    #endregion
};

This trigger exposes two DependencyProperties: Method and UserState. The Method property allow you to specify the method name that you want to be fired and the UserState property allows you to provide any custom information that you would like.

The Invoke method is where all the exciting stuff lives. The first thing it tries to do is grab the FrameworkElement and store it in a class level variable. It tries to walk the VisualTree and looks for the host control which is a UserControl. If it cannot find a UserControl than it makes sure that the current AssociatedObject is not null and has a DataContext.

Once we have our _elmeent object hydrated, we then use Reflection to try and find a method on the object with the same name as the one provided in our DependencyProperty.

If we have a valid method, we then check and see what parameters are associated with the method. If there are no parameters, we can go ahead and invoke the method immediately. Otherwise, we have currently support one, two , or three parameters. One parameter will either call the method passing in the UserState or passing in the AssociatedObject itself. It also performs a check to be sure that type of each object can be passed in as the parameter.

Two parameters will pass in the AssociatedObject as the first parameter and the parameter object that is passed into the Invoke method. Again a check is performed to be sure that the type of each object can be passed in as the correct corresponding parameter.

Three parameters will do the same as two parameters but it will also pass in the UserState object as the third parameter.

Here is what the ViewModel method looks like for the Xaml sample provided above:

public void ParseQuery()
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("parse");
}

public void RunQuery()
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("run");
}

These methods could also look like this with the UserState passed in:

public void ParseQuery(object userState)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("parse");
}

public void RunQuery(object userState)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("run");
}

They could also look like this as you have seen in normal EventHandlers:

public void ParseQuery(object sender, EventArgs e)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("parse");
}

public void RunQuery(object sender, EventArgs e)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("run");
}

Finally, they could look like this with the added UserState passed in as well:

public void ParseQuery(object sender, EventArgs e, object userState)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("parse");
}

public void RunQuery(object sender, EventArgs e, object userState)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("run");
}

I typically use the UserState when I have a generic method in my ViewModel and I need to switch on what control or business logic needs to be performed.

Hopefully this gives you some ideas and options as to how you would go about providing some user enhancements. The real power of these Triggers is that you write them once and then you can use them everywhere. It adds to your overall consistency of your application and provides better maintenance.

Advertisements
Categories: English Tags: , ,
  1. Shim
    August 6, 2010 at 1:02 pm

    Hi, this is quite interesting. May I’ve advice about to capture ‘F5’ in LayoutRoot_KeyDown(object sender, KeyEventArgs e) ?

    My purpose is to prevent the Silverlight page back to login page when user press ‘F5’. Do you have any idea about this?

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: