Le dispatcher Thread de WPF et le databinding
Une des grandes forces de WPF est le databinding. Il est possible de lier le contenu d'un contrôle de type ItemsControl, comme une ListBox, ou ListView, à une collection. Si cette collection implémente l'interface IObservableCollection, la moindre modification du contenu de la collection sera répercutée automatiquement sur le contrôle. Cette particularité de WPF peut faire économiser de nombreuses heures aux développeurs.
Voici un petit exemple, tout d'abord un code XAML :
<Grid x:Name="LayoutRoot">
<ItemsControl Name="Messages" Margin="12,12,12,0" Style="{DynamicResource MessagesControlTemplate}"/>
</Grid>
Ensuite une classe C# :
class Callback
{
ObservableCollection<Message> messages = new ObservableCollection<Message>();
public void ReceiveMessage(Message message)
{
messages.Add(message);
}
public IList<Message> Messages
{
get { return messages; }
}
}
Nous allons maintenant créer une instance de cette classe Callback et lier le contenu de l'ItemsControl à la propriété Messages de cette instance.
Callback callback = new Callback();
Messages.ItemsSource = callback.Messages;
Le tour est joué. Lors d'un appel à la fonction ReceiveMessage, la collection est mise à jour, et le contrôle aussi.
Par contre, ce système a une limitation : la collection ne peut maintenant être modifiée que par le dispatcher thread de WPF. C'est à dire que si ReceiveMessage est appelée depuis l'évènement de clic sur un bouton, cela fonctionnera. Par contre si cela provient d'un thread extérieur à WPF, l'exception suivante sera levée :
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
C'est mon cas, puisqu'en réalité c'est un thread de WCF qui va exécuter la méthode ReceiveMessage. Heureusement, il existe une solution à ce problème : elle consiste à récupérer le dispatcher thread de WPF et à exécuter la méthode dessus :
class Callback
{
ObservableCollection<Message> messages = new ObservableCollection<Message>();
public void ReceiveMessage(Message message)
{
Application app = System.Windows.Application.Current;
if (app != null)
app.Dispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(Add), message);
}
private object Add(object str)
{
messages.Add((Message)str);
return null;
}
public IList<Message> Messages
{
get { return messages; }
}
}
Et cette fois ci, cela fonctionne correctement.
Si vous voulez des informations plus détaillées à ce sujet, je vous conseille d'aller jeter un coup d'oeil à cet article sur le blog de Beatriz Costa.
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 :