DocumentViewer to RichTextBox Binding Error - wpf

I have an application with RichTextBox and DocumentViewer (placed in a TabControl), and I want to make something like "hot preview". I've binded DocumentViewer.Document property to RichTextBox.Document
Binding:
<DocumentViewer Document="{Binding Document, Converter={StaticResource FlowDocumentToPaginatorConverter}, ElementName=mainRTB, Mode=OneWay}" />
And this is Converter code:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FlowDocument d = value as FlowDocument;
DocumentPaginator pagin = ((IDocumentPaginatorSource)d).DocumentPaginator;
FixedDocumentSequence result = null;
Size s = new Size(793.700787402, 1122.519685039);
pagin.PageSize = s;
using (MemoryStream ms = new MemoryStream())
{
TextRange tr = new TextRange(d.ContentStart, d.ContentEnd);
tr.Save(ms, DataFormats.XamlPackage);
Package p = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
Uri uri = new Uri(#"memorystream://doc.xps");
PackageStore.AddPackage(uri, p);
XpsDocument xpsDoc = new XpsDocument(p);
xpsDoc.Uri = uri;
XpsDocument.CreateXpsDocumentWriter(xpsDoc).Write(pagin);
result = xpsDoc.GetFixedDocumentSequence();
}
return result;
}
When I start this application everything is ok until I switch to tab with DocumentViewer. Application crushes and I get such Exception:
Cannot perform a read operation in write-only mode.
What I am doing wrong? Is it possible to make this binding?

The error message is indeed confusing and reason not immediately obvious. Basically you are closing the MemoryStream that holds XpsDocument too early and when the DocumentViewer attempts to read the document it cannot as it is write-only mode (because the stream was closed).
The solution is to not immediately close the MemoryStream until after you have finished viewing the document. To achieve this I wrote an XpsDocumentConverter that returns XpsReference.
Also, as you never been able to convert and display a single XpsDocument you won't have yet encountered the next issue of having multiple packages in the PackageStore with the same Uri. I have taken care of this in my implementation below.
public static XpsDocumentReference CreateXpsDocument(FlowDocument document)
{
// Do not close the memory stream as it still being used, it will be closed
// later when the XpsDocumentReference is Disposed.
MemoryStream ms = new MemoryStream();
// We store the package in the PackageStore
Uri uri = new Uri(String.Format("pack://temp_{0}.xps/", Guid.NewGuid().ToString("N")));
Package pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
PackageStore.AddPackage(uri, pkg);
XpsDocument xpsDocument = new XpsDocument(pkg, CompressionOption.Normal, uri.AbsoluteUri);
// Need to force render the FlowDocument before pagination.
// HACK: This is done by *briefly* showing the document.
DocumentHelper.ForceRenderFlowDocument(document);
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsDocument), false);
DocumentPaginator paginator = new FixedDocumentPaginator(document, A4PageDefinition.Default);
rsm.SaveAsXaml(paginator);
return new XpsDocumentReference(ms, xpsDocument);
}
public class XpsDocumentReference : IDisposable
{
private MemoryStream MemoryStream;
public XpsDocument XpsDocument { get; private set; }
public FixedDocument FixedDocument { get; private set; }
public XpsDocumentReference(MemoryStream ms, XpsDocument xpsDocument)
{
MemoryStream = ms;
XpsDocument = xpsDocument;
DocumentReference reference = xpsDocument.GetFixedDocumentSequence().References.FirstOrDefault();
if (reference != null)
FixedDocument = reference.GetDocument(false);
}
public void Dispose()
{
Package pkg = PackageStore.GetPackage(XpsDocument.Uri);
if (pkg != null)
{
pkg.Close();
PackageStore.RemovePackage(XpsDocument.Uri);
}
if (MemoryStream != null)
{
MemoryStream.Dispose();
MemoryStream = null;
}
}
}
XpsReference implements IDisposable so remember to call Dispose() on it.
Also, once you resolve the above error the next problem you are likely to encounter will be content not rendering as you would expect. This is caused by the fact you need to clone FlowDocument and it has not undergone a full measure and arrange layout pass. Read
Printing BlockUIContainer to XpsDocument/FixedDocument on how to solve this.

