I am trying to find a good way to print a flow document in WPF. What I want is to have a possibility to see how the document turns out as I design it, so therefore creating a pure FlowDocument as a XAML is out of the questions (as Visual Studio wont show the design view for it).
So what I have done now is to create a window that contains a FlowDocument like this (some excessive parts have been removed to make the code more consise):
<Window x:Class="MyNamespace.ProjectPrintout...>
<Grid>
<FlowDocumentReader>
<FlowDocument ColumnWidth="500" Name="Document">
<!-- Header -->
<Paragraph Name="HeaderText">
The header will go here
</Paragraph>
</FlowDocument>
</FlowDocumentReader>
</Grid>
</Window>
This is a bit strange since I will never show this Window to the user, and I only wrap the FlowDocument with a Window so that I can see how it looks like as I develop it. This Ican live with.
So somewhere else in my application, I want to print this FlowDocument to the default printer, but I also have to set the header dynamically (in addition to many other parts of the documents that needs dynamic data that are omitted here).
The code to print looks like this:
var printout = new ProjectPrintout();
printout.HeaderText= new Paragraph(new Run("Proper header text"));
var document = printout.Document;
var pd = new PrintDialog();
IDocumentPaginatorSource dps = document;
pd.PrintDocument(dps.DocumentPaginator, "Document");
The document is getting printed, and looks fine except that the header text still shows "The header will go here", even if I replaced it from my code with "Proper header text". I also tried changing it like this:
(printout.HeaderText.Inlines.FirstInline as Run).Text = "Proper header text";
But the result is the same.
So the question is: How can I change the contents in the FlowDocument from code before I print it, or are there a better way to do this instead of my approach?
MVVM to the rescue:
Epiphany: UI is not Data. UI is not a data store. UI is meant to show Data, not to store it.
1 - Create a simple object to hold your data
public class MyDocumentViewModel: INotifyPropertyChanged //Or whatever viewmodel base class
{
private string _header;
public string Header
{
get { return _header; }
set
{
_header = value;
NotifyPropertyChange(() => Header);
}
}
//Whatever other data you need
}
2 - Define Bindings in your Document;
<Paragraph>
<Run Text="{Binding Header}"/>
</Paragraph>
3 - Set your FlowDocument's DataContext to an instance of this class:
var flowdoc = new YourFlowDocument();
var data = new MyDocumentViewModel { Header = "this is the Header" };
//whatever other data
flowdoc.DataContext = data;
//do the printing stuff.
Related
i get exception when i run the FlowDoc in separate thread,even using the dispatcher invok the problem cannot be solved.
i read some articles about serializing the flowdoc into xdoc and serialize back but its sounds like trouble.
i have been with this problem for 2 days and my head is cracked, please help :)
here is my code behind
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
for (int i = 0; i < 10; i++)
{
mcFlowDoc = new FlowDocument();
// Create a paragraph with text
Paragraph para = new Paragraph();
para.Inlines.Add(new Bold(new Run("This is a Title \n")));
para.Inlines.Add(new Run("I am a flow document. Would you like to edit me? \n"));
Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
mcFlowDoc.Blocks.Add(para);
}));
}
}).Start();
and comes my XAML, which i just use a normal RichTextBox and bound the flowdoc to the Document property :
<fsrtb:FsRichTextBox x:Name="EditBox" Document="{Binding mcFlowDoc}" Grid.Row="0" Margin="10,10,10,91" ToolbarBackground="#FFD2CB84" ToolbarBorderBrush="#FFC6BE75" ToolbarBorderThickness="1,1,1,0" CodeControlsVisibility="Collapsed" />
Thank you.
Ok, well the bad news is RTB doesn't directly support data binding. There's a page somewhere on the MSDN site that explains why they did this but either way it was a deliberate design decision.
As far as I'm aware you have two options. The first is to roll your own solution. This basically involves creating view models for all the item types you want to display, maintaining a collection of them in your view model and using a behavior to bind to it and populate the RTB. Your behavior would also have to set up the bindings manually, i.e. instead of this:
para.Inlines.Add(new Bold(new Run("This is a Title \n")));
...you would instead do something like this:
var binding = new Binding
{
Path = new PropertyPath("BackgroundProperty"),
Mode = BindingMode.OneWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
var run = new Run();
BindingOperations.SetBinding(run, Run.TextProperty, binding);
para.Inlines.Add(new Bold(run));
...which in this case adds a binding for the text property. I imagine this could be greatly simplified by using DataTemplates to specify the controls to use for each view model element and specify the bindings in XAML etc. If implemented correctly this would allow you create the document itself in the GUI thread once at load time but then allow you to update the view model properties for text, color etc in any thread you like without having to recreate the entire document.
The second option is to use a library where someone else has already done this work for you, and for that I would recommend you have a look at the WPF Tookkit's implementation of a bindable RichTextBox.
This question already has an answer here:
Concatenate StaticResources in WPF
(1 answer)
Closed 5 years ago.
I'm writing a WPF application and I need to italicise words within descriptions on my view while still having them within resources to provide multi-lingual support later on. These descriptions are resourced and pass through a director that matches the descriptions in the view model by their code to their appropriate placement.
What I have tried:
I've begun resourcing the words of importance separately and added placeholder codes within the description resources. I then wanted to insert the words of importance into the description strings as they pass through the director but I can't italicise them at this stage either as they're just strings! How can i do achieve this?
This is typically a case where you are intertwining your data with the view of your data. You should keep them separated, as Model-View-Controller (MVC) advises.
You have some data that consists of two parts:
your description
the texts within your description that needs to be emphasized
This data can be displayed on several ways. One way - the method you want - is to display the complete description while the texts that need to be emphasized must be shown in italics.
But one could also think of a viewing method where the emphasized words are shown in bold fond, or a different font, or in red, or upside down.
By separating your data from the way you view your data, it is possible to change this view without having to change the data itself
Now back to your question. So you have some text, and some subtext that need to be emphasized. How you know which text needs to be emphasized is out of scope of your question. How you put this information inside this class is also out-of-scope of this question. All you need is some translate function that will translate your text into a format that allows interpreters of the translated text to know the complete text, and which phrases must be emphasized.
How to mark where the emphasize starts and where it stops is not really important, just use some well-defined method.
For instance, you could use the HTML method for emphasis:
Some normal text <em>is emphasized</em> and normal again
If you want, you could use any other method, like adding double forward slashes to switch emphasizing on or off, or anything else. It doesn't matter, as long as it is well defined.
So the class that holds your text and your emphasized needs a procedure to extract the translated text
public string ExtractTranslatedText()
{
// TODO: take your original text and add <em> and </em>
// to mark begin and end of emphasis
}
It seems that you almost succeeded creating this function.
Now once you've got a string that holds this emphases, you need to display it. It depends on your display class how this can be done.
Suppose you want to display it italicized in a RichTextBox. You could derive your class from RichTextBox, or you could create an extension function for RichTextBox.
See Extension Methods Demystified
public static class RichTestBoxExtensions
{
// TODO: add the required functions
}
We need a function that will append some text to the already existing text in the text box in a given System.Drawing.FontStyle:
public static void AppendText(this RichTextBox box, string text, FontStyle fontStyle)
{
box.SelectionStart = box.TextLength;
box.SelectionLength = 0;
Font savedSelectionFont = box.SelectionFont;
box.SelectionFont = new Font(box.SelectionFont, fontStyle);
box.AppendText(text);
box.SelectionFont = savedSelectionFont;
}
If you want, you could also add methods to show the text in a different color.
The code will be very similar.
Now you need to put your translated string with emphasis markers in the rich text box
public static ShowEmphasis(this RichTextBox, string text, FontStyle emphasisStyle)
{
const string emphasisOn = "<em>";
const string emphasisOff = "</em>";
while(!String.IsNullOrEmpty(text))
{ // still some text to print
// get the substring until first emphasisOn
int indexStartEmphasis = text.Index(emphasisOn);
if (indexStartEmphasis == -1)
{ // no emphasisOn anymore: write all in emphasisStyle
richTextBox.AppendText(text, emphasisStyle);
text = String.Empty; // no text left
}
else
{ // write until emphasisOn:
string normalText = text.SubString(0, indexStartEmphasis);
richTextBox.AppendText(normalText, FontStyle.Normal);
// remove the normalText + <em> from text:
text = text.Substring(indexStartEmphasis + emphasisOn.Length);
// do the same until emphasisOff
int indexStopEmphasis = text.Index(emphasisOff);
if (indexStopEmphasis == -1)
{ // no emphasisOff anymore: write all in emphasisStyle
richTextBox.AppendText(text, FontStyle.Normal);
text = String.Empty; // no text left
}
else
{ // write until emphasisOff in emphasisStyle:
string emphasizedText = text.SubString(0, indexStopEmphasis);
richTextBox.AppendText(emphasizedText, emphasisStyle);
// remove the emphasizedlText + </em> from text:
text = text.Substring(indexStopEmphasis + emphasisOff.Length);
}
}
}
}
If your resources will be consumed by a TextBlock you have to separate the text into separate inlines.
E.g.:
The text you want to show is This is an italic, bold and underlinded text.
This string need to be split up into separate parts for your resource file like the following:
Res1: This is an
Res2: italic
Res3: ,
Res4: bold
Res5: and
Res6: underlined
Res7: text
Your XAML then should look like the following:
<TextBlock FontSize="14">
<Run Text="{x:Static p:Resources.Res1}" />
<Run Text="{x:Static p:Resources.Res2}" FontStyle="Italic" />
<Run Text="{x:Static p:Resources.Res3}" />
<Run Text="{x:Static p:Resources.Res4}" FontWeight="Bold" />
<Run Text="{x:Static p:Resources.Res5}" />
<Run Text="{x:Static p:Resources.Res6}" TextDecorations="Underline" />
<Run Text="{x:Static p:Resources.Res7}" />
</TextBlock>
This results in the following format:
If you have an entire description as one string presented in f.e. a label, and you only want one part of this string italic, then this is not possible.
In case of a label (not sure what control is presenting your descriptions) you can create a seperated label for this important word, and place this specific label (with the italic property set) where you want it to be.
I am devoloping a wpf c# program that manages a school. From time to time the user has to print certifications for a single student. For instance a certification that this student is learning in our institution, or a certification with the amount of his stipend.
On these reports there is:
fixed text
dynamic information about the student like his name and so on
For this task I built rdls and it serves the purpose, but the users requested faster speed.
My question is: are rdls (working off a report server) the right method for this task? or is there a different faster option?
I use a FlowDocument for simple printing functionality such as this. You add XAML elements to the document just as you would when programmatically creating XAML in a window. Use the same layout controls (grids, stack panels, etc) to arrange all the other controls (text paragraphs, images, etc), and when the FlowDocument is printed it will be "flowed" into the page(s) based on various factors such as the paper size selected in the printer dialog.
Disclaimer: this was copy/pasted in a rush but it should give you an idea of how it works!
// Show the print dialog
var dlg = new PrintDialog();
if (!dlg.ShowDialog().GetValueOrDefault())
{
// User cancelled
return;
}
// Create and initialise the FlowDocument
_doc = new FlowDocument();
_doc.FontFamily = new FontFamily("Arial");
_doc.FontSize = 14;
// Add a paragraph of text
var para = new Paragraph(new Run("My paragraph....."))
{
FontSize = 14,
Foreground = new SolidColorBrush(Colors.Black),
Margin = new Thickness(0,0,0,12)
};
_doc.Blocks.Add(para);
// Add an image
var para = new Paragraph();
var img = new Image
{
Source = bitmapSource,
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(0,0,0,12)
};
para.Inlines.Add(img);
_doc.Blocks.Add(para);
// Print
var documentPaginator = ((IDocumentPaginatorSource)_doc).DocumentPaginator;
dlg.PrintDocument(documentPaginator, "My print job");
In my application I have all this wrapped in a class (as I have a few places where I need printing functionality). The class creates and initialises the FlowDocument in its constructor, and provides various methods such as "AddParagraph()", "AddImage()", with different overloads for specifying margins, fonts, font sizes, etc.
The document paginator bit at the end is a simplified version of my implementation, but it may be sufficient for your needs. (I've created a custom document paginator that provides the ability to set a header and footer on each page).
How to bind data dynamically with the document property of the rich textbox. I am using MVVM in Wpf with c#?
EDIT:
I tried with this example in "codeproject.com/KB/WPF/BindableWPFRichTextBox.aspx"; but i can't understand what is happening in that example. I am very new to WPF and MVVM.
It's throwing error in the line
try {
var stream = new MemoryStream(Encoding.UTF8.GetBytes(GetDocumentXaml(richTextBox)));
var doc = (FlowDocument)XamlReader.Load(stream);
// Set the document
richTextBox.Document = doc;
}
catch (Exception) { richTextBox.Document = new FlowDocument(); }
the error is like "Data at the root level is invalid. Line 1, position 1." i am giving value like "Sample Text"
I found the xaml text should be like
<FlowDocument PagePadding="5,0,5,0" AllowDrop="True" xmlns="schemas.microsoft.com/winfx/2006/xaml/… generated by app back-end</Paragraph>
</FlowDocument>" But how to get this text?
I hope I interpret your question correctly:
I assume you are binding to a normal string (sample text) with the RichTextBox you got from codeproject. This will not work, 'cause the Document you have to bind is a FlowDocument and it has a specific format. If you assign a string you will get the error "data invalid" when it tries to create a FlowDocument from the string
Here's a link on how to create a FlowDocument via XAML or via CodeBehind.
http://msdn.microsoft.com/en-us/library/aa970909.aspx
Then the converter comes into play: Out of the string representation it creates a real FlowDocument.
So, if you want to display your sample text bind to a string in the VM like this:
<FlowDocument PagePadding=\"5,0,5,0\" AllowDrop=\"True\" "
+ "xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">"
+ "<Paragraph>Your sample text</Paragraph></FlowDocument>"
We have a RichTextBox WPF control and since we control the layout, we simply cannot allow any rich content...
Therefor, we need to strip all data except text from the clipboard. For example, if someone is trying to copy/paste lets say text from a table directly from Microsoft Word, the RichTextBox also takes into account that this text was 1. originally from a table, 2. bold and 3. underlined, and create all sorts of inline content to accomodate all these properties of the text...
This is not appropiate behaviour in our case, because it can break our inline layouts.. we just want the clean text...
The most simple approach would be, in the preview paste command:
Clipboard.SetText(Clipboard.GetText());
and be done with it... But you guessed it.. Clipboard operations are not allowed in partial trust...
We also tried a dirty nasty hack, using a hidden Textbox suggested by this link:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/5b5bcd71-2eea-4762-bf65-84176c919fce/
Like so:
public static class ClipboardManager
{
private static readonly TextBox textBox = new TextBox {AcceptsReturn = true, AcceptsTab = true};
public static void SetText(string text)
{
textBox.Text = text;
textBox.SelectAll();
textBox.Copy();
}
public static string GetText()
{
textBox.Clear();
textBox.Paste();
return textBox.Text;
}
}
And then call it like this:
ClipboardManager.SetText(ClipboardManager.GetText());
This works well in full trust, but for some reason, both Copy and Paste methods of TextBox do not work in partial trust...
Does anyone know how to retrieve the Clipboard's content in WPF/partial trust ?
Thanks
Edit: As Nir pointed out.. I know it's not very nice to mutate data from your clipboard.. But my question will be answered just the same if someone can just point me out how to retrieve only the text from the clipboard in partial trust :)..
It's simply not possible.
In the end we used a toggle button where you could toggle to a textbox, paste it in there, and toggle back to our control. Nasty, but it works.
http://msdn.microsoft.com/en-us/library/aa970910.aspx says only "Plaintext and Ink Clipboard Support" in Partial Trust. Full Trust is required for "Rich Text Format Clipboard"