FlowDocument sourced from external resource - wpf

I am trying to externalise some of the wording within my WPF application, however I would like to be able to use some degree of formatting as well.
My initial thought was to use a string resource which represented a FlowDocument or Paragraph such as:
<FlowDocument>
<Paragraph FontSize="16" Foreground="Blue">Some display text under content management</Paragraph>
</FlowDocument>
In the UI I have been trying to bind this using a IValueConverter:
<ContentControl Content="{Binding Path=CMSText,Source={StaticResource Resources},Converter={StaticResource flowDocConverter}"/>
In the converter:
StringReader sr = new StringReader(value.ToString());
XamlReader xamlReader = XamlReader.Create(sr);
return (FlowDocument)xamlReader.Parse();
but it keeps throwing an exception on the return statement.
Is it even possible to do this via a binding?
And where am I going wrong in the XamlReader?
EDIT
XamlParseException
'Cannot create unknown type 'FlowDocument'.' Line number '1' and line position '2'.

Change your input string FlowDocument Tag, adding the NamePpace like so:
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:local="clr-namespace:MARS">
<Paragraph FontSize="16" Foreground="Blue">Some display text under content management</Paragraph>
</FlowDocument>

I'd say you simply cannot cast the result of xamlReader.Parse() into a FlowDocument (I'm not sure why).
you should rather try something like this as your converter:
FlowDocument myFlowDoc = new FlowDocument();
myFlowDoc.Blocks.Add(new Paragraph(new Run(value)))
return myFlowDoc;
(I find FlowDocument management lacks simplicity and tends to be a hassle)

Related

Why does data binding on a dynamically loaded control break?

Currently I'm designing an application that should at one point be able to create a report from a Xaml template File using Data Binding (involving a FlowDocument).
The idea was to simply convert a dynamically loaded control via BlockUIContainer to be printable in a FlowDocument.
As long as I load an entire file into a single FrameworkElement and set the DataContext property, the Data Binding works like a charm.
foreach (Order order in orders)
{
BlockUIContainer container = new BlockUIContainer();
container.Child = (FrameworkElement)GetOrderControl();
(container.Child as FrameworkElement).DataContext = order;
document.Blocks.Add(container);
}
Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.SystemIdle,
new Action(() => { return; }));
All the GetOrderControl() method does is read from a FileStream a parse the content via XamlReader.Load(). The file is structured like this:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
...
<TextBlock Text="{Binding Path=Country}" />
...
</Gird>
Now the application should add BlockUIContainers dynamically according to the dataset. I need to do it in code behind to implement custom pagination, because the reports might get longer than one page.
Since I only want a single template file, I've packed my header, footer and grouping controls all in a single xaml file like this:
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BlockUIContainer Name="PageHeader">
<Grid ... />
</BlockUIContainer>
<BlockUIContainer Name="Element">
<Grid ... />
</BlockUIContainer>
</FlowDocument>
The <Grid ... /> control inside the "Element" named BlockUIContainer is just exactly the Grid control used in the example before.
Now all I do is get the child of the BlockUIContainer and create a copy of that by saving it to a string and back to a FrameworkElement and set the DataContext.
foreach (Order order in orders)
{
BlockUIContainer container = new BlockUIContainer();
container.Child = (FrameworkElement)XamlReader.Parse(XamlWriter.Save(elementControl));
(container.Child as FrameworkElement).DataContext = order;
document.Blocks.Add(container);
}
Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.SystemIdle,
new Action(() => { return; }));
Here however the data binding is not evaluating. I tried calling the UpdateLayout() method on the FrameworkElement after setting the DataContext.
That does evaluate at least some Bindings in a <DataTrigger Binding="{Binding Path=DangerousGoods}" /> for a <Style> Element setting the Visibility of some child controls, but none of the Bindings like <TextBlock Text="{Binding Path=Country}" /> are not evaluated.
I'm at a loss here. How do I get the remaining bindings to work again after parsing them? I don't really want to create several files for one document.
Nevermind, I found the error... the Bindings get evaluated the first time the control is created.
The XamlWriter then "destroys" the Binding by evaluating the text and writing the original text output (which was empty) into the element's Text property.

Changing a datatemplate at runtime

