Printing a Collection in WPF - wpf

Is there any way to print in memory collection or variable size in WPF?
I am using the following code in which I print the ListView control. But when the content is larger than the vertical scroll bar takes over and cuts the content.
PrintDialog printDialog = new PrintDialog();
printDialog.ShowDialog();
printDialog.PrintVisual(lvDocumentSummary, "testing printing!");

To print multiple pages you just need to use a class that implements DocumentPaginator FixedDocument is one of the more complex implementations, FlowDocument is a simpler one.
FlowDocument fd = new FlowDocument();
foreach(object item in items)
{
fd.Blocks.Add(new Paragraph(new Run(item.ToString())));
}
fd.Print();
or
PrintDialog pd = new PrintDialog();
pd.PrintDocument(fd);

FixedDocument supports DataBinding (other than FlowDocument) like any other xaml document. just host the listview in a fixeddocument and display it in a DocumentViewer (which has built-in print support).
however, if your list is too long for one page, FixedDocument does not automatically generate a new page (like flowdocument does). therefore you have to create a new page maually with code, as this cannot be done in pure xaml.

If you want nice printing from WPF you need to build a FixedDocument and print that, unfortunately it can be very complex depending on what you are trying to print.
There's some example code that creates a FixedDocument here: http://www.ericsink.com/wpf3d/B_Printing.html

Here's a 2019 answer. Some of the old answers don't work anymore, eg. FlowDocumentReader doesn't have a Print method.
private void Button_Click(object sender, RoutedEventArgs e)
{
FlowDocument fd = new FlowDocument();
foreach (var item in COLLECTION) //<- put your collection here
{
fd.Blocks.Add(new Paragraph(new Run(item.ToString())));
}
PrintDialog pd = new PrintDialog();
if (pd.ShowDialog() != true) return;
fd.PageHeight = pd.PrintableAreaHeight;
fd.PageWidth = pd.PrintableAreaWidth;
IDocumentPaginatorSource idocument = fd as IDocumentPaginatorSource;
pd.PrintDocument(idocument.DocumentPaginator, "Printing Flow Document...");
}
}

Interesting, Is the ListView virtualized? If it is, the object are not drawn, that is a possibility. Take a look at the Printing example from Petzold.

Here is my solution to this problem. It is kinda shaky but works for my scenario.
I read my collection and transform it into a string. The whole collection now resides in a StringBuilder object. Next, I saw the text/string into a file on the client's machine and then run the notepad process with /p to print the contents of the file.
It works and it prints the contents successfully.
Finally, there is a timer which is called after 5 seconds and which removes the file. Basically within 5 seconds the request is already sent to the printer queue. But a better solution will be to make sure that the print job has been processed this way you will be 100% sure that the job has been performed.

Related

trouble displaying flowdocument

I'm having some problems displaying the contents of a flowdocument in a flowdocumentscrollviewer. I create a generic list that holds a class which contains an int, string and a flowdocument.
In a WPF listbox, I am trying to display the flowdocument in the scrollviewer alongside a button. I use the following function called from the WPF window constructor to populate the listbox
private void populateListBox()
{
foreach(Element el in _notesList)
{
StackPanel sp = new StackPanel();
sp.Orientation = Orientation.Horizontal;
Button b = new Button();
b.Content = el._theID;
sp.Children.Add(b);
FlowDocumentScrollViewer fdsv = new FlowDocumentScrollViewer();
fdsv.MinWidth = 400;
fdsv.Document = el._theDoc;
sp.Children.Add(fdsv);
ListBoxItem lbi = new ListBoxItem();
lbi.Content = sp;
noteList.Items.Add(lbi);
}
}
But the code does not work. There are no errors but the scrollviewers are just blank in the listbox. I also tried storing the classes in an ObservableList and binding to the Document property but that didn't work either.
Any ideas what is happening?
Nevermind. I figured it out.
Further down in the program execution I was copying the flowdocument blocks to a merged document in a foreach statement. This doesn't work even if you use Blocks.ToList(). I eventually found a way to copy the document contents to another document here.

Why do I need to call Dispatcher.BeginInvoke() to allow a visual to properly bind before printing?

