Printing RTF with headers and footers - FlowDocument or something else? - wpf

I've been looking at importing an RTF into a flowdocument, obstinately for the purposes of unattended printing; after a lot of poking, using a FlowDocument seemed to be the right approach. I've got the RTF to generally work, however footers disappear when loading the RTF into a FlowDocument.
The RTF was generated in Word, and when loaded into Wordpad, the footers are visible, so I can only assume I'm loading the document wrong, or it's an issue with the default paginator, or possibly both.
This is what I have so far, which loads the file and does the printing:
[STAThread]
public static int Main(string[] args)
{
var documentPath = #"C:\temp\Example.rtf";
var fileStream = File.Open(documentPath, FileMode.Open, FileAccess.Read, FileShare.Read);
// Load the RTF into the flow document
var flowDocument = new FlowDocument();
TextRange textRange = new TextRange(flowDocument.ContentStart, flowDocument.ContentEnd);
textRange.Load(fileStream, DataFormats.Rtf);
flowDocument.ColumnWidth = double.PositiveInfinity;
var t = new Thickness(72);
flowDocument.PagePadding = t; // Page margin
// Get printer
var queue = LocalPrintServer.GetDefaultPrintQueue();
var capa = queue.GetPrintCapabilities();
// Configure paginator
var paginator = ((IDocumentPaginatorSource)flowDocument).DocumentPaginator;
paginator.PageSize = new Size(capa.OrientedPageMediaWidth.Value, capa.OrientedPageMediaHeight.Value);
// ...and print.
var writer = System.Printing.PrintQueue.CreateXpsDocumentWriter(queue);
writer.Write(paginator);
return 0;
}
...beyond that, I'm at a loss. It's not clear whether the footers simply haven't loaded (and thus aren't part of the FlowDocument) - I suspect this is the case as we're loading into a TextRange that marks the start and end of the main FlowDocument content; but I'm also suspecting I might need a custom paginator as well.
Are there any pointers to the docs I'm missing - a google search for "RTF footer import flowdocument paginator" (and similar) has (so far) revealed no pertinent results. The closest I've so far found is this SO question, but this doesn't cover fetching the header/footer from an RTF file.

Take a look at this MSDN blogpost they are doing almost the same you are doing. With a custom DocumentPaginator to scale the the original pages. I think this should do the trick. I also used it a long time ago and headers and footers were no problem.

To provide the header and footer property during printing, we usually handle the PrintPage event and provide these features in this event handler. Here is a sample for your reference. Although this sample is a printer class for DataGridView, you can take the logic of add the header and footer features.
· http://www.codeproject.com/KB/cs/DGVPrinter.aspx
If you are using the WebBrowser to print the HTML, you can custom the header and footer like mentioned in the following article.
· http://www.codeproject.com/KB/miscctrl/tips.aspx#tip3.8.2

Related

How to remove/disable the print button from the DocumentViewer in code (when on its own thread)?

I am running the following code in a background thread as an STA apartment to provide a Print Preview in a document viewer:
// Print Preview
public static void PrintPreview(FixedDocument fixeddocument)
{
MemoryStream ms = new MemoryStream();
using (Package p = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite))
{
Uri u = new Uri("pack://TemporaryPackageUri.xps");
PackageStore.AddPackage(u, p);
XpsDocument doc = new XpsDocument(p, CompressionOption.Maximum, u.AbsoluteUri);
XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(doc);
writer.Write(fixeddocument.DocumentPaginator);
var previewWindow = new Window();
var docViewer = new DocumentViewer();
previewWindow.Content = docViewer;
THIS FAILS ---> docViewer.CommandBindings.Remove(???Print Button???);
FixedDocumentSequence fixedDocumentSequence = doc.GetFixedDocumentSequence();
docViewer.Document = fixedDocumentSequence as IDocumentPaginatorSource;
previewWindow.ShowDialog();
PackageStore.RemovePackage(u);
doc.Close();
}
}
All works well. However, since this is running in its own thread--not the main thread--the print dialogue on the Document Viewer crashes.
In code, how can I remove and/or disable the Print button from the DocumentViewer?? (I have read everything I could find in Google, and it all is in XAML, not very helpful).
Any help is much appreciated. TIA
Update#1: The only way I can see to do this, is to drop the Print Button from the control template and use a custom Document Viewer. A workable style is given at Document Viewer Style.
It still would be nice if I could simply remove the button from the system Document viewer?
Using the method from this answer, you can alter the visibility of the PrintButton programmatically like this. Let's say I put the method in a class called UIElementHelper:
var button = UIElementHelper.FindChild<Button>(docViewer, "PrintButton");
button.Visibility = Visibility.Collapsed;

