Silverlight : Hooking up a Default Button Action

One of the typical things that you may want to accomplish on a particular UI implementation in Silverlight is to have a default button that is triggered through the use of the enter key. For this Silverlight provides support UI Automation through a set of peer classes that derive from AutomationPeer. Most controls contain an AutomationPeer object …in our example the Button control has a ButtonAutomationPeer class.

 

So in order to make this happen we are going to set up a behavior that we can simply attach to a control and configure to point to the button that we would like for the Enter key to trigger. So we simply start out with a simple behavior class

public class DefaultButtonBehavior : TargetTriggerAction<ButtonBase> 

{

 

   private AutomationPeer _automationPeer {get;set;}

 

   private ButtonBase _targetButton {get;set;}

 

}

Nothing fancy here. Wu use TargetTriggerAction to say that this is specifically targeting an object of type ButtonBase. This will enable use to target anything that also derives from ButtonBase. So in regards to controls like Telerik and their RadButton control, you would be able to use the same code to target their control. The AutomationPeer and ButtonBase objects are specifically there so that we can get hooks into the targeted button and more importantly it’s AutomationPeer object.

Now we are ready to start implementing some basic code in order to override what happens when the behavior is attached or the target is changed.

protected override void OnAttached()

{

   base.OnAttached();

 

  /// Set our target button equal to the target that is assigned

   _targetButton = this.Target

 

  /// Double check to ensure that they set the target properly

  if( _targetButton == null )

     return;

 

   /// Grab an automation peer for our target button

   _automationPeer = FrameworkElementAutomationPeer.FromElement(_targetButton);

 

   /// Check to ensure that it exists. If not then create one

   if( _automationPeer == null )

       _automationPeer = FrameworkElementAutomationPeer.CreatePeerForElement(_targetButton);

 

}

 

protected override void OnTargetChanged(ButtonBase oldTarget, ButtonBase newTarget)

{

     base.OnTargetChanged(oldTarget, newTarget);

 

     /// Set our target button equal to the new target 

     _targetButton = newTarget;

 

     if( _targetButton == null ) 

         return;

 

     /// Grab our new automation peer

     _automationPeer = FrameworkElementAutomationPeer.FromElement(_targetButton);

 

     /// Double check that it exists and if not then create one 

     if( _automationPeer == null)

         _automationPeer = FrameworkElementAutomationPeer.CreatePeerForElement(_targetButton);

 

}

The two method control the what happens when the behavior is first attached and then when the target is changed. Essentially they do the same thing. Grab a reference to our target button and then also to that button’s AutomationPeer and if it does not have one then we will create one for it.

Now we are ready to write the code for actually doing something once the behavior is triggered. For this we override the Invoke method.

protected override void Invoke(object parameter)

{

   /// Convert the parameter object to a KeyEventArgs 

   KeyEventArgs args = parameter as KeyEventArgs;

 

   /// Now let's double check that we actually have something and that something

   /// is actually the Enter key

   if( args != null && args.Key == Key.Enter )

   {

       /// Check that we have a reference to the automation peer and it is enabled

       if( _automationPeer != null && _automationPeer.IsEnabled() )

       {

          IInvokeProvider provider = _automationPeer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;

          provider.Invoke();

 

        }

 

    }

}

So basically in the Invoke method we are looking to do two things. The first is basic validation…Did we get some key arguments?…Was the key pressed the Enter key?…..Is the automation peer of our button there and is it enabled?

The next part uses that AutomationPeer’s method of GetPattern. This method is used to pass a PatternInterface enumeration to and then it will pass back the proper provider. In our example, we are merely getting the button’s Invoke provider because we simply want to “click” the button. Then we can simply call it’s Invoke method to fire it off. Interestingly enough, the GetPattern method is an extremely powerful method when creating custom AutomationPeer implementations because you can pass back either a provider for the particular base object or even a subelement. So if you look at how the ItemsControl works you would see that it’s GetPattern method checks to see if the pattern interface enumeration being passed to it is asking for Scroll. If it is then it delegates that off to the ScrollViewer control that is a subelement.

Possibly in another post, I will go over how to create custom AutomationPeers that override the base GetPattern to do some cool things.

 

Hopefully, this will help some of you out there that have been trying to figure out how to implement something like this.

 

Cheers!
AJ