1
votes

#Context:

Generating PNGs from PDF pages and showing them in a ListView through an ImageList

#Desired behaviour:

When the user chooses another file to be displayed in the listview -> clear both the ImageList and ListView items (obvious) and also delete the generated image files.

#Issue:

Exception thrown on delete: "The process cannot access the file 'X.png' because it is being used by another process."

previewImageList.Images.Clear();
previewListView.Items.Clear();

// folder containing the generated PNGs, created at runtime
string folderToDelete = Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(selectedSong));

try
{                
  Directory.Delete(folderToDelete, true);  // Image file "X.png" is used by another process
}
catch (Exception)
{}

Solution found:

  • calling the Garbage Collector manually before deleting the said PNG files seems to do the trick.

    previewImageList.Images.Clear(); previewListView.Items.Clear();

    string folderToDelete = Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(selectedSong));

    try { GC.Collect(); GC.WaitForPendingFinalizers();

    Directory.Delete(folderToDelete, true);
    

    } catch (Exception ex) { MessageBox.Show(ex.Message); }

But as i researched so far it isn't a good thing to do. Do I have an alternative to calling the Garbage collector? The reason I need to delete the PNGs is to be able to reuse respective filename with a PDF from another path...

The closest I got to are here and here but didn't help much.

EDIT additional details Generating the PNGs:

  • i use ImageMagik like this:

    startInfo = new ProcessStartInfo(); startInfo.FileName = """ + UtilitiesFolder + "\convert.exe""; startInfo.Arguments = """ + selectedPDFFile.pdf" + "" " + " -resample 168x140 "" + Path.Combine(outputFolder, selectedPDFFileShortName) + ".png" + """; startInfo.UseShellExecute = false; startInfo.CreateNoWindow = true; startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.RedirectStandardError = true; startInfo.RedirectStandardOutput = true;

    process = Process.Start(startInfo); process.WaitForExit(5000); // wait no more than 5 seconds

    if (process.HasExited) { //good } else{ //abort everything }

Loading images into ImageList & ListView:

void previewSelectedPresentation(string presentationName)
{
  previewImageList.Images.Clear();
  previewListView.Items.Clear();
            
  DirectoryInfo dir = new DirectoryInfo(@"E:\\test\\" + presentationName);
            
  foreach (FileInfo file in dir.GetFiles())
  {
    try
    {
      this.previewImageList.Images.Add(Image.FromFile(file.FullName));
    }
    catch
    {
      Console.WriteLine("This is not an image file");
    }
  }

  for (int j = 0; j < this.previewImageList.Images.Count; j++)
  {
    ListViewItem item = new ListViewItem();
    item.ImageIndex = j;
    item.Text = (j + 1).ToString();                
    this.previewListView.Items.Add(item);
  }
}
1
Any particular reason why invoking the gc is bad?Tudor
Show the code where you are loading the .png files. If you're using Bitmap.FromFile then that may be what is locking the file.Chris Dunaway
@Tudor, it is bad; it's a very expensive operation, and the runtime usually knows better than you when the GC should run.Thomas Levesque
@DenisC, how are you generating the images? Are you closing the file after you do? Please show the relevant code.Thomas Levesque
This went wrong a long time ago, you forgot to dispose the images after you added them to the ImageList. That it makes a copy of the image isn't exactly crystal-clear.Hans Passant

1 Answers

1
votes

Problem solved. Per Hans Passant above, (paraphrasing)

This went wrong a long time ago. I forgot to dispose the images after I added them to the ImageList.

So here's the correct way to add into ImageList (please see original question for comparison) - changes signaled by comment:

void previewSelectedPresentation(string presentationName)
{
  previewImageList.Images.Clear();
  previewListView.Items.Clear();

  DirectoryInfo dir = new DirectoryInfo(@"E:\\test\\" + presentationName);

  foreach (FileInfo file in dir.GetFiles())
  {
    try
    {  
       // these three lines have changed
       Image slideImage = Image.FromFile(file.FullName);  // change 1
       this.previewImageList.Images.Add(slideImage);      // change 2
       slideImage.Dispose();                              // change 3
    }
    catch
    {
      Console.WriteLine("This is not an image file");
    }
  }

  for (int j = 0; j < this.previewImageList.Images.Count; j++)
  {
    ListViewItem item = new ListViewItem();
    item.ImageIndex = j;
    item.Text = (j + 1).ToString();                
    this.previewListView.Items.Add(item);
  }          
}

Cleaning up works now without calling the GC. Thanks for helping me figure this out myself! Also, helpful answer here.