Missing images in FlowDocument saved as XPS document - wpf

I am having some difficulties getting images contained in a FlowDocument to show when the FlowDocument is saved as an XPS document.
Here is what I do:
Create an image using the Image control of WPF. I set the image source bracketed by calls to BeginInit/EndInit.
Add the image to the FlowDocument wrapping it in a BlockUIContainer.
Save the FlowDocument object to an XPS file using a modified version of this code.
If I then view the saved file in the XPS viewer, the image is not shown. The problem is that the images are not loaded until actually shown on the screen by WPF so they are not saved to the XPS file. Hence, there is a workaround: If I first show the document on screen using the FlowDocumentPageViewer and then save the XPS file afterwards, the image is loaded and shows up in the XPS file. This works even if the FlowDocumentPageViewer is hidden. But that gives me another challenge. Here is what I wish to do (in pseudocode):
void SaveDocument()
{
AddFlowDocumentToFlowDocumentPageViewer();
SaveFlowDocumentToXpsFile();
}
This of course does not work since the FlowDocumentPageViewer never gets a chance to show its contents before the document is saved to the XPS file. I tried wrapping SaveFlowDocumentToXpsFile in a call to Dispatcher.BeginInvoke but it did not help.
My questions are:
Can I somehow force the images to load before saving the XPS file without actually showing the document on screen? (I tried fiddling with BitmapImage.CreateOptions with no luck).
If there is no solution to question #1, is there a way to tell when FlowDocumentPageViewer has finished loading its contents so that I know when it is save to create the XPS file?

The eventual solution was the same as you came to, which is to put the document in a viewer and briefly show it on screen. Below is the helper method that I wrote to do this for me.
private static string ForceRenderFlowDocumentXaml =
#"<Window xmlns=""http://schemas.microsoft.com/netfx/2007/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
<FlowDocumentScrollViewer Name=""viewer""/>
</Window>";
public static void ForceRenderFlowDocument(FlowDocument document)
{
using (var reader = new XmlTextReader(new StringReader(ForceRenderFlowDocumentXaml)))
{
Window window = XamlReader.Load(reader) as Window;
FlowDocumentScrollViewer viewer = LogicalTreeHelper.FindLogicalNode(window, "viewer") as FlowDocumentScrollViewer;
viewer.Document = document;
// Show the window way off-screen
window.WindowStartupLocation = WindowStartupLocation.Manual;
window.Top = Int32.MaxValue;
window.Left = Int32.MaxValue;
window.ShowInTaskbar = false;
window.Show();
// Ensure that dispatcher has done the layout and render passes
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Loaded, new Action(() => {}));
viewer.Document = null;
window.Close();
}
}
Edit: I just added window.ShowInTaskbar = false to the method as if you were quick you could see the window appear in the taskbar.
The user will never "see" the window as it is positioned way off-screen at Int32.MaxValue - a trick that was common back in the day with early multimedia authoring (e.g. Macromedia/Adobe Director).
For people searching and finding this question, I can tell you that there is no other way to force the document to render.
HTH,

Couple things...
You sure the image is sized before its written? Usually you have to call Measure on the control so that it may size itself accordingly (infinity lets the control expand to its Width and Height)
image.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Also, sometimes you have to bump the UI thread so that everything gets updated in the control
Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>{}));

