Delete an image bound to a control - c#

I am writing an Image Manager WPF application. I have a ListBox with the following ItemsTemplate:
<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>
Note that the "Image" control is bound to the "FullName" property, which is a string representing the absolute path to a JPG.
Several application features require that I alter the JPG file (move, rename, or delete). When I try to do so (currently trying to Move the file) I receive an IOException: "The process cannot access the file because it is being used by another process." The process locking the file is my WPF application.
I did some searching online and found several postings indicating that Images in particular have trouble letting go of their resources. I have tried the following:
Setting the ListBox.Source to null
Adding a 10 second wait time before
attempting the move.
Issuing GC.Collect().
Moving the operation to a different
thread.
What else can I try? I thought about finding a reference to the Image object in the ItemsTemplate and trying to dispose of the Image, but I can't figure out how to get the reference.
One possible solution I read about was to create copies of the Images rather than the actual images, but since the Binding is to the filename and not the actual Image I don't know if I could make this work.
Any help or suggestions would be most appreciated.

My Intuipic application allows users to delete images, too. I had to write this converter to achieve it. Relevant code:
//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();

I marked Kent's response as an answer, and I would have marked bendewey's as well, because I used both of them in the final solution.
The file was definitely locked because the file name was all that was being bound, so the Image control opened the actual file to produce the image.
To Solve this, I created a Value Converter like bendewey suggested, and then I used (most of) the code form Kent's suggestion to return a new 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
}
As the comments suggest above, however, this did not solve the problem. I have been away on other projects and recently returned to this one reinvigorated to find the solution.
I created another project that only tested this code, and of course it worked. This told me that there was more amiss in the original program.
Long story short, the Image was being generated in three places, which I thought had been addressed:
1) The ImageList, now bound using the Converter.
2) The main Image which was bound to the ImageList SelectedItem property.
3) The DeleteImage popup, which was bound using the Converter.
It turns out the problem was in #2. By binding to the SelectedItem, I mistakenly assumed I was binding to the newly rendered Image (based on the Converter). In reality, the SelectedItem object was in fact the file name. This meant that the main Image was again being built by directly accessing the file.
So the solution was to bind the main Image control to the SelectedItem property AND employ the Converter.

Check out this post here.
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/dee7cb68-aca3-402b-b159-2de933f933f1/
Sample
Basically you'll have to preload the image using a stream. I would create a PreLoadImageConverter, something like this, I haven't tested it.
<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.");
}
}

This is very old, but the framework has changed and the resolve for this is much easier, in .NET Core at least.
From what I gather, BitmapImage.UriSource didn't used to be bindable. It is now. Simply specify your image source explicitly in xaml. Bind your UriSource and set cache mode to OnLoad. Done. No converters required.
<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>

Related

Image not being displayed in Windows 8.1 Store application

