System.Windows.Interactivity, késako ?

System.Windows.Interactivity.dll Blend 3

Tous les jours, nous découvrons des choses… Et aujourd’hui, j’espère vous en faire découvrir. À la sortie d’Expression Blend 3, ce petit bijou nous a ramené discrètement deux assemblys :

  1. Microsoft.Expression.Interactions.dll
  2. System.Windows.Interactivity.dll

Il existe une version Silverlight, que j’utiliserai pour l’article, et une version WPF.

I. System.Windows.Interactivity.dll

Pour commencer, analysons System.Windows.Interactivity.dll à coup d’Object Browser:

Interactivity Object Browser

En fait, cette assembly nous fournit différentes classes pour interagir avec les composants de notre interface. Aujourd’hui, je vais vous montrer plusieurs manières pour exécuter une ICommand lors d’un appui sur la touche « Entrée » dans une TextBox. Pratique non ?

1. Behavior<T>

Cette classe permet de définir quoi que ce soit sur un composant graphique. Typiquement, l’objet s’attache et se détache d’un DependencyObject. À nous de choisir ce que l’on veut faire. Pour exécuter une commande lors d’un appui sur « Entrée », il faut pouvoir s’abonner à l’événement KeyDown. Créons un Behavior à l’aide de Blend 3.

Create Behavior Blend 3

Blend 3 crée une classe avec un constructeur vide et le plus intéressant, deux méthodes surchargées. Tel que prévu et voulu, cette classe est un héritage de la classe Behavior<T> mais au fait à quoi sert le paramètre générique ?
Il est utilisé pour définir à quel type d’objet peut être attaché notre Behavior, pour nous ça sera UIElement, pour toucher au plus large sur l’événement KeyDown.

1
2
protected override void OnAttached()
protected override void OnDetaching()

Comme vous l’avez surement compris, OnAttached sera appelé après que notre Behavior ai été attaché à notre DependencyObject et OnDetaching pendant le détachement…

Après une micro réflexion, on comprend qu’il est nécessaire de s’abonner à l’événement KeyDown de l’UIElement attaché à notre Behavior.
L’objet attaché à notre Behavior est accessible avec la propriété AssociatedObject. Voici à quoi ressemble maintenant la méthode OnAttached :

1
2
3
4
5
6
protected override void OnAttached()
{
    base.OnAttached();
 
    this.AssociatedObject.KeyDown += this.AssociatedObjectKeyDown;
}

En tant que bonnes personnes, nous allons laisser la possibilité de spécifier quelle ICommand doit être exécutée, créons donc une DependencyProperty :

1
2
3
4
5
6
7
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandOnEnterBehavior), null);
 
public ICommand Command
{
    get { return (ICommand) this.GetValue(CommandProperty); }
    set { this.SetValue(CommandProperty, value); }
}

Enfin, exécutons notre ICommand dans AssociatedObjectKeyDown:

1
2
3
4
5
6
7
8
private void AssociatedObjectKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Enter)
    {
        if (this.Command.CanExecute(null))
            this.Command.Execute(null);
    }
}

Voilà, on a fini notre Behavior ! Pour le tester, voici une petite classe HelloWorldCommand, ca rox hein ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HelloWorldCommand : ICommand
{
    public bool CanExecute(object parameter)
    {
        return true;
    }
 
    public void Execute(object parameter)
    {
        MessageBox.Show("Hello world !");
    }
 
    public event EventHandler CanExecuteChanged;
}

Voici un exemple d’utilisation avec une TextBox :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<UserControl
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:my="clr-namespace:SilverlightInteractions"
	x:Class="SilverlightInteractions.MainPage"
	Width="640" Height="480">
    <UserControl.Resources>
        <my:HelloWorldCommand x:Name="helloWorldCommand" />
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <TextBox> 
            <i:Interaction.Behaviors>
                <my:CommandOnEnterBehavior Command="{StaticResource helloWorldCommand}" />
            </i:Interaction.Behaviors>
        </TextBox>
    </Grid>
</UserControl>

Pour conclure, il faut retenir que la classe Behavior<T> permet de s’attacher un DependencyObject et donne donc la possibilité de contrôler plus ou moins ce dernier.

2. TriggerBase<T> & TriggerAction<T>

L’assembly fournit aussi un système de déclencheurs, d’ailleurs semblable à ce qu’on pouvait déjà trouver dans System.Windows, mais plus souple. En effet, pour définir un déclencheur, il suffit d’hériter TriggerBase<T> et pour créer des actions hériter de TriggerAction<T>.

Encore une micro réflexion, pour comprendre qu’il faut créer un déclencheur pour déclencher une action qui exécutera une commande. Créons un Trigger à l’aide de Blend 3.

Create Trigger Blend 3

