Deleting a bitmap from local directory - c#

Alright it has come to this. I searched this website among many others and no one can seem to give me a straight answer so I'm going to try just asking outright. Been on this issue for about a solid 3 days and I can't afford to waste any more time on it.
Goal: The app I am building is in WPF and is going to be used as a bug tracker for a project my design team and I will be undertaking soon. Since we are going to be building a game in C++ most of the errors that occur will have a visual element to them so I inlcuded functionality to provide an image of the error in question when the user adds a bug to the list. I then take that image and save it to a local directory (for testing). Now the image path in the Error object points to a path that leads to the local directory. This functionality has been tested and works fine. My problem showes up when I want to delete a bug from the list. I am getting that very infamous "IO Exception" saying that the image I want to delete is being used by another process.
So Far: At first I tried very elegant solutions, but as with all things you get to a point where you just want to see if you can get the thing to even work at all. So I am at the point where most of the code I am using is experimental and radical. So please when looking at it note that the code being used is out of desperation, so any "simple" solutions have probably already been tried (I did research this a lot becuase I hate having to do this). Things i can think of off the top of my head are the obsurd amount of disposes and forced garbage collections being called so please to not comment on the negative nature of this practice, I am well aware :).
The Code
Saving image to local directory
public void OnBrowseClick()
{
Microsoft.Win32.OpenFileDialog openBox = new Microsoft.Win32.OpenFileDialog();
// Show dialog box to user and grab output
Nullable<bool> result = openBox.ShowDialog();
if (result == true)
{
// Create temp variable to hold local path string
string localPath = Directory.GetCurrentDirectory();
// Grab the extension of the specified file path
string extension = openBox.FileName.Substring(openBox.FileName.LastIndexOf("\\"));
// Add extension to local path
localPath += extension;
// Create local copy of image at given file path (being ridiculous at this point)
using (Stream stream = new FileStream(openBox.FileName, FileMode.Open, FileAccess.ReadWrite))
{
using (Bitmap bmp = LoadImage(stream))
{
using (Bitmap temp = (Bitmap)bmp.Clone())
{
temp.Save(localPath);
temp.Dispose();
}
bmp.Dispose();
}
stream.Dispose();
}
// Set the URL in the image text box (UI stuff)
LocalError.ImagePath = localPath;
}
}
The following is the LoadImage function that is used in the function above
private Bitmap LoadImage(Stream stream)
{
Bitmap retval = null;
using (Bitmap bitmap = new Bitmap(stream))
{
retval = new Bitmap(bitmap.Width, bitmap.Height, bitmap.PixelFormat);
using (Graphics gdi = Graphics.FromImage(retval))
{
gdi.DrawImageUnscaled(bitmap, 0, 0);
gdi.Flush();
gdi.Dispose();
bitmap.Dispose();
}
}
// Garbage collection here to be safe
GC.WaitForPendingFinalizers();
GC.Collect();
return retval;
}
And finally we come to where I try to delete the image
public void OnDeleteClick()
{
// Ask user to make sure they want to delete selected item(s)
MessageBoxResult result = MessageBox.Show("Are you sure you want to delete selected item(s) from the list?",
"Delete", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
for( int i = 0; i < Parent.ErrorListControl.ErrorDataGrid.SelectedItems.Count; ++i)
{
// Get path to image
string path = (Parent.ErrorListControl.ErrorDataGrid.SelectedItems[i] as Error).ImagePath;
// Even tried calling garbage collection here!!!!!
System.GC.WaitForPendingFinalizers();
System.GC.Collect();
File.Delete(path);
// Remove the error from the error list
Parent.ErrorListVM.ErrorList.Remove((Error)Parent.ErrorListControl.ErrorDataGrid.SelectedItems[i]);
// Decrement counter because we altered the list while in a loop
i--;
}
}
}
Notes: If anyone would like me to explain anything further or if you need to know something I left out please just ask I will get back to you ASAP! Any suggestions are helpful at this point I have absolutley no idea what I am doing wrong. I generally only program in a C++ environment so I tend to manage my own memory this whole "garbage collection" thing is really throwing a wrench in our project! (Off topic note: I do not know why I am not getting any color highlighting so I apologize to anyone who takes the time to read this).