I have a Windows 8.1 Store application and in one of the views I need to display an image. I've done this a million times in WPF desktop application, so it should be easy to do and yet my image is not displayed. I get this image from the repository as a byte array. I checked and I have all the bytes in my ViewModel at the time my view is displayed. Yet, I don't see it. This is the first time I actually try to display an image in a Windows 8.1 Store application, so I wonder if things are done differently.
Here is my XAML code:
<Border Grid.Column="2" BorderBrush="White" BorderThickness="5" Margin="2">
<Image Source="{Binding ImageBuffer}" AutomationProperties.Name="{Binding CompanyName}"
Width="280" Height="190" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Border>
The code behind contains an "ImageBuffer" public property that, like I said, is properly initialized with all the bytes of the image.
private byte[] _imageBuffer;
public byte[] ImageBuffer
{
get { return _imageBuffer; }
set { Set(() => ImageBuffer, ref _imageBuffer, value); }
}
I use MVVM Light Toolkit for my project.
Any suggestions?
Thanks,
Eddie
Apparently there is no automatic type conversion from byte[] to ImageSource in WinRT (as it is in WPF). You should have seen an error message in the Output Window in Visual Studio like
Error: Converter failed to convert value of type
'Windows.Foundation.IReferenceArray`1' to type 'ImageSource'; ...
So you should use a Binding Converter like this:
using System;
using System.IO;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media.Imaging;
...
public class ImageConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, string language)
{
var bitmap = new BitmapImage();
var buffer = value as byte[];
if (buffer != null)
{
using (var stream = new InMemoryRandomAccessStream())
{
stream.AsStreamForWrite().Write(buffer, 0, buffer.Length);
stream.Seek(0);
bitmap.SetSource(stream);
}
}
return bitmap;
}
public object ConvertBack(
object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
Then use it in the Image.Source Binding like this:
<Page.Resources>
<local:ImageConverter x:Key="ImageConverter"/>
</Page.Resources>
...
<Image Source="{Binding ImageBuffer, Converter={StaticResource ImageConverter}}" ... />

Image to showing when Loading Image from antoher Assembly

I'm using Visual Studio 2013 WPF and the MVVM Pattern.
I have a Modular Solution, in 1 Project Solution I have many Projects.
Common.Library (Class Library)
This is basically my Library that will contain all the basic stuff e.g the class ViewModelBase.cs Implements the INotifyPropertyChanged. I also have Images folder that contains Images (jpg, png etc.) and another class called BinaryImageConverter.cs to Convert binary images to BitmapImages
Customer (WPF Project)
In here I have a user Control that has a Image control in it. I got my Model/View/ViewModels
CustomerModel : ViewModelBase
private BitmapImage _ProfilePicture;
private BitmapImage ProfilePicture
{
get { return this._ProfilePicture; }
set
{
if (this._ProfilePicture == value)
return;
this._ProfilePicture = value;
OnPropertyChanged("ProfilePicture");
}
}
BinaryImageConverter.cs
public BitmapImage BinaryPictureConverter(byte[] Picture)
{
BitmapImage Image;
if (Picture == null)
return null;
Image = new BitmapImage();
using (MemoryStream imageStream = new MemoryStream())
{
imageStream.Write(Picture, 0, Picture.Length);
imageStream.Seek(0, SeekOrigin.Begin);
Image.BeginInit();
Image.CacheOption = BitmapCacheOption.OnLoad;
Image.StreamSource = imageStream;
Image.EndInit();
Image.Freeze();
}
return Image;
}
CustomerView.xaml
<UserControl.Resources>
<ResourceDictionary>
<BitmapImage x:Key="DefaultCustomerPicture" UriSource="/Common.Library;component/Images/DefaultCustomerPicture.jpg" />
</ResourceDictionary>
</UserControl.Resources>
<Image Name="ImgProfile" Width="150" HorizontalAlignment="Left" DockPanel.Dock="Left" Source="{Binding Path=CustomerProfile.ProfilePicture, TargetNullValue={StaticResource DefaultCustomerPicture}}" Stretch="Fill" />
I have a varbinary field to store my Images in the DB.
So I load my Data in my CustomerViewModel. Everything shows excepts my Image on my UserControl.
I have the FirstName and LastName displayed which is perfect but the Image doesn't appear.
EDIT :
Forgot to mention that my User Control is loaded in a ListBox and the ItemSource is binded to my CustomerProfile which is a ObservableCollection of the CustomerModel
<ListBox Name="listCustomers" Background="Transparent" BorderThickness="0" Margin="5,10,0,0" ItemsSource="{Binding Path=CustomerProfile}">
<ListBox.ItemTemplate>
<DataTemplate>
<vw:CustomerView />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
WPF data binding works with public properties only:
public BitmapImage ProfilePicture
{
get { ... }
set { ... }
}

FileAttributes to ImageSource: IValueConverter not working

So, I made a "tiny" file explorer page inside my music player (Universal App) and I need to place an image informing wheter is is Directory or file. But the code isn't working.
This is converter itself: namespace myApp before its own namespace.
namespace Converters
{
public sealed class AttributesToImageConverter : Windows.UI.Xaml.Data.IValueConverter
{
public object Convert ( object value, Type targetType, object parameter, string language )
{
FileAttributes f = (FileAttributes)value;
Windows.UI.Xaml.Media.Imaging.BitmapImage img = new Windows.UI.Xaml.Media.Imaging.BitmapImage ( );
img.DecodePixelWidth = 50;
if ( f == FileAttributes.Directory )
{
img.UriSource = new Uri ( "ms-appx:/Asstes/folder.png", UriKind.Absolute );
}
else
img.UriSource = new Uri ( "ms-appx:/Asstes/file.png", UriKind.Absolute );
return img;
}
public object ConvertBack ( object value, Type targetType, object parameter, string language )
{
throw new NotImplementedException ( );
}
}
}
This is XAML:
<Page
...
xmlns:converter="using:myApp.Converters" >
<Page.Resources>
<converter:AttributesToImageConverter x:Key="AttributesToImageConverter" />
</Page.Resources>
...
<Grid x:Name="LayoutRoot" DataContext="">
...
<ListView x:Name="ContentRoot" ItemsSource="{Binding List}" Height="500" Margin="10,-10,10,15" Background="Transparent" BorderBrush="Transparent" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="2,2,2,2">
<Image Width="50" Height="50" Margin="5,0,5,0" Source="{Binding Attributes, Converter={StaticResource AttributesToImageConverter}}" />
<TextBlock Text="{Binding Name}" Foreground="White" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
...
</Grid>
Other Bindings to this context work, binding to Name property in the same IStorageItem works perfectly, this one doesn't. Additionally, using ListView causes the app to shutdown few seconds AFTER displaying the loaded data without any debug information or exception but code -2147483645 (0x80000003). I'd appreciate any help.
Is "Attributes" an actual property for each item within ItemsSource "List" or is it a separate property within your view-model?
Create a storage file with the file path you listed and then leverage the following example:
var imageFile = args.Files[0] as StorageFile;
// Ensure the stream is disposed once the image is loaded
using (IRandomAccessStream fileStream = await imageFile.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
// Set the image source to the selected bitmap
var bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(fileStream);
return bitmapImage;
}

How do I bind a WPF Image element to a PNG on the local hard drive using a relative filepath derived from a DB?

I've got a folder on the local hard drive with several images in it. The image names/paths are stored in a local SQLCE database. In a WPF application I'm trying to bind those images to an Image element (which eventually goes into a listbox). I've got the application to run and compile and the listbox shows up but there is no image where it is supposed to be.
This is the XAML that defines the data template that the listbox uses...
<Window.Resources>
<DataTemplate x:Key="assetLBTemplate">
<StackPanel Orientation="Horizontal">
<Image Height="32" Width="32" Source="{Binding imageFileName}" />
<TextBlock Text="{Binding imageFileName}" />
<TextBlock Text="{Binding assetName}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
The XAML for the listbox...
<ListBox x:Name="lbAssetsLiquid"
ItemsSource="{Binding Tables[0]}"
ItemTemplate="{StaticResource assetLBTemplate}"
BorderThickness="1, 1, 1, 1" Grid.Column="0" Grid.Row="1" />
The code that I run on Window_Loaded:
private void BindLiquidAssetsListBoxData()
{
SqlCeConnection connection;
SqlCeCommand command;
string sql = "SELECT tblLiquidAssets.assetName, tblLiquidAssets.assetQuantity, tblLiquidAssets.assetValueGP, tblLiquidAssets.assetDescription, tblImages.imageFileName FROM tblLiquidAssets INNER JOIN tblImages ON tblLiquidAssets.assetImageIndex=tblImages.imageID;";
string connectionString = "Data Source=sharecalc_db.sdf;Persist Security Info=False;";
DataSet dtSet = new DataSet();
try
{
using (connection = new SqlCeConnection(connectionString))
{
command = new SqlCeCommand(sql, connection);
SqlCeDataAdapter adapter = new SqlCeDataAdapter();
connection.Open();
adapter.SelectCommand = command;
adapter.Fill(dtSet, "tblLiquidAssets");
lbAssetsLiquid.DataContext = dtSet;
connection.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
The result from the SQL Query is...
Again...the program loads with the listbox but no images get loaded.
I get this in the output window, which makes me I think I am missing something important here...
converter failed to convert value 'gold64.png' (type 'String')
When I add the images to the project itself in Solution Explorer it seems to work (the images appear where they are supposed to be)...but it does not work otherwise. Can someone shove me in the right direction?
You need to use custom value converter to convert strings to images if you want to load files from the file system. Image.Source, when a string is passed, expects a file name from resources. You can find implementation of a such converter here: Display an image in WPF without holding the file open.
Thank you Athari, you got me on the right path!
Revised chunk of XAML...
<Window x:Class="pf_sharecalc.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Share Calculator" WindowStyle="ThreeDBorderWindow" Loaded="Window_Loaded"
xmlns:local="clr-namespace:pf_sharecalc">
<Window.Resources>
<local:PathToImageConverter x:Key="PathToIMageConverter"/>
<DataTemplate x:Key="assetLBTemplate">
<StackPanel Orientation="Horizontal">
<Image Height="32" Width="32" Source="{Binding imageFileName, Converter={StaticResource PathToIMageConverter}}" />
<TextBlock Text="{Binding imageFileName}" />
<TextBlock Text="{Binding assetName}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
And I added this code...
public class PathToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string path = value as string;
if (path != null)
{
BitmapImage image = new BitmapImage();
using (FileStream stream = File.OpenRead(path))
{
image.BeginInit();
image.StreamSource = stream;
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit(); // load the image from the stream
} // close the stream
return image;
}
else
return null;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I'll have to do some fine tuning to get exactly what I want, but I've passed the road-block.

Binding Image's Source in XAML DataTemplate to a URI in Silverlight 4

Okay, what I'm trying to create is a list of images (using a listbox) with a thumbnail on the left, and image title on the right. My XAML is set up this way:
<ListBox HorizontalAlignment="Left" Margin="6,6,0,6" Name="CurrentPhotos" Width="184" SelectionChanged="CurrentPhotos_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Converter={StaticResource FilePathConverter}}" />
<sdk:Label Content="{Binding Title}"></sdk:Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've got the FilePathConverter key defined in App.xaml and the code is set up:
public class FilePathConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType == typeof(string))
{
return (value as PhotoSummary).FullThumbPath();
}
else
{
return (value as PhotoSummary).Thumb();
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
There are breakpoints in both Convert and ConvertBack methods. ConvertBack never gets fired (so there's no exception etc.), and in Convert method, the Thumb returns correctly (string input is left for some test reasons, and not currently used. it's not fired anyways), and the Thumb extension method is this:
public static object Thumb(this PhotoSummary ps)
{
Uri uri = new Uri("http://" + Settings.Host + "/Content/Thumbs/" + ps.Uploaded.Year + "/" + ps.Uploaded.Month + "/" + ps.ID + ".jpg", UriKind.Absolute);
return new BitmapImage(uri);
}
This one gets called, and the Uri is built up correctly (tested several times). However, when I run the app, the list only contains the Title of the photo, and no image is there. All the images are small (they are just thumbs), local files, so they need to load up instantly so it's not a loading issue either. But it's as if there's no Image tag there. It just displays the labels of the photos. Converter is working, Uri is correct, there are no errors at all, but no image shows up.
Any suggestions?
You might have to load the image explicitly in your converter. The MSDN page for BitmapImage shows the following code snippet:
// Create the image element.
Image simpleImage = new Image();
simpleImage.Width = 200;
simpleImage.Margin = new Thickness(5);
// Create source.
BitmapImage bi = new BitmapImage();
// BitmapImage.UriSource must be in a BeginInit/EndInit block.
bi.BeginInit();
bi.UriSource = new Uri(#"/sampleImages/cherries_larger.jpg",UriKind.RelativeOrAbsolute);
bi.EndInit();
// Set the image source.
simpleImage.Source = bi;
Okay, the problem was about a security restriction in SL. It was running locally, and making a call to http://localhost... was failing due to security restriction (at least, for images). I've read that if I made a test page, launch from the local server etc. and then run it, the error would go away, but instead of that workaround, I just checked require elevated trust, and it suddenly started to work. So question is solved.
Im using this for exactly same problem, But Im using Silverlight 5 with eleated trust, dont know it that matters
<DataTemplate x:Key="DataTemplate1">
<Grid>
<Image Margin="0" Width="25" Height="25" Source="{Binding EventType, StringFormat=/Icons/EventType\{0:d\}.png}"/>
</Grid>
</DataTemplate>
And it works well. Its data template for DataGrid, where this EventType is enum of types.
this is what fiddler shows later
http://www.localdomain.loc/ClientBin/Icons/EventType1.png

Categories

Resources