Image.Source (with binding) is blocking image file after binding is removed - c#

In a GridView, I've templated the items so an Image control inside that template receives an Uri using a binding (also tried with BitmapImage) to show a picture file. It works, but I cannot delete those files because those files are blocked. Even if I clear the collection feeding the GridView.ItemsSource, and even if I call GC.Collect(), they are still blocked.
Thanks to Philip for his working suggestion... as I used a Converter to feed a BitmapImage to every Image control, found some particular things had to do to make it work, and here it is for future reference:
InMemoryRandomAccessStream Ras = new InMemoryRandomAccessStream();
var archivo = CartoonsDownloader.FolderImagenes.GetFileAsync(TheFileName);
var fileStream = CartoonsDownloader.FolderImagenes.OpenStreamForReadAsync(TheFileName);
fileStream.Result.CopyTo(Ras.AsStreamForWrite());
BitmapImage MapaDeBits = new BitmapImage();
// Even it's RANDOM, I have to manually "Seek" it at 0.
Ras.Seek(0);
MapaDeBits.SetSource(Ras);
MapaDeBits.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
return MapaDeBits;

This could be a bug in the XAML stack (so you could report it on MSDN forums), or just a side effect of its nature of caching images. As an alternative - you can open the file yourself and set the image using the SetSource method. Then you should have better control over the file access.

Related

How can I change an image/bitmap that is already in use?

I have a directory with .png images which I display in a third party combobox of my c# program. So the user is able to choose one of this images using the combo box. Basic code used:
Bitmap thump = new Bitmap(<path>);
ComboItem item = new ComboItem();
item.Image = thump;
MyComboBox.Items.Add(item);
Now I would like to update one of this images at runtime. Unfortunately I can't delete the old image because it is still opened in my program, so somehow I either need to close it or open it in a way that does not keep the image in use by my program. The changes to the bitmap are not done in my program, I just pass the path of the dirctory to another program which saves the bitmap there (but fails at the moment because it can't delete the old bitmap).
I guess this is a simple problem but I could not find a solution here or on the internet.
First read the file to memory, then create the Bitmap using that data.
var m = new MemoryStream(File.ReadAllBytes(filename));
Bitmap thump = (Bitmap)Bitmap.FromStream(m);

DirectoryNotFoundException when creating a BitmapImage

I develop an IOC framework in the form of a DLL that creates the application's main window and displays various WPF Pages in it. There is a folder of PNG images with their Build Action set to Resource. I use code like this to set the sources of WPF Image elements...
MyImage.Source = new BitmapImage(new Uri("/MyAssembly;component/Images/MyImage.png", UriKind.Relative));
This works great. However, there is also a feature where the user can choose to display one of these WPF Pages in a separate window. Here is the mystery: If the user navigates to the page in the main window before showing it in a separate window, all is well. However, if the user shows the page in a separate window before it is shown in the main window, I get a DirectoryNotFoundException saying " Could not find a part of the path 'C:\MyAssembly;component\Images\MyImage.png'."
I discovered the OnLoad cache option and tried the following code...
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new Uri("/MyAssembly;component/Images/MyImage.png", UriKind.Relative);
image.EndInit();
MyImage.Source = image;
However, I then get the DirectoryNotFoundException on the very first image that I attempt to create.
I also tried an absolute URI...
MyImage.Source = new BitmapImage(new Uri("pack://application:,,,/MyAssembly;component/Images/MyImage.png", UriKind.Absolute));
I then get a UriFormatException saying "Invalid URI: Invalid port specified."
I solved the problem by using the 'pack' URI format as shown in the question. However, I needed to make a call to the Application class before I did so. This has the effect of executing the Application's static constructor which registers the 'pack' URI scheme. I found my answer here. The call I used was simply this:
var app = Application.Current;

Change Image at Runtime Based On RadioButton Selection

