<?xml version="1.0" encoding="utf-8"?><rss version="2.0"><channel><title>c2i.fr - Task</title><link>http://www.c2i.fr:80/Tags/Task</link><description>c2i.fr - Task</description><item><title>Comment charger et afficher beaucoup d'images (WinForms)</title><link>http://www.c2i.fr:80/articles/comment-charger-et-afficher-beaucoup-d-rsquo-images-winforms</link><description>&lt;p&gt;L&amp;rsquo;id&amp;eacute;e de cet article me vient d&amp;rsquo;une question pos&amp;eacute;e sur le forum MSDN de C#. La question &amp;eacute;tait la suivante :&lt;/p&gt;
&lt;p&gt;&lt;em&gt;J&amp;rsquo;ai un r&amp;eacute;pertoire avec beaucoup d&amp;rsquo;images et je souhaite les afficher dans une Windows Form. Le probl&amp;egrave;me est que j&amp;rsquo;ai un OutOfMemoryException qui se d&amp;eacute;clenche. Comment faire ?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Bien entendu, si l&amp;rsquo;on a beaucoup d&amp;rsquo;images en haute r&amp;eacute;solution, vous aurez beau avoir un PC de la mort qui tue, la m&amp;eacute;moire n&amp;rsquo;est pas extensible &amp;agrave; l&amp;rsquo;infini. Donc charger "simplement&amp;rdquo; les images dans des PictureBox ou dans une DataGrid fera que votre PC chauffera.&lt;/p&gt;
&lt;h2&gt;Le principe&lt;/h2&gt;
&lt;p&gt;Nous allons donc cr&amp;eacute;er un projet Windows Form (je crois que ca fait 6 ans que je n&amp;rsquo;en avais pas fait &lt;img style="border-style: none;" class="wlEmoticon wlEmoticon-winkingsmile" alt="Clignement d'&amp;oelig;il" src="http://www.c2idotnet.com/Media/Default/Windows-Live-Writer/Comment-charger-et-afficher-beaucoup-dim_8CD0/wlEmoticon-winkingsmile_2.png" /&gt;). Il comportera simplement un bouton pour lancer l&amp;rsquo;analyse et un contr&amp;ocirc;le Panel dans lequel nous allons ajouter dynamiquement des contr&amp;ocirc;les PictureBox.&lt;/p&gt;
&lt;p&gt;Quand on clique sur le bouton d&amp;rsquo;analyse, une premi&amp;egrave;re m&amp;eacute;thode fera le m&amp;eacute;nage dans notre panel (supprimera les picturebox existants) :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;private void ClearImageControls()
{
    // on supprime les picturebox existants
    if (pnImages.Controls.Count &amp;gt; 0)
    {
        for (var index = pnImages.Controls.Count - 1; index &amp;gt;= 0; index--)
        {
            var ctrlImage = pnImages.Controls[index] as PictureBox;
            if (ctrlImage != null)
            {
                pnImages.Controls.RemoveAt(index);
                ctrlImage.Image = null;
                ctrlImage.Dispose();
            }
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Et donc nous appellerons cette m&amp;eacute;thode pour vider notre contr&amp;ocirc;le Panel.&lt;/p&gt;
&lt;p&gt;Maintenant, le c&amp;oelig;ur de notre code va &amp;ecirc;tre pour chaque fichier image contenu dans notre r&amp;eacute;pertoire :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Charger l&amp;rsquo;image,&lt;/li&gt;
&lt;li&gt;Cr&amp;eacute;er une vignette de limage,&lt;/li&gt;
&lt;li&gt;D&amp;eacute;charger l&amp;rsquo;image,&lt;/li&gt;
&lt;li&gt;Ajouter un PictureBox contenant la vignette &amp;agrave; notre Panel.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C&amp;rsquo;est donc, dans un premier temps, une approche synchrone, qui prend du temps et qui g&amp;egrave;le l&amp;rsquo;interface (pas cool) :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;private void btnSync_Click(object sender, EventArgs e)
{
    // La madame elle fait le m&amp;eacute;nache
    ClearImageControls();
    // on mesure le  temps d'ex&amp;eacute;cution
    var sw = Stopwatch.StartNew();
    
    var currentColumn = 0;
    var currentRow = 0;
    // pour toutes les images du r&amp;eacute;pertoire
    foreach (var fileName in Directory.GetFiles(FolderName, "*.jpg"))
    {
        // On charge l'image
        Image img = new Bitmap(fileName);
        // On en fait une vignette
        var newImage = img.GetThumbnailImage(PictureWidth, PictureWidth, null, IntPtr.Zero);
        // on d&amp;eacute;charge l'image
        img.Dispose();

        // on cr&amp;eacute;e le PictureBox
        var newPic = new PictureBox
                         {
                             Width = PictureWidth,
                             Height = PictureWidth,
                             Location = new Point
                                            {
                                                X = currentColumn*(PictureWidth + PictureMargin),
                                                Y = currentRow*(PictureWidth + PictureMargin)
                                            },
                             Image = newImage,
                         };

        // On ajoute le PictureBox dans le Panel
        pnImages.Controls.Add(newPic);

        // On g&amp;egrave;re les colonnes, lignes
        currentColumn++;
        if (currentColumn &amp;gt;= ColumnsNumber)
        {
            currentColumn = 0;
            currentRow++;
        }
    }

    // Affichage du temps d'ex&amp;eacute;cution
    lbl.Text = string.Format("{0} ms", sw.ElapsedMilliseconds);
}
&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;R&amp;eacute;sultat sur un r&amp;eacute;pertoire contenant 350 images tr&amp;egrave;s haute r&amp;eacute;solution (1 Go d&amp;rsquo;images jpeg) :&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.c2idotnet.com/Media/Default/Windows-Live-Writer/Comment-charger-et-afficher-beaucoup-dim_8CD0/LoadImages0.jpg"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="LoadImages0" border="0" alt="LoadImages0" src="http://www.c2idotnet.com/Media/Default/Windows-Live-Writer/Comment-charger-et-afficher-beaucoup-dim_8CD0/LoadImages0_thumb.jpg" width="755" height="419" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Une minute, c&amp;rsquo;est pas mal, mais j&amp;rsquo;ai un &amp;ldquo;beau PC&amp;rdquo; &lt;img style="border-style: none;" class="wlEmoticon wlEmoticon-winkingsmile" alt="Clignement d'&amp;oelig;il" src="http://www.c2idotnet.com/Media/Default/Windows-Live-Writer/Comment-charger-et-afficher-beaucoup-dim_8CD0/wlEmoticon-winkingsmile_2.png" /&gt;.&lt;/p&gt;
&lt;h2&gt;L&amp;rsquo;approche asynchrone&lt;/h2&gt;
&lt;p&gt;Bien entendu, le mieux est d&amp;rsquo;avoir une approche asynchrone, ce que nous permet facilement le Framework 4.0. Nous allons donc lancer une t&amp;acirc;che qui va it&amp;eacute;rer sur l&amp;rsquo;ensemble des fichiers et retourner la liste des vignettes :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;// D&amp;eacute;marrage en asynchrone
            Task&amp;lt;IEnumerable&amp;lt;Image&amp;gt;&amp;gt;.Factory.StartNew(
                () =&amp;gt;
{
    // chargement des vignettes dans un ConcurrentBag
    var images = new ConcurrentBag&amp;lt;Image&amp;gt;();
    Parallel.ForEach(Directory.GetFiles(FolderName, "*.jpg"),
                     fileName =&amp;gt;
                         {
                             // On charge l'image
                             Image img = new Bitmap(fileName);
                             // On en fait une vignette
                             var newImage = img.GetThumbnailImage(
                                 PictureWidth,
                                 PictureWidth,
                                 null,
                                 IntPtr.Zero);
                             images.Add(newImage);
                             img.Dispose();
                         });
    return images;
}).ContinueWith(...);
&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Donc maintenant, dans ContinueWith, on a notre collection d&amp;rsquo;images que l&amp;rsquo;on peut ajouter dans le Panel :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;.ContinueWith(
task =&amp;gt;
    {
        // on n'a pas de gestion d'erreur ici, mais ce serait une bonne id&amp;eacute;e quand m&amp;ecirc;me
        var currentColumn = 0;
        var currentRow = 0;
        foreach (var image in task.Result)
        {
            var newPic = new PictureBox
            {
                Width = PictureWidth,
                Height = PictureWidth,
                Location = new Point
                {
                    X = currentColumn * (PictureWidth + PictureMargin),
                    Y = currentRow * (PictureWidth + PictureMargin)
                },
                Image = image,
            };
            pnImages.Controls.Add(newPic);

            // On g&amp;egrave;re les colonnes, lignes
            currentColumn++;
            if (currentColumn &amp;gt;= ColumnsNumber)
            {
                currentColumn = 0;
                currentRow++;
            }
        }
        lbl.Text = string.Format("{0} ms", sw.ElapsedMilliseconds);
    },
TaskScheduler.FromCurrentSynchronizationContext());
&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Notez l&amp;rsquo;utiilisation de TaskScheduler.FromCurrentSynchronizationContext() pour s&amp;rsquo;assurer que le r&amp;eacute;sultat est analyser dans le thread de l&amp;rsquo;UI.&lt;/p&gt;
&lt;p&gt;Moralit&amp;eacute; :&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.c2idotnet.com/Media/Default/Windows-Live-Writer/Comment-charger-et-afficher-beaucoup-dim_8CD0/LoadImages1.jpg"&gt;&lt;img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="LoadImages1" border="0" alt="LoadImages1" src="http://www.c2idotnet.com/Media/Default/Windows-Live-Writer/Comment-charger-et-afficher-beaucoup-dim_8CD0/LoadImages1_thumb.jpg" width="755" height="419" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Le r&amp;eacute;sultat est le m&amp;ecirc;me (bonne nouvelle !) et surtout, le temps d&amp;rsquo;ex&amp;eacute;cution a &amp;eacute;t&amp;eacute; s&amp;eacute;rieusement diminu&amp;eacute; (14 secondes au lieu d&amp;rsquo;une minute) et l&amp;rsquo;interface de l&amp;rsquo;utilisateur n&amp;rsquo;a pas &amp;eacute;t&amp;eacute; fig&amp;eacute;e. On pourrait rajouter l&amp;rsquo;affichage d&amp;rsquo;une image anim&amp;eacute;e pour signifier que l&amp;rsquo;on travaille, ajouter des informations sur le d&amp;eacute;roulement de l&amp;rsquo;analyser, etc. Mais je vais pas vous m&amp;acirc;cher tout le travail quand m&amp;ecirc;me &lt;img style="border-style: none;" class="wlEmoticon wlEmoticon-winkingsmile" alt="Clignement d'&amp;oelig;il" src="http://www.c2idotnet.com/Media/Default/Windows-Live-Writer/Comment-charger-et-afficher-beaucoup-dim_8CD0/wlEmoticon-winkingsmile_2.png" /&gt;.&lt;/p&gt;
&lt;p&gt;Le code complet en mode asynchrone est donc le suivant :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;private void async_btnSync_Click(object sender, EventArgs e)
{
    // La madame elle fait le m&amp;eacute;nache
    ClearImageControls();
    // on mesure le  temps d'ex&amp;eacute;cution
    var sw = Stopwatch.StartNew();

    // D&amp;eacute;marrage en asynchrone
    Task&amp;lt;IEnumerable&amp;lt;Image&amp;gt;&amp;gt;.Factory.StartNew(
        () =&amp;gt;
            {
                // chargement des vignettes dans un ConcurrentBag
                var images = new ConcurrentBag&amp;lt;Image&amp;gt;();
                Parallel.ForEach(Directory.GetFiles(FolderName, "*.jpg"),
                                 fileName =&amp;gt;
                                     {
                                         // On charge l'image
                                         Image img = new Bitmap(fileName);
                                         // On en fait une vignette
                                         var newImage = img.GetThumbnailImage(
                                             PictureWidth,
                                             PictureWidth,
                                             null,
                                             IntPtr.Zero);
                                         images.Add(newImage);
                                         img.Dispose();
                                     });
                return images;
            }).ContinueWith(
                task =&amp;gt;
                    {
                        // on n'a pas de gestion d'erreur ici, mais ce serait une bonne id&amp;eacute;e quand m&amp;ecirc;me
                        var currentColumn = 0;
                        var currentRow = 0;
                        foreach (var image in task.Result)
                        {
                            var newPic = new PictureBox
                            {
                                Width = PictureWidth,
                                Height = PictureWidth,
                                Location = new Point
                                {
                                    X = currentColumn * (PictureWidth + PictureMargin),
                                    Y = currentRow * (PictureWidth + PictureMargin)
                                },
                                Image = image,
                            };
                            pnImages.Controls.Add(newPic);

                            // On g&amp;egrave;re les colonnes, lignes
                            currentColumn++;
                            if (currentColumn &amp;gt;= ColumnsNumber)
                            {
                                currentColumn = 0;
                                currentRow++;
                            }
                        }
                        lbl.Text = string.Format("{0} ms", sw.ElapsedMilliseconds);
                    },
                TaskScheduler.FromCurrentSynchronizationContext());
}
&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Comme quoi, l&amp;rsquo;asynchrone peut servir m&amp;ecirc;me pour les applications &amp;ldquo;old fashion&amp;rdquo;, aka Windows Form.&lt;/p&gt;</description><pubDate>Fri, 06 Jul 2012 08:25:37 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/comment-charger-et-afficher-beaucoup-d-rsquo-images-winforms</guid></item></channel></rss>