Cuando se presentó WPF hace ya algunos años, éste venía con un nuevo modelo de eventos que resultaban muy potentes, los eventos enrutados. Podemos definir los eventos enrutados como un tipo de evento que puede invocar controladores o varios agentes de escucha en un árbol de elementos, en lugar de simplemente en el objeto que lo desencadenó. Este modelo surge por necesidad al poder incluir controles dentro de otros, por ejemplo, si incluimos una imagen dentro de un  botón, éste conservará su comportamiento al hacer clic sobre la imagen. Windows Forms no dispone de este modelo de eventos pero podemos crear un comportamiento similar a los eventos enrutados de WPF que nos puede ser muy útil para propagar por varios elementos un evento que se haya producido.

En primer lugar vamos a definir la interfaz que nos va a permitir que un objeto pueda procesar un evento y/o dejarlo como trabajo para otros. La propiedad EventSuccesor define el elemento al que se le enviará el evento en caso de que no se procese en el objeto actual, la propiedad EventManager nos proporciona acceso al objeto que gestiona los eventos instanciados para el objeto y, por último, el método HandleBubbleEvent es el que se encarga de procesar un evento.

public interface IRoutedEventHandler

{

    IRoutedEventHandler EventSuccesor { get; set; }

 

    RoutedEventManager EventManager { get; }

 

    void HandleBubbleEvent(string eventName, RoutedEventArgs e);

}

La clase RoutedEventArgs, que extiende a la clase EventArgs que se utiliza como clase base para pasar información a cualquier evento define, principalmente, dos propiedades Source y Handled. Con la primera indicaremos el objeto origen donde se produce el evento y con la segunda indicamos si el evento ha sido manejado o no. En caso de que la propiedad Handled se defina como true el evento dejará de propagarse por la jerarquía establecida.

public class RoutedEventArgs : EventArgs

{

    private object _source;

    private bool _handled;

    private object _param;

 

    public RoutedEventArgs(object source)

    {

        _source = source;

    }

 

    public object Source

    {

        get { return _source; }

    }

 

    public bool Handled

    {

        get { return _handled; }

        set { _handled = value; }

    }

 

    public object Param

    {

        get { return _param; }

        set { _param = value; }

    }

}

Mediante la clase RoutedEventManager proporcionamos un almacén para guardar los eventos a los que responderá el objeto que implemente la interfaz IRoutedEventHandler previamente definida. Simplemente se proporciona un mecanismo que asocia a un nombre una lista de delegados de tipo RoutedEventDelegate, los cuales apuntan a los métodos que establezcamos como manejadores. Utilizamos una lista en vez de un único delegado para permitir que un evento tenga varios manejadores, de igual modo que podemos hacer con un evento de siempre.

public delegate void RoutedEventDelegate(Object sender, RoutedEventArgs e);

 

public class RoutedEventManager

{

    private Dictionary<string, List<RoutedEventDelegate>> _container;

 

    public RoutedEventManager()

    {

        _container = new Dictionary<string, List<RoutedEventDelegate>>();

    }

 

    public void AddHandler(string eventName, RoutedEventDelegate dlg)

    {

        List<RoutedEventDelegate> list;

 

        if (_container.ContainsKey(eventName))

        {

            list = _container[eventName];

            list.Add(dlg);

        }

        else

        {

            list = new List<RoutedEventDelegate>();

            list.Add(dlg);

            _container.Add(eventName, list);

        }

    }

 

    public bool RemoveHandler(string eventName)

    {

        if (_container.ContainsKey(eventName))

            return _container.Remove(eventName);

        else

            return false;

    }

 

    public bool RemoveHandler(string eventName, RoutedEventDelegate dlg)

    {

        List<RoutedEventDelegate> list;

 

        if (_container.ContainsKey(eventName))

        {

            list = _container[eventName];

 

            return list.Remove(dlg);

        }

        else

            return false;

    }

 

    public List<RoutedEventDelegate> GetHandler(string eventName)

    {

        if (_container.ContainsKey(eventName))

            return _container[eventName];

        else

            return null;

    }

A continuación implementamos el modelo de evento definido para un botón que por defecto al hacer clic sobre él, cambia de color. Aunque definamos un manejador enrutado dentro del propio código de la clase también podemos hacerlo desde cualquier otra que tenga acceso a una instacia de MyButton, de igual modo que ocurre con un evento.

public class MyButton : Button, IRoutedEventHandler

{

    private RoutedEventManager _eventManager;

    private IRoutedEventHandler _succesor;

 

    public MyButton()

        : base()

    {

        _eventManager = new RoutedEventManager();

 

        EventManager.AddHandler("Click", new RoutedEventDelegate(ClickHandler));

 

        this.Click += new EventHandler(MyButton_Click);

    }

 

    void MyButton_Click(object sender, EventArgs e)

    {

        RoutedEventArgs re = new RoutedEventArgs(this);

        re.Param = e;

 

        HandleBubbleEvent("Click", re);

    }

 

    public void ClickHandler(object sender, RoutedEventArgs re)

    {

        if (sender == this)

        {

            if (BackColor == DefaultBackColor)

                BackColor = Color.Red;

            else

                BackColor = DefaultBackColor;

        }

        else

        {

            if (re != null)

            {

                if (BackColor == DefaultBackColor)

                    BackColor = Color.Red;

                else

                    BackColor = DefaultBackColor;

 

                MessageBox.Show("Origen del evento: " + re.Source);

            }

        }

    }

 

    #region IRoutedEventHandler Members

 

    public IRoutedEventHandler EventSuccesor

    {

        get { return _succesor; }

        set { _succesor = value; }

    }

 

    public RoutedEventManager EventManager

    {

        get { return _eventManager; }

    }

 

    public void HandleBubbleEvent(string eventName, RoutedEventArgs e)

    {

        List<RoutedEventDelegate> dlgList = EventManager.GetHandler(eventName);

 

        if (dlgList != null)

        {

            foreach (RoutedEventDelegate dlg in dlgList)

            {

                if (!e.Handled)

                    dlg.Invoke(e.Source, e);

                else

                    break;

            }

        }

 

        if (!e.Handled && EventSuccesor != null)

            EventSuccesor.HandleBubbleEvent(eventName, e);

    }

 

    #endregion

}

Si nos fijamos en cómo definimos los eventos, tenemos que el manejador para el evento enrutado referencia al método ClickHandler, el cual implementa la lógica que aplicamos al evento Click, y por otro, definimos el evento Click del propio botón sobre el método MyButton_Click que simplemente realiza una llamada sobre HandleRoutedEvent para procesar el evento. Si en lugar de hacerlo de este modo incluimos la lógica en el propio evento Click y el evento enrutado hace referencia al mismo manejador entraríamos en un bucle que acabaría produciendo un desbordamiento de pila, pues la llamada a HandleRoutedEvent desencadenaría en una llamada al mismo método de forma recursiva.