I need to wrap a datatemplate in a datatemplate that gets built at run time. The wrapped datatemplate is WPF element where as the wrapping template needs to be created in code.
Something like:
public DataTemplate GetTemplate(DataTemplate template)
{
string xaml = string.Format(#"
<DataTemplate>
<ContentControl Content=""{{Binding}}"">
<ContentControl.ContentTemplate>
{0}
</ContentControl.ContentTemplate>
</ContentControl>
</DataTemplate>", template);
return CreateTemplate(xaml);
}
Obviously my datatemplate is more complicated then the one I'm using above.
I dont know of anyway to take an existing xaml element and convert it to a string. It seems like I might be able to use FrameworkElementFactory but I see it is depricated, which leads me to think I'm missing something obvious.
EDITED ---
What I'm doing is creating a control that users will supply a datatemplate but I need to make changes to the the template. Maybe this example will make more sense...
public DataTemplate GetTemplate2()
{
// this template would be supplied by the user
// I'm creating it here as an example
string t = string.Format(#"
<DataTemplate>
<TextBlock Text=""{{Binding Value}}""/>
</DataTemplate>");
T = CreateTemplate(t);
string xaml = string.Format(#"
<DataTemplate>
<ContentControl Content=""{{Binding}}"">
<ContentControl.ContentTemplate>
{0}
</ContentControl.ContentTemplate>
</ContentControl>
</DataTemplate>", t);
return CreateTemplate(xaml);
}
This all works because I'm using the string template (e.g. t). However I need to figure out some way to do it with the actual DataTemplate (e.g. T). Unfortunately XamlWriter can't deal with the Binding.
You can create a DataTemplate selector. There you can add your logic to build your DataTemplate at runtime. Also you can create a dependencyProperty in your DataTemplate selector. Then bind it in your xaml to a DataTemplate stored in some backing model, and there do what ever ...
This link might be a good place to start
You can use XamlWriter (the analog to XamlReader) but it has limitations on what can be properly serialized. Things like event handlers and x:Names cause issues.
**UPDATE
Seeing the additional detail I think you should try reversing your approach. Rather than combining the templates using strings and then trying to turn that into the object you want, you can avoid all the weird parsing restrictions by just creating the user's template as a DataTemplate object and then building your own DataTemplate object around it. Your example code is also using 2 Value Paths, which is going to give you .Value.Value on the inner template Text so check to make sure on your real one that you're ending up with the Paths you want. Here's the basics of your example using the objects instead, with the paths updated to expect a String and display its length:
DataTemplate T = XamlReader.Parse(string.Format(#"
<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
<TextBlock Text=""{{Binding}}""/>
</DataTemplate>")) as DataTemplate;
FrameworkElementFactory controlFactory = new FrameworkElementFactory(typeof(ContentControl));
controlFactory.SetBinding(ContentControl.ContentProperty, new Binding("Length"));
controlFactory.SetValue(ContentControl.ContentTemplateProperty, T);
DataTemplate mainTemplate = new DataTemplate { VisualTree = controlFactory };

WPF FlowDocument Binding

I am using Microsoft's XAML/HTML converter to convert HTML from a database into a XAML string. The Microsoft converter seems to be formatting the text correctly, but I'm having trouble binding the output to a XAML object.
For example, using the following HTML:
<span style="font-weight: bold; font-family: Georgia; color: rgb(0, 96, 144); text-decoration: underline;">Hello world.</span>
I will get the XAML output:
<Section xml:space="preserve" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Paragraph>
<Run FontWeight="bold" TextDecorations="Underline" FontFamily="georgia">Hello world.</Run>
</Paragraph>
Assuming the HTML is coming into the WPF application as the "Text" property of a database object, I then use Binding and Converters like so:
<TextBlock Text="{Binding Path=ActiveDataItem.Text, Converter={StaticResource convertHTMLToXaml}}" />
Unfortunately, this just prints the XAML to the page and doesn't parse it. I'm assuming this is because I'm binding to the TextBlock and that's the expected outcome. My question is how do I bind this output a FlowDocument related control like a Paragraph, Run, Section, or whatever?
Note: I realize there a quite a few threads dedicated to converting HTML to XAML. I have referenced most of them, but they are all lacking on this specific step. Any help or links are appreciated, thanks in advance.
For your example, you have the xaml as text and bound to the Text-property. This only shows the xaml as text.
If there is a direct way to bind it as the content of a FlowDocument, I don't known. IMO this is not possible due to the structure of FlowDocument. But maybe someone knows a way and posts you a solution for this.
To do it manually, look at the example of this page (dead link, see Archive.org). There I have seen that the author loads a XAML-string into a RichTextBox. You can change the code to your needs (RichtTextBox works also with FlowDocs). Search for public static class RichTextboxAssistant, there is the code your looking for. Take care for the encoding. He uses ASCII. Maybe you have to change this to UTF.
Hope this helps.
For .NET versions previous to 4.0: In this link, Vincent Van Den Berghe explain how to extend FlowDocument to support 'Bindable Runs', check it out
For .NET 4.0: Run.Text property is bindable

Silverlight 4 RichTextBox Bind Data using DataContext

I am working with Silverlight 4 and trying to put my test apps multilingual but I am having some trouble when I arrive to the "RichTextBox" control. I am able to bind it properly by doing back-code (c#), but when trying using the "DataContext" attributes I am not able to load it at all.
I have created a FormatConverter that return a Block (paragraph) for testing and my code where I have my RichTextBox looks like:
<RichTextBox x:Name="rtaTest" BorderThickness="0" IsReadOnly="True" UseLayoutRounding="True"
DataContext="{Binding Source={StaticResource Localization}, Path=Home.MainContent, Converter={StaticResource ParagraphFormatConverter}}">
</RichTextBox>
I am wondering if there is a way of binding a RichTextBox from the XAML.
Run seems to support databinding in SL4, as in:
<RichTextBox>
<Paragraph>
<Run Text="{Binding Path=LineFormatted}" />
</Paragraph>
</RichTextBox>
I think you may be a little confused about the used of the DataContext. You might for example have some Rich text where some children of one or more InlineUIContainer elements may retrieve their text from a property of some object. You would assign the object to the DataContext.
Whilst I'm not quite sure what you were expecting to achieve but I suspect that what you really need is for your converter to actually return a BlocksCollection (even if it just contains the single Block you were originaly returning) and then to bind as:-
<RichTextArea x:Name="rtaTest" BorderThickness="0" IsReadOnly="True"
UseLayoutRounding="True"
Blocks="{Binding Source={StaticResource Localization},
Path=Home.MainContent, Converter={StaticResource ParagraphFormatConverter}}" />
This FillFromXml is a WPF thing? Don't see it in Silverlight.
Blocks cannot be set, they can only be fetched. One way to set the blocks for a RichTextArea is
public static void UpdateRichTextArea(RichTextArea area, string xmlText)
{
if (area == null)
return;
area.Blocks.FillFromXml(xmlText, true);
}

Display images in TextBlock (WPF)

I'm working on a simple chat application. Currently the messages are binded to a custom-styled listbox like this (simplified XAML):
<ListBox ItemsSource="{Binding MessageCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now I would like to be able to put images (like graphical smileys) into the displayed message text. Is there any way to achieve this using TextBlock (or any other standart component) or do I need to use some special control for this?
Thanks in advance
Just use the InlineUIContainer.
<TextBlock TextWrapping="Wrap">
<Run>Some text.</Run>
<InlineUIContainer>
<Image Source="http://sstatic.net/stackoverflow/img/apple-touch-icon.png" Height="20"></Image>
</InlineUIContainer>
<Run>Some more text.</Run>
</TextBlock>
The Content of a TextBlock is always just a series of Inlines, so you should use the InlineUIContainer. You can insert this container as one of the Inlines in your TextBlock wherever you want an Image to appear, alternating with text Runs. You could parse a message and at the same time keep adding the tokens (either text or images) that you find to the Inlines collection of the TextBlock.
If you want the Images actually inside the text (like an emoticon), then you are going to have to do some work. This sounds like one of the few times I would actually want a User Control, the point of which would be one that scans the Text looking for emoticon values and building a Data Template on the fly.
Remember that anything you can do in XAML you can do in code, so the code I'm thinking of would follow this general idea:
Scan text for emoticon values and
create a list of values for data
elements.
Create a DockPanel.
Foreach element in the List, add
either a TextBlock or an Image
(based on value).
Set this.Content to the DockPanel.
I think something like this is actually what you are looking for, but if you want just an Image, then the ValueConverter suggestion would work.
You could use a value converter to convert the text to another type which has a list of segments which are composed of either text or the smiley face (in the order in which they appear).
Then, you can use a data template to bind to that new type and display the text and smiley faces appropriately.
I also encountered this problem recently and I overcome this by
Creating an ListBox ItemTemplate containing an ItemsControl that has a WrapPanel in the ItemsPanelTemplate and then binding my string to the ItemsSource of the ItemsControl with a IValueConverter that houses all the logic.
Split out your words and query/search your emoticons strings, hyperlinks etc and create your TextBlock, Image, Hyperlink, Button elements and set your values and event handles.
In the function create a List<UIElement> and populate the List with the controls you have generated and return the List as the object in the Convert function of the IValueConverter.
Because you have the WrapPanel in there you get your wrapping done.
Use the Image element instead of the TextBlock and use a Converter to map the text value to the smile image.
<ListBox ItemsSource="{Binding MessageCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Text, Converter={StaticResource MyImageConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>

Resources