Reputation: 1759
We are trying to program with VS 2013 and WPF a WPF window designer application . In this application controls are created and placed on a Canvas at runtime; including the setting of the properties and bindings. After the completion of such a dynamic WPF window we want to serialize the Canvas and it's child controls to a XML file. For this we are using the XamlWriter like that:
public string SerializeControlToXaml(FrameworkElement control)
{
StringBuilder outstr = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
XamlDesignerSerializationManager dsm =
new XamlDesignerSerializationManager(XmlWriter.Create(outstr, settings));
dsm.XamlWriterMode = XamlWriterMode.Expression;
System.Windows.Markup.XamlWriter.Save(control, dsm);
string xaml = outstr.ToString();
return xaml;
}
The "control" Parameter contains in our case the Canvas panel, which is the parent control for all code-behind created controls. Among others we are creating TextBoxes which are binded to the SelectedItem and the column of a DataGrid.
private void CreateTextboxes()
{
CreateTextbox("firstname", _datagridname, "SelectedItem.vorname", 220, 10);
CreateTextbox("familyname", _datagridname, "SelectedItem.nachname", 220, 40);
}
private void CreateTextbox(string name, string sourceName, string path, double leftPos, double topPos)
{
TextBox tb = new TextBox();
tb.SetValue(Canvas.LeftProperty, leftPos);
tb.SetValue(Canvas.TopProperty, topPos);
tb.Width = 150;
tb.Name = name;
// Binding to the selected item of the DataGrid.
Binding tbbinding = new Binding();
FrameworkElement sourceElement;
ControlList.TryGetValue(sourceName, out sourceElement);
if (sourceElement != null)
{
tbbinding.Source = sourceElement;
}
tbbinding.Path = new PropertyPath(path);
tb.SetBinding(TextBox.TextProperty, tbbinding);
_canvasPanel.Children.Add(tb);
// The new TextBox is added to the Controllist.
ControlList.Add(name, tb);
}
In our example the method for creating a TextBox and Setting it's properties and bindings is called twice. At the end we have two TextBoxes in the window, which are bound to the DataGrid columns "firstname" and "familyname".
But when we serialize the parent control, the bindings are not serialized. What we get looks like that:
<Canvas Background="#FFF0F8FF" Name="DropInCanvas" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:sd="clr-namespace:System.Data;assembly=System.Data" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataGrid CanUserAddRows="False" AutoGenerateColumns="True" Name="datagrid1" Canvas.Left="20" Canvas.Top="10">
<DataGrid.ItemBindingGroup>
<BindingGroup Name="{x:Null}" NotifyOnValidationError="False" ValidatesOnNotifyDataError="True" SharesProposedValues="True" />
</DataGrid.ItemBindingGroup>
<sd:DataRowView />
<sd:DataRowView />
<sd:DataRowView />
<sd:DataRowView />
</DataGrid>
<TextBox Name="firstname" Width="150" Canvas.Left="220" Canvas.Top="10" xml:space="preserve"></TextBox>
<TextBox Name="familyname" Width="150" Canvas.Left="220" Canvas.Top="40" xml:space="preserve"></TextBox>
</Canvas>
Does anybody know why?
Thanks in advance!
Patrick
Upvotes: 1
Views: 340
Reputation: 1759
To serialize BindingExpressions we need a converter class.
/// <summary>
/// Class for conversion of binding-attributes while XML-serialization.
/// With this converter it is possible to serialize the control-bindings.
/// Works together with the class EditorHelper.
/// </summary>
public class BindingConvertor : ExpressionConverter
{
/// <summary>
/// Finds out if the converter can convert an expression-object to the given destinationtype.
/// </summary>
/// <param name="context">An ITypeDescriptorContext-interface which provides a context for formatting.</param>
/// <param name="destinationType">A type-class which represents the target-type of the conversion.</param>
/// <returns>Returns an object of type bool. True = the destinationtype can be converted.</returns>
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(MarkupExtension))
return true;
return false;
}
/// <summary>
/// Converts the expression to the given destinationtype.
/// </summary>
/// <param name="context">An ITypeDescriptorContext-interface which provides a context for formatting.</param>
/// <param name="culture">The System.Globalization.CultureInfo which is actually used as culture.</param>
/// <param name="value">The object to convert.</param>
/// <param name="destinationType">A type-class which represents the target-type of the conversion.</param>
/// <returns></returns>
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value,
Type destinationType)
{
if (destinationType == typeof(MarkupExtension))
{
BindingExpression bindingExpression = value as BindingExpression;
if (bindingExpression == null)
throw new Exception();
return bindingExpression.ParentBinding;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
Next we need an editor to register which converter is responsible for which type of expression:
/// <summary>
/// Class for registering the class BindingConvertor as the type-converter for the type BindingExpression
/// With this converter it is possible to serialize the control-bindings.
/// </summary>
public class EditorHelper
{
/// <summary>
/// Registers which converter is responsible for which type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <typeparam name="TC">The converter.</typeparam>
public static void Register<T, TC>()
{
Attribute[] attribute = new Attribute[1];
TypeConverterAttribute typeConverterAttribute = new TypeConverterAttribute(typeof(TC));
attribute[0] = typeConverterAttribute;
TypeDescriptor.AddAttributes(typeof(T), attribute);
}
}
To finish it, we need to implement the following line of code somewhere in the code of our MainWindow (or where it is needed):
EditorHelper.Register<BindingExpression, BindingConvertor>();
That's it!
Upvotes: 2