WPF Dynamic HyperLinks RichTextbox

I have seen several posts on various forms varying in times from 2006 to now about how to add hyperlinks to RichTextBox, but they all seem overly complex for what I want. I am creating a desktop chat client, and I receive input as strings, now among these strings may be some urls, I need those urls to be clickable. Which from what I gather means they need to be HyperLink objects.
Navigating through the RichTextBox and replacing the urls with HyperLinks seems to be no small feat. Does anyone have a relatively simple solution for this?
In my web client it's a simple one liner
value = value.replace(/(http:\/\/[^\s]+)/gi, '$1');
Never thought I'd see the day where C# actually makes it harder.
If you want to do an equivalent of value.replace(/(http:\/\/[^\s]+)/gi, '$1') in WPF:
<RichTextBox x:Name="MyRichTextBox" IsDocumentEnabled="True" IsReadOnly="True" />
And the code that converts the string is the following:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var htmlText = "Google's website is http://www.google.com";
MyRichTextBox.Document = ConvertToFlowDocument(htmlText);
}
private FlowDocument ConvertToFlowDocument(string text)
{
var flowDocument = new FlowDocument();
var regex = new Regex(#"(http:\/\/[^\s]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
var matches = regex.Matches(text).Cast<Match>().Select(m => m.Value).ToList();
var paragraph = new Paragraph();
flowDocument.Blocks.Add(paragraph);
foreach (var segment in regex.Split(text))
{
if (matches.Contains(segment))
{
var hyperlink = new Hyperlink(new Run(segment))
{
NavigateUri = new Uri(segment),
};
hyperlink.RequestNavigate += (sender, args) => Process.Start(segment);
paragraph.Inlines.Add(hyperlink);
}
else
{
paragraph.Inlines.Add(new Run(segment));
}
}
return flowDocument;
}
}
It uses the same regular expression you provided, which is lacking if you properly want to recognize URLs with a regular expression. This one doesn't recognize https ones and the last dot in the following sentence would be a part of the URL: "This is a URL: http://www.google.com/."
What the code does is to split the text based on the regular expression, iterate it and adds the correct elements to the FlowDocument constructed on the fly.
Clicking the Hyperlink should open your default browser.
Result:
That said, this is only good for read only usage of the RichTextBox (as indicated by the question in the comment).

Providing the WPF designer with an image at DesignTime

This is a restatement of my question, the revision history contains the original mess.
What it boils down to is "How do I get the application's directory from my WPF application, at design time?"
Which duplicates the question here so if you happen to be passing by please vote to close, thanks.
Do you need the image to be "Content - Copy if newer"? If you switch it to "Resource" you can use the following path to reference the file:
"/MyImage.JPG"
or a longer version
"pack://application:,,,/MyImage.JPG"
given that the image is in the root of the project, otherwise just change the URI to
"/Some/Path/MyImage.JPG"
UPDATE 1:
For me, the longer pack uri syntax works with an image marked as "Content - Copy if newer" as well. However, the shorter syntax does not work. I.e:
This works:
"pack://application:,,,/MyImage.JPG"
This does NOT work:
"/MyImage.JPG"
I my example I added the image to the root of the project, and marked it as "Content". I then bound the design time data context to a view model with a property returning the longer pack URI above. Doing that results in the Content image being shown correctly at design time.
UPDATE 2:
If you want to load a bitmap source from a pack uri, you can do so by using another overload of the BitmapFrame.Create which takes an URI as the first parameter.
If I understand your problem correctly you get the string with the pack uri as the first item in the object array that is passed to your converter. From this string you want to load a BitmapSource.
Since the string contains a pack URI, you can create an actual URI from the string and then use that URI to load the BitmapSource:
var imagePath = values[0] as string;
// ...
try
{
var packUri = new Uri(imagePath);
BitmapSource bitmap = BitmapFrame.Create(packUri, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
{
// ...
}
}
}
Just return the exact path of the image from your entity in ImagePath property, such as ..
"C:\MyImage.JPG"
..
OR
..
"C:\MyApp\bin\Debug\MyImage.JPG"
Then your binding (i.e. <Image Source="{Binding ImagePath}" />) in .xaml will start working..
I solved it by leveraging the clevers found in this stackoverflow answer.
public class DMyViewModel : PhotoViewModelBase
{
public override string ImagePath
{
get
{
string applicationDirectory =
(from assembly in AppDomain.CurrentDomain.GetAssemblies()
where assembly.CodeBase.EndsWith(".exe")
select System.IO.Path.GetDirectoryName(assembly.CodeBase.Replace("file:///", ""))
).FirstOrDefault();
return applicationDirectory + "\\MyImage.JPG";
}
}
}

