Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Thomas Lebrun

Tout sur WPF, LINQ, C# et .NET en général !

Actualités

[WPF] Comment actionner une commande WPF lorsqu’un évènement est déclenché ?

Lorsque l’on travaille avec WPF et le pattern MVVM (Model View ViewModel), on essaye d’éviter au maximum de mettre du code directement dans la code behind. Ainsi, le code suivant n’est pas le mieux:

public partial class MainWindow : Window

{

    public MainWindow()

    {

        this.InitializeComponent();

    }

 

    <Button x:Name="btnTest"

            Content="Test Command"

            HorizontalAlignment="Center"

            VerticalAlignment="Center"

            MouseLeave="btnTest_MouseLeave" />

 

    private void btnTest_MouseLeave(object sender, MouseEventArgs e)

    {

        MessageBox.Show("Appel réussit !");

    }

}

A choisir, on préfèrera plutôt utiliser les commandes WPF plutôt que de s’abonner aux évènements directement dans le code behind.

OK mais dans ce cas, comment faire pour déclencher une commande lorsqu’un évènement survient ? La solution, toute bête, passe par l’utilisation des propriétés attachées de WPF. Ainsi, il suffit de créer une simple classe qui va containir 2 propriétés:

  • une pour la commande à appeler
  • une pour le nom de l’évènement à surveiller

Dès que vous affectez une valeur à la propriété servant à surveiller l’évènement qui vous intéresse, vous vous abonnez en fait à cet évènement. Puis, lorsque celui-ci est déclenché, vous appelez la commande que vous avez branché ! Voici un exemple d’implémentation (attention, notez qu’il s’’agit d’un exemple/prototype):

public class EventBehavior

{

    public static ICommand GetCommand(DependencyObject obj)

    {

        return (ICommand)obj.GetValue(CommandProperty);

    }

 

    public static void SetCommand(DependencyObject obj, string value)

    {

        obj.SetValue(CommandProperty, value);

    }

 

    public static readonly DependencyProperty CommandProperty =

        DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EventBehavior), new FrameworkPropertyMetadata(null));

 

    public static string GetEventName(DependencyObject obj)

    {

        return (string)obj.GetValue(EventNameProperty);

    }

 

     public static void SetEventName(DependencyObject obj, string value)

    {

        obj.SetValue(EventNameProperty, value);

    }

 

    public static readonly DependencyProperty EventNameProperty =

        DependencyProperty.RegisterAttached("EventName", typeof(string), typeof(EventBehavior), new FrameworkPropertyMetadata(string.Empty, new PropertyChangedCallback(OnEventNameChanged)));

 

    private static void OnEventNameChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)

    {

        var element = target as UIElement;

        if(element != null)

        {

            if ((!string.IsNullOrEmpty((string)e.NewValue) && (string.IsNullOrEmpty((string)e.OldValue))))

            {

                var eventName = (string)e.NewValue;

 

                var eventInfo = element.GetType().GetEvent(eventName);

                if(eventInfo != null)

                {

                    SinkControlEvent(element, eventName);

                }

                else

                {

                    throw new NullReferenceException(string.Format("Event name {0} has not been found on this object !", eventName));

                }

            }

        }

        else

        {

            throw new NullReferenceException("This behavior can only be installed on UIElement objects !");

        }

    }

 

    /// <summary>

    /// Sinks the control event.

    /// Source: http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/ae1b155e-1388-431c-bc0e-e7846f9368c8

    /// </summary>

    /// <param name="sender">The sender.</param>

    /// <param name="eventName">Name of the event.</param>

    static void SinkControlEvent(object sender, string eventName)

    {

        // Get the event information for the specified event.   

        var eventInfo = sender.GetType().GetEvent(eventName);

 

        // Get the handler type for the specified event.   

        Type handlerType = eventInfo.EventHandlerType;

 

        // Get the ParameterInfo collection for the event handler's invoke method.   

        if (handlerType != null)

        {

            var parameterInfos = handlerType.GetMethod("Invoke").GetParameters();

 

            // Get the collection of types corresponding to the event handler's parameters.  

            Type[] parameterTypes = parameterInfos.Select(p => p.ParameterType).ToArray();

 

            // Get the type of the AnonymousDelegateClass, and fill the generic parameters.   

            Type type = typeof(AnonymousDelegateClass<,>).MakeGenericType(parameterTypes);

 

            // Get the constructor for the class that takes the string value.   

            var constructor = type.GetConstructor(new[] { typeof(string) });

 

            // Create an instance of the class, which will subscribe to the event specified.   

            object instance = constructor.Invoke(new object[] { eventName });

 

            // Get the Handler method, which will be the event handler for the event.   

            var method = type.GetMethod("OnEventRaised");

 

            // Create a delegate of the same type of the handler type based on the Handler method.  

            Delegate eventhandler = Delegate.CreateDelegate(handlerType, instance, method);

 

            // Finally, add the event handler to the object specified.   

            eventInfo.AddEventHandler(sender, eventhandler);

        }

    }  

}

 

