Ahh, que cette année est (était) intéressante. Entre les sorties de Silverlight Toolkit, puis les Bétas de Visual Studio et plus généralement Framework 4.0, il y a un autre produit qui mérite notre attention et dont on parle un peu moins. Il s’agit de la bibliothèque Reactive Extensions (Rx) qui permet de pratiquer la programmation réactive façon Microsoft. Vous pouvez la télécharger sur le site de DevLabs et elle existe pour les Frameworks .NET 3.5 et .NET 4.0 ainsi que pour Silverlight 3.

Si vous n’avez jamais entendu parler de la programmation réactive je vais essayer de faire une petite introduction afin de mieux comprendre la suite de ce post.

 

La programmation asynchrone est difficile

Dans la mesure du possible les développeurs tentent d’éviter la programmation asynchrone pour ne pas être noyé dans la marré des callback et parce que nos programmes sont non déterministes. Cependant la programmation asynchrone est devenue incontournable dans les applications d’aujourd'hui. Les applications clientes ont besoin d’une interface graphique fluide, les développeurs de Silverlight n’ont pas vraiment le choix, les application connectées, l’utilisation des web services…tous ces aspects font que l’utilisation d’une programmation asynchrone devient une nécessité.

 

Comment créer des applications asynchrones dont le code est plus facile à comprendre et plus maintenable ?

C’est ici que rentre en jeu la bibliothèque System.Reactive.dll donc des extension réactives. Tout le monde connait l’utilisation de l’interface IEnumerable. Toutes les collections l’implémentent ce qui permet leur parcours dans une boucle foreach. Nous pouvons par exemple utiliser Linq afin de manipuler les séquences de IEnumerables comme dans l’exemple ci-dessous:

1
2
int[] nombres = new int[] { 1, 11, 34, 2, 7, 98, 78, 4 }; 
IEnumerable<int> nombresTries = from n in nombres orderby n select n;

Enumérables sont donc des séquences de données que nous tirons de la source de données. Par contre quand les données ne sont pas tirées mais poussées, nous devons réagir d’une manière appropriée est c’est ça la Programmation Réactive.

 

Programmation Réactive

La programmation réactive est omniprésente. Nous utilisons la programmation réactive à chaque fois lorsqu’on enregistre un event handler pour un évènement ou lorsqu’on on spécifie une fonction de callback lors de la programmation asynchrone.

Nous sont tous habitués à ce type de programmation réactive mais il y a une autre manière de le faire. Comment ? Qu’est-ce qu’il se passerait si chaque élément de données était passé en tant qu’un élément dans une séquence à une méthode appelée réactivement ?

 

Observateur = Itérateur ?

Les données qui sont passées aux event handlers peuvent être conceptualisées en tant que séquences de données qui sont poussées au lieu de tirer.

A chaque fois lorsqu’un évènement est déclenché un élément de données est poussé et le plus souvent c’est le EventArgs. De même lors de la programmation asynchrone. Lorsqu’une méthode de callback est évoquée des données sont poussées, le résultat de l’opération asynchrone.

Toutes les opérations que vous pouvez effectuer sur les séquences en tirant le données peuvent également être effectuées sur les séquences en poussant les données.

Enumarateur = les données sont tirées de la source de données.

Observateur = les données sont poussées de la source de données.

Cela veut tout simplement dire et c’est la conclusion à laquelle sont arrivé les créateurs de la bibliothèque de la programmation réactive que les Design Patterns Observateur et Iterateur sont en fait les même patterns !

 

Exemple Drag & Drop en WinForm

Un petit exemple pour vous démontrer la puissance de la programmation réactive s’appuiera sur le Drag & Drop en WinForms (libre à vous de le faire en WPF ou Silverlight). Je suis en train de découvrir cette magnifique bibliothèque donc je ne vais pas vous parler des détails d’implémentation. C’est peut-être l’exemple le plus banal évoqué sur les blogs traitant de la programmation réactive. Il est possible d’y arriver par différents moyens mais étant donné que cette technique n’a pas encore été évoquée sur les blogs de code sources je me permets de le faire.

Le but de cette exercice et de permettre à tous les contrôles sur un form windows d’être draggables. Par exemple avec le code suivant :

1
2
3
4
foreach (Control control in this.Controls) 
{
control.AllowDragging();
}

Mais avant de le faire voyons comment nous pouvons le faire façon “OLD SCHOOL” donc sans les extensions réactives.

Old School style

Le code n’est pas le plus optimisé et il y a d’autre manière de le faire mais imaginons que nous voulons prendre un bouton “button1” et le placer n’importe où sur la form.

dragDrop

Les évènements qui nous intéresserons sont :

button1_MouseDown : pour initier le dragging

button1_MouseMove : pour récalculer la nouvelle position du bouton lorsque la souris se déplace sur la forme.

button1_MouseUp : pour lâcher le bouton sur la forme et terminer le dragging.

L’implémentation de ces événements peut ressembler à ceci :

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
bool dragging = false; 
int mousex;
int mousey;

private void button1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
dragging = true;
mousex = -e.X;
mousey = -e.Y;
int clipleft = this.PointToClient(MousePosition).X - button1.Location.X;
int cliptop = this.PointToClient(MousePosition).Y - button1.Location.Y;
int clipwidth = this.ClientSize.Width - (button1.Width - clipleft);
int clipheight = this.ClientSize.Height - (button1.Height - cliptop);
Cursor.Clip = this.RectangleToScreen(new Rectangle(clipleft, cliptop, clipwidth, clipheight));

button1.Invalidate();
}
}

private void button1_MouseMove(object sender, MouseEventArgs e)
{
if (dragging)
{
var position = new Point();
position = this.PointToClient(MousePosition);
position.Offset(mousex, mousey);
button1.Location = position;
}
}

