Today another example of the powerful ramora pattern : execute a RoutedCommand on an Control when you click on a button outside of the aimed control scope.

When is it useful ? : for example when you use the infragistics grid (xamdatagrid) and you want to execute the command which remove the selected row from a button outside of the grid. You then have to exectue the DeleteSelectedDataRecords command on the grid from a button which is outside of the scope of the grid ... then this behavior is useful !

Our implementation will also show how we can apply the "Weak Events pattern" with the dependency property trick (attached property / ramora pattern).

And of course this example can be adapted to subscribe on any event you wants and not only the click of a button.

The behavior itself

Our behavior will listen to the click button, and the handling of the click event will execute the routed command on the target element.

public class ExecuteCommandOnControl : DependencyObject
{
  #region Command
 
  /// <summary>
  /// Command Attached Dependency Property
  /// </summary>
  public static readonly DependencyProperty RoutedCommandProperty =
    DependencyProperty.RegisterAttached("RoutedCommand", typeof(RoutedCommand)
	     , typeof(ExecuteCommandOnControl));
 
  /// <summary>
  /// Gets the Command property.  This dependency property 
  /// indicates ....
  /// </summary>
  public static RoutedCommand GetRoutedCommand(DependencyObject d)
  {
    return (RoutedCommand)d.GetValue(RoutedCommandProperty);
  }
 
  /// <summary>
  /// Sets the Command property.  This dependency property 
  /// indicates ....
  /// </summary>
  public static void SetRoutedCommand(DependencyObject d, RoutedCommand value)
  {
    d.SetValue(RoutedCommandProperty, value);
  }
 
  #endregion
 
  #region IInputElement
  /// <summary>
  /// IInputElement Attached Dependency Property
  /// </summary>
  public static readonly DependencyProperty TargetProperty =
    DependencyProperty.RegisterAttached("Target", typeof(IInputElement), 
	   typeof(ExecuteCommandOnControl),
      new FrameworkPropertyMetadata(null,
        new PropertyChangedCallback(OnTargetChanged)));
 
  /// <summary>
  /// Gets the IInputElement property.  This dependency property 
  /// indicates ....
  /// </summary>
  public static IInputElement GetTarget(DependencyObject d)
  {
    return (IInputElement)d.GetValue(TargetProperty);
  }
 
  /// <summary>
  /// Sets the IInputElement property.  This dependency property 
  /// indicates ....
  /// </summary>
  public static void SetTarget(DependencyObject d, IInputElement value)
  {
    d.SetValue(TargetProperty, value);
  }
 
  /// <summary>
  /// Handles changes to the IInputElement property.
  /// </summary>
  private static void OnTargetChanged(DependencyObject d, 
     DependencyPropertyChangedEventArgs e)
  {
    Button button = d as Button;
 
    if (button != null)
    {
      ButtonClickEventManager.AddListener(button, _customEventListener);
 
    } else
    {
      throw 
	  new ArgumentException("This behavior can only be installed on non-null Button !");
    }
  }
 
  #endregion
 
  private static CustomEventListener _customEventListener = new CustomEventListener();
 
}



WeakEvents

So what is the class CustomEventListener ? This is the object which will handle the click on the button. This class implements the interface IWeakEventListener which is a necessary things to use the Weak event pattern.

We will then not subscribe the click event with the usual way ( using the operator +=) but by calling a WeakEventManager of our own : ButtonClickEventManager.

This manager will then keep a weak reference of our listener and the button on which we operate will then be free to be garbage collected. Otherwise, every button on which we subscribe would be keeped in the memory because each one would be connected to our behavior...

Here is the CustomEventListener class :

public class CustomEventListener : IWeakEventListener
{
  #region IWeakEventListener Members
  public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
  {
    if (managerType == typeof(ButtonClickEventManager))
    {
      var target = 
	  ExecuteCommandOnControl.GetTarget(sender as DependencyObject);
      var command = 
	  ExecuteCommandOnControl.GetRoutedCommand(sender as DependencyObject);
 
      if (target == null || command == null) return true;
 
      command.Execute(null, target);
      return true;
    } else
    {
      return false;
    }
  }
  #endregion
}

And here is our WeakEventMAnager :

public class ButtonClickEventManager : WeakEventManager
{
 
  public static void AddListener(Button source, 
       IWeakEventListener listener)
  {
    ButtonClickEventManager.
	   CurrentManager.ProtectedAddListener(source, listener);
  }
 
  public static void RemoveListener(Button source, 
       IWeakEventListener listener)
  {
    ButtonClickEventManager.
	   CurrentManager.ProtectedRemoveListener(source, listener);
  }
 
  protected override void StartListening(object source)
  {
    Button button = (Button)source;
    button.Click += this.OnButtonClick;
  }
 
  protected override void StopListening(object source)
  {
    Button button = (Button)source;
    button.Click -= this.OnButtonClick;
  }
 
  private void OnButtonClick(Object sender, RoutedEventArgs args)
  {
    base.DeliverEvent(sender, args);
  }
 
  private static ButtonClickEventManager CurrentManager
  {
    get
    {
      Type managerType = typeof(ButtonClickEventManager);
      ButtonClickEventManager manager = 
	  (ButtonClickEventManager)WeakEventManager.GetCurrentManager(managerType);
      if (manager == null)
      {
        manager = new ButtonClickEventManager();
        WeakEventManager.SetCurrentManager(managerType, manager);
      }
      return manager;
    }
  }
}



Example of use

Here a little example of how you can use it to launch the deleteSelected command of the infragistics datagrid from a button outside of the grid :

<Button Content="Delete selected"
  tools:ExecuteCommandOnControl.RoutedCommand="{x:Static igDP:DataPresenterCommands.DeleteSelectedDataRecords}"
  tools:ExecuteCommandOnControl.Target="{Binding ElementName=dataGrid}" />
<igDP:XamDataGrid Name="dataGrid" DataSource="{Binding Data}" />



Interesting readings



Shout it kick it on DotNetKicks.com Awaaz Up