IValueConverter blocks prevents deletion - c#

I have a Listbox with image paths (ObservableCollection as source), and IValueConverter is turning them into the image thumbnails.
When I try to delete any of those files, I get the error:
"The process cannot access the file 'C:\test.jpg' because it is being used by another process."
public class UriToBitmapConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
BitmapFrame bit = BitmapFrame.Create(new Uri(value.ToString()), BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnDemand);
if (bit.Thumbnail != null)
{
return bit.Thumbnail;
}
else
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 200;
bi.UriSource = new Uri(value.ToString());
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.EndInit();
return bi;
}
}
catch
{
return null;
}
}
How do I force-release the image, or force file deletion? Thank you.

You could probably use a FileStream and the StreamSource.
Set the CacheOption property to BitmapCacheOption.OnLoad if you wish to close the stream after the BitmapImage is created. The default OnDemand cache option retains access to the stream until the bitmap is needed, and cleanup is handled by the garbage collector.

Related

Edit file after removing binding

I am trying to stream a desktop screen from one PC to another. I've started with just sending an image first which was really easy. My next thought was to just display the image using WPF and by continuously editing the saved image I would have a live stream of the desktop. After trying this I noticed that editing an image that is currently being used results in an exception.
The process cannot access the file 'D:\Desktop\ConsoleApp1\WpfApp1\img\Bild1.jpeg' because it is being used by another process.
So when I tried using two different images that would just swap every time, the same exception kept appearing.
The code below deletes the unbound image and creates a new file that will be bound to an Image.
// The Image that is currently not binded will be deleted with the code below
if (img1){
if (File.Exists("./../../img/Bild2.jpeg"))
{
File.Delete("./../../img/Bild2.jpeg");
}
}
else
{
if (File.Exists("./../../img/Bild1.jpeg"))
{
File.Delete("./../../img/Bild1.jpeg");
}
}
// This code save the Image
if (File.Exists("../../img/Bild1.jpeg")) // Image1 exists so I am editing image2
{
bmp.Save("../../img/Bild2.jpeg", ImageFormat.Jpeg);
ms.Position = 0;
FileInfo fi = new FileInfo("../../img/Bild2.jpeg");
ImageBind = fi.FullName; // Image Bind is a string property which is binded to a Image Tag
img1 = false; // To check which img has been edited last
}
else if (File.Exists("../../img/Bild2.jpeg"))
{
bmp.Save("./../../img/Bild1.jpeg", ImageFormat.Jpeg);
ms.Position = 0;
FileInfo fi = new FileInfo("../../img/Bild1.jpeg");
ImageBind = fi.FullName;
img1 = true;
}
else // If neither Image1 or Image2 exists
{
bmp.Save("../../img/Bild1.jpeg", ImageFormat.Jpeg);
ms.Position = 0;
FileInfo fi = new FileInfo("../../img/Bild1.jpeg");
ImageBind = fi.FullName;
img1 = true;
}
I do not think that copying and swapping images is the right approach for live desktop streaming in many ways, but that is another question. I will only focus on the exception that you get.
The image files that you assign by file path to Image are locked until the application is shut down. In order to change that, you have to change caching options of the underlying BitmapImage.
You can create a value converter to do this, so you can keep the ImageBind property as string.
public class PathToBitmapSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var bi = new BitmapImage();
var uriString = (string)value;
bi.BeginInit();
bi.UriSource = new Uri(uriString, UriKind.RelativeOrAbsolute);
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
bi.EndInit();
return bi;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
This code will convert your file path to a BitmapImage. The OnLoad and IgnoreImageCache will prevent the image from being locked. From the documentation:
OnLoad - Caches the entire image into memory at load time. All requests for image data are filled from the memory store.
IgnoreImageCache - Loads images without using an existing image cache. This option should only be selected when images in a cache need to be refreshed.
In your XAML you have to create an instance of the converter in any resource dictionary and adapt the binding of your Image to use the converter, assuming its key is PathToBitmapSourceConverter.
<Image Source="{Binding ImageBind, Converter={StaticResource PathToBitmapSourceConverter}}" ... />

Change Listbox ItemsSource with different Thread