Related

Print a XpsDocument result in a corrupted PrintDialog

I have an issue with the printing of XPS documents. The first print works correctly but then the print dialog and the prints appears corrupted as you can see in that image.
I tried to use other overloads of the Write function such as the one using a DocumentPaginator as a parameter or the PrintDocument function of the print dialog but it didn't work either.
I noticed that the results depend on the choice of printer. Using the Microsoft XPS Document Writer works correctly but use a real printer or a PDF printer didn't.
Here's the simplest code that reproduces the issue :
public void Execute(object parameter)
{
var printDialog = new PrintDialog();
if (printDialog.ShowDialog() == true)
{
PrintDocument(
"xps_document_filename.xps",
printDialog);
}
}
private static void PrintDocument(string documentPath, PrintDialog printDialog)
{
using (var xpsDocumentSource = new XpsDocument(documentPath, FileAccess.Read))
{
XpsDocumentWriter xpsDocumentWriter = PrintQueue.CreateXpsDocumentWriter(printDialog.PrintQueue);
xpsDocumentWriter.Write(xpsDocumentSource.GetFixedDocumentSequence());
}
}

Why would you create a new Bitmap to convert to BitmapImage?

I have a converter in some legacy code, that is doing something that seems wrong, but I don't know bitmaps very well.
It looks like it's based on https://stackoverflow.com/a/3427114/57883
with some added capabilities.
using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows.Media.Imaging;
namespace CompanyName.Converters
{
[ValueConversion(typeof(System.Drawing.Image), typeof(System.Windows.Media.ImageSource))]
/// <summary>
/// One-way converter from System.Drawing.Image to System.Windows.Media.ImageSource
/// </summary>
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// empty images are empty...
if (value == null)
{
return null;
}
if (value.GetType() == typeof(System.Drawing.Image))
{
var image = (System.Drawing.Image)value;
// Winforms Image we want to get the WPF Image from...
var bitmap = new System.Windows.Media.Imaging.BitmapImage();
bitmap.BeginInit();
MemoryStream memoryStream = new MemoryStream();
// Save to a memory stream...
image.Save(memoryStream, ImageFormat.Bmp);
// Rewind the stream...
memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
bitmap.StreamSource = memoryStream;
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
else if (value.GetType() == typeof(System.Drawing.Bitmap))
{
var image = value as System.Drawing.Bitmap;
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(image);
using (MemoryStream memory = new MemoryStream())
{
bitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
bitmapImage.Freeze();
return bitmapImage;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}
Why in the world would you System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(image); ? isn't this just creating a copy of the bitmap in memory without any benefit? Is this just horrible code or is there a legitimate reason to copy the bitmap before converting it?
Copying the bitmap via the constructor without explicitly specifying the PixelFormat will result in the image being converted to 32bpp argb, which will in turn prevent problems arising if you try to bind to an image with a pixel format that the target doesn't support. In the case of WPF the Image control does render indexed images correctly but there might be other formats or other control types that will fail. The original author was probably just trying to cover all bases.

loading ResourceDictionary from baml using Baml2006Reader

How do can I read through a baml stream that contains a ResourceDictionaory using the Baml2006Reader and without acually instantiating the the ResourceDictionary?
I can ready through regular baml that just contains a UserControl just fine and I can examine the XAML tree using Baml2006Reader.NodeType etc.
But once the reader hits a ResourceDictionary, Baml2006Reader.Member.Name is "DeferrableContent" and Baml2006Reader.Value contains a MemoryStream that can not be parsed by another instance of Baml2006Reader. I can't event instantiate the reader:
System.IO.EndOfStreamException occurred HResult=-2147024858
Message=Unable to read beyond the end of the stream. Source=mscorlib
StackTrace:
at System.IO.MemoryStream.InternalReadInt32()
at System.Windows.Baml2006.Baml2006Reader.Process_Header()
at WpfApplication10.AssemblyExtensions.Read(Stream stream, List`1 result) in d:\Documents\Visual Studio
2012\Projects\WpfApplication10\WpfApplication10\AssemblyExtensions.cs:line
84 InnerException:
It seems that whenever the Baml2006Reader encounters an element where Baml2006Reader.Member.Name is "DeferrableContent" it is followed by another node where BamlReader.Value is a MemoryStream. It seems that this stream only contains a baml fragment and does not have a header (that's why System.Windows.Baml2006.Baml2006Reader.Process_Header() fails.)
So we need to tell the baml reader to read a baml fragment. This can be done be giving the reader an instance of System.Windows.Baml2006.Baml2006ReaderSettings where the IsBamlFragment property istrue.
Unfortunately both the Baml2006ReaderSettings class and the appropriate constructor of Baml2006Reader are internal. So we need to resort to reflection:
private static string PresentationFrameworkAssemblyName = "PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
private static Baml2006Reader CreateBamlFragmentReader(MemoryStream substream, XamlSchemaContext schemaContext)
{
var bamlSettingsType =
Type.GetType(
"System.Windows.Baml2006.Baml2006ReaderSettings, " + PresentationFrameworkAssemblyName);
var settingsCtor =
bamlSettingsType.GetConstructor(Type.EmptyTypes);
var bamlSettings = settingsCtor.Invoke(null);
var isBamlFragmentProp = bamlSettingsType.GetProperty("IsBamlFragment",
BindingFlags.NonPublic |
BindingFlags.Instance);
isBamlFragmentProp.SetValue(bamlSettings, true, null);
var ctor = typeof (Baml2006Reader).GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[]
{
typeof (Stream),
Type.GetType(
"System.Windows.Baml2006.Baml2006SchemaContext, " + PresentationFrameworkAssemblyName),
bamlSettingsType
},
null);
return (Baml2006Reader)ctor.Invoke(new[] { substream, schemaContext, bamlSettings });
}
usage:
var substream = reader.Value as MemoryStream;
if (substream != null)
{
using (var subReader = CreateBamlFragmentReader(substream, reader.SchemaContext))
{
// continue reading with subReader
}
}
I know this is rather fragile code and very hackish, but what the heck - it works (for me, currently)!

RestSharp - Download / Use image in WPF/Silverlight

I'm trying to use RestSharp to download an image from a WCF/Rest service. The result should be saved in a file and displayed in a Image control an a WPF/SL page.
private void GetImage()
{
RestClient _Client = new RestClient(BASE_URI);
RestRequest request = new RestRequest("/api/img/{FileName}");
request.AddParameter("FileName", "dummy.jpg", ParameterType.UrlSegment);
_Client.ExecuteAsync<MemoryStream>(
request,
Response =>
{
if (Response != null)
{
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = Response.Data;
String fn = String.Format(#"c:\temp\{0}.jpg", Guid.NewGuid().ToString());
System.IO.File.WriteAllBytes(fn,Response.Data.ToArray());
bitmapImage.EndInit();
img.Source = bitmapImage;
}
});
}
When I look in fiddler the image got downloaded correctly BUT no image is saved and nothing is displayd. There is no exception thown. ANy suggestions ?
UPDATED
A part of the problem turns out that RestSharp is not returning the expected memorystream. Moving to another methed and accessing the raw data in byte[] format solves part of the problem, saving the picutere to disk.
private void GetImage()
{
RestClient _Client = new RestClient(BASE_URI);
RestRequest request = new RestRequest("/api/img/{FileName}");
request.AddParameter("FileName", "dummy.jpg", ParameterType.UrlSegment);
_Client.ExecuteAsync(
request,
Response =>
{
if (Response != null)
{
byte[] imageBytes = Response.RawBytes;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = new MemoryStream(imageBytes);
bitmapImage.CreateOptions = BitmapCreateOptions.None;
bitmapImage.CacheOption = BitmapCacheOption.Default;
bitmapImage.EndInit();
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
Guid photoID = System.Guid.NewGuid();
String photolocation = String.Format(#"c:\temp\{0}.jpg", Guid.NewGuid().ToString());
encoder.Frames.Add(BitmapFrame.Create(bitmapImage));
using (var filestream = new FileStream(photolocation, FileMode.Create))
encoder.Save(filestream);
this.Dispatcher.Invoke((Action)(() => { img.Source = bitmapImage; }));
;
}
});
}
Although calling this.dispatcher.Invoke I still get the error : The calling thread cannot acces this object because a different thread owns it.
As the BitmapImage is created in another thread than the UI thread, you also have to call Freeze to make it accessible in the UI thread.
Although not strictly necessary here, it is good practise to always dispose of any IDisposable objects, including MemoryStream. Therefore you will also have to set the BitmapImage.CacheOption property to OnLoad.
using (var memoryStream = new MemoryStream(imageBytes))
{
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = memoryStream;
bitmapImage.EndInit();
bitmapImage.Freeze();
}
The frozen BitmapImage is accessible in the UI thread:
Dispatcher.Invoke((Action)(() => img.Source = bitmapImage));
Are you checking for exceptions using the debugger? If an exception is thrown on a background task, it won't be rethrown on the caller code unless you access Task.Result or use the await operator.
My guess is that you don't have access to the location of C: you are writing to. That block of code seems unnecessary anyway though, you should be able to directly set the source of the image to the stream you have without writing it to disk. Try commenting the writing to drive piece of code out and see if that solves the issue.

Convert HttpPostedFileBase to byte[]

In my MVC application, I am using following code to upload a file.
MODEL
public HttpPostedFileBase File { get; set; }
VIEW
#Html.TextBoxFor(m => m.File, new { type = "file" })
Everything working fine .. But I am trying to convert the result fiel to byte[] .How can i do this
CONTROLLER
public ActionResult ManagePhotos(ManagePhotos model)
{
if (ModelState.IsValid)
{
byte[] image = model.File; //Its not working .How can convert this to byte array
}
}
As Darin says, you can read from the input stream - but I'd avoid relying on all the data being available in a single go. If you're using .NET 4 this is simple:
MemoryStream target = new MemoryStream();
model.File.InputStream.CopyTo(target);
byte[] data = target.ToArray();
It's easy enough to write the equivalent of CopyTo in .NET 3.5 if you want. The important part is that you read from HttpPostedFileBase.InputStream.
For efficient purposes you could check whether the stream returned is already a MemoryStream:
byte[] data;
using (Stream inputStream = model.File.InputStream)
{
MemoryStream memoryStream = inputStream as MemoryStream;
if (memoryStream == null)
{
memoryStream = new MemoryStream();
inputStream.CopyTo(memoryStream);
}
data = memoryStream.ToArray();
}
You can read it from the input stream:
public ActionResult ManagePhotos(ManagePhotos model)
{
if (ModelState.IsValid)
{
byte[] image = new byte[model.File.ContentLength];
model.File.InputStream.Read(image, 0, image.Length);
// TODO: Do something with the byte array here
}
...
}
And if you intend to directly save the file to the disk you could use the model.File.SaveAs method. You might find the following blog post useful.
byte[] file = new byte[excelFile.ContentLength];
excelFile.InputStream.Read(file, 0, file.Length);
//Create memory stream object from your bytes
MemoryStream ms = new MemoryStream(file);
// Set WorkbookPart , Sheet
using (var myDoc = DocumentFormat.OpenXml.Packaging.SpreadsheetDocument.Open(ms, true))

Resources