Here's a simple way to do what you want. In this example, I'm using Path.GetTempFileName() to generate a random file name in the local user's temp directory. If you don't need to persist the files then it's a good place to store them temporarily. Also, the user could theoretically import two files with the same name. So you want to use some kind of random filename generation or other mechanism to avoid conflicts.
private void browseButton_Click(object sender, RoutedEventArgs e)
{
var openFileDialog = new Microsoft.Win32.OpenFileDialog();
if (openFileDialog.ShowDialog(this) == true)
{
using (Bitmap originalImage = new Bitmap(openFileDialog.FileName))
{
string tempFileName = System.IO.Path.GetTempFileName();
originalImage.Save(tempFileName);
// LocalError.LocalPath
LocalPath = tempFileName;
}
}
}
private void deleteButton_Click(object sender, RoutedEventArgs e)
{
if (File.Exists(LocalPath))
{
File.Delete(LocalPath);
}
}
Although a simple File.Copy should suffice as long as you have the right paths, I was just providing a solution that matched your question.
EDIT:
Actually the current directory does not seem to be changed by the OpenFileDialog. I could swear that it did at some point. So I don't think this is your problem. Regardless, this code still works for me and you shouldn't require anything more complicated than this.
EDIT #2:
It seems the lock is actually caused by the image being databound to the view and presumably locked by the BitmapSource. You should be able to create it without locking the file. Generally, this is slower so don't do it this way unless you need to be able to modify or delete the file.
bitmapSource = new BitmapImage();
bitmapSource.BeginInit();
bitmapSource.CacheOption = BitmapCacheOption.OnLoad;
bitmapSource.CreateOption = BitmapCreateOptions.IgnoreImageCache;
bitmapSource.UriSource = new Uri(ImagePath, UriKind.Absolute);
bitmapSource.EndInit();

Since your LoadImage method does simple copy of the image, why not use File.Copy(source, dest) and avoid all the bitmaps, drawings, etc? Your goal might be to modify local bitmap after it's created, but it can still be done after copy.
Also, when using the using block, explicit .Dispose() is not required, as using block does it for you:
using (var obj = new SomeDisposableObject())
{
// code here
// obj.Dispose(); <-- not needed, since...
} // ...at this point obj.Dispose is called automatically.

Related

File remains in use until client refreshes page

I am attempting to delete image files from the server file system but I get a 'file in use by another process' error. I am trying to delete the image and it's associated thumbnail in one fowl swoop. I can get one to delete but the other will try and fail forever, as far as I've seen. However, if I refresh the page while it's looping through a try-catch, the operation succeeds immediately. I am accessing the files via ajax requests and API controllers (my site is mostly one page) when I load the page, but I am doing so with 'using' statements. This should close the handle after use, but it seems to be staying open. I have to get the files on the client side to be able to have the option to delete them(browser content manager), so I don't see how they are still in use. I load the thumbnail and use it's ID to get it's name from the database, and use the name to find it in the file system.
I have tried putting a try-catch in a loop and waiting for the last process to finish, but it never does, until I refresh the page.
I also tried deleting one at a time with separate ajax requests, but it seems that if I have opened a file at all, I can't delete it (until I refresh the page).
Code for deleting file (method 1):
public static void DeleteFromFileSystem(string path)
{
for (int i = 1; i <= 10000; ++i)
{
try
{
System.IO.File.Delete(path);
break;
}
catch (IOException e) when (i <= 10000)
{
Thread.Sleep(3);
}
}
}
Code for deleting file (method 2):
public static void DeleteFromFileSystem(string path)
{
using(FileStream f = System.IO.File.OpenWrite(path))
{
System.IO.File.Delete(path);
}
}
Code for getting image (works fine, but could be the problem):
public static byte[] ImageToByte(string name, bool getThumbnail)
{
if (name == null) name = "user.png"; //default profile picture
string path = getThumbnail ? "wwwroot/ImgThumb/" : "wwwroot/ImgFull/";
// this line is my best suspect
System.Drawing.Image img = System.Drawing.Image.FromFile(path + name);
byte[] b;
using (MemoryStream mStream = new MemoryStream())
{
img.Save(mStream, img.RawFormat);
b = mStream.ToArray();
mStream.Close(); // I added this as a precautionary. It didn't help.
}
return b;
}
I expect that the images get deleted, but I get the 'file X cannot be accessed because it is in use by another process' error.
SOLVED - I added img.Dispose() after the using statement.