/// <summary>

/// Anonymous delegate class.

/// </summary>

/// <typeparam name="TSender">The type of the sender.</typeparam>

/// <typeparam name="TEventargs">The type of the eventargs.</typeparam>

public class AnonymousDelegateClass<TSender, TEventargs> where TEventargs : EventArgs

{

    // This is the name of the event subscribed to.   

    public string Name { get; private set; }

 

    public AnonymousDelegateClass(string name)

    {

        Name = name;

    }

 

    // This the generic handler that is used to subscribe to the object.   

    public void OnEventRaised(TSender sender, TEventargs e)

    {

        var element = sender as UIElement;

        if (element != null)

        {

            var command = (ICommand)element.GetValue(EventBehavior.CommandProperty);

            command.Execute(e);

        }

        else

        {

            throw new NullReferenceException("This behavior can only be installed on UIElement objects !");

        }

    }

}

Pour utiliser tout cela, rien de plus simple:

<Button x:Name="btnTest"

        Content="Test Command"

        HorizontalAlignment="Center"

        VerticalAlignment="Center"

        Behaviors:EventBehavior.Command="{Binding ShowMessageCommand}"

        Behaviors:EventBehavior.EventName="MouseEnter" />

Et le tour est joué Smile

Pour votre gouverne, sachez que ce pattern est connu sous le nom d’Attached Behavior Pattern (la notion de Behaviors revient avec Expression Blend 3 Wink). Si vous voulez en savoir plus, ou bien si vous souhaitez avoir des implémentations plus complètes, je vous conseille les liens suivants:

 

A+

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :
Posted: mardi 14 avril 2009 11:39 par Thomas LEBRUN
Classé sous : ,

Commentaires

tomlev a dit :

Excellent :)

Dans le même ordre d'idée, j'avais posté une astuce pour lier un InputBinding sur une commande du ViewModel :

http://tomlev.wordpress.com/2009/03/17/wpf-utiliser-les-inputbindings-avec-le-pattern-mvvm/

# avril 14, 2009 18:44
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Emportez votre sélection de la MSDN dans la poche ? par Blog de Jérémy Jeanson le il y a 16 heures et 41 minutes

- [ #Office365 ] Pb de connexion du flux Yammer ajouté à un site SharePoint par Le blog de Patrick [MVP SharePoint] le il y a 22 heures et 2 minutes

- NFluent & Data Annotations : coder ses propres assertions par Fathi Bellahcene le il y a 22 heures et 10 minutes

- Installer un site ASP.net 32bits sur un serveur exécutant SharePoint 2013 par Blog de Jérémy Jeanson le 04-17-2014, 06:34

- [ SharePoint Summit 2014 ] Tests de montée en charge SharePoint par Le blog de Patrick [MVP SharePoint] le 04-16-2014, 20:44

- [ SharePoint Summit 2014 ] Bâtir un site web public avec Office 365 par Le blog de Patrick [MVP SharePoint] le 04-16-2014, 18:30

- Kinect + Speech Recognition + Eedomus = Dommy par Aurélien GALTIER le 04-16-2014, 17:17

- [ SharePoint Summit 2014 ] Une méthodologie simple pour concevoir vos applications OOTB SharePoint de A à Z par Le blog de Patrick [MVP SharePoint] le 04-16-2014, 16:51

- //Lean/ - Apprendre à faire des Apps Windows universelles par Blog de Jérémy Jeanson le 04-16-2014, 12:57

- Une culture de la donnée pour tous… par Le blog de Patrick [MVP SharePoint] le 04-16-2014, 11:00