WPF DocumentViewer Find-function and FixedPage documents - wpf

.Net contains a nice control called DocumentViewer. it also offers a subcontrol for finding text in the loaded document (that's at least what it is supposed to do).
When inserting FixedPage's objects as document source for the DocumentViewer, the find-functionality just does not find anything. Not even single letters. I haven't tried FlowDocument's yet,
as the documentation for DocumentViewer is not that useful and the resources on the net are not actually existing, I now want to ask the stackoverflow community:
What does it need to get the Find-Function of the WPF DocumentViewer working with FixedPage documents?
[btw, I don't use custom ControlTemplates for DocumentViewer]

I had this same problem with FixedDocuments. If you convert your FixedDocument to an XPS document then it works fine.
Example of creating an XPS Document in memory from a FixedDocument then displaying in a DocumentViewer.
// Add to xaml: <DocumentViewer x:Name="documentViewer" />
// Add project references to "ReachFramework" and "System.Printing"
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.IO;
using System.IO.Packaging;
using System.Windows.Xps.Packaging;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Set up demo FixedDocument containing text to be searched
var fixedDocument = new FixedDocument();
var pageContent = new PageContent();
var fixedPage = new FixedPage();
fixedPage.Children.Add(new TextBlock() { Text = "Demo document text." });
pageContent.Child = fixedPage;
fixedDocument.Pages.Add(pageContent);
// Set up fresh XpsDocument
var stream = new MemoryStream();
var uri = new Uri("pack://document.xps");
var package = Package.Open(stream, FileMode.Create, FileAccess.ReadWrite);
PackageStore.AddPackage(uri, package);
var xpsDoc = new XpsDocument(package, CompressionOption.NotCompressed, uri.AbsoluteUri);
// Write FixedDocument to the XpsDocument
var docWriter = XpsDocument.CreateXpsDocumentWriter(xpsDoc);
docWriter.Write(fixedDocument);
// Display XpsDocument in DocumentViewer
documentViewer.Document = xpsDoc.GetFixedDocumentSequence();
}
}
}

I had trouble with searching text in richtextbox, it was too slow. What I did was crunch the xaml every time I wanted to search. I improved several orders of magnitude.
It's a big workaround based in a part of the Chris Anderson's book.
Cheers

Related

Draw large DrawingVisual in Windows Forms

Background
I have a large report in my WinForms application that is generated into a WPF DrawingVisual. I want to display the report in a separate resizable window that enables the users to scroll up/down as they read it. In this case the report doesn't have any page breaks and consist of one large page.
First attempt:
Use a customized DocumentViewer to display the report.
Added the DrawingVisual into a FixedDocument with a custom height, added the FixedDocument into a FixedDocumentSequence and finally passed the document sequence into the print preview window (that uses a custom DocumentViewer).
var previewWindow = new ReportPrintPreview(docSeq);
previewWindow.Show();
Classes:
public class ReportPrintPreview : Window
{
private readonly DocumentViewer docViewer;
public ReportPrintPreview(IDocumentPaginatorSource doc)
{
docViewer = new CustomDocumentViewer();
docViewer.Document = doc;
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
AddChild(docViewer);
}
}
public class CustomDocumentViewer : DocumentViewer
{
public CustomDocumentViewer()
{
ShowPageBorders = false;
}
protected override void OnPrintCommand()
{
}
}
This works and gives good performance for large reports, but I find the customization possibilities of the DocumentViewer limiting. Want to add combo boxes in the toolbar and control the zoom of the report when the window is resized for example.
Second attempt:
Use a regular Windows Forms form and draw the DrawingVisual using a ElementHost. The element host is placed inside a panel to enable scrolling.
var form = new ReportViewer(visual, pSize);
form.ShowDialog();
Classes:
public partial class ReportViewer : System.Windows.Forms.Form
{
public ReportViewer(DrawingVisual visual, Size size)
{
InitializeComponent();
var wpfPanel = new WpfDrawingUserControl(visual);
elementHost1.Width = size.Width;
elementHost1.Height = size.Height;
elementHost1.Child = wpfPanel;
}
}
public class WpfDrawingUserControl : System.Windows.Controls.UserControl
{
public WpfDrawingUserControl(DrawingVisual visual)
{
var image = new Image();
image.Source = new DrawingImage(visual.Drawing);
Content = image;
}
}
I create a UserControl that contains an image that is created from the DrawingGroup of the DrawingVisual.
This kind of works, but the performance when scrolling is bad. And it even crashes when the report is large enough.
Solution?
How can I do more effective drawing of the DrawingVisual in the form? I guess the whole DrawingVisual will be painted in the UserControl even when just a part of the report is showing in the scroll enabled panel.
Alternatively, how can I customize the DocumentViewer or create my own WPF control to display the DrawingVisual.