How to write to a stream then to file

As part of another project, I found this article explaining how to bring up a SaveFileDialog.
But in the center of the code there is a comment that simply reads
//Code to write the stream goes here.
and seeing as I don't know how to do this either I am at a bit of a loss.
In the end my code will be compiling a list of user selections, each separated by a newline character, and then saving that list to a .json with a name and a location specified by the user. The user will have the option to either create a new .json or overwrite an old one.
I'm not including any code snippets since right now, without the knowledge of how to properly write to a stream, there's really nothing to show that is relevant. If you would like more details though just ask. I'll do my best to flesh out my issue.
This should do the job for you:
private void SaveString(string data)
{
var byteData = Encoding.UTF8.GetBytes(data);
var saveFileDialog = new SaveFileDialog
{
DefaultExt = "json",
AddExtension = true,
Filter = "JSON|*.json"
};
if (saveFileDialog.ShowDialog() != DialogResult.OK ||
string.IsNullOrEmpty(saveFileDialog.FileName)) return;
using (var saveFileDialogStream = saveFileDialog.OpenFile())
{
saveFileDialogStream.Write(byteData, 0, byteData.Length);
}
}

C# Disposing and Ressurrecting/Loading Bitmaps using a Dictionary

i have written a programm that uses some picture ressources and creates a file from them.
As a Customer preview the images are shown before they get worked with. Therefore i need to dispose them before i can work with them. But i want to load the preview again afterwards and since it might be a huge number of pictures I want to do it dynamically, there i fail.
I use a Dictionary with the filename as String:
//Creating a Bitmap so we can preview the Picture.
try
{
mainDrawingBackgroundBitmap = new Bitmap(filePathBackgroundBackgroundImage);
if(mainAllBitmapsDictionary.ContainsKey(mainDrawingBackgroundBitmap))
{
mainAllBitmapsDictionary.Remove(mainDrawingBackgroundBitmap);
}
mainAllBitmapsDictionary.Add(mainDrawingBackgroundBitmap,filePathBackgroundBackgroundImage);
}
Now my Bitmap and path are in the Dictionary, later i dispose them this way:
private void DisposeAllFiles()
{
foreach (var tempBitmap in mainAllBitmapsDictionary.Keys)
{
tempBitmap.Dispose();
}
}
Which works just fine.
Now when i try to recreate the Bitmaps:
private void RessurrectAllFiles()
{
foreach (var tempAllBitmap in mainAllBitmapsDictionary)
{
try
{
var tempBitmap = tempAllBitmap.Key;
tempBitmap = new Bitmap(tempAllBitmap.Value);
}
catch (ArgumentException ae)
{
}
}
}
He doesn't fail or throw an error, the Dictionary even gets filled with Correct Bitmaps and strings but, those don't seem to affect the original objects anymore, so the Dictionary is as it was but when i inspect a Bitmap like the: mainDrawingBackgroundBitmap, i only see ArgumentExceptions.
To put it bluntly, where do i fail?
Keep path to images as keys, and bitmap data as values, so then it will be easy to you to manipulate data and searching dictionary will perform fast.
Dictionary<string, Bitmap>

Could this use of System.Drawing.Image.FromStream be pointing the same memory?