How to prevent certain kinds of formatting from getting pasted into WPF RichTextBox

I want to allow some simple formatting commands within a WPF RichTextBox but not others.
I've created a toolbar that allows users to apply bold or italics, and use bulleted or numbered lists. (Basically, I only want to support the formatting commands that would be appropriate for a blog or wiki.)
The problem is that users can perform cut and paste operations that insert text with foreground and background colors, among other kinds of disallowed formatting. This can lead to nasty usability issues like users pasting white text onto a white background.
Is there any way to turn these advanced formatting features off? If not, is there a way I can intercept the paste operation and strip out the formatting I don't want?
You can intercept the paste operation like this:
void AddPasteHandler()
{
DataObject.AddPastingHandler(richTextBox, new DataObjectPastingEventHandler(OnPaste));
}
void OnPaste(object sender, DataObjectPastingEventArgs e)
{
if (!e.SourceDataObject.GetDataPresent(DataFormats.Rtf, true)) return;
var rtf = e.SourceDataObject.GetData(DataFormats.Rtf) as string;
// Change e.SourceDataObject to strip non-basic formatting...
}
and the messy part is keeping some but not all of the formatting. The rtf variable will be a string in RTF format that you can use a third-party libary to parse, walk the tree using a DOM-like pattern, and emit new RTF with just text, bold and italics. Then cram that back into e.SourceDataObject or a number of other options (see the docs below).
Here are PastingHandler docs:
DataObject.AddPastingHandler Method
Here is one of many RTF parsers:
NRTFTree - A class library for RTF processing in C#
Here is the code if you wanted to strip all formatting from pasted content (Not what you asked, but may be usefull to someone):
void OnPaste(object sender, DataObjectPastingEventArgs e)
{
if (!e.SourceDataObject.GetDataPresent(DataFormats.Rtf, true)) return;
var rtf = e.SourceDataObject.GetData(DataFormats.Rtf) as string;
FlowDocument document = new FlowDocument();
document.SetValue(FlowDocument.TextAlignmentProperty, TextAlignment.Left);
TextRange content = new TextRange(document.ContentStart, document.ContentEnd);
if (content.CanLoad(DataFormats.Rtf) && string.IsNullOrEmpty(rtf) == false)
{
// If so then load it with RTF
byte[] valueArray = Encoding.ASCII.GetBytes(rtf);
using (MemoryStream stream = new MemoryStream(valueArray))
{
content.Load(stream, DataFormats.Rtf);
}
}
DataObject d = new DataObject();
d.SetData(DataFormats.Text, content.Text.Replace(Environment.NewLine, "\n"));
e.DataObject = d;
}
}

Saving FlowDocument to SQL Server

I need to save WPF FlowDocuments to SQL Server. What is the best format for doing that? String? Blob? Does it matter in a document less than 5K words or so?
FlowDocument is not serializable so SWeko's answer above will not work.
You can use the methods below to get the FlowDocument to and from a Xaml string which can then be saved in the database using nvarchar(max).
var stringReader = new StringReader(info);
var xmlTextReader = new XmlTextReader(stringReader);
return (FlowDocument)XamlReader.Load(xmlTextReader);
and
var infoString = XamlWriter.Save(info);
If you just want to store the FlowDocument objects in a database, without any processing, I would recommend using binary serialization, and storing the resulting byte array into a varbinary(max). This is fast and scales well.
However, if you already have the FlowDocuments as XML files, than it would be easier just to dump them into a nvarchar(max) field, with no (added) serialization/deserialization overhead. This scales trivially for values under 8k, and then performs kinda OK until you hit around the 10MB mark.
You can serialize FlowDocument using the TextRange class. You can even use the RTF format. Saving:
FlowDocument docToSave; // Lets suppose this var is initialized.
var tr = new TextRange(docToSave.ContentStart,docToSave.ContentEnd);
var dst = new MemoryStream();
tr.Save(dst, DataFormats.Rtf);
dst.Close();
And loading:
FlowDocument docToLoad = new FlowDocument();
var tr = new TextRange(docToLoad.ContentStart,docToLoad.ContentEnd);
Stream src; // Lets suppose it is initialized.
tr.Load(src, DataFormats.Rtf);
src.Close();
See also https://www.wpf-tutorial.com/rich-text-controls/how-to-creating-a-rich-text-editor/

Resources