How can you clone a WPF object?
Asked Answered
C

5

43

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

Commemoration answered 28/8, 2008 at 15:14 Comment(0)
S
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);
Sigmoid answered 28/8, 2008 at 18:38 Comment(6)
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.Piwowar
I don't think this preserves animations, does it?Illnatured
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.Commemoration
Amazing Just Perfect :)Hepner
Just a pity XamlWriter is so sloooow. Wish there was an alternative.Unravel
not working with Controls that uses binding with generic types.Figment
C
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();
Commemoration answered 11/2, 2011 at 19:52 Comment(5)
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.Docilu
@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.Commemoration
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.Docilu
@JPRichardson: Serialize to text and examine the result to see if the {Binding}s are preserved.Commemoration
I market it useful, I am writing a dynamic data entry form and this solved the binding problem which I had. Thank you.Saturated
A
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.");
        }
    }
}
Azurite answered 6/2, 2013 at 22:54 Comment(0)
A
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... :)

Alecto answered 28/8, 2008 at 15:24 Comment(0)
V
0

Just use this:

string json = JsonConvert.SerializeObject(OldObj);
ObjectType NewOBJ = JsonConvert.DeserializeObject<ObjectType>(json);
Venuti answered 18/7 at 15:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.