Question
How do you print a FlowDocument that have BlockUIContainer?
How can I force a Measure/Update/Arrange on a FlowDocument?
Background
I have a generated FlowDocument with paragraphs of text with a few Rectangle elements filled DrawingBrushes from a resource dictionary and BlockUIContainer with custom controls.
The document renders correctly when viewed in any of the FlowDocument* controls HOWEVER when the document is converted to a FixedDocument/XpsDocument, none of the Rectangle or BlockUIContainer elements render.
I'm almost certain it is because the control has not been measured/arranged, however cannot figure out how to force that to happen before it is converted to the XpsDocument.
I have walked the LogicalTree recursively and done the following,
UIElement element = (UIElement)d;
element.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
element.Arrange(new Rect(element.DesiredSize));
element.UpdateLayout();
where d is a DependencyObject. I can see that this sets the ActualWidth and ActualHeight properties when break-pointed in the debugger.
I have tried forcing the Dispatcher to render as suggested by Will ♦.
Code used to print the XpsDocument
public class XpsDocumentConverter
{
public static XpsDocumentReference CreateXpsDocument(FlowDocument document)
{
// Need to clone the document so that the paginator can work
FlowDocument clonedDocument = DocumentHelper.Clone<FlowDocument>(document);
Uri uri = new Uri(String.Format("pack://temp_{0}.xps/", Guid.NewGuid().ToString("N")));
MemoryStream ms = new MemoryStream();
Package pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
PackageStore.AddPackage(uri, pkg);
XpsDocument xpsDocument = new XpsDocument(pkg, CompressionOption.Normal, uri.AbsoluteUri);
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsDocument), false);
DocumentPaginator paginator = new FixedDocumentPaginator(clonedDocument, A4PageDefinition.Default);
rsm.SaveAsXaml(paginator);
return new XpsDocumentReference(ms, xpsDocument);
}
}
As you can see I'm also using a custom DocumentPaginator named 'FixedDocumentPaginator'; however I will not post that code as I doubt the issue is there as by the time it starts paginating the document in GetPage(int pageNumber) everything has already been converted a Visual and it is too late for layout.
Edit
Hmm. As I typed this, a thought just occurred to me that the cloned document may not have had a Measure/Arrange/UpdateLayout done.
Question: How can I force a Measure/Update/Arrange on a FlowDocument?
A possible hack that I could work would be to show the cloned document in one of the FlowDocumentViewers (perhaps off-screen).
Another possible solution that I just learnt about and haven't tried would be to call: ContextLayoutManager.From(Dispatcher.CurrentDispatcher).UpdateLayout();
ContextLayoutManager walks the logical tree for you and updates the layout.
Code used for cloning the document
public static FlowDocument Clone(FlowDocument originalDocument)
{
FlowDocument clonedDocument = new FlowDocument();
TextRange sourceDocument = new TextRange(originalDocument.ContentStart, originalDocument.ContentEnd);
TextRange clonedDocumentRange = new TextRange(clonedDocument.ContentStart, clonedDocument.ContentEnd);
try
{
using (MemoryStream ms = new MemoryStream())
{
sourceDocument.Save(ms, DataFormats.XamlPackage);
clonedDocumentRange.Load(ms, DataFormats.XamlPackage);
}
clonedDocument.ColumnWidth = originalDocument.ColumnWidth;
clonedDocument.PageWidth = originalDocument.PageWidth;
clonedDocument.PageHeight = originalDocument.PageHeight;
clonedDocument.PagePadding = originalDocument.PagePadding;
clonedDocument.LineStackingStrategy = clonedDocument.LineStackingStrategy;
return clonedDocument;
}
catch (Exception)
{
}
return null;
}
Posting this as future reference for others that are having similar rendering issues with FlowDocument/FixedDocument/XpsDocument.
A few things to note:
BlockUIContainers are not cloned when you use the above method. This was not immediately obvious until I printed the logical tree out the debug window using some helper methods (these methods are posted below - they are incredibly useful).
You need to display the document in a viewer and briefly show it on screen. Below is the helper method that I wrote to do this for me.
ForceRenderFlowDocument
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.
Visual and Logical Tree Helpers
public static string WriteVisualTree(DependencyObject parent)
{
if (parent == null)
return "No Visual Tree Available. DependencyObject is null.";
using (var stringWriter = new StringWriter())
using (var indentedTextWriter = new IndentedTextWriter(stringWriter, " "))
{
WriteVisualTreeRecursive(indentedTextWriter, parent, 0);
return stringWriter.ToString();
}
}
private static void WriteVisualTreeRecursive(IndentedTextWriter writer, DependencyObject parent, int indentLevel)
{
if (parent == null)
return;
int childCount = VisualTreeHelper.GetChildrenCount(parent);
string typeName = parent.GetType().Name;
string objName = parent.GetValue(FrameworkElement.NameProperty) as string;
writer.Indent = indentLevel;
writer.WriteLine(String.Format("[{0:000}] {1} ({2}) {3}", indentLevel,
String.IsNullOrEmpty(objName) ? typeName : objName,
typeName, childCount)
);
for (int childIndex = 0; childIndex < childCount; ++childIndex)
WriteVisualTreeRecursive(writer, VisualTreeHelper.GetChild(parent, childIndex), indentLevel + 1);
}
public static string WriteLogicalTree(DependencyObject parent)
{
if (parent == null)
return "No Logical Tree Available. DependencyObject is null.";
using (var stringWriter = new StringWriter())
using (var indentedTextWriter = new IndentedTextWriter(stringWriter, " "))
{
WriteLogicalTreeRecursive(indentedTextWriter, parent, 0);
return stringWriter.ToString();
}
}
private static void WriteLogicalTreeRecursive(IndentedTextWriter writer, DependencyObject parent, int indentLevel)
{
if (parent == null)
return;
var children = LogicalTreeHelper.GetChildren(parent).OfType<DependencyObject>();
int childCount = children.Count();
string typeName = parent.GetType().Name;
string objName = parent.GetValue(FrameworkElement.NameProperty) as string;
double actualWidth = (parent.GetValue(FrameworkElement.ActualWidthProperty) as double?).GetValueOrDefault();
double actualHeight = (parent.GetValue(FrameworkElement.ActualHeightProperty) as double?).GetValueOrDefault();
writer.Indent = indentLevel;
writer.WriteLine(String.Format("[{0:000}] {1} ({2}) {3}", indentLevel,
String.IsNullOrEmpty(objName) ? typeName : objName,
typeName,
childCount)
);
foreach (object child in LogicalTreeHelper.GetChildren(parent))
{
if (child is DependencyObject)
WriteLogicalTreeRecursive(writer, (DependencyObject)child, indentLevel + 1);
}
}
Usage
#if DEBUG
Debug.WriteLine("--- Start -------");
Debug.WriteLine(VisualAndLogicalTreeHelper.WriteLogicalTree(document));
Debug.WriteLine("--- End -------");
#endif
I found this solution here, and it helped me get the printing of the FlowDocment without having to render it off screen...So I hope it can help you!!
String copyString = XamlWriter.Save(flowDocViewer.Document);
FlowDocument copy = XamlReader.Parse(copyString) as FlowDocument;
Related
I'm trying to edit a paragraph in pptx through changing its text, font size, font style and alignment.
This is what i have done so far:
**this is the method im using to call the update paragraph**
public static void Main(string[] args)
{
using (PresentationDocument presentationDocument = PresentationDocument.Open("ppturl", true))
{
// Get the presentation part of the presentation document.
PresentationPart presentationPart = presentationDocument.PresentationPart;
// Verify that the presentation part and presentation exist.
if (presentationPart != null && presentationPart.Presentation != null)
{
// Get the Presentation object from the presentation part.
Presentation presentation = presentationPart.Presentation;
// Verify that the slide ID list exists.
if (presentation.SlideIdList != null)
{
SlideId sourceSlide = presentation.SlideIdList.ChildElements[0] as SlideId;
SlidePart slidePart = presentationPart.GetPartById(sourceSlide.RelationshipId) as SlidePart;
updateParagraph(slidePart);
}
}
}
Console.ReadLine();
CreateHostBuilder(args).Build().Run();
}
**Here im extracting the title in the slide because this is what i need.**
public static void updateParagraph(SlidePart slidePart)
{
if (slidePart == null)
{
throw new ArgumentNullException("presentationDocument");
}
if (slidePart.Slide != null)
{
// Find all the title shapes.
var shapes = from shape in slidePart.Slide.Descendants<Shape>()
where IsTitleShape(shape)
select shape;
foreach (P.Shape shape in shapes)
{
D.Paragraph paragraph = shape.TextBody.Elements<D.Paragraph>().FirstOrDefault();
shape.TextBody.RemoveAllChildren<D.Paragraph>();
AddNewParagraph(shape, "This is a new Slide");
}
}
}
**This is where i am trying to add a new paragraph with specific style**
public static void AddNewParagraph(this P.Shape shape, string NewText)
{
D.Paragraph p = new D.Paragraph();
P.TextBody docBody = shape.TextBody;
Justification justification1 = new Justification() { Val = JustificationValues.Center };
p.ParagraphProperties=new D.ParagraphProperties(justification1);
D.Run run = new D.Run(new D.Text(NewText));
D.RunProperties runProp = new D.RunProperties() { Language = "en-US", FontSize = 9, Dirty = false };
run.AppendChild(runProp);
D.Text newText = new D.Text(NewText);
run.AppendChild(newText);
Console.WriteLine("--------------------------------------------------------------");
Console.WriteLine(runProp.FontSize.ToString());
Console.WriteLine("--------------------------------------------------------------");
p.Append(run);
docBody.Append(p);
}
This is giving me an error whenever im trying to open the pptx "repair pptx error".
Can someone please provide a clear solution specific to pptx and not doc.?
Thankful..
You can try using Aspose.Slides for .NET. The following code example shows you how to change some paragraph properties with this library:
using (var presentation = new Presentation("example.pptx"))
{
var firstShape = (IAutoShape) presentation.Slides[0].Shapes[0];
var firstParagraph = firstShape.TextFrame.Paragraphs[0];
var firstPortion = firstParagraph.Portions[0];
firstPortion.Text = "New text.";
firstPortion.PortionFormat.FontHeight = 24;
firstPortion.PortionFormat.FontBold = NullableBool.True;
firstParagraph.ParagraphFormat.Alignment = TextAlignment.Center;
presentation.Save("example.pptx", SaveFormat.Pptx);
}
You can also evaluate Aspose.Slides Cloud SDK for .NET. This REST-based API allows you to make 150 free API calls per month for API learning and presentation processing. The following code example shows you how to change the paragraph settings using Aspose.Slides Cloud:
var slidesApi = new SlidesApi("my_client_id", "my_client_key");
var fileName = "example.pptx";
var slideIndex = 1;
var shapeIndex = 1;
var paragraphIndex = 1;
var portionIndex = 1;
var firstPortion = slidesApi.GetPortion(
fileName, slideIndex, shapeIndex, paragraphIndex, portionIndex);
firstPortion.Text = "New text.";
firstPortion.FontHeight = 24;
firstPortion.FontBold = Portion.FontBoldEnum.True;
slidesApi.UpdatePortion(
fileName, slideIndex, shapeIndex, paragraphIndex, portionIndex, firstPortion);
var firstParagraph = slidesApi.GetParagraph(
fileName, slideIndex, shapeIndex, paragraphIndex);
firstParagraph.Alignment = Paragraph.AlignmentEnum.Center;
slidesApi.UpdateParagraph(
fileName, slideIndex, shapeIndex, paragraphIndex, firstParagraph);
I work as a Support Developer at Aspose.
The program for ordering statements on the registry, I can not go to their pop-up window, selenium does not see that any new is being created.
Is it possible to do it through Xpath without using the transition to the Popup window, a browser function, or in another way in Selenium (Chrome)?
New window detection function:
public static string ClickAndSwitchWindow(IWebElement elementToBeClicked,
IWebDriver driver, int timer = 2000)
{
System.Collections.Generic.List<string> previousHandles = new
System.Collections.Generic.List<string>();
System.Collections.Generic.List<string> currentHandles = new
System.Collections.Generic.List<string>();
previousHandles.AddRange(driver.WindowHandles);
elementToBeClicked.Click();
Thread.Sleep(timer);
for (int i = 0; i < 20; i++)
{
currentHandles.Clear();
currentHandles.AddRange(driver.WindowHandles);
foreach (string s in previousHandles)
{
currentHandles.RemoveAll(p => p == s);
}
if (currentHandles.Count == 1)
{
driver.SwitchTo().Window(currentHandles[0]);
Thread.Sleep(100);
return currentHandles[0];
}
else
{
Thread.Sleep(500);
}
}
return null;
}
The piece of code itself:
//After this click of this element, a window opens:
//"Send request"
IWebElement PopWindowsstart = ww.Until(ExpectedConditions.ElementIsVisible(By.XPath("/html/body/div[1]/div[6]/div[4]/div/div/section/div[2]/div[2]/div/div/div[2]/div/div[2]/div/div/div/div[1]/div/div/div/div[1]/div/div/div/div[4]/div/div/div/div[1]/div/div/div/div[1]/div/div/span/span")));
//Search for a new window
string newWin = ClickAndSwitchWindow(PopWindowsstart, Browser, 2500);
PopupWindowFinder finder = new PopupWindowFinder(Browser);
//Switch to a new window
Browser.SwitchTo().Window(newWin);
//Statement Number:
IWebElement NumExctract = ww.Until(ExpectedConditions.ElementIsVisible(By.XPath("div[class='v-label v-label-tipFont tipFont v-label-undef-w'] b")));
//Read check
MessageBox.Show(NumExctract.Text);
//"Continue work"
ww.Until(ExpectedConditions.ElementIsVisible(By.XPath("/html/body/div[7]/div/div/div/div[3]/div/div/div/div[1]/div/div/div/div[2]/div/div/div/div[1]/div/div/div/div[1]/div/div/span/span"))).Click();
//"Change"
ww.Until(ExpectedConditions.ElementIsVisible(By.XPath("/html/body/div[1]/div[6]/div[4]/div/div/section/div[2]/div[2]/div/div/div[2]/div/div[2]/div/div/div/div[1]/div/div/div/div[2]/div/div/div/div[1]/div/div/div/div[2]/div/div/span/span"))).Click();
Thread.Sleep(300000);
Type window:
Let's make this a bit easier.
If you need to switch to a popup, try the below.
public static string SwitchToPopup()
{
var mainHandle = Driver.CurrentWindowHandle;
var handles = Driver.WindowHandles;
foreach (var handle in handles)
{
if (mainHandle == handle)
{
continue;
}
Driver.SwitchTo().Window(handle);
break;
}
var result = Url;
return result;
}
When you need to switch back, use:
public static void GoToMainHandle()
{
var handles = Driver.WindowHandles;
foreach (var handle in handles)
{
Driver.SwitchTo().Window(handle);
break;
}
}
That being said, your xpath is not something that should ever be used. Please look at https://www.w3schools.com/xml/xpath_intro.asp and rewrite it. When you use chrome to give you your xpath like:
ww.Until(ExpectedConditions.ElementIsVisible(By.XPath("/html/body/div[1]/div[6]/div[4]/div/div/section/div[2]/div[2]/div/div/div[2]/div/div[2]/div/div/div/div[1]/div/div/div/div[2]/div/div/div/div[1]/div/div/div/div[2]/div/div/span/span"))).Click();
If your dev adds a div in here somewhere, all of your tests will now fail. If your devs are not providing unique identifiers, work with them to resolve that. You should have id's, class names etc.
Try:
public static void PopUp()
{
_webDriver.SwitchTo().Alert().Accept();
}
I have a icon which has a few different sizes (16px, 32px, 64px). I am calling ToBitmap() on it, but it is always returning the 32px image. How do I retrieve the 64px one?
Does this help?
Icon sizedIcon = new Icon(Resources.ResourceIcon, new Size(64,64));
For anyone else stumbling upon the same problem, I've found a nice little solution.
Image img = new Icon(Properties.Resources.myIcon, width, height).ToBitmap()
This is a fairly painful limitation in the ResourceManager class. Its GetObject() method doesn't provide a way to pass extra arguments that would allow selecting the returned icon by size. A workaround is to add the icon to the project instead. Use Project + Add Existing Item, select your .ico file. Select the added icon and change the Build Action property to "Embedded Resource".
You can now retrieve the desired icon with code like this:
public static Icon GetIconFromEmbeddedResource(string name, Size size) {
var asm = System.Reflection.Assembly.GetExecutingAssembly();
var rnames = asm.GetManifestResourceNames();
var tofind = "." + name + ".ICO";
foreach (string rname in rnames) {
if (rname.EndsWith(tofind, StringComparison.CurrentCultureIgnoreCase)) {
using (var stream = asm.GetManifestResourceStream(rname)) {
return new Icon(stream, size);
}
}
}
throw new ArgumentException("Icon not found");
}
Sample usage:
var icon1 = GetIconFromEmbeddedResource("ARW04LT", new Size(16, 16));
var icon2 = GetIconFromEmbeddedResource("ARW04LT", new Size(32, 32));
Beware one possible failure mode: this code assumes that the icon was added to the same assembly that contains the method.
The following sets the icon size for all the buttons in the toolbar.
It relies on the resource name being stored in the button tag.
public void SetButtons(object toolstrip, int IconWidth, int IconHeight)
{
var ts = (ToolStrip) toolstrip;
var size = new System.Drawing.Size();
size.Height = IconSize;
size.Width = IconSize;
foreach (ToolStripButton tsBtn in ts.Items)
{
tsBtn.ImageScaling = ToolStripItemImageScaling.None;
var resourcename = (String) tsBtn.Tag;
if (resourcename != null)
{
var myIcon = (Icon) Properties.Resources.ResourceManager.GetObject(resourcename);
var newIcon = new Icon(myIcon, IconWidth, IconHeight);
tsBtn.Image = newIcon.ToBitmap();
}
}
}
The size is determined when you first create the Icon instance, so you need to specify which size you want to use when you create it, using one of the Icon constructors that take a Size parameter.
internal static class IconHelper {
public static Icon GetSize(this Icon icon, int width, int height) {
return icon.GetSize(new Size(width, height));
}
public static Icon GetSize(this Icon icon, Size size) {
using(var mem = new MemoryStream()) {
icon.Save(mem);
mem.Position = 0;
return new Icon(mem, size);
}
}
}
There is no built-in method in the .Net framework that does this.
Instead, you can use this library.
I have a icon which has a few different sizes (16px, 32px, 64px). I am calling ToBitmap() on it, but it is always returning the 32px image. How do I retrieve the 64px one?
Does this help?
Icon sizedIcon = new Icon(Resources.ResourceIcon, new Size(64,64));
For anyone else stumbling upon the same problem, I've found a nice little solution.
Image img = new Icon(Properties.Resources.myIcon, width, height).ToBitmap()
This is a fairly painful limitation in the ResourceManager class. Its GetObject() method doesn't provide a way to pass extra arguments that would allow selecting the returned icon by size. A workaround is to add the icon to the project instead. Use Project + Add Existing Item, select your .ico file. Select the added icon and change the Build Action property to "Embedded Resource".
You can now retrieve the desired icon with code like this:
public static Icon GetIconFromEmbeddedResource(string name, Size size) {
var asm = System.Reflection.Assembly.GetExecutingAssembly();
var rnames = asm.GetManifestResourceNames();
var tofind = "." + name + ".ICO";
foreach (string rname in rnames) {
if (rname.EndsWith(tofind, StringComparison.CurrentCultureIgnoreCase)) {
using (var stream = asm.GetManifestResourceStream(rname)) {
return new Icon(stream, size);
}
}
}
throw new ArgumentException("Icon not found");
}
Sample usage:
var icon1 = GetIconFromEmbeddedResource("ARW04LT", new Size(16, 16));
var icon2 = GetIconFromEmbeddedResource("ARW04LT", new Size(32, 32));
Beware one possible failure mode: this code assumes that the icon was added to the same assembly that contains the method.
The following sets the icon size for all the buttons in the toolbar.
It relies on the resource name being stored in the button tag.
public void SetButtons(object toolstrip, int IconWidth, int IconHeight)
{
var ts = (ToolStrip) toolstrip;
var size = new System.Drawing.Size();
size.Height = IconSize;
size.Width = IconSize;
foreach (ToolStripButton tsBtn in ts.Items)
{
tsBtn.ImageScaling = ToolStripItemImageScaling.None;
var resourcename = (String) tsBtn.Tag;
if (resourcename != null)
{
var myIcon = (Icon) Properties.Resources.ResourceManager.GetObject(resourcename);
var newIcon = new Icon(myIcon, IconWidth, IconHeight);
tsBtn.Image = newIcon.ToBitmap();
}
}
}
The size is determined when you first create the Icon instance, so you need to specify which size you want to use when you create it, using one of the Icon constructors that take a Size parameter.
internal static class IconHelper {
public static Icon GetSize(this Icon icon, int width, int height) {
return icon.GetSize(new Size(width, height));
}
public static Icon GetSize(this Icon icon, Size size) {
using(var mem = new MemoryStream()) {
icon.Save(mem);
mem.Position = 0;
return new Icon(mem, size);
}
}
}
There is no built-in method in the .Net framework that does this.
Instead, you can use this library.
I'm using a class I've derived from DocumentPaginator (see below) to print simple (text only) reports from a WPF application. I've got it so that everything prints correctly, But how do I get it to do a print preview before printing? I have a feeling I need to use a DocumentViewer but I can't figure out how.
Here's my Paginator Class:
public class RowPaginator : DocumentPaginator
{
private int rows;
private Size pageSize;
private int rowsPerPage;
public RowPaginator(int rows)
{
this.rows = rows;
}
public override DocumentPage GetPage(int pageNumber)
{
int currentRow = rowsPerPage * pageNumber;
int rowsToPrint = Math.Min(rowsPerPage, rows - (rowsPerPage * pageNumber - 1));
var page = new PageElementRenderer(pageNumber + 1, PageCount, currentRow, rowsToPrint)
{
Width = PageSize.Width,
Height = PageSize.Height
};
page.Measure(PageSize);
page.Arrange(new Rect(new Point(0, 0), PageSize));
return new DocumentPage(page);
}
public override bool IsPageCountValid { get { return true; } }
public override int PageCount { get { return (int)Math.Ceiling(this.rows / (double)this.rowsPerPage); } }
public override Size PageSize
{
get { return this.pageSize; }
set
{
this.pageSize = value;
this.rowsPerPage = PageElementRenderer.RowsPerPage(this.pageSize.Height);
if (rowsPerPage <= 0)
throw new InvalidOperationException("Page can't fit any rows!");
}
}
public override IDocumentPaginatorSource Source { get { return null; } }
}
The PageElementRenderer is just a simple UserControl that displays the data (at the moment just a list of rows).
Here's how I use my Row Paginator
PrintDialog dialog = new PrintDialog();
if (dialog.ShowDialog() == true)
{
var paginator = new RowPaginator(rowsToPrint) { PageSize = new Size(dialog.PrintableAreaWidth, dialog.PrintableAreaHeight) };
dialog.PrintDocument(paginator, "Rows Document");
}
Sorry for the code dump, but I didn't want to miss something relevant.
So I got it working after reading Pro WPF in C# 2008 (Page 726).
Basically the DocumentViewer class needs an XPS file to present a print preview of it. So I do the following:
PrintDialog dialog = new PrintDialog();
var paginator = new RowPaginator(rowsToPrint) { PageSize = new Size(dialog.PrintableAreaWidth, dialog.PrintableAreaHeight) };
string tempFileName = System.IO.Path.GetTempFileName();
//GetTempFileName creates a file, the XpsDocument throws an exception if the file already
//exists, so delete it. Possible race condition if someone else calls GetTempFileName
File.Delete(tempFileName);
using (XpsDocument xpsDocument = new XpsDocument(tempFileName, FileAccess.ReadWrite))
{
XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
writer.Write(paginator);
PrintPreview previewWindow = new PrintPreview
{
Owner = this,
Document = xpsDocument.GetFixedDocumentSequence()
};
previewWindow.ShowDialog();
}
I'm creating the print dialog to get the default page size. There's probably a better way to do this.
XpsDocument is in ReachFramework.dll (Namespace System.Windows.Xps.Packaging);
Here's the PrintPreview Window.
<Window x:Class="WPFPrintTest.PrintPreview"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="previewWindow"
Title="PrintPreview" Height="800" Width="800">
<Grid>
<DocumentViewer Name="viewer"
Document="{Binding ElementName=previewWindow, Path=Document}" />
</Grid>
</Window>
The code behind just has a Document property like so:
public IDocumentPaginatorSource Document
{
get { return viewer.Document; }
set { viewer.Document = value; }
}
The following code uses a MemoryStream to do a print preview.
MemoryStream stream = new MemoryStream();
Package package = Package.Open(stream, FileMode.Create, FileAccess.ReadWrite);
var uri = new Uri(#"memorystream://myXps.xps");
PackageStore.AddPackage(uri, package);
var xpsDoc = new XpsDocument(package);
xpsDoc.Uri = uri;
XpsDocument.CreateXpsDocumentWriter(xpsDoc).Write(paginator);
documentViewer.Document = xpsDoc.GetFixedDocumentSequence();
Remember to use close() and remove package from PackageStore when the print preview is closed.
WPF doesn't come with any built-in Print Preview functionality, if you want to do a print preview, you're going to have to construct it yourself. Fortunately, it's shouldn't be that difficult.
You've already got the pagination code, which creates your DocumentPage objects. These objects contain a Visual, which you can go ahead and display in your UI.
What you'll end up doing, is paginating your entire document, collecting all the DocumentPage objects, then displaying their visuals inside of a scrolling StackPanel or something similar. These are the same DocumentPage objects that you can then send to the printer.
XpsDocument doc = new XpsDocument("filename.xps", FileAccess.Read);
docViewer.Document = doc.GetFixedDocumentSequence();
doc.Close();
The WinForm code for print preview is:
PrintPreviewDialog PrintPreviewDialog = new PrintPreviewDialog();
PrintPreviewDialog.Document = PrintDocument;
PrintPreviewDialog.ShowDialog();
P.s.: The original poster has commented that this is the wrong answer for an WPF application.
I dont think there is a built in way of doing this
Here is how I got it working in NHaml
var documentViewHostDialog = new DocumentDialog();
documentViewHostDialog.LoadDocument(load);
documentViewHostDialog.ShowDialog();
Where "load" is either a FlowDocument or IDocumentPaginatorSource
and here is the DocumentDialog
http://code.google.com/p/nhaml/source/browse/trunk/src/NHaml.Xps/DocumentDialog.xaml
http://code.google.com/p/nhaml/source/browse/trunk/src/NHaml.Xps/DocumentDialog.xaml.cs
Hope it works for your case.