web-dev-qa-db-fra.com

Bonne façon de disposer Image / Bitmap et PictureBox

J'essaie de développer une application Windows Mobile 6 (en WF/C #). Il n'y a qu'un seul formulaire et sur le formulaire, il n'y a qu'un objet PictureBox. J'y dessine tous les contrôles souhaités ou ce que je veux.

Il y a deux choses que je fais. Dessiner des formes personnalisées et charger des bitmaps à partir de fichiers .png.

La ligne suivante verrouille le fichier lors du chargement (ce qui est un scénario indésirable):

Bitmap bmp = new Bitmap("file.png");

J'utilise donc une autre façon de charger le bitmap.

public static Bitmap LoadBitmap(string path) {
    using (Bitmap original = new Bitmap(path))
    {
        return new Bitmap(original);
    }
}

Je suppose que c'est beaucoup plus lent, mais je ne connais pas de meilleure façon de charger une image, tout en libérant rapidement le verrou de fichier.

Maintenant, lors du dessin d'une image, il existe une méthode que j'utilise:

public void Draw() {
    Bitmap bmp = new Bitmap(240,320);
    Graphics g = Graphics.FromImage(bmp);

    // draw something with Graphics here.
    g.Clear(Color.Black);
    g.DrawImage(Images.CloseIcon, 16, 48);
    g.DrawImage(Images.RefreshIcon, 46, 48);
    g.FillRectangle(new SolidBrush(Color.Black), 0, 100, 240, 103);

    pictureBox.Image = bmp; 
}

Cela semble cependant être une sorte de fuite de mémoire. Et si je continue à le faire trop longtemps, l'application se bloque finalement.

Par conséquent, j'ai 3 questions:

1.) Quelle est la meilleure façon de charger des bitmaps à partir de fichiers sans verrouiller le fichier?

2.) Quels objets doivent être éliminés manuellement dans la fonction Draw () (et dans quel ordre) pour qu'il n'y ait pas de fuite de mémoire et pas de lancement d'ObjectDisposedException?

3.) Si pictureBox.Image est défini sur bmp, comme dans la dernière ligne du code, pictureBox.Image.Dispose () ne disposerait que des ressources liés à la maintenance de la PictureBox.Image ou du bitmap sous-jacent qui lui est associé?

23
Kornelije Petak

1: Je ne sais pas si cela fonctionne dans Windows Mobile, mais essayez ceci:

FileStream bitmapFile = new FileStream("mybitmap.bmp", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
Image loaded = new Bitmap(bitmapFile);

2: le SolidBrush doit être supprimé. Il existe une règle générale de disposition. -> "chaque objet, instancié par vous, qui implémente dispose doit être supprimé manuellement, sauf lorsque l'objet est une valeur de retour/ref/out"

Dans ce cas, il est préférable d'utiliser une instruction using

using (new objecttodispose){ ..... } 

L'instruction using assurera dans tous les cas l'appel de Dispose() (exception par exemple).

3: Dispose() libérera les ressources bitmap.

7
Jack

Je ne pense pas qu'il y ait une véritable fuite de mémoire . Le problème est que vous ne disposez pas de l'ancien bitmap, c'est au GC de nettoyer le truc. Mais il n'y a aucun moyen déterministe de dire quand cela se produira.

Donc, je pense que si vous allez parcourir beaucoup d'images, vous verrez une certaine augmentation de la mémoire et à un autre moment, elle tombera ou résistera à une position.

Je ne l'ai pas testé, mais peut-être que cela aidera un peu à le rendre plus déterministe:

public void Draw() {
    Bitmap bmp = new Bitmap(240,320);
    using(var g = Graphics.FromImage(bmp))
    using(var solidBrush = SolidBrush(Color.Black))
    {
        // draw something with Graphics here.
        g.Clear(Color.Black);
        g.DrawImage(Images.CloseIcon, 16, 48);
        g.DrawImage(Images.RefreshIcon, 46, 48);
        g.FillRectangle(solidBrush, 0, 100, 240, 103);

        //Backup old image in pictureBox
        var oldImage = pictureBox.Image;
        pictureBox.Image = bmp; 
        //Release resources from old image
        if(oldImage != null)
            ((IDisposable)oldImage).Dispose();            
    }
}

Mettre à jour

Et une autre idée inspirée de jack30lena:

public static Bitmap LoadBitmap(string path)
{
    //Open file in read only mode
    using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
    //Get a binary reader for the file stream
    using (BinaryReader reader = new BinaryReader(stream))
    {
        //copy the content of the file into a memory stream
        var memoryStream = new MemoryStream(reader.ReadBytes((int)stream.Length));
        //make a new Bitmap object the owner of the MemoryStream
        return new Bitmap(memoryStream);
    }
}

L'idée derrière mon deuxième exemple de code est de se débarrasser du descripteur de fichier et de copier le contenu du fichier dans la mémoire. Ensuite, le Bitmap deviendra propriétaire du MemoryStream qui sera supprimé dans mon premier échantillon en appelant la oldImage.Dispose().

En utilisant cette approche, il ne devrait jamais y avoir plus de deux images en mémoire, des milliers ne conduisant qu'à des OutOfMemoryExceptions par de très grandes images ou une petite quantité de RAM.

13
Oliver