What's the correct way to add a PageContent/ FixedPage to a FixedDocument in WPF?

In WPF, in order to add a FixedPage to a FixedDocument in code one needs to:
var page = new FixedPage();
var pageContent = new PageContent();
((IAddChild)pageContent).AddChild(page);
This appears to be the only way, however:
The MSDN documentation explicitly says one shouldn't do this ('This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.'- PageContent.IAddChild.AddChild Method).
It's ugly to have to cast to an explicit interface implementation in order to add the content to PageContent.
It's not simple to perform the basic operation of PageContent.
The documentation doesn't actually explain how to do this and I couldn't find any other information on how to do it. Is there another way? A 'correct' way?
According to MSDN documentation, you simply add a FixedPage object to the PageContent.Child property and then add that to the FixedDocument by calling the FixedDocument.Pages.Add method.
For instance:
public FixedDocument RenderDocument()
{
FixedDocument fd = new FixedDocument();
PageContent pc = new PageContent();
FixedPage fp = new FixedPage();
TextBlock tb = new TextBlock();
//add some text to a TextBox object
tb.Text = "This is some test text";
//add the text box to the FixedPage
fp.Children.Add(tb);
//add the FixedPage to the PageContent
pc.Child = fp;
//add the PageContent to the FixedDocument
fd.Pages.Add(pc);
return fd;
}

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

Writing out FlowDocument xaml with namespace using XmlWriter

I've got a collection of data that needs to be converted to a .xaml file that can later be loaded as a FlowDocument into a FlowDocumentReader. I don't directly instantiate Paragraphs, Runs, rather I generate the xaml to create the document later.
What I've tried:
I iterate through the data, creating XElements for Paragraphs, Runs, InlineUIContainers, etc. and build up the FlowDocument structure just fine and then call:
XmlWriter writer = XmlWriter.Create("output.xaml");
flowDocElem.WriteTo(writer);
writer.Close();
In the consuming app, I do this:
flowDocument = XamlReader.Load(xamlFile) as FlowDocument;
flowDocumentReader.Document = flowDocument;
xamlFile.Close();
But the loading fails because it doesn't know what a FlowDocument is. The FlowDocument element looks like so:
<FlowDocument Name="testDoc">
(There's no namespace there to shed light as to what a FlowDocument is when it is read in.)
If I hand edit the .xaml and modify the element to be:
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Name="testDoc">
Then it'll load just fine.
When creating the XElement for the FlowDocument, I've tried to do this:
new XElement("FlowDocument", new XAttribute("Name", "testDoc"), new XAttribute("xmlns", "http://schemas.microsoft.com/winfx/2006/xaml/presentation"));
but that doesn't work either - gives me an error if I try to create the namespace attribute.
I can completely cheat and stuff that xmlns into the element and then call something like
File.WriteAllText("output.xaml", fixedTxt);
but that feels dirty and so I think I'm just plain doing it wrong.
Thoughts?
Update:
While this probably isn't the prescriptive solution to the problem, it does work:
By adding a ParserContext to the XamlReader, I was able to get past the problem with loading the FlowDocument xml.
FileStream xamlFile = new FileStream("output.xaml", FileMode.Open, FileAccess.Read);
XamlReader x = new XamlReader();
ParserContext parserContext = new ParserContext();
parserContext.XmlnsDictionary.Add("","http://schemas.microsoft.com/winfx/2006/xaml/presentation");
flowDocument = XamlReader.Load(xamlFile, parserContext) as FlowDocument;
flowDocumentReader.Document = flowDocument;
xamlFile.Close();
Try using XamlWriter instead of XmlWriter.
If you use XLinq you should try the following:
XNamespace ns = #"http://schemas.microsoft.com/winfx/2006/xaml/presentation";
XNamespace xns = #"http://schemas.microsoft.com/winfx/2006/xaml";
XElement someElement = new XElement(ns + "FlowDocument",
new XAttribute(xns + "Name", name),
...);

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

Resources