You do not have to display the document in order to have images saved into the xps. Are you calling commit on the XpsSerializationManager?
FlowDocument fd = new FlowDocument();
fd.Blocks.Add(new Paragraph(new Run("This is a test")));
string image = #"STRING_PATH";
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri(image, UriKind.RelativeOrAbsolute);
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.EndInit();
MemoryStream ms = new MemoryStream();
Package pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
Uri pkgUri = bi.UriSource;
PackageStore.AddPackage(pkgUri, pkg);
Image img = new Image();
img.Source = bi;
BlockUIContainer blkContainer = new BlockUIContainer(img);
fd.Blocks.Add(blkContainer);
DocumentPaginator paginator = ((IDocumentPaginatorSource)fd).DocumentPaginator;
using (XpsDocument xps = new XpsDocument(#"STRING PATH WHERE TO SAVE FILE", FileAccess.ReadWrite, CompressionOption.Maximum))
{
using (XpsSerializationManager serializer = new XpsSerializationManager(new XpsPackagingPolicy(xps), false))
{
serializer.SaveAsXaml(paginator);
serializer.Commit();
}
}

I was able to address this by throwing the flowdocument into a viewer, and then do a measure/arrange.
FlowDocumentScrollViewer flowDocumentScrollViewer = new FlowDocumentScrollViewer();
flowDocumentScrollViewer.Document = flowDocument;
flowDocumentScrollViewer.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
flowDocumentScrollViewer.Arrange(new Rect(new Point(0, 0), new Point(Double.MaxValue, Double.MaxValue)));

Related

WPF DataGrid GridLines not visible when saved as PDF

I'm using a DataGrid to represent some data in a WPF application. In a feature where I'm saving a particular WPF Window which has the DataGrid into a PDF using PDFSharp, I'm facing an issue that the DataGrid GridLines are not visible when the saved PDF is viewed in smaller viewing percentages.
(Refer attached images, only when the PDF view is set at 139%, the GridLines are visible. However, in smaller viewing %, some grid lines get omitted.)
Here's the PDF Saving Code:-
MemoryStream lMemoryStream = new MemoryStream();
Package package = Package.Open(lMemoryStream, FileMode.Create);
var doc = new System.Windows.Xps.Packaging.XpsDocument(package);
XpsDocumentWriter writer = System.Windows.Xps.Packaging.XpsDocument.CreateXpsDocumentWriter(doc);
VisualBrush sourceBrush = new VisualBrush(this);
DrawingVisual drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(this.ActualWidth, this.ActualHeight)));
}
writer.Write(drawingVisual);
doc.Close();
package.Close();
var pdfXpsDoc = PdfSharp.Xps.XpsModel.XpsDocument.Open(lMemoryStream);
XpsConverter.Convert(pdfXpsDoc, sFileName, 0);
I believe it has to do with the quality with which the visual is drawn. Then I tried this snippet where I'm using DrawImage to make the visual at a higher resolution. Here's the snippet:-
MemoryStream lMemoryStream = new MemoryStream();
Package package = Package.Open(lMemoryStream, FileMode.Create);
var doc = new System.Windows.Xps.Packaging.XpsDocument(package);
XpsDocumentWriter writer = System.Windows.Xps.Packaging.XpsDocument.CreateXpsDocumentWriter(doc);
double dpiScale = 600.0 / 96.0;
var renderBitmap = new RenderTargetBitmap(Convert.ToInt32(this.Width * dpiScale),
Convert.ToInt32(this.Height * dpiScale),
600.0,
600.0,
PixelFormats.Pbgra32);
renderBitmap.Render(this);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawImage(renderBitmap, new Rect(0, 0, this.Width, this.Height));
}
writer.Write(visual);
doc.Close();
package.Close();
var pdfXpsDoc = PdfSharp.Xps.XpsModel.XpsDocument.Open(lMemoryStream);
XpsConverter.Convert(pdfXpsDoc, _pdfFileName, 0);
This snippet is working as in the grid lines are visible even in smaller viewing percentages but it makes my application stuck at the PDF save operation and also it throws System.OutofMemoryException with message "Insufficient memory to continue the execution of the program." However, the application doesn't crash.
To check the behavior of PDF viewer, I generated a table with multiple rows and columns in MS Word and saved it as a PDF. In that case, the table grid lines are clearly visible even at small viewing percentages.
Can anyone help me with this?
I assume the first code snippet creates a table in vector format (you do not supply a PDF that allows to verify this).
The second code snippet attempts to create a bitmap image (raster format).
Either way: with both vector and raster images it depends on the PDF viewer whether thin lines are visible. Adobe Reader has many options (like "Enhance thin lines", "Smooth line art", "Smooth images") that will have an effect on the actual display - to be set on the client computer, nothing to be set in the PDF.
I assume your test with MS Word also created a table in vector format, but maybe with thicker lines. So this test proofs nothing.
I had the same problem with disappearing grid lines when zooming out a PDF created with WPF.
The problem was that the TextBox objects in the Grid cells had a default background color (white) and a border color (black), and both were painted in the same place when zooming out. The solution was to not have a background at all, by setting the background to Transparent.
TextBox tx = new TextBox();
tx.Text = "X";
tx.SetValue(Grid.RowProperty, row);
tx.SetValue(Grid.ColumnProperty, col);
tx.BorderThickness = new Thickness(0.3, 0.3, 0, 0);
tx.BorderBrush = System.Windows.Media.Brushes.Black;
tx.Background = Brushes.Transparent;
grid.Children.Add(tx);
But what if you want to have some background in the grid cell? Then the solution is to add a separate Border object to the same Grid cell, and use Zindex to make sure that the Border object is painted in front of the other content.
TextBox tx = new TextBox();
tx.Text = "X";
tx.SetValue(Grid.RowProperty, row);
tx.SetValue(Grid.ColumnProperty, col);
tx.BorderThickness = new Thickness(0);
tx.Background = Brushes.LightPink;
grid.Children.Add(tx);
Border ct = new Border();
ct.SetValue(Grid.RowProperty, row);
ct.SetValue(Grid.ColumnProperty, col);
ct.BorderThickness = new Thickness(0.3, 0.3, 0, 0);
ct.BorderBrush = System.Windows.Media.Brushes.Black;
ct.Background = Brushes.Transparent;
ct.HorizontalAlignment = HorizontalAlignment.Stretch;
ct.VerticalAlignment = VerticalAlignment.Stretch;
Grid.SetZIndex(ct, 100);
grid.Children.Add(ct);
Also, UseLayoutRounding must be set to false (false is default). Otherwise lines with Thickness 0.5 or lower will disappear completely.

