Fichier verrouillé par l'application

MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 - Modifié le 16 déc. 2017 à 13:57
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 - 17 déc. 2017 à 10:03
Bonjour,
encore un problème dans lequel je m'englue...

Environnement :
- A gauche, un TreeView dans lequel sont affichées des noms de photos, classés par album. Le Tag du nœud contient une classe où figure le chemin de la photo.
- A droite, un PictureBox dans lequel on affiche la photo quand on clique sur une feuille du TreeView.

Copie d'écran :

Jusque là, tout va bien : je peux ajouter des photos, les afficher, les trier (depuis mon dernier post), enregistrer et restituer le total.
Le problème survient lorsque je veux supprimer une photo. J'obtiens alors une exception System.InvalidOperationException :
"Le processus ne peut pas accéder au fichier 'xxxxx', car il est en cours d'utilisation par un autre processus".

Bien sûr, avant d'utiliser la méthode File.Delete(), j'ai mis la propriété Image du PictureBox à null.
Et j'ai vérifié que le chemin était correct.

Quand la photo est affichée, elle est effectivement bien verrouillée (et c'est normal). On ne peut pas la supprimer avec l'explorateur Windows. Mais quand on en sélectionne une autre, pas moyen non plus.
En fait, toutes les photos qui ont été affichées au moins une fois ne sont plus supprimables. Il faut fermer l'application pour pouvoir effacer les fichiers avec l'explorateur Windows.

Voici le code qui affiche la photo, et celui qui voudrait l'effacer
Note : Les photos existent en deux exemplaires : une grande dans le répertoire "large", et une vignette dans le répertoire "small". La suppression échoue sur la grande photo (celle qui est affichée par défaut).

// Affichage de la photo sélectionnée
private void tvwAlbums_AfterSelect(object sender, TreeViewEventArgs e)
{
    try
    {
        TreeNode Nd = tvwAlbums.SelectedNode;  // Nœud sélectionné
        TreeNode ndRoot = tvwAlbums.Nodes[0];  // Racine du TreeView
        TreeNode ndAlb = Nd.Parent;    // Nœud de l'album contenant la photo
        if (Nd == null) return;

        if (Nd.Tag == null || Nd.Tag.GetType() != typeof(alb_slide_class)) 
            picPhoto.Image = null;   // C'est pas une photo
        else
        {
            string sSmallPath = Path.Combine(ndRoot.Name, ndAlb.Name, Global.gksSmallPath, Nd.Name);
            string sLargePath = Path.Combine(ndRoot.Name, ndAlb.Name, Global.gksLargePath, Nd.Name);
            if (File.Exists(sLargePath))
                picPhoto.Image = Image.FromFile(sLargePath);
            else if (File.Exists(sSmallPath))
                picPhoto.Image = Image.FromFile(sSmallPath);
            else
                picPhoto.Image = null;
        }
    }
    catch (Exception ex)
    {
        ErrorHandling.TraiteErreur(ex);
    }
}
//-----------------------------------------------------------------
// Suppression de la photo sélectionnée
private void mnuPhotoDelete_Click(object sender, EventArgs e)
{
    try
    {
        TreeNode Nd = tvwAlbums.SelectedNode;
        if (Nd == null)
        {
            MessageBox.Show("Veuillez sélectionner la photo à supprimer.");
            return;
        }
        alb_slide_class Slide = Nd.Tag as alb_slide_class;
        if (Slide == null)    // C'est pas une photo (Tag pas du bon type)
        {
            MessageBox.Show("Veuillez sélectionner une photo.");
            return;
        }

        // Demande de confirmation
        string sSlide = Slide.title != "" ? Slide.title : Slide.path; 
        if (MessageBox.Show("Vous allez supprimer la photo \"" + sSlide + "\".\n\nVous confirmez ?", "Suppression photo", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation)
            == DialogResult.Cancel)
            return;

        // La photo est sélectionnée et donc son image est affichée. 
        // Le fichier est donc verrouillé.
        // il faut effacer l'image pour pouvoir supprimer le fichier
        picPhoto.Image = null;
        tvwAlbums.SelectedNode = Nd.Parent;
        tvwAlbums_AfterSelect(null, null);
        Application.DoEvents();
        picPhoto.Refresh();
// Ça ne marche pas !!!

        album_class album = Nd.Parent.Tag as album_class;
        string sSlidePath;
        sSlidePath = Path.Combine(mConf.config.LocalPath, album.common.path, "large", Slide.path);
        File.Delete(sSlidePath);    // Suppression de la grande image
        sSlidePath = Path.Combine(mConf.config.LocalPath, album.common.path, "small", Slide.path);
        File.Delete(sSlidePath);    // Suppression de la vignette
        album.slides.Remove(Slide);
        album.Modified = true;
        tvwAlbums.Nodes.Remove(Nd);
    }
    catch (Exception ex)
    {
        ErrorHandling.TraiteErreur(ex);
    }
}


Quelqu'un saurait me dire comment détacher la main-mise de l'application sur le fichier ?

4 réponses

NHenry Messages postés 15112 Date d'inscription vendredi 14 mars 2003 Statut Modérateur Dernière intervention 13 avril 2024 159
16 déc. 2017 à 15:32
Il ne faut pas seulement mettre à null et attendre que le GC passe, il faut appeler le Dispose() de l'image à supprimer de la mémoire.
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
16 déc. 2017 à 18:28
J'avais déjà essayé, sans succès :
picPhoto.Image.Dispose();
picPhoto.Image = null;

Le fichier est toujours verrouillé et j'ai l'exception sur le File.Delete()
Ce n'est donc pas la solution.

Il faudrait peut-être supprimer carrément le PictureBox, mais cela m'ennuie car il faudrait recréer à chaque fois ses propriétés, qui sont assez différentes de celles par défaut.
D'ailleurs j'ai même essayé
picPhoto = new PictureBox();
et j'ai toujours l'exception Fichier verrouillé.

La documentation de Image.FromFile() dit :
Page française : "Le fichier reste verrouillé jusqu'à ce que le Image est supprimé."
Page anglaise : "The file remains locked until the Image is disposed."
Il me semble qu'après Dispose() et =null, ce devrait être le cas. Il s'avère que non.
0
NHenry Messages postés 15112 Date d'inscription vendredi 14 mars 2003 Statut Modérateur Dernière intervention 13 avril 2024 159
Modifié le 16 déc. 2017 à 19:00
De base, je préfère stocker en local pour supprimer :
MaVar=icPhoto.Image;
picPhoto.Image=null;
MaVar.Dispose();


Il est aussi possible que le PictureBox sauvegarde certaines données (donc in .Dispose pour la PictureBox).
Pour régen les propriétés, tu peux regarder dans le fichier .Designer de ta form pour avoir un code tout prêt de màj des propriétés.
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
Modifié le 16 déc. 2017 à 19:40
Je crois que j'ai trouvé une solution.
Lors de la suppression d'un album entier, j'avais le même problème pour supprimer les répertoires "large" et "small". Après beaucoup d'essais, j'avais fini par forcer le répertoire courant avec SetCurrentDirectory() dans un autre répertoire (celui de l'application, qui existe forcément). Le problème avait disparu.

Je viens de faire la même chose avant la suppression d'une photo, et à priori je n'ai plus de problème.
J'ai du mal à comprendre pourquoi le fait de charger une photo verrouille l'ensemble du répertoire.
Mais je dis souvent que l'informatique n'est pas une science exacte. C'est particulièrement vrai avec Windows...

Quelques minutes plus tard...
Je crois que j'ai chanté victoire un peu vite.
Certaines photos ne posent pas de problème, d'autres créent systématiquement l'erreur, sans que j'arrive à déterminer la différence.
Quand une photo coince, j'arrive à la supprimer parfois en changeant d'album (donc de répertoire) et en affichant une de ses photo, puis en revenant sur la photo qui coince, qui alors accepte d'être supprimée.

Pourtant, dans la routine de suppression, on fait déjà un changement de répertoire, comme dit ci-dessus :

picPhoto.Image.Dispose();
picPhoto.Image = null;
Directory.SetCurrentDirectory(Path.GetDirectoryName(Application.ExecutablePath));

J'y perds mon latin (que je n'ai pas appris)...

Encore quelques minutes plus tard...
J'ai pris le mors aux dents, et j'ai carrément chargé dans le PictureBox une photo hors de tout album avant la suppression du fichier courant.
Après avoir supprimé une cinquantaine de photos dans différents albums, je n'ai plus eu le problème que sur une photo ou deux. Et en changeant d'album puis en revenant sur la photo, ça passe.

Il va falloir que je mettre un grand texte explicatif sur le message d'erreur pour expliquer ce qui se passe et comment faire...
C'est pas très top pour une application qui est censée être de niveau professionnel et diffusée sur le web.
D'autant que les versions précédentes, développées en VB6 depuis près de 10 ans, n'avaient aucun problème.

J'ai l'impression de faire du Microsoft : chaque version est pire que la précédente... (98 => 2000/Millenium, XP => Vista, Win7 => Win8, Win10 => je crains le pire)
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
16 déc. 2017 à 20:12
Suite (et fin j'espère) du feuilleton

J'ai placé le code suivant dans la routine d'affichage de la photo (tvwAlbums_AfterSelect - voir le début du topic) AVANT le chargement de la photo en cours :

if(picPhoto.Image != null)
    picPhoto.Image.Dispose();
picPhoto.Image = null;

Depuis, plus de problème (enfin, jusqu'à présent).
En fait, le problème semblait se produire lorsque je revenais plusieurs fois sur la photo avant de la supprimer. D'où l'idée qu'elle était plusieurs fois en mémoire.
J'en ai déduit que FromFile remplace l'image, mais ne supprime pas la précédente (j'ai pourtant attendu longtemps que le GC fasse son boulot, mais rien à faire).
En déchargeant explicitement l'image avant de charger la suivante, il semblerait que le problème soit résolu.

Mais je vais encore attendre un peu avant de marquer ce sujet comme résolu.
0
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
16 déc. 2017 à 21:04
Bonsoir, plus simplement peut-être,
regarder quelle photo est chargée et ne pas la recharger une nouvelle fois (en stockant le chemin par exemple).
Tu gagnerais en temps d'exécution, en accès disque, etc...
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
17 déc. 2017 à 09:49
Mais justement, je ne veux pas qu'il y ait une photo chargée afin de pouvoir supprimer n'importe laquelle. Il n'y a en permanence qu'une seule photo affichée à la fois, et encore pas toujours : si on clique sur un nœud d'album, il n'y a pas de photo affichée. Le problème que je rencontre n'est pas le chargement, mais le déchargement. Le temps de chargement est dérisoire par rapport à l'action de l'utilisateur, et le but de l'application n'est pas de présenter un diaporama mais de le préparer pour un envoi sur Internet.
Je pense que la gestion de la mémoire des photos chargées est beaucoup plus complexe que décharger la dernière à chaque fois, et de toute façon ne résout pas le problème du déchargement. Si je n'ai plus d'erreur de verrouillage, je vais rester comme ça.
0
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
17 déc. 2017 à 10:03
ok
0
Rejoignez-nous