I have a UserControl with a fixed size of 850x1100 to give me the same aspect ratio as a letter-sized piece of paper. I display this in my window inside a Viewbox, and it acts much like a print preview. The control inherits my window's DataContext, and when it's displayed on the screen, all the bindings work and it looks wonderful.
I've written the code below in my window's code behind (I feel it's a totally view-oriented operation) to attempt to print it. If I execute this code as written, the control prints, but does not appear to be data bound.
private void PrintButton_Click(object sender, RoutedEventArgs e) {
var dlg = new PrintDialog();
var result = dlg.ShowDialog();
if (result == null || !(bool)result)
return;
var page = new InspectionFormPrintView { DataContext = this.DataContext };
page.Measure(new Size(dlg.PrintableAreaWidth, dlg.PrintableAreaHeight));
page.Arrange(new Rect(new Point(0, 0), page.DesiredSize));
dlg.PrintVisual(page, "Inspection Form");
}
If I modify the last line in that method to
Dispatcher.BeginInvoke(new Action(() => dlg.PrintVisual(page, "Inspection Form")), DispatcherPriority.ApplicationIdle, null);
it will print just fine. Why is this?
As mentioned by LPL in the comments, this is required because WPF needs to perform the all the data bindings. Since WPF operations are queued on the Dispatcher, you need to queue your print operation to complete after DispatcherPriority.DataBind. Therefore, calling BeginInvoke with DispatcherPriority.Render or lower will give WPF time to process the bindings on the control, so they show up in your printed output.

How to Serialize an Image Control whose source is set at runtime?

I have a Grid containing several controls, the most important being three image controls.
I need to make a duplicate of this Grid. Serializing by saving the XAML in a MemoryStream doesnt seem to help because, obviously, when I set the source of the Image Control in the code behind at runtime, this change is not reflected in the XAML designer code. [Technically it is, but as
<Image.Source> System.Windows.Interop.InteropBitmap</Image.Source>
and I get some wierd exception]
So, how can I serialize my Image control?
More generally, how can I Clone my Grid control to reflect any changes to the UI that happened after the window loaded?
If I understood your question properly, I think you just need to create a clone of your grid at runtime. If so take a look at these threads
How can you clone a WPF object?
http://social.msdn.microsoft.com/Forums/en-HK/wpf/thread/e1a63ed2-a432-4c46-8f3b-4f172702cd7c
Use this function to clone an Object
public static T DeepClone<T>(T from)
{
using (MemoryStream s = new MemoryStream())
{
BinaryFormatter f = new BinaryFormatter();
f.Serialize(s, from);
s.Position = 0;
object clone = f.Deserialize(s);
return (T)clone;
}
}
above function was by Arcturus

Missing images in FlowDocument saved as XPS document

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)));

WPF flowdocument element names are reset?

I have a flowdocument with a named Span" test1" which I want to replace the contents of programmatically
TextRange tr = new TextRange(this.test1.ContentStart,this.test1.ContentEnd);
Run run = this.test1.Inlines.First() as Run;
run.Text = "replaced text";
This works, however the problem is when doing the update the name of the span is removed (looking at the xaml source). Is this by design? Is there any way to retain the Id's?
Hi, this is the method I use for debugging whats actually in the richtextbox (setting xaml source to textbox)
using (MemoryStream ms = new MemoryStream())
{
tr = new TextRange(this.richTextBox1.Document.ContentStart, this.richTextBox1.Document.ContentEnd);
tr.Save(ms, DataFormats.Xaml);
ms.Position = 0;
using (StreamReader sr = new StreamReader(ms))
{
this.textBox1.Text = sr.ReadToEnd();
}
}
This is the test contents of the richtextbox:
<FlowDocument>
<Paragraph>
This is my first <Span Name="test1">a huge amount of space</Span> between it and the second paragraph?
</Paragraph>
</FlowDocument>
Unfortunately, this is just the way FlowDocuments work (same with tags). The only (very hacky) solution I have found for this is to hide a name in the FontFamily property. This works because this property is a comma delimited list of strings, with the first string that matches an existing font family being used, and the rest being ignored. So if you do something like this:
document.FontFamily = "RealFont1, RealFont2, name=test1";
The whole string will be preserved. You may then access the "name" by searching the FontFamily for "name=".
Again, quite hacky, but after months of trying it was the only solution I could find.

Resources