Printing In Silverlight. Some pages missing

I am maintaining an old application that prints checks from silverlight.
The checks are in a grid and the user selects them and presses the print button.
I verified that all the checks selected in the grid do get sent to the printer but I noticed that sometimes some are missing in the actual printout. I check the EndPrint even for errors and there is none.
How can I make sure all the data gets actually printed?
Here is the code for the printpage event
StackPanel stackPanel = new StackPanel();
CheckInfo check = selectedChecks[printItemIndex];
PrintCheck printCheck = BuildPrintCheck(check);
stackPanel.Children.Add(printCheck);
stackPanel.Measure(new Size(args.PrintableArea.Width, double.PositiveInfinity));
if (++printItemIndex < selectedChecks.Count)
args.HasMorePages = true;
args.PageVisual = stackPanel;
I found a workaround to this issue posted here
http://www.thomasclaudiushuber.com/blog/2009/11/25/how-to-print-dynamically-created-images-in-silverlight-4-beta/
Basically instead of putting the Image straight into the page, put a Rectangle and and at run time load the image dynamically, set it as the image source for an image brush and then set the fill property of the rectangle to the image brush.
OK it turned out that SilverLight5 (runtime) has an issue with printing images. The problem does not exist on clients running SilverLight4.
Here is how I fixed it in my code
private void PlaceImages()
{
var logoStreamResourceInfo = Application.GetResourceStream(new Uri("myApp;/Images/logo.png", UriKind.Relative));
var logo = new BitmapImage();
logo.SetSource(logoStreamResourceInfo.Stream);
var logoImageBrush = new ImageBrush();
logoImageBrush.ImageSource = logo;
upperLogo.Fill = logoImageBrush;
lowerLogo.Fill = logoImageBrush;
}

The content copied from a richtextbox is not getting saved in my database

Am trying to copy the content of a Richtextbox to another Richtextbox using the below code.
FlowDocument doc = RTB1.Document;
RTB1.Document = new FlowDocument();
RTB2.Document = doc;
But the copied line disappears if i try to save the screen where the RichTextBox(RTB2) is there.
Any help on this will be greatful.
In your code RTB1.Document = new FlowDocument(); will asign a new FlowDocument value to the RTB1.that's why the copied line disappears.
Try this
first you need to include the namespace and add the code below
using System.IO;
using System.Windows.Markup;
MemoryStream ms = new MemoryStream();
XamlWriter.Save(RTB1.Document, ms);
ms.Seek(0, SeekOrigin.Begin);
RTB2.Document = XamlReader.Load(ms) as FlowDocument;
After copying the content from one RichTextBox to another the content used to disappear because the focus was not coming back to the copied RichTextBox.
So the solution i used was to set the focus of RichTextBox2 after copying.
FlowDocument doc = RTB1.Document;
RTB1.Document = new FlowDocument();
RTB2.Document = doc;
RTB2.Focus();

Deleting a window's background image WPF

