3 votes

Supprimer une image liée à un contrôle

J'écris une application WPF de gestion d'images. J'ai une ListBox avec le ItemsTemplate suivant :

        <Grid x:Name="grid" Width="150" Height="150" Background="{x:Null}">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="27.45"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="150"/>
            </Grid.ColumnDefinitions>
            <Border Margin="5,5,5,5.745" Grid.RowSpan="2" Background="#FF828282" BorderBrush="{DynamicResource ListBorder}" CornerRadius="5,5,5,5" BorderThickness="1,1,2,2" x:Name="border">
                <Grid>
                    <Viewbox Margin="0,0,0,21.705">
                        <Image Width="Auto" Height="Auto" x:Name="picture" Source="{Binding Path=FullName}" />
                    </Viewbox>
                    <TextBlock Height="Auto" Text="{Binding Path=Name}" TextWrapping="Wrap" x:Name="PictureText" HorizontalAlignment="Left" Margin="70,0,0,0" VerticalAlignment="Bottom" />
                </Grid>
            </Border>
        </Grid>

Notez que le contrôle "Image" est lié à la propriété "FullName", qui est une chaîne représentant le chemin d'accès absolu à un fichier JPG.

Plusieurs fonctions de l'application exigent que je modifie le fichier JPG (en le déplaçant, en le renommant ou en le supprimant). Lorsque j'essaie de le faire (j'essaie actuellement de déplacer le fichier), je reçois une exception IOException : "Le processus ne peut pas accéder au fichier car il est utilisé par un autre processus". Le processus qui verrouille le fichier est mon application WPF.

J'ai fait quelques recherches en ligne et j'ai trouvé plusieurs messages indiquant que les images en particulier ont du mal à se débarrasser de leurs ressources. J'ai essayé ce qui suit :

  1. Définition de ListBox.Source à null
  2. Ajout d'un temps d'attente de 10 secondes avant de tenter le déplacement.
  3. Émission de GC.Collect().
  4. Déplacement de l'opération vers un autre thread.

Que puis-je essayer d'autre ? J'ai pensé à trouver une référence à l'objet Image dans le modèle ItemsTemplate et à essayer de disposer de l'image, mais je n'arrive pas à trouver comment obtenir la référence.

Une solution possible que j'ai lue consistait à créer des copies des images plutôt que les images elles-mêmes, mais comme la liaison est faite avec le nom du fichier et non avec l'image elle-même, je ne sais pas si je pourrais faire fonctionner cette solution.

Toute aide ou suggestion serait très appréciée.

7voto

Kent Boogaart Points 97432

Mon Intuipic permet également aux utilisateurs de supprimer des images. J'ai dû écrire ce convertisseur pour y parvenir. Code pertinent :

//create new stream and create bitmap frame
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = new FileStream(path, FileMode.Open, FileAccess.Read);
bitmapImage.DecodePixelWidth = (int) _decodePixelWidth;
bitmapImage.DecodePixelHeight = (int) _decodePixelHeight;
//load the image now so we can immediately dispose of the stream
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();

//clean up the stream to avoid file access exceptions when attempting to delete images
bitmapImage.StreamSource.Dispose();

6voto

Joel Cochran Points 2643

J'ai marqué la réponse de Kent comme une réponse, et j'aurais également marqué celle de Bendewey, car je les ai toutes deux utilisées dans la solution finale.

Le fichier était définitivement verrouillé car le nom du fichier était le seul à être lié, de sorte que le contrôle Image a ouvert le fichier réel pour produire l'image.

Pour résoudre ce problème, j'ai créé un convertisseur de valeur comme l'a suggéré Bendewey, puis j'ai utilisé (en grande partie) le code de la suggestion de Kent pour renvoyer une nouvelle BitmapImage :

    [ValueConversion(typeof(string), typeof(BitmapImage))]
public class PathToBitmapImage : IValueConverter
{
    public static BitmapImage ConvertToImage(string path)
    {
        if (!File.Exists(path))
            return null;

        BitmapImage bitmapImage = null;
        try
        {
            bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = new FileStream(path, FileMode.Open, FileAccess.Read);
            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
            bitmapImage.EndInit();
            bitmapImage.StreamSource.Dispose();
        }
        catch (IOException ioex)
        {
        }
        return bitmapImage;
    }

    #region IValueConverter Members

    public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null || !(value is string))
            return null;

        var path = value as string;

        return ConvertToImage(path);
    }

    public virtual object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

Comme le suggèrent les commentaires ci-dessus, cela n'a toutefois pas résolu le problème. J'ai travaillé sur d'autres projets et je suis revenu récemment sur celui-ci, revigoré par la recherche de la solution.

J'ai créé un autre projet qui testait uniquement ce code, et bien sûr, il a fonctionné. Cela m'a indiqué qu'il y avait d'autres problèmes dans le programme original.

Pour faire court, l'image était générée à trois endroits, ce que je pensais avoir réglé :

1) La liste d'images, maintenant liée à l'aide du convertisseur. 2) L'image principale, liée à la propriété SelectedItem de la liste d'images. 3) La fenêtre contextuelle DeleteImage, liée à l'aide du convertisseur.

Il s'est avéré que le problème se situait au niveau du numéro 2. En me liant à l'élément sélectionné, j'ai supposé à tort que je me liais à l'image nouvellement rendue (sur la base du convertisseur). En réalité, l'objet SelectedItem était en fait le nom du fichier. Cela signifie que l'image principale était à nouveau construite en accédant directement au fichier.

La solution a donc consisté à lier le contrôle d'image principal à la propriété SelectedItem ET à utiliser le convertisseur.

2voto

bendewey Points 25437

Consultez ce billet ici.

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/dee7cb68-aca3-402b-b159-2de933f933f1/

Échantillon

En fait, vous devrez précharger l'image à l'aide d'un flux. Je créerais un PreLoadImageConverter, quelque chose comme ceci, je ne l'ai pas testé.

<Grid>
  <Grid.Resources>
    <local:PreLoadImageConverter x:Key="imageLoadingConverter" />
  </Grid.Resources>
  <Image Width="Auto" Height="Auto" x:Name="picture" Source="{Binding Path=FullName, Converter={StaticResource imageLoadingConverter}}" />
</Grid>

PreLoadImageConverter.cs

public class PreLoadImageConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    if (value == null) return null;
    string imagePath = value.ToString();

    ImageSource imageSource;
    using (var stream = new MemoryStream())
    {
      Bitmap bitmap = new Bitmap(imagePath);
      bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);   
      PngBitmapDecoder bitmapDecoder = new PngBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
      imageSource = bitmapDecoder.Frames[0];
      imageSource.Freeze();
    }
    return imageSource;
  }

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    throw new Exception("The method or operation is not implemented.");
  }
}

0voto

Travis Points 370

C'est très ancien, mais le cadre a changé et la résolution de ce problème est beaucoup plus facile, du moins dans .NET Core.

D'après ce que j'ai compris, BitmapImage.UriSource n'était pas bindable auparavant. C'est désormais le cas. Il suffit de spécifier explicitement la source de l'image dans le xaml. Liez votre UriSource et définissez le mode de cache à OnLoad. C'est fait. Aucun convertisseur n'est nécessaire.

<Image Grid.Row="1">
    <Image.Source>
        <!-- specify the source explicitly and set CacheOption to OnLoad to avoid file locking-->
        <BitmapImage UriSource="{Binding ImagePath}" CacheOption="OnLoad" />
    </Image.Source>
</Image>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X