Creating XPS in WPF - used image files are being locked until my app quits - wpf

In my WPF app, I am creating a FlowDocument by building its XAML markup as a string, and then using XamlReader.Parse to turn the string into a FlowDocument object, which I then save to an XPS document file. It works.
I needed to include an image in my document, and so to achieve this, I create and save the image as a temporary file in the temp directory, and then reference it with an absolute path in my FlowDocument's XAML. This works too - during the XPS document creation process, the image actually gets embedded into the XPS document, which is great.
But the problem is, my app retains a file lock on this image until the app quits.
I am cleaning up all resources. There is NO file lock on my generated XPS file - just the image file. If I comment out the part of my code which creates the XPS file, then the image file does not get locked.
My code (I'm on .NET 4 CP):
var xamlBuilder = new StringBuilder();
// many lines of code like this
xamlBuilder.Append(...);
// create and save image file
// THE IMAGE AT THE PATH imageFilePath IS GETTING LOCKED
// AFTER CREATING THE XPS FILE
var fileName = string.Concat(Guid.NewGuid().ToString(), ".png");
var imageFilePath = string.Format("{0}{1}", Path.GetTempPath(), fileName);
using (var stream = new FileStream(imageFilePath, FileMode.Create)) {
var encoder = new PngBitmapEncoder();
using (var ms = new MemoryStream(myBinaryImageData)) {
encoder.Frames.Add(BitmapFrame.Create(ms));
encoder.Save(stream);
}
stream.Close();
}
// add the image to the document by absolute path
xamlBuilder.AppendFormat("<Paragraph><Image Source=\"{0}\" ...", imageFilePath);
// more lines like this
xamlBuilder.Append(...);
// create a FlowDocument from the built string
var document = (FlowDocument) XamlReader.Parse(xamlBuilder.ToString());
// set document settings
document.PageWidth = ...;
...
// save to XPS file
// THE XPS FILE IS NOT LOCKED. IF I LEAVE OUT THIS CODE
// AND DO NOT CREATE THE XPS FILE, THEN THE IMAGE IS NOT LOCKED AT ALL
using (var xpsDocument = new XpsDocument(filePath, FileAccess.ReadWrite)) {
var documentWriter = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
documentWriter.Write(((IDocumentPaginatorSource) document).DocumentPaginator);
xpsDocument.Close();
}
(Actually, the fact that's it's a dynamically generated image in the temp directory is irrelevant - this issue occurs if I hard code in the path of any image file on my machine - it will get locked.)
One would think that there is a bug in the XPS creation code that causes the file lock.
Is there something else I can try? Or a way to remove the file lock via code?

You could change your xaml like this instead:
<Image>
<Image.Source>
<BitmapImage CacheOption="None" UriSource="your path" />
</Image.Source>
</Image>
to be able to play with the CacheOption parameter, to specify how the Xaml Builder should load the image file, as the default value seems to be keeping a lock on it (waiting for the GC to do its work it seems).
Here is some related question here on SO: How do you make sure WPF releases large BitmapSource from Memory?

Related

WPF: How to save a GIF file in a string or file from an URL?

I try to retrieve the content of a .gif located on an URL into a string -and eventually save it to disk-, ran from a WPF appliactions using the webbrowser. After trying dozens of solutions I am not further than I was yesterday. I thought that the code below would do the job, but the saved string cGif contains...the url itself. From http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.gifbitmapdecoder.aspx I then thought I needed GifBitmapEncoder instead, but the sample suggests that this creates an empty gif instead of a downloaded one.
(PS: what I basically want to do is to retrieve the GIF bytes straight from the WPF webbrowser but I haven't found anything working there either)
private void GifSave()
{
var uri = new Uri(#"http://www.archivearts.com/GIRAFFE2.gif");
var Img = new GifBitmapDecoder(uri, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
string cGif = Img.ToString();
}
Modified from a sample on the GifBitmapDecoder MSDN page:
// Open a Stream and decode a GIF image
var uri = new Uri(#"http://www.archivearts.com/GIRAFFE2.gif");
GifBitmapDecoder decoder = new GifBitmapDecoder(uri, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
BitmapSource bitmapSource = decoder.Frames[0];
Then use the CopyPixels() method of the BitmapSource to copy the pixel data into an array. Then convert the array into a string?
I haven't done this, I just took a look at MSDN to get some hints.

Using .wav files in wpf without directory

Hi I have a wpf application that plays sounds on events such as button click. And the current code I have now plays the sound file, but Ive realized that it has to be in another folder which for example if the user deletes, makes the application pretty much not useable.
I was wondering, How could I get a .wav file without creating a whole new folder in the application Release directory.
Any ideas?
Thanks.
If the .wav files is added to your project at the root then marked as content (Right Click on the .wav file and click properties. Then change the build action to content) then it will be copied directly to the root output folder
You can the reference the wav file in code or XAML as a relative path (simple "Myfile.wav")
I believe this is what you are asking for?
How about putting the .wav-file as en embedded resource in the dll?
Just add the file to the project, right-click on the file and set Build Action to Embedded Resource.
Then you can load the .wav file as a stream and save it to disc like this:
private void WriteEmbeddedResourceToFile(string writePath, string assemblyName)
{
Assembly asm = Assembly.GetExecutingAssembly();
Stream s = asm.GetManifestResourceStream(assemblyName);
if (s != null)
{
var file = new byte[s.Length];
s.Read(file, 0, (int)s.Length);
File.WriteAllBytes(writePath, file);
s.Close();
}
}
The string assemblyName must be set like this: namespace.(if you put the file in a folder, insert folder subpath here).filename.wav
Alternatively, you can drop saving it to disc, and just use the stream directly:
private byte[] GetEmbeddedResource(string assemblyName)
{
Assembly asm = Assembly.GetExecutingAssembly();
Stream s = asm.GetManifestResourceStream(assemblyName);
if (s != null)
{
var file = new byte[s.Length];
s.Read(file, 0, (int)s.Length);
s.Close();
return file;
}
}
Then, when you want to load the file, GetEmbeddedResource will return the byte array.

How to modify an XML file present in a folder in the Silverlight project?

I'm trying to read & modify an XML file present in the Silverlight project from a view's code behind.
This is how I've read & modified the XML file:
StreamResourceInfo s = Application.GetResourceStream(new Uri("XML/Settings.xml", UriKind.Relative));
XElement doc = XElement.Load(s.Stream, LoadOptions.None);
IEnumerable<XElement> settingElement = (from b in doc.Descendants(
"setting")
select b).Take(1);
if (settingElement.Count<XElement>() > 0)
{
foreach (var node in newsIdNode)
{
node.Remove();
}
}
What I want to do now, is to save the XML file. I tried the following:
doc.Save(s.Stream, SaveOptions.None);
But got a runtime error that the stream is not writable.
How can I save changes to this XML file?
You can't- the stream is only for reading. If you want to save something consider isolated storage, saving to a file or persisting state via Web services.

How to read an XML file present in a folder in the Silverlight project?

While searching for the solution, I've come across various links which talk about reading an xml file present in the ClientBin folder. But what I want to achieve is that, I want to read an xml file which is not present in the web project, but the silverlight project containing views.
Try this:
StreamResourceInfo s = Application.GetResourceStream(new Uri("Directory Under Silverlight Root/FileName.xml", UriKind.Relative));
var sr = new StreamReader(s.Stream);
// do whatever you need to do with the streamreader
while (!sr.EndOfStream)
{
var line = sr.ReadLine();
//do stuff
}
EDIT: the xml file's Build Action should be 'Content' and its Copy to Output Directory should be 'Do not copy'

Load image into memory immediately

I need to open all frames from Tiff image in WPF into memory and then delete the source. And after that I eventually need to render that image (resized according to window size). My solution is quite slow and I cannot delete file source before the first require. Any best practices?
Use CacheOption = BitmapCacheOption.OnLoad
This option can be used with the BitmapImage.CacheOption property or as an argument to BitmapDecoder.Create() If you want to access multiple frames once the images is loaded you'll have to use BitmapDecoder.Create. In either case the file will be loaded fully and closed.
See also my answer to this question
Update
The following code works perfectly for loading in all the frames of an image and deleting the file:
var decoder = BitmapDecoder.Create(new Uri(imageFileName), BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
List<BitmapFrame> images = decoder.Frames.ToList();
File.Delete(imageFileName);
You can also access decoder.Frames after the file is deleted, of course.
This variant also works if you prefer to open the stream yourself:
List<BitmapFrame> images;
using(var stream = File.OpenRead(imageFileName))
{
var decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
images = decoder.Frames.ToList();
}
File.Delete(imageFileName);
In either case it is more efficient than creating a MemoryStream because a MemoryStream keeps two copies of the data in memory at once: The decoded copy and the undecoded copy.
I figured it out. I have to use MemoryStream:
MemoryStream ms = new MemoryStream(File.ReadAllBytes(image));
TiffBitmapDecoder decoder = new TiffBitmapDecoder(ms, BitmapCreateOptions.None, BitmapCacheOption.None);
List<BitmapFrame> images = new List<BitmapFrame>();
foreach (BitmapFrame frame in decoder.Frames) images.Add(frame);

Resources