I'm having a problem in WPF where a window doesn't release it's file-lock on the background image file after closing, before another part of the application tries to write to the image.
So as an example; say I have a WPF app consisting of 3 windows, 1 "menu" selection window and 2 others. Both of the windows create an ImageBrush using a BitmapImage as the ImageSource (the same image).
Window A has a button that when pressed, cycles through the available background images by copying them each over the file used as the original ImageSource and creating a new ImageBrush and setting the Window.Background to the new brush.
Window B simply uses the ImageBrush to draw the Window.Background.
If Window A is launched, backgrounds switched, closed and then Window B launched, everything is fine.
If Window B is launched, closed, then Window A is launched and backgrounds switched it crashes. Trying to switch the backgrounds throws an IOException because:
"The process cannot access the file 'C:\Backgrounds\Background.png' because it is being used by another process."
So Window B must still be holding onto it somehow!? I have tried doing a GC.Collect(); GC.WaitForPendingFinalizers(); to see if that cures the problem but it doesn't.
The answer Thomas gave is correct, and works well if you have a file path, don't want to cache the bitmap, and don't want to use XAML.
However it should also be mentioned that BitmapImage has a built-in way to load the bitmap immediately by setting BitmapCacheOption:
BitmapImage img = new BitmapImage { CacheOption = BitmapCacheOption.OnLoad };
img.BeginInit();
img.UriSource = imageUrl;
img.EndInit();
or
<BitmapImage CacheOption="OnLoad" UriSource="..." />
This will load the bitmap immediately and explicitly close the stream, just as using a FileStream would, but with several differences:
It will work with any Uri, such as a pack:// Uri.
It can be used directly from XAML
The bitmap is cached in the bitmap cache, so future use of the same Uri won't go to the disk. In your particular application this may be a bad thing, but for other uses it may be a desirable feature.
I assume you're loading the image directly from the file, like that ?
BitmapImage img = new BitmapImage();
img.BeginInit();
img.UriSource = imageUrl;
img.EndInit();
Try to load it from a stream instead ; that way you can close the stream yourself after the image is loaded, so that the file isn't locked :
BitmapImage img = new BitmapImage();
using (FileStream fs = File.OpenRead(imageFilePath)
{
img.BeginInit();
img.StreamSource = fs;
img.EndInit();
}

Resize an Uploaded Image in Silverlight 3

I'm trying to resize an image in Silverlight 3 that has been submitted by a user via the OpenFileDialog control. I can grab the contents of the file and put it into a WriteableBitmap object and then display it on the screen just fine into an Image control. The Image control will even resize it to fit the size of the image control for me which is great.
The problem is the in memory image is still the original full resolution image, I kinda need to resize it in memory because I have a bunch of expensive operations I need to perform on it on a per pixel basis. So far I have the following code...
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
btnUploadPhoto.Click += new RoutedEventHandler(UploadPhoto_Click);
}
private void UploadPhoto_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "Image files (*.png;*.jpg;*.gif;*.bmp)|*.png;*.jpg;*.gif;*.bmp";
if (dialog.ShowDialog() == true)
{
WriteableBitmap bitmap = new WriteableBitmap(500, 500);
bitmap.SetSource(dialog.File.OpenRead());
imgMainImage.Source = bitmap;
txtMessage.Text = "Image size: " + bitmap.PixelWidth + " x " + bitmap.PixelHeight;
}
}
}
Problem is the WriteableBitmap class doesn't have a Resize method on it, and setting the height and width in the constructor doesn't seem to have any effect.
What you can do is create a new Image element and set its source to a Writeable bitmap created from the stream. Don't add this Image element to the visual tree. Create another WriteableBitmap of the final size you want. Then call Render on this WriteableBitmap passing the Image element and a ScaleTransform to resize the image to the appropriate size. You can then use the second WriteableBitmap as the source for a second Image element and add that to the visual tree. You can then allow the first Image and WriteableBitmap objects to get GCed so you get the memory back.
Have you looked at the WriteableBitmapEx project? It's an open source project with a tonne of extension methods for the WriteableBitmap class. Here's how you resize:
BitmapImage image = new BitmapImage();
image.SetSource(dialog.File.OpenRead());
WriteableBitmap bitmap = new WriteableBitmap(image);
WriteableBitmap resizedBitmap = bitmap.Resize(500, 500, WriteableBitmapExtensions.Interpolation.Bilinear);
// For uploading
byte[] data = resizedBitmap.ToByteArray();
I have used FJCore with some success, it's an open source C# imaging toolkit from Occipital. Includes in-memory resizing capability.
Also check out ImageMagick.

Resources