42

Anybody have a good example how to deep clone a WPF object, preserving databindings?


The marked answer is the first part.

The second part is that you have to create an ExpressionConverter and inject it into the serialization process. Details for this are here:
http://www.codeproject.com/KB/WPF/xamlwriterandbinding.aspx?fid=1428301&df=90&mpp=25&noise=3&sort=Position&view=Quick&select=2801571

Arcturus
  • 26,677
  • 10
  • 92
  • 107

4 Answers4

65

The simplest way that I've done it is to use a XamlWriter to save the WPF object as a string. The Save method will serialize the object and all of its children in the logical tree. Now you can create a new object and load it with a XamlReader.

ex: Write the object to xaml (let's say the object was a Grid control):

string gridXaml = XamlWriter.Save(myGrid);

Load it into a new object:

StringReader stringReader = new StringReader(gridXaml);
XmlReader xmlReader = XmlReader.Create(stringReader);
Grid newGrid = (Grid)XamlReader.Load(xmlReader);
Alan Le
  • 8,683
  • 7
  • 36
  • 31
  • 1
    Keep in mind that it also clones the name which complicates their using for UI Elements if they are to be placed is the same root container. – toad Apr 28 '09 at 16:17
  • I don't think this preserves animations, does it? – Erich Mirabal Apr 29 '09 at 11:52
  • 8
    To be clear, this is only half the solution (as it stood back in 08). This will cause bindings to be evaluated and the results be serialized. If you wish to preserve bindings (as the question asked) you must either add a ExpressionConverter to the Binding type at runtime (see the second part of my question for the relevant link) or see my own answer below for how to do it in 4.0. –  Mar 10 '11 at 14:13
  • Amazing Just Perfect :) – Ahmed Mahmoud May 13 '15 at 01:31
  • Just a pity XamlWriter is so sloooow. Wish there was an alternative. – willem Feb 09 '16 at 08:22
  • not working with Controls that uses binding with generic types. – Henka Programmer May 30 '17 at 11:12
37

In .NET 4.0, the new xaml serialization stack makes this MUCH easier.

var sb = new StringBuilder();
var writer = XmlWriter.Create(sb, new XmlWriterSettings
{
    Indent = true,
    ConformanceLevel = ConformanceLevel.Fragment,
    OmitXmlDeclaration = true,
    NamespaceHandling = NamespaceHandling.OmitDuplicates, 
});
var mgr = new XamlDesignerSerializationManager(writer);

// HERE BE MAGIC!!!
mgr.XamlWriterMode = XamlWriterMode.Expression;
// THERE WERE MAGIC!!!

System.Windows.Markup.XamlWriter.Save(this, mgr);
return sb.ToString();
  • I don't see how this is any different than just using XamlWriter.Save? At least I didn't see any different results when trying to serialize a DataGrid. – JP Richardson Mar 09 '11 at 17:27
  • 6
    @JP sorry, this isn't all that clear.... The question was how to clone *while preserving bindings*. The marked answer is half correct; in fact, if you do that you will find your bindings will be evaluated and the results (not the bindings themselves) will be serialized. In my question, I added the second half to the solution, which is to add an ExpressionConverter and add it to the Binding type at runtime. Its a bit obscure. The same thing can be accomplished by this answer--see the HERE BE MAGIC comment? That instructs the serializer **not** to evaluate bindings while serializing. Neat. –  Mar 10 '11 at 14:11
  • Ya, I saw this. I'm still working on the same problem with my Datagrid and didn't notice any different result in regards to data binding. My Datagrid is still blank. I must be missing something else. None the less, I'll give you an upvote for pointing this all out. – JP Richardson Mar 10 '11 at 20:28
  • @JPRichardson: Serialize to text and examine the result to see if the `{Binding}`s are preserved. –  Mar 10 '11 at 21:20
  • 1
    I market it useful, I am writing a dynamic data entry form and this solved the binding problem which I had. Thank you. – Masoud Jul 16 '13 at 08:35
5

There are some great answers here. Very helpful. I had tried various approaches for copying Binding information, including the approach outlined in http://pjlcon.wordpress.com/2011/01/14/change-a-wpf-binding-from-sync-to-async-programatically/ but the information here is the best on the Internet!

I created a re-usable extension method for dealing with InvalidOperationException “Binding cannot be changed after it has been used.” In my scenario, I was maintaining some code somebody wrote, and after a major DevExpress DXGrid framework upgrade, it no longer worked. The following solved my problem perfectly. The part of the code where I return the object could be nicer, and I will re-factor that later.

/// <summary>
/// Extension methods for the WPF Binding class.
/// </summary>
public static class BindingExtensions
{
    public static BindingBase CloneViaXamlSerialization(this BindingBase binding)
    {
        var sb = new StringBuilder();
        var writer = XmlWriter.Create(sb, new XmlWriterSettings
        {
            Indent = true,
            ConformanceLevel = ConformanceLevel.Fragment,
            OmitXmlDeclaration = true,
            NamespaceHandling = NamespaceHandling.OmitDuplicates,
        });
        var mgr = new XamlDesignerSerializationManager(writer);

        // HERE BE MAGIC!!!
        mgr.XamlWriterMode = XamlWriterMode.Expression;
        // THERE WERE MAGIC!!!

        System.Windows.Markup.XamlWriter.Save(binding, mgr);
        StringReader stringReader = new StringReader(sb.ToString());
        XmlReader xmlReader = XmlReader.Create(stringReader);
        object newBinding = (object)XamlReader.Load(xmlReader);
        if (newBinding == null)
        {
            throw new ArgumentNullException("Binding could not be cloned via Xaml Serialization Stack.");
        }

        if (newBinding is Binding)
        {
            return (Binding)newBinding;
        }
        else if (newBinding is MultiBinding)
        {
            return (MultiBinding)newBinding;
        }
        else if (newBinding is PriorityBinding)
        {
            return (PriorityBinding)newBinding;
        }
        else
        {
            throw new InvalidOperationException("Binding could not be cast.");
        }
    }
}
John Zabroski
  • 2,212
  • 2
  • 28
  • 54
0

How about:

    public static T DeepClone<T>(T from)
    {
        using (MemoryStream s = new MemoryStream())
        {
            BinaryFormatter f = new BinaryFormatter();
            f.Serialize(s, from);
            s.Position = 0;
            object clone = f.Deserialize(s);

            return (T)clone;
        }
    }

Of course this deep clones any object, and it might not be the fastest solution in town, but it has the least maintenance... :)

Arcturus
  • 26,677
  • 10
  • 92
  • 107