private void button1_MouseUp(object sender, MouseEventArgs e)
{
if (dragging)
{
dragging = false;
Cursor.Clip = new Rectangle();
button1.Invalidate();
}
}

Voyons maintenant comment nous pouvons implémenter la même chose avec la programmation réactive.

Reactive Programming Style

Comment nous allons faire. Tout d’abord commençons à ajouter un référence vers la bibliothèque System.Reactive.dll et System.CoreEx.dll. Ensuite, les extensions Rx nous permettent de traiter les évènements .NET (et non seulement) comme une source de données que nous pouvons interroger avec une requête LINQ. Nous allons utiliser la méthode Observable.FromEvent.

Tout d’abord occupons nous de la méthode MouseDown. Si nous nous rappelons ce que j’ai écrit plus haut dans la programmation réactive nous pouvons considérer l’évènement MousDown en tant qu’une séquence de EventArgs qui sont poussés à chaque fois qu’un évènement se produit. Donc, l’évènement MouseDown est représenté en tant qu’une collection de données où chaque donnée représente un click avec la souris. Voyons le code qui permet de nous convertir l’évènement MouseDown en cette collection de données. Le plus simple est de faire une méthode d’extension pour que cette évènement soit valable pour tout type de contrôle.

1
2
3
4
5
6
7
8
public static IObservable<IEvent<MouseEventArgs>> DoMouseDown(this Control control) 
{
return Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
handler => new MouseEventHandler(handler),
handler => control.MouseDown += handler,
handler => control.MouseDown -= handler
);
}

Grâce à cette implémentation nous pouvons d’une manière différente que juste un simple enregistrement à un évènement afficher un message box lorsqu’on clique sur le bouton :

1
button1.DoMouseDown().Subscribe(handler => MessageBox.Show("Click sur le bouton1 avec évènement MouseDown"));

Vous me diriez ça sert à quoi vu que nous pouvons utiliser la manière standard de le faire ? Mais dites-moi, si je vous demande d’implémenter la même chose de la façon old school et afficher le MessageBox juste pour les 3 premiers click. Ce n’est plus si banal que cela. Nous devrions garder un compteur interne de clicks que nous devrions incrémenter lors de chaque click et de consulter lorsqu’un évènement est évoqué, se désabonner…enfin cela se complique très vite ! Voyons comment c’est facile avec la programmation réactive:

1
button1.DoMouseDown().Take(3).Subscribe(handler => MessageBox.Show("Click sur le bouton1 avec évènement MouseDown"));

Après 3 click plus aucun évènement n’est lancé. Cooooooool !

Passons maintenant à notre implémentation du Drag façon réactive ! Comment le Drag & Drop se passe en réalité ?

  1. Tout d’abord il y a l’évènement MouseDown. Nous sommes intéressé que par le click avec le bouton gauche de la souris.
  2. Ensuite il y a le MouseMove qui se passe lorsqu’on bouge la souris.
  3. Et à la fin il y a MouseUp lorsqu’on relâche la souris.

Définissons d’abord les méthodes d’extension pour les évènements MouseMove et MouseUp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static IObservable<IEvent<MouseEventArgs>> DoMouseMove(this Control control) 
{
return Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
handler => new MouseEventHandler(handler),
handler => control.MouseMove += handler,
handler => control.MouseMove -= handler
);
}

public static IObservable<IEvent<MouseEventArgs>> DoMouseUp(this Control control)
{
return Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
handler => new MouseEventHandler(handler),
handler => control.MouseUp += handler,
handler => control.MouseUp -= handler
);
}

Ensuite, définissons une classe qui contiendra les informations que nous voulons passer à ces évènements. Ce qui nous intéresse c’est le contrôle que nous sommes en trains de bouger, la position initiale de la souris par rapport à la position du contrôle et la position actuelle de la souris. Nous pouvons l’implémenter de la manière suivante :

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DragInformation 
{
public Control DragableControl { get; private set; }
public Size InitialLocation { get; private set; }
public Point NewLocation { get; private set; }

public DragInformation(Control dragableControl, Size initialLocation, Point newLocation)
{
DragableControl = dragableControl;
InitialLocation = initialLocation;
NewLocation = newLocation;
}
}

De cette manière avec les extensions réactives nous pouvons COMPOSER les évènements et les implémenter avec une expression LINQ d’après le scénario écrit ci-dessus (1 – MouseDown, 2 – MouseMove, 3 – MouseUp)…

1
2
3
4
5
6
7
8
public static IObservable<DragInformation> DoDrag(this Control control) 
{
return from mouseDown in control.DoMouseDown()
where mouseDown.EventArgs.Button == MouseButtons.Left
from mouseMove in control.DoMouseMove().Until(control.DoMouseUp())
select new DragInformation(control, new Size(mouseDown.EventArgs.Location),
Point.Add(control.Location, new Size(mouseMove.EventArgs.Location)));
}

Ensuite il faut ajouter une méthode d’extension qui ajoutera à chaque contrôle la capacité d’être dragable :

1
2
3
4
5
6
7
public static void AllowDragging(this Control control) 
{
control.DoDrag().Subscribe(info =>
{
info.DragableControl.Location = Point.Subtract(info.NewLocation, info.InitialLocation);
});
}

De cette manière nous pouvons simplement activer les drag & drop pour tous les contrôles sur la forme:

1
2
foreach (Control control in this.Controls) 
control.AllowDragging();

Conclusion

Nous avons juste vu à peine toutes les possibilités qui offre cette bibliothèque. Je vais certainement consacrer quelques posts à son sujet. Bien que cette bibliothèque ouvre des horizons le deboguage des applications semble un peu plus compliqué. C’est également un sujet intéressant à creuser.

A bientôt.