Déjà, juste le titre, ça calme bien. Un peu autant que le temps que j’ai passé à trouver la solution en fait.
Voici donc le problème : J’ai un wrap panel en WPF qui sert d’ItemPanel à une ListBox. Jusque là on ne se fatigue pas trop non plus:
<ListBox ItemsSource="{Binding}" x:Name="imagesList">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel>
</WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Toutefois, lorsque je veux binder une grosse liste d’images (plus de 300) en provenance de ma base de données à ma ListBox, je me récupére en supplément un bon gros lag car le gentil Wrap Panel ne sait pas virtualiser ses items et doit donc tous les créer pour s’afficher (contrairement par exemple à une ListView qui saura ne construire que les items qu’elles affichent en temps réel).
Donc, je me suis lancé à la recherche d’une solution qui me permettrait d’asynchroniser le remplissage de mon Wrap panel.
Première idée, on met benoitement notre imagesList.DataContext = maListe dans un ThreadPool.QueueUserWorkItem. Et là c’est le drame car WPF me bloque comme j’essaye d’accéder à un DispatcherObject depuis un autre thread que celui qui a servit à construire le contrôle.
La solution est un tantinet plus ardue:
Premièrement, il faut modifier notre objet qui porte l’image à afficher pour lui ajouter une propriété supplémentaire de type BitmapFrame (qui a l’énorme avantage de ne pas vérifier l’origine du thread appelant). Il est ici important de ne pas laisser le BitmapFrame se mettre en DelayLoad depuis le cache d’image d’ou l’utilisation du paramètre BitmapCacheOption.OnLoad:
public partial class Card
{
BitmapFrame bitmapImage;
public BitmapFrame Bitmap
{
get
{
return bitmapImage;
}
}
public void GenerateBitmap()
{
if (bitmapImage != null)
{
return;
}
using (MemoryStream ms = new MemoryStream(Image))
{
bitmapImage = BitmapFrame.Create(ms, BitmapCreateOptions.None,
BitmapCacheOption.OnLoad);
}
}
}
Deuxièmement, avant d’affecter le DataContext de notre ListBox, nous allons appeler de manière asynchrone sur chacun de nos objets la méthode GenerateBitmap.
Finalement, il suffit de faire un petit Dispatcher.Invoke pour affecter notre DataContext:
ThreadPool.QueueUserWorkItem(o =>
{
foreach (Card card in list)
{
card.GenerateBitmap();
}
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(()=>
{
imagesList.DataContext = source;
}));
});
Le résultat fait plaisir à voir je vous l’assure :)
.Net, WPF