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.
Related
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).
I wanted to implement itemsControl printing with page breaks like it's described in the following blog: http://blogs.u2u.be/diederik/post/2013/05/21/Printing-a-XAML-ItemsControl-from-a-Windows-8-Store-app.aspx
The problem is that in Windows 10 it works incorrectly (I added background to show items in itemsControl for debugging):
While 8.1 version works as expected:
Is there any idea what can be wrong? All it does is just adds paragraphs to richtext, measures each item content and then splits it by pages.
Windows 10 sample is here.
Windows 8.1 sample is here.
UPDATE:
More info:
I changed textblock to textbox (wanted to add background to text). It turns out that it works better, but text is cut at the end of the page. It started working on all pages except for the first page in preview (text is single line and not wrapping on the first page for some unknown reasons). It works correctly in printed document. Weird thing is that it works correctly if I close preview and click print again (even though last line on page is still cut).
I have tried your sample code: The problem is that the system default templates for your two applications are not all the same, so that there is a little difference in the display mode for the item. To fix the problem in the win10 app, you'd better define a proper layout style for the control. There are various ways to achieve this: adding a proper template in resources, or defining in code.
Please try following code in the "PreparePrintContent" function. Please notice that I adjust the layout by setting margin of the UIElement. This is a very simple approach just for your reference:
var x = itemsControl.ContainerFromItem(item) as ContentPresenter;
Paragraph p = new Paragraph();
InlineUIContainer c = new InlineUIContainer();
var o = x.ContentTemplate.LoadContent() as UIElement;
(o as FrameworkElement).DataContext = item;
(o as FrameworkElement).Margin = new Thickness(40);
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.
I am having a scenario by which I have to dynamically create the form based on the user selection. In the form, there are few textboxes which should be added at the end to the Total Textbox.
The way I am distinguishing the textboxes to be added at the end is by specifying as below..
TextBox txt1 = new TextBox();
txt1.ID = "txt1";
txt1.CssClass = "addToTotal";
TextBox txt2 = new TextBox();
txt2.ID = "txt2";
txt2.CssClass = "addToTotal";
TextBox txt3 = new TextBox();
txt3.ID = "txt3";
txt3.CssClass = "txtTotalPoints";
PlaceHolder1.Controls.Add(txt1);
PlaceHolder1.Controls.Add(txt2);
PlaceHolder1.Controls.Add(txt3);
In reality, there is no css class named 'addToTotal' in the site css file. It's just used as a flag to notify me for adding at the end.
Is it a good practice to add a .CssClass even though the actual class does not exist. Are there any pitfalls in using this methodology?
I would assume that the overhead of using a CSS class which does not exist as a marker is minimal, so I wouldn't change your implementation based on that. If you're concerned about best practices - which you could rightly be, CSS classes were never intended to be used like this - you could add a data-* attribute to the input and use that instead:
txt2.Attributes["data-addToTotal"] = "true";
...then finding those elements with JQuery:
$("input[data-addToTotal='true']")
data-* attributes are part of HTML5, but are fully backwards compatible.
I need to change a slide's layout programmaticaly with C# (Add-In Express 2009 for Office and .NET is used). If the new layout is a predefined one then everything is fine, but not if I need to set a custom layout as a new one (without slide recreating). Unfortunately, I didn't find any information on how to do it, PowerPoint object model reference documentation didn't answer me as well. There is just the ability to create a new slide that uses custom layout.
I've done an experiment and have ensured that the Slide object stayed being the same while I have been changing layout both predefined and custom ones. I don't want to create a new slide when I need just switch the layout.
Is it possible at all? Please help me to find a way of doing it.
The only way it will work is if your custom layout is actually used in the deck first. Then you simply take that layout and apply it to the slide you want. You could programatically create a new slide with your custom layout, use it's layout to apply to another slide and then delete that new slide you had created. Here's code to apply the custom layout (note that my ap.Slides(2) is a Custom Layout)
Sub ChangeLayout()
Dim ap As Presentation
Set ap = ActivePresentation
Dim slide1 As Slide
Set slide1 = ap.Slides(1)
Dim customLayout As PpSlideLayout
customLayout = ap.Slides(2).Layout
slide1.Layout = ly
End Sub
You could do that, but it's really not recommended. Also, creating a new slide this way and applying the layout is prone to errors. In the following code snippet you can see how to retrieve a layout by name from the master....
private PowerPoint.CustomLayout DpGetCustomLayout(
PowerPoint.Presentation ppPresentation, string myLayout)
{
//
// Given a custom layout name, find the layout in the master slide and return it
// Return null if not found
//
PowerPoint.CustomLayout ppCustomLayout = null;
for (int i = 0; i < ppPresentation.SlideMaster.CustomLayouts.Count; i++)
{
if (ppPresentation.SlideMaster.CustomLayouts[i + 1].Name == myLayout)
ppCustomLayout = ppPresentation.SlideMaster.CustomLayouts[i + 1];
}
return ppCustomLayout;
}
then you can assign it to the slide as you saw above. However, if the layouts are incompatible, then results may be unpredictable. I assume that the slides are at least relatively the same. You should try to create a new slide and copy the content over to avoid being hostage to changes in the underlying theme or template.
See code descriptions for more on this.