La classe créée est ressemblante à celle d’un Behavior, mais en lisant les beaux commentaires verts et farfouillant un peu, on saisit vite que la différence est qu’un Trigger contient une liste d’actions qu’il est possible d’exécuter grâce à la méthode InvokeActions. Voici le code de notre Trigger :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class EnterKeyDownTrigger : TriggerBase<TextBox>
{
	protected override void OnAttached()
	{
		base.OnAttached();
 
        this.AssociatedObject.KeyDown += this.AssociatedObjectKeyDown;
	}
 
    private void AssociatedObjectKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            this.InvokeActions(this.AssociatedObject.Text);
        }
    }
}

Les plus lecteurs aurons remarqué cette fois-ci que mon Trigger peut s’attacher seulement aux objets de type TextBox. La raison est toute simple, je voulais que dans cet exemple, l’appui sur « Entrée » avec le focus dans une TextBox provoque l’affichage du contenu dans une MessageBox.

Vous trouverez une version plus souple et plus intelligente de ce Trigger dans les sources.

Codons maintenant notre fameuse classe héritant d’ActionTrigger :

Create Action Blend 3

Ici, simple, une classe avec un constructeur et une méthode surchargée :

1
protected override void Invoke(object o)

On saisit vite que cette méthode sera appelée par le Trigger qui contient l’action.
Notre action aura pour rôle d’exécuter une commande laissons donc le choix à notre utilisateur de spécifier quelle commande utilisée :

1
2
3
4
5
6
7
8
9
    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }
 
 
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandAction), null);

Enfin exécutons notre commande lors de l’appel à Invoke :

1
2
3
4
5
protected override void Invoke(object o)
{
	if(this.Command.CanExecute(o))
        this.Command.Execute(o);
}

Voilà, notre système de déclencheur est créé ! Testons-le !

Une petite commande pour tester :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MessageBoxCommand : ICommand
{
    public bool CanExecute(object parameter)
    {
        return parameter as String != null;
    }
 
    public void Execute(object parameter)
    {
        MessageBox.Show(parameter as String);
    }
 
    public event EventHandler CanExecuteChanged;
}

Et un petit exemple d’utilisation :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<UserControl
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:my="clr-namespace:SilverlightInteractions"
	x:Class="SilverlightInteractions.MainPage"
	Width="640" Height="480">
    <UserControl.Resources>
        <my:HelloWorldCommand x:Name="helloWorldCommand" />
        <my:MessageBoxCommand x:Name="messageBoxCommand" />
    </UserControl.Resources>
    <StackPanel x:Name="LayoutRoot" Background="White">
        <TextBox> 
            <i:Interaction.Behaviors>
                <my:CommandOnEnterBehavior Command="{StaticResource helloWorldCommand}" />
            </i:Interaction.Behaviors>
        </TextBox>
        <TextBox>
            <i:Interaction.Triggers>
                <my:EnterKeyDownTrigger>
                    <my:CommandAction Command="{StaticResource messageBoxCommand}" />
                </my:EnterKeyDownTrigger>
            </i:Interaction.Triggers>
        </TextBox>
    </StackPanel>
</UserControl>

Conclusion, cette assembly nous offre un système de déclencheurs facile à mettre en place. Pour notre besoin, cette approche est beaucoup plus propre et plus extensible, typiquement il serait facile de rajouter une nouvelle action en cas d’appui sur « Entrée ».

3. DefaultTriggerAttribute

Pour finir, une petite astuce sympa, si vous êtes amené à travailler avec Blend 3, vous vous rendez vite compte qu’il est possible de faire un Drag&Drop d’une action sur un contrôle. C’est la qu’intervient l’attribut DefaultTrigger, il permet de définir quel Trigger doit être associé à l’action Drag&Dropé. Exemple pour notre CommandAction :

1
[DefaultTrigger(typeof(Button),typeof(System.Windows.Interactivity.EventTrigger), "Click")]

Si vous Drag&Dropé une action de type CommandAction sur un contrôle alors Blend 3 associera cette action avec un Trigger de type EventTrigger qui à pris « Click » comme argument lors de la construction.

II. Et Microsoft.Expression.Interactions.dll ?

Je peux vous avouer maintenant que beaucoup de choses que je vous ai présentées sont déjà a votre disposition dans nos assembly. En effet, Microsoft.Expression.Interactions.dll nous sert sur un plateau la classe KeyTrigger et System.Windows.Interactivity.dll, la classe InvokeCommandAction. Je vous les laisse découvrir par vous-même, avec ce qui précède vous ne serais pas dépaysé !

Je pense que cet article est une bonne introduction aux possibilités apportées par ces assemblys. Je ne vous ai pas tous montrés, je pense notamment à la classe TargetedTriggerAction<T>, c’est pour cela que je vous invite à tester en condition réelle ces librairies pour comprendre toute la puissance apportée par ces dernières. Voilà, merci de m’avoir lu et à bientôt pour des aventures encore plus fabuleuses.

Alors, je vous ai appris des choses ? Dites-moi que Oui ! :D Laissez un commentaire !

Les sources sont disponibles ici, sur le repository.

Publié dans .NET, Développement, Silverlight, WPF.

Classé dans , , , , , , , , , , .


0 réponses

Suivez la conversation, abonnez-vous au flux RSS des commentaires..



Un peu de HTML est permis

ou héberger un rétrolien.