I want to change ListBox ItemsSource without UI freezing. For this challenge, I wrote the code that you see below;
When User Click The Button
Thread th = new Thread(DisplayFilesAsync);
th.Start(new object[] { FolderPath, fdv.FileType });
Async Method
private void DisplayFilesAsync(object param)
{
object[] args = param as object[];
string searchText = Convert.ToString(args[0]);
FileType type = (FileType)args[1];
IEnumerable<FileListItem> list = uDirectoryHelper.GetFileFromFolder(searchText, type);
Dispatcher.BeginInvoke(new Action<IEnumerable<FileListItem>>(DisplayFiles), DispatcherPriority.Background, new object[] { list });
}
Change The ItemsSource
private void DisplayFiles(IEnumerable<FileListItem> fileList)
{
lstFiles.ItemsSource = fileList;
}
In the last method, if I change directly ItemSource of ListBox, program isn't break up but when program pass to closed curly brackets of the method, is break up and throw an Must create DependencySource on same Thread as the DependencyObject exception.
if I changed that; it's break up on closed curly brackets again, not over the ADD method and throw an same exception.
private void DisplayFiles(IEnumerable<FileListItem> fileList)
{
foreach (FileListItem item in fileList)
{
lstFiles.Items.Add(item);
}
}
But if I change the code like that, it's work perfectly and add items to my listbox.
foreach (FileListItem item in fileList)
{
lstFiles.Items.Add("EXAMPLE");
}
I'm really don't understand to my missing. Why I can add to some string but I can't add to my FileListItem class. What am I missing? I've tried a lot of different code but it's always break on closed curly braces NOT OVER THE ASSIGMENT LINE.
I'm really need to help. Thanks for answers. Have good day, good works.
Your FileListItem class has a property of type ImageSource
public ImageSource ItemImage { get; set; }
Although you haven't show that in your question, you certainly assign a value to that property in your GetFileFromFolder method, e.g. like
fileList.ItemImage = new BitmapImage(new Uri(...));
Unless the BitmapImage is not frozen in the background thread, it can not be accessed by the UI thread, hence you get that exception.
Make sure you call the Freeze() method on the BitmapImage after it has loaded a bitmap. Note that you can't do asynchronous bitmap loading (e.g. by a remote URI) in this situation. If your bitmap is loaded from a local file, do something like this:
var bitmap = new BitmapImage();
using (var stream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read))
{
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
bitmap.Freeze();
}
fileList.ItemImage = bitmap;
Or, instead of BitmapImage, use the BitmapFrame.Create() method, which already returns a frozen BitmapFrame (another subclass of ImageSource).
using (var stream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read))
{
fileList.ItemImage = BitmapFrame.Create(
stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}

What is the correct implementation of Image.Save(Stream s, ImageFormat f)?

I have written the following code with the intention of converting a System.Drawing.Image into a Microsoft.Xna.Framework.Graphics.Texture2D.
public Texture2D convertImage(Image img)
{
if (parent == null)
{
return null;
}
if (img == null)
{
return null;
}
//make a new Texture2D
Stream mystream = ToStream(img, ImageFormat.Png);
Texture2D tex = Texture2D.FromStream(parent.GraphicsDevice, mystream);
return tex;
}
private Stream ToStream(Image img, ImageFormat f)
{
var stream = new MemoryStream();
img.Save(stream, f);
stream.Position = 0;
return stream;
}
Parent refers to the Microsoft.Xna.Framework.Game that is calling the function, the issue is that there are no compiling errors but when the code is run the program throws an ArgumentException "Parameter is not valid." on line 22. I can't work out what the problem is, I can only assume there is something wrong with the way I have used the code.
The image being passed to the function is a standard image and I am able to save it to a file using "img.Save(filname);", the error only arises when I attempt to save to a stream.
Update
It seems that the problem is that the System.Drawing.Image is being disposed of prior to my call to Image.Save(), this is resulting in all of the Image parameters returning "Argument exception occurred. Parameter is not valid.", so to elaborate on my original question, how do I stop an object from being disposed until I have finished using it?
Thanks for your help.

ImageSource in a class library won't show

My application links to a class library (.dll). In the class library project, three images were put into Resources.resx. One image needs to be chosen at run time and shown on a button. After googling around, I choose to use a converter to help the binding in xaml:
[ValueConversion(typeof(Side), typeof(ImageSource))]
public class SideToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Side side = (Side)value;
switch (side)
{
case Side.Left:
return ToWpfBitmap(Properties.Resources.LeftArmOnCart);
case Side.Right:
return ToWpfBitmap(Properties.Resources.RightArmOnCart);
case Side.Both:
return ToWpfBitmap(Properties.Resources.RightArmOnCart);
default:
throw new ArgumentException("Current configuration is invalid");
}
}
private static BitmapSource ToWpfBitmap(Bitmap bitmap)
{
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Png);
stream.Position = 0;
BitmapImage result = new BitmapImage();
result.BeginInit();
// According to MSDN, "The default OnDemand cache option retains access to the stream until the image is needed."
// Force the bitmap to load right now so we can dispose the stream.
result.CacheOption = BitmapCacheOption.OnLoad;
result.StreamSource = stream;
result.EndInit();
result.Freeze();
return result;
}
}
}
The xaml code looks like
<Image Source="{Binding Side, Converter={StaticResource SideToImageConverter}}" .../>
Unfortunately, the image won't show silently without throwing any exception. Debug into the converter code, the bitmap argument in ToWpfBitmap() looks fine (the Width/Height values were correct).
BTW, as another trial, the following code works fine.
[ValueConversion(typeof(Side), typeof(ImageSource))]
public class SideToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Side side = (Side)value;
switch (side)
{
case Side.Left:
return LoadBitmap("LeftArmOnCart.png");
case Side.Right:
return LoadBitmap("RightArmOnCart.png");
case Side.Both:
return LoadBitmap("RightArmOnCart.png");
default:
throw new ArgumentException("Current configuration is invalid");
}
}
private static BitmapSource LoadBitmap(string name)
{
BitmapImage result = new BitmapImage();
result.BeginInit();
string uri = "c:/.../Resources/Images/" + name;
result.CacheOption = BitmapCacheOption.OnLoad;
result.UriSource = new Uri(uri, UriKind.Absolute);
result.EndInit();
return result;
}
}
However, absolute path is not desired. So, do I miss anything?
I understand that it is feasible to use relative path by releasing the three image files. But, is it possible NOT to release the three files but just the class library dll?
Thanks!
There are a few things I think you could read up on to help you with your problem. I would start with Pack URIs in WPF so you can understand how to use a URI to check a specific library's location. Since these images are in another assembly's resource file, read the "Referenced Resource Assembly File" in the link.
I think the other thing worth mentioning is the difference between the two methods is one you provide a UriSource to (bottom) and one you don't (top). Looking at Image.Source, when used in XAML it expects a imageUri. With that hint, we can say it is definitely something to do with URIs probably.
In fact, your images are already saved as a stream inside of the libraries that they exist in, so we don't need to do anything with creating a new bitmap or whatever you're doing, instead we can simply provide a valid pack URI and return a string.
I'm thinking something like this should work, provided the name of the library the images are in is YourApp.Models, in sub folder Resources\Images\:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Side side = (Side)value;
switch (side)
{
case Side.Left:
return "pack://application:,,,/YourApp.Models;component/Resources/Images/LeftArmOnCart.png";
case Side.Right:
case Side.Both:
return "pack://application:,,,/YourApp.Models;component/Resources/Images/RightArmOnCart.png";
default:
throw new ArgumentException("Current configuration is invalid");
}
}
Note: Path names are sensitive to forward/back slash, assembly names, component. Be sure everything is correctly formatted.
This is considered an absolute URI. A relative URI would be just "/Resources/Images/LeftArmOnCart.png" which would only work if that file path existed within the assembly that called it--meaning it wouldn't work for you since you're calling from another assembly.
Hope this helps!
There is already an InteropBitmap class that can be used to convert a GDI bitmap to a WPF ImageSource, though I find it doesn't handle alpha channels very well. This is the mechanism I use:
public static BitmapSource CreateBitmapSourceFromGdiBitmap(Bitmap bitmap)
{
if (bitmap == null)
throw new ArgumentNullException("bitmap");
var rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
var bitmapData = bitmap.LockBits(
rect,
ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
try
{
var size = (rect.Width * rect.Height) * 4;
return BitmapSource.Create(
bitmap.Width,
bitmap.Height,
bitmap.HorizontalResolution,
bitmap.VerticalResolution,
PixelFormats.Bgra32,
null,
bitmapData.Scan0,
size,
bitmapData.Stride);
}
finally
{
bitmap.UnlockBits(bitmapData);
}
}
Note that this implementation assumes 32bpp ARGB pixel format. That should be fine if you're using PNG images. Otherwise, you'll need to add a conversion step.

Cannot serialize member 'vehicleImage' of type 'Windows.UI.Xaml.Media.Imaging.BitmapImage'

When I add the type BitmapImage to my class for my Windows 8 application (below), I get the:
Cannot serialize member vehicleImage' of type 'Windows.UI.Xaml.Media.Imaging.BitmapImage', see inner exception for more details.
Inner Exception Details:
System.Runtime.InteropServices.WindowsRuntime.RuntimeClass is inaccessible due to its protection level. Only public types can be processed.
Code:
public BitmapImage vehicleImage
{
get
{
return m_vehicleImage;
}
set
{
Uri _baseUri = new Uri("ms-appx:///");
BitmapImage imageBitmap = new BitmapImage(new Uri(_baseUri, ImagePath));
m_vehicleImage = imageBitmap;
OnPropertyChanged("vehicleImage");
}
}
private async void SetImage()
{
var file = await Windows.Storage.KnownFolders.PicturesLibrary.GetFileAsync(ImagePath);
var fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
var img = new BitmapImage();
img.SetSource(fileStream);
vehicleImage = img;
}
I'm serializing the object into XML. When I remove this bit of code everything works. I'd like to have the ability to have an image that the user selects from their computer (which is why I'm trying to use the BitmapImage type).
Any help is appreciated.
Use the System.Xml.Serialization namespace to access [XMLIgnore()] for these sort of properties.
You should want to store the string of where the image is, correct? Then you serialize the string, and [XMLIgnore()] the BitMapImage.
This allows the BitMap Image to still be accessed while serialization can occur.

Categories