I need to change the background image at runtime, based on which RadioButton the user clicks. I'm doing this in a WPF project in Visual Studio, and I need to put the code in the Checked event in the xaml.cs file
I have an Image control called imgBackground, with 6 images in its Source collection, which are listed in an Images folder in the Solution Explorer.
I've tried:
this.imgBackground.Source = "filename.jpg";
both with and without the quotes, and with various paths (I've tried too many different variations to list them all here) and nothing works - everything I've tried just gives an error in the editor, before I even try to build and run anything (the error given varies depending on what I'm trying at the time).
If you are using relative paths as filenames like
this.imgBackground.Source = "filename.jpg";
then these files must be in the same directory as the .exe of your program is.
One workaround would be to use absolute paths like
this.imgBackground.Source = #"C:\MyFolder\MyProject\filename.jpg";
Or, even further use the packaging mechanism of WPF or pack your images as resources into your assembly. Look at this thread.
EDIT:
For your clarification:
The Source-property demands an System.Windows.Media.ImageSource-object, which you must provide.
Do it like this:
BitmapImage bi3 = new BitmapImage();
bi3.BeginInit();
bi3.UriSource = new Uri("filename.jpg", UriKind.Relative);
bi3.EndInit();
this.imgBackground.Source = bi3;
Please refer to this documentation here.

How to show Image from byte array in Microsoft report

I am using a Report file and a ReportViewer control to show a report which loads data dynamically from objects during run-time.
I need to show an image which is stored as a byte array in the object.
The PictureBox's value is currently set to:
=First(Fields!ImageData.Value, "dtstItemImage")
And I set the DataSource using:
ImageBindingSource.DataSource = this.item.Image.ImageData;
The code compiles and runs but the image is not displayed in the report.
Is this because the PictureBox needs to be bound to an Image object (and not to a byte array)? Or are there perhaps some properties of the PictureBox which I need to set?
UPDATE 1
I've added a border to the PictureBox just to make sure that's its visible and it does show up in the report. It just doesn't contain the image.
UPDATE 2
I've fixed a mistake in my code. I've changed:
ImageBindingSource.DataSource = this.item.Image.ImageData;
to:
ImageBindingSource.DataSource = this.item.Image;
as the PictureBox is bound to the ImageData field BUT the DataSource is the Image object.
Now I get a small cross icon instead of nothing which (at least for me) indicates some progress but I don't know where the byte[]-bitmap conversion code needs to be.
I managed to solve this by setting the report's Image box Source property to Database (it was previously set to External).
More info about the different available Source values can be found at (MSDN) HowTo: Add an Image (Reporting Services).
You need to create an image object from the byte array and use that as the source.
To do this, you can use a helper function like the following
public static Image LoadImage(byte[] imageBytes)
{
Image image = null;
using (var ms = new MemoryStream(imageBytes))
image = Image.FromStream(ms);
return image;
}
Edit
For WPF, you need to use BitmapSource (MSDN) instead of Image (MSDN)
public static BitmapSource LoadImage(Byte[] imageBytes)
{
var image = new BitmapImage();
using (var ms = new MemoryStream(binaryData))
{
image.BeginInit();
image.StreamSource = ms;
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
}
if (image.CanFreeze)
image.Freeze();
return image;
}
NB: You can could also do this using a IValueConverter, see this blog post for the source code.
and then modify your data binding
ImageBindingSource.DataSource = LoadImage(item.Image.ImageData);
...
Make sure that the image (and MemoryStream) is disposed properly when you finished with it, as otherwise you will leak memory.
Also, depending on the format of your byte array you may need to do some work. See one of my question/answers for some helpful information.

Add Image to Base64 Encoded ImageStream in Resources (resx)

I'm working on an older project, updating it. Part of the program has a toolstrip with many buttons on it, each with an image. I found that the images are stored in a Base64 encoded imagestream in the resx for the form and accessed as such:
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
...
this.imageList1 = new System.Windows.Forms.ImageList(this.components);
...
this.toolStrip1.ImageList = this.imageList1;
...
this.imageList1.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("imageList1.ImageStream")));
...
this.toolStripButton1.ImageIndex = 0; //There are 41 images, so this can be between 0 and 40
I need to add another button with a new image. How can I add an image to this stream?
I cannot use the designer, as it crashes as soon as I load the form (I believe because it uses a custom component with unsafe code).
I could always just add a new image resource separate from the stream, but that would make that one button different and thus it would create problems with consistency, causing upkeep problems later. So I'm wondering if there is any way for me to edit the imagestream. I can access the raw base64 string, but I have no idea where to go from here.
Create another form.
Add an ImageList component.
Add an arbitrary image in order to generate an "imagestream".
Open old resx and copy the "value" element.
Open new resx and paste value element.
Open new form.
Add images as needed.
Save new form.
Open new form's resx file.
Copy value element.
Open old form's resx file.
Paste in new value element.
I found a way to do this using code:
imageList1.Images.Add( NEWIMAGE );
ResXResourceWriter writer = new ResXResourceWriter("newresource.resx");
writer.AddResource("imageList1.ImageStream",imageList1.ImageStream);
writer.Generate();
writer.Close();
writer.Dispose();
The code will write the updated ImageStream to the new resource file. I can then copy it to my current resource file.

Categories