I'm having to fix a bug in some very old code that converts a Base64 string to a image using Memory Stream. Basically it loops through a list of images stored as base64 strings and converts them to images then draws them using ActiveReports.
The bug is that once it loads one image all following images will be a copy of the first image.
I found the code that is doing the conversion of string to image and immediately noticed it isn't disposing of the memory stream. If I wrap the memory stream in a using block I get a GDI exception. I'm guessing this is because the image isn't really read from memory yet or something, but I'd like to hear if anyone has a guess. Thanks in advance!
byte[] oGraphic = null;
try
{
oGraphic = Convert.FromBase64String(psGraphic);
DataDynamics.ActiveReports.Picture oImg = new Picture();
oImg.Top = this.Legend.Top + this.fTopFirst;
oImg.Visible = true;
oImg.Name = sLabelName;
oImg.PictureAlignment = PictureAlignment.Center;
oImg.Image = null;
if (oGraphic != null)
{
var oStream = new MemoryStream(oGraphic);
oImg.Image = System.Drawing.Image.FromStream(oStream);
oImg.Height = Convert.ToSingle(oImg.Image.Height)/(oImg.Image.VerticalResolution);
oImg.Width = Convert.ToSingle(oImg.Image.Width)/(oImg.Image.HorizontalResolution);
oImg.SizeMode = SizeModes.Zoom;
this.fGraphicHeight = oImg.Height;
this.fGraphicWidth = oImg.Width;
if (this.fConstantGraphic > this.fGraphicWidth)
oImg.Left = this.Legend.Left + this.fLeftFirst +
((this.fConstantGraphic - this.fGraphicWidth)/2);
else
oImg.Left = this.Legend.Left + this.fLeftFirst;
}
else
{
this.fGraphicHeight = 0f;
this.fGraphicWidth = 0f;
}
this.GHMap.Controls.Add(oImg);
}
catch (Exception oE)
{
.....
}
The only thing I can imagine is if there is a line of code missing from what you have here:
if (oGraphic == null) // missing line
oGraphic = Convert.FromBase64String(psGraphic);
There is no reason for this byte[] to be declared outside of the try { } block. That array gets wrapped into a MemoryStream which is then wrapped into an Image. That image is attached to a brand new Picture which is added to a Picture collection.
What is it we don't see?
Here's another guess (I'll leave the first guess alone for posterity):
I'm not familiar with Active Reports, but it looks like you're setting the Top and PictureAlignment properties of the Picture object to the same value and adding more than one Picture. Is it possible they are all there, but one on top of each other? So the result is a single picture?
Guess #3 (one of these is going to get a checkmark, I just know it!)
Everything looks OK in the code provided, therefore the problem is somewhere else (though it's still entirely possible I'm wrong about it being OK).
Are you certain that psGraphic is different each time this code is executed?
The cause of the problem is that the Picture control is a single control instance on a single section. So you're just overwriting the image on this single control over and over.
If the only thing you want to see in this report is the images, then the best thing to do is use ActiveReports' Unbound mode and treat each image as another "record". See this walkthrough for an example of using unbound mode (see the DataInitialize and FetchData events for the meat of the matter).
Using unbound mode, ActiveReports will render the image one after the other in sections treating each image like a new record. The code would be something like the following (sorry I don't have ActiveReports handy at the moment so I can't check this code, but this should be pretty close. Let me know if you run into any problems and I'll clean it up in the morning):
In ActiveReports' ReportStart Event:
DataDynamics.ActiveReports.Picture oImg = new Picture();
oImg.Top = 0;
oImg.Visible = true;
oImg.Name = sLabelName;
oImg.PictureAlignment = PictureAlignment.Center;
// setting DataField "binds" the Picture control to get it's data from the MyImageField field which we'll initialize and bind in the events below
oImg.DataField = "MyImageField";
this.Sections["Detail"].Controls.Add(oImg);
In ActiveReports' DataInitialize Event:
this.Fields.Add("MyImageField");
In ActiveReports FetchData Event:
var imageBytes = Convert.FromBase64String(_imageStrings.Current); // I'm not sure where the base64 image strings come from, some I'm assuming you can put them in an enumerator field in the report like "_imageStrings"
var imageStream = new MemoryStream(imageBytes);
var image = Image.FromStream(imageStream);
Fields["MyImageField"].Value = image;
// This tells ActiveReports if there are more records, and if it should raise the FetchData event again (allowing you to add another image).
eArgs.EOF = !_imageStrings.MoveNext();
If you need to resize the image control for each image, use the section's Format event for that. You could use something like the following:
In Detail_Format Event:
var pictureControl = this.Sections["Detail"].Controls["MyImageControl"] as DataDynamics.ActiveReports.Picture;
pictureControl.Width = Convert.ToSingle(pictureControl.Image.Width)/(pictureControl.Image.VerticalResolution);
pictureControl.Width = Convert.ToSingle(pictureControl.Image.Width)/(pictureControl.Image.HorizontalResolution);
Finally, ActiveReports will also just automatically bind to a set of POCO objects in an IEnumerable (or IList, I forget). So you could simply have a "MyImage" class with a property like "MyImage" and ActiveReports will read it and bind with it (you wouldn't have to write any code in DataInitialize and FetchData). I think you might also just be able to put the MemoryStream in there as the binding too and ActiveReports will read it, but I'm not positive on that.
BTW: The reason that GDI exception occurs when disposing the MemoryStream is because GDI attempts to just seek within that single MemoryStream for the image data rather than making a copy of it. So you'll need to supply each System.Drawing.Image instance with a new stream (don't worry MemoryStream will clean itself up when everything is released).
It turned out to be a problem with the way we were creating the image. Adding the code below fixed the problem.
oImg.Image = System.Drawing.Image.FromStream(oStream);
TO THIS
oImg.Image = ImageFromBase64String(psGraphic);
private Image ImageFromBase64String(string sBase64String)
{
using (var sStream = new MemoryStream(Convert.FromBase64String(sBase64String)))
using (var iSourceImage = Image.FromStream(sStream))
{
return new Bitmap(iSourceImage);
}
}

save file without using save file dialog

I am working on this project still and I am running into a problem. Well here is what I need to do.
When the user clicks the “Save” button, write the selected record to
the file specified in txtFilePath (absolute path not relative) without
truncating the values currently inside and handle any exceptions that arise.
Ok here is my code:
private void Save_Click(object sender, EventArgs e)
{
string filePath = txtFilePath.Text;
if (!File.Exists(filePath))
{
FileStream fs = File.Create(filePath);
fs.Close();
}
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(fs))
{
foreach (string line in employeeList.Items)
{
sw.WriteLine(line);
}
}
}
}
Now when I go onto my program and want to save something from the employeelist.text that its not being saved to the place I am saving it at. I don;t know if I am missing something in my code or what but it will not save. Here is an example:
I add a person name to this list in employeelist and in the textbox I
have a file called C:\employess\employeelist.txt I want to save it to.
I click the save button then I go to that employeelist and it is not
being saved.
I don't know what I am doing wrong I have been looking online for a solution but I haven't found anything yet. Thanks
Some things to double-check:
Make sure you don't have the employeelist.txt file open when you're testing
Make sure you don't have invalid characters in your file name
Make sure your application has permission to save the file to the location you specified
Use the debugger to step-through your code and look for swallowed exceptions -- there must be a reason the file is not created.
Check that your Save_Click event is wired up to your button -- is the code in your example even running?
Once you check those things, you may want to follow this example for the create vs. append requirement of your project:
string path = txtFilePath.Text;
// This text is added only once to the file.
if (!File.Exists(path))
{
using (StreamWriter sw = File.CreateText(path))
{
foreach (var line in employeeList.Items)
sw.WriteLine(line.ToString());
}
}
else
{
using (StreamWriter sw = File.AppendText(path))
{
foreach (var line in employeeList.Items)
sw.WriteLine(line.ToString());
}
}
This will create the file if it doesn't exist, or append to it if it does.
Checking that the file exists and then creating it is a bit unnecessary as this can all be handled by the StreamWriter/FileStream parts. So your above function can be simplified into the following:
public void Save_Click(object sender, EventArgs e)
{
StreamWriter file =
new StreamWriter(txtFilePath.Text, true);//Open and append
foreach (object item in employeeList.Items) {
file.WriteLine(item.toString());
}
file.Close();
}
[Updated]
What are the types of txtFilePath and employeeList the former suggests it's a text box, the later suggests it's bound to a non-GUI element perhaps? (WAG)
You might also want to append a blank line at the end so that on further saves you can tell it was an append rather than one long list (depending on your needs of course)
Starting with .Net Framework 4 you can do it like this:
private void Save_Click(object sender, EventArgs e)
{
File.AppendAllLines(txtFilePath.Text, employeeList.Items);
}
Of course, you probably would want to add a check to have a valid path and a valid enumeration of strings.
If the path looks like a relative one (i.e. doesn't begin with a drive letter), then it will be interpreted that way.
If you put a full path in the text box, does the file get saved in the proper place? If so, perhaps this is what was intended.
If the user doesn't put in a full path, do you have a way to make it one (for example, just sticking C:\ at the beginning)? Or at least can you tell when there isn't a